Ejemplo n.º 1
0
    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
Ejemplo n.º 2
0
    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)
Ejemplo n.º 3
0
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)
Ejemplo n.º 4
0
    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()))
Ejemplo n.º 5
0
 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
Ejemplo n.º 6
0
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
Ejemplo n.º 7
0
    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)
Ejemplo n.º 8
0
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
Ejemplo n.º 9
0
    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
Ejemplo n.º 10
0
    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
Ejemplo n.º 11
0
 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