def process_connection(self, quick_reply_with=None): """ Continually serve an http connection. :param quick_reply_with: If given will be sent to the client immediately as a response without parsing his request. :return: This method doesn't return until the connection is closed. """ if quick_reply_with: assert isinstance(quick_reply_with, ws.http.structs.HTTPResponse) self.push_exchange() self.respond(quick_reply_with) while True: error_log.debug( 'HTTP Connection is open. Pushing new http exchange ' 'context and parsing request...') self.push_exchange() with record_rusage(self.exchange): request, body_start = ws.http.parser.parse(self.sock) self.exchange['request'] = request self.exchange['body_start'] = body_start response = self.handle_request(request) error_log.debug3('Request handler succeeded.') response = self.respond(response) client_persists = request and request_is_persistent(request) server_persists = response and response_is_persistent(response) if not (client_persists and server_persists): break
def __next__(self): if self.current_chunk: try: return next(self.current_chunk) except StopIteration: pass elif self.socket_broke: raise StopIteration() if self.connected_on + self.connection_timeout < time.time(): raise ClientSocketException(code='CS_CONNECTION_TIMED_OUT') try: chunk = self.sock.recv(self.__class__.buffer_size) except ws.sockets.TimeoutException as e: error_log.warning('Socket timed out while receiving request.') raise ClientSocketException(code='CS_PEER_SEND_IS_TOO_SLOW') from e error_log.debug3('Read chunk %s', chunk) if chunk == b'': error_log.info('Socket %d broke', self.sock.fileno()) self.socket_broke = True raise ClientSocketException(code='CS_PEER_NOT_SENDING') self.current_chunk = iter(chunk) return next(self.current_chunk)
def parse_request_line(line, *, methods=ALLOWED_HTTP_METHODS): error_log.debug3('Parsing request line...') parts = line.split(b' ') if len(parts) != 3: raise ParserException(code='PARSER_BAD_REQUEST_LINE') method, request_target, http_version = parts method = method.decode('ascii') if method not in methods: raise ParserException(code='PARSER_UNKNOWN_METHOD') error_log.debug2('Parsed method %r', method) uri = parse_request_target(request_target) error_log.debug2('Parsed uri %r', uri) if not HTTP_VERSION_REGEX.match(http_version): raise ParserException(code='PARSER_BAD_HTTP_VERSION') http_version = http_version.decode('ascii') return ws.http.structs.HTTPRequestLine(method=method, request_target=uri, http_version=http_version)
def recv_new_sockets(self): error_log.debug3('Receiving new sockets through fd transport.') msg, fds = self.fd_transport.recv_fds() for fd in fds: error_log.debug3('Received file descriptor %s', fd) sock = ws.sockets.Socket(fileno=fd) self.connections.append((sock, sock.getpeername()))
def reindex_files(self): error_log.info('Reindexing files under dir %s', self.document_root) file_keys = set() for dir_path, dir_names, file_names in os.walk(self.document_root): for fn in file_names: fp = os.path.join(dir_path, fn) stat = os.stat(fp) file_keys.add((stat.st_ino, stat.st_dev)) self.file_keys = frozenset(file_keys) error_log.debug3('Indexed file keys are: %s', self.file_keys) self.reindex_is_scheduled = False
def parse_headers(lines): """ Parses HTTP headers from an iterable into a dictionary. NOTE: Does not parse indented multi-line headers """ headers = {} for line in lines: field, _, value = line.partition(b':') if not value: raise ParserException(code='PARSER_BAD_HEADER') field = field.decode('ascii').strip() headers[field] = value.lstrip() error_log.debug3('Parsed header field %s with value %r', field, value) return headers
def handle_connection(self, socket, address, quick_reply_with=None, ssl_only=config.getboolean('ssl', 'strict')): assert isinstance(socket, ws.sockets.Socket) assert isinstance(address, collections.Sequence) error_log.debug3('handle_connection()') if self.rate_controller.is_banned(address[0]): socket.close(pass_silently=True) return wrapped_sock = socket if self.ssl_ctx: if socket.client_uses_ssl(): wrapped_sock = ws.sockets.SSLSocket.from_sock( sock=socket, context=self.ssl_ctx, server_side=True) elif ssl_only: quick_reply_with = hutils.build_response(403) else: error_log.info('Client on %s / %s does not use SSL/TLS', socket, address) conn_worker = ws.cworker.ConnectionWorker( sock=wrapped_sock, address=address, auth_scheme=self.auth_scheme, static_files=self.static_files, worker_ctx={'request_stats': self.request_stats}) try: with conn_worker: conn_worker.process_connection( quick_reply_with=quick_reply_with) finally: self.rate_controller.record_handled_connection( ip_address=address[0], status_codes=conn_worker.status_codes()) for exchange_stats in conn_worker.generate_stats(): for stat_name, val in exchange_stats.items(): self.request_stats[stat_name]['total'] += val self.request_stats[stat_name]['count'] += 1 for exchange in conn_worker.exchanges: access_log.log(**exchange)
def recv_request(sock, chunk_size=4096, timeout=config.getint('http', 'connection_timeout'), connection_timeout=config.getint('http', 'request_timeout')): error_log.debug3('recv_request() from %s', sock) assert isinstance(sock, ws.sockets.Socket) assert isinstance(chunk_size, int) total_length = 0 chunks = [] body_offset = -1 start = time.time() sock.settimeout(timeout) while body_offset == -1: if total_length > MAX_HTTP_META_LEN: raise ParserException(code='PARSER_REQUEST_TOO_LONG') elif time.time() - start > connection_timeout: raise ParserException(code='CS_PEER_CONNECTION_TIMED_OUT') try: chunk = sock.recv(chunk_size) except ws.sockets.TimeoutException: raise ParserException(code='CS_PEER_SEND_IS_TOO_SLOW') if not chunk: raise ParserException(code='CS_PEER_NOT_SENDING') chunks.append(chunk) total_length += len(chunk) body_offset = chunk.find(b'\r\n\r\n') lines = [] leftover_body = b'' for i, chunk in enumerate(chunks): if i == len(chunks) - 1: line_chunk = chunk[:body_offset] leftover_body = chunk[body_offset:] else: line_chunk = chunk lines.extend(line_chunk.split(b'\r\n')) return lines, leftover_body
def handle_request(self, request): auth_check = self.auth_scheme.check(request=request, address=self.address) is_authorized, auth_response = auth_check route = request.request_line.request_target.path method = request.request_line.method error_log.debug3('Incoming request {} {}'.format(method, route)) if not is_authorized: response = auth_response elif method == 'GET': if ws.serve.is_status_route(route): request_stats = self.worker_ctx['request_stats'] response = ws.serve.worker_status(request_stats=request_stats) else: static_response = self.static_files.get_route(route) if static_response.status_line.status_code == 200: response = static_response elif ws.cgi.can_handle_request(request): response = ws.cgi.execute_script( request, socket=self.sock, body_start=self.exchange['body_start'] ) else: response = ws.http.utils.build_response(404) elif ws.cgi.can_handle_request(request): response = ws.cgi.execute_script( request, socket=self.sock, body_start=self.exchange['body_start'] ) else: response = ws.http.utils.build_response(405) return response
def reap_children(self, signum, stack_frame): # TODO use a lock error_log.debug3('reap_children() called.') if self.reaping: return self.reaping = True try: while True: pid, exit_indicator = os.waitpid(-1, os.WNOHANG) if pid: error_log.debug('Reaped pid %s', pid) assert pid not in self.reaped_pids self.reaped_pids.add(pid) else: break except OSError as err: error_log.debug( 'During reaping of zombie child: wait() sys call ' 'failed with ERRNO=%s and MSG=%s. This is mostly ' 'not a problem.', err.errno, err.strerror) finally: self.reaping = False
def schedule_reindex(self, signum, stack_frame): """ Method to be used as a signal handler to avoid race conditions.""" error_log.debug3('Scheduled reindex.') self.reindex_is_scheduled = True