Пример #1
0
    def read_message(self):
        if self.request is not None:
            note('err',derpid(self),self.request)
            raise RuntimeError

        try:
            start_line = yield self.stream.read_until(b'\r\n',max_bytes=self.max_start_header_length)
        except iostream.StreamClosedError:
            note('done with',self.address,derpid(self))
            raise
        except iostream.UnsatisfiableReadError:
            print("garbage from address",self.address)
            self.stream.close()
            raise iostream.StreamClosedError
        start_line = httputil.parse_request_start_line(start_line.decode('utf-8'))
        note('setting request',derpid(self),start_line)
        self.request = self.requestFactory(self, self.stream, start_line)
        headers = yield maybeTimeout(self.stream, self.header_timeout, self.read_headers())
        
        yield maybeTimeout(self.stream, self.body_timeout, self.read_body(headers))
        
        # Now we've read the request, so start writing it, asynchronously
        yield self.startWriting()
        note('del self.request (started writing)',derpid(self))
        del self.request
        assert self.request is None, "boop"
        if self.old_client:
            self.stream.close()
        else:
            self.read_next_message('next')
Пример #2
0
 def handle_http_connection(self, conn):
     # Read the trailing HTTP request and process it with protocol_switcher.
     # We can't rely on ioloop to trigger read because it has been already
     # triggered for SSL handshake.
     payload = yield conn.stream.read_bytes(1024, partial=True)
     logger.debug("Received %r", payload[:128])
     # Simulate conn._read_message side effect. This is required by
     # HTTP1Connection.write_headers()
     conn._request_start_line = parse_request_start_line('GET / HTTP/1.1')
     try:
         start_line, headers = parse_http_headers(payload)
         conn._request_start_line = start_line
         request = HTTPRequest(
             connection=conn,
             headers=headers,
             start_line=start_line,
         )
         request.config = self.request_callback.config
         response = protocol_switcher(request)
     except Exception as e:
         logger.error("Failed to switch to HTTPS: %s", e)
         response = HTTPResponse(
             request=object(),
             code=500,
             headers=HTTPHeaders({'Content-Length': '0'}),
             effective_url='https://useless_effective_url')
     yield conn.write_headers(
         start_line=ResponseStartLine(
             'HTTP/1.1',
             response.code,
             response.reason,
         ),
         headers=response.headers,
     )
 def _handle_request(self, data):
     try:
         start_line, headers = self.parse_headers(data)
         request = httputil.HTTPServerRequest(
             headers=headers,
             start_line=httputil.parse_request_start_line(start_line))
         deli = self._handler.application.find_handler(request)
         getattr(self._handler, request.method.lower(),
                 self._handler.r404)(*deli.path_args, **deli.path_kwargs)
     except httputil.HTTPInputError as e:
         Log.e(TAG, b"_handle_request error", e)
Пример #4
0
def parse_http_headers(payload):
    # Implements simple HTTP1Connection._read_message but IO-free.
    lines = payload.splitlines()
    # Drop start line
    lines.pop(0)
    # Drop contents
    if '' in lines:
        lines[:] = lines[:lines.index('')]
    return (
        parse_request_start_line('GET / HTTP/1.1'),
        HTTPHeaders.parse('\r\n'.join(lines)),
    )
Пример #5
0
 def handle_http_connection(self, conn):
     # Read the trailing HTTP request and process it with protocol_switcher.
     # We can't rely on ioloop to trigger read because it has been already
     # triggered for SSL handshake.
     addr, port = conn.stream.socket.getsockname()
     try:
         # This is not blocking. Just read available bytes.
         payload = conn.stream.socket.recv(1024)
     except Exception:
         # Exception includes EWOULDBLOCK, when no bytes are available. In
         # this case just skip.
         payload = ""
     else:
         logger.debug("Received %r", payload[:128])
     # Simulate conn._read_message side effect. This is required by
     # HTTP1Connection.write_headers()
     conn._request_start_line = parse_request_start_line('GET / HTTP/1.1')
     conn._request_headers = HTTPHeaders()
     try:
         start_line, headers = parse_http_headers(payload)
         conn._request_start_line = start_line
         request = HTTPRequest(
             connection=conn,
             headers=headers,
             start_line=start_line,
         )
         request.config = self.request_callback.config
         response = protocol_switcher(request)
     except Exception as e:
         logger.error("Failed to switch to HTTPS: %s", e)
         response = HTTPResponse(
             request=object(),
             code=500,
             headers=HTTPHeaders({'Content-Length': '0'}),
             effective_url='https://useless_effective_url')
     yield conn.write_headers(
         start_line=ResponseStartLine(
             'HTTP/1.1',
             response.code,
             response.reason,
         ),
         headers=response.headers,
     )
Пример #6
0
    def _read_message(self, delegate):
        need_delegate_close = False
        try:
            header_future = self.stream.read_until_regex(
                b"\r?\n\r?\n",
                max_bytes=self.params.max_header_size)
            if self.params.header_timeout is None:
                header_data = yield header_future
            else:
                try:
                    header_data = yield gen.with_timeout(
                        self.stream.io_loop.time() + self.params.header_timeout,
                        header_future,
                        quiet_exceptions=iostream.StreamClosedError)
                except gen.TimeoutError:
                    self.close()
                    raise gen.Return(False)
            start_line, headers = self._parse_headers(header_data)
            if self.is_client:
                start_line = httputil.parse_response_start_line(start_line)
                self._response_start_line = start_line
            else:
                start_line = httputil.parse_request_start_line(start_line)
                self._request_start_line = start_line
                self._request_headers = headers

            self._disconnect_on_finish = not self._can_keep_alive(
                start_line, headers)
            need_delegate_close = True
            with _ExceptionLoggingContext(app_log):
                header_future = delegate.headers_received(start_line, headers)
                if header_future is not None:
                    yield header_future
            if self.stream is None:
                # We've been detached.
                need_delegate_close = False
                raise gen.Return(False)
            skip_body = False
            if self.is_client:
                if (self._request_start_line is not None and
                        self._request_start_line.method == 'HEAD'):
                    skip_body = True
                code = start_line.code
                if code == 304:
                    # 304 responses may include the content-length header
                    # but do not actually have a body.
                    # http://tools.ietf.org/html/rfc7230#section-3.3
                    skip_body = True
                if code >= 100 and code < 200:
                    # 1xx responses should never indicate the presence of
                    # a body.
                    if ('Content-Length' in headers or
                            'Transfer-Encoding' in headers):
                        raise httputil.HTTPInputError(
                            "Response code %d cannot have body" % code)
                    # TODO: client delegates will get headers_received twice
                    # in the case of a 100-continue.  Document or change?
                    yield self._read_message(delegate)
            else:
                if (headers.get("Expect") == "100-continue" and
                        not self._write_finished):
                    self.stream.write(b"HTTP/1.1 100 (Continue)\r\n\r\n")
            if not skip_body:
                body_future = self._read_body(
                    start_line.code if self.is_client else 0, headers, delegate)
                if body_future is not None:
                    if self._body_timeout is None:
                        yield body_future
                    else:
                        try:
                            yield gen.with_timeout(
                                self.stream.io_loop.time() + self._body_timeout,
                                body_future,
                                quiet_exceptions=iostream.StreamClosedError)
                        except gen.TimeoutError:
                            gen_log.info("Timeout reading body from %s",
                                         self.context)
                            self.stream.close()
                            raise gen.Return(False)
            self._read_finished = True
            if not self._write_finished or self.is_client:
                need_delegate_close = False
                with _ExceptionLoggingContext(app_log):
                    delegate.finish()
            # If we're waiting for the application to produce an asynchronous
            # response, and we're not detached, register a close callback
            # on the stream (we didn't need one while we were reading)
            if (not self._finish_future.done() and
                    self.stream is not None and
                    not self.stream.closed()):
                self.stream.set_close_callback(self._on_connection_close)
                yield self._finish_future
            if self.is_client and self._disconnect_on_finish:
                self.close()
            if self.stream is None:
                raise gen.Return(False)
        except httputil.HTTPInputError as e:
            gen_log.info("Malformed HTTP message from %s: %s",
                         self.context, e)
            if not self.is_client:
                yield self.stream.write(b'HTTP/1.1 400 Bad Request\r\n\r\n')
            self.close()
            raise gen.Return(False)
        finally:
            if need_delegate_close:
                with _ExceptionLoggingContext(app_log):
                    delegate.on_connection_close()
            header_future = None
            self._clear_callbacks()
        raise gen.Return(True)
Пример #7
0
 def test_parse_request_start_line(self):
     start_line = " ".join([self.METHOD, self.PATH, self.VERSION])
     parsed_start_line = parse_request_start_line(start_line)
     self.assertEqual(parsed_start_line.method, self.METHOD)
     self.assertEqual(parsed_start_line.path, self.PATH)
     self.assertEqual(parsed_start_line.version, self.VERSION)
Пример #8
0
 async def _read_message(self, delegate: httputil.HTTPMessageDelegate) -> bool:
     need_delegate_close = False
     try:
         header_future = self.stream.read_until_regex(
             b"\r?\n\r?\n", max_bytes=self.params.max_header_size
         )
         if self.params.header_timeout is None:
             header_data = await header_future
         else:
             try:
                 header_data = await gen.with_timeout(
                     self.stream.io_loop.time() + self.params.header_timeout,
                     header_future,
                     quiet_exceptions=iostream.StreamClosedError,
                 )
             except gen.TimeoutError:
                 self.close()
                 return False
         start_line_str, headers = self._parse_headers(header_data)
         if self.is_client:
             resp_start_line = httputil.parse_response_start_line(start_line_str)
             self._response_start_line = resp_start_line
             start_line = (
                 resp_start_line
             )  # type: Union[httputil.RequestStartLine, httputil.ResponseStartLine]
             # TODO: this will need to change to support client-side keepalive
             self._disconnect_on_finish = False
         else:
             req_start_line = httputil.parse_request_start_line(start_line_str)
             self._request_start_line = req_start_line
             self._request_headers = headers
             start_line = req_start_line
             self._disconnect_on_finish = not self._can_keep_alive(
                 req_start_line, headers
             )
         need_delegate_close = True
         with _ExceptionLoggingContext(app_log):
             header_recv_future = delegate.headers_received(start_line, headers)
             if header_recv_future is not None:
                 await header_recv_future
         if self.stream is None:
             # We've been detached.
             need_delegate_close = False
             return False
         skip_body = False
         if self.is_client:
             assert isinstance(start_line, httputil.ResponseStartLine)
             if (
                 self._request_start_line is not None
                 and self._request_start_line.method == "HEAD"
             ):
                 skip_body = True
             code = start_line.code
             if code == 304:
                 # 304 responses may include the content-length header
                 # but do not actually have a body.
                 # http://tools.ietf.org/html/rfc7230#section-3.3
                 skip_body = True
             if 100 <= code < 200:
                 # 1xx responses should never indicate the presence of
                 # a body.
                 if "Content-Length" in headers or "Transfer-Encoding" in headers:
                     raise httputil.HTTPInputError(
                         "Response code %d cannot have body" % code
                     )
                 # TODO: client delegates will get headers_received twice
                 # in the case of a 100-continue.  Document or change?
                 await self._read_message(delegate)
         else:
             if headers.get("Expect") == "100-continue" and not self._write_finished:
                 self.stream.write(b"HTTP/1.1 100 (Continue)\r\n\r\n")
         if not skip_body:
             body_future = self._read_body(
                 resp_start_line.code if self.is_client else 0, headers, delegate
             )
             if body_future is not None:
                 if self._body_timeout is None:
                     await body_future
                 else:
                     try:
                         await gen.with_timeout(
                             self.stream.io_loop.time() + self._body_timeout,
                             body_future,
                             quiet_exceptions=iostream.StreamClosedError,
                         )
                     except gen.TimeoutError:
                         gen_log.info("Timeout reading body from %s", self.context)
                         self.stream.close()
                         return False
         self._read_finished = True
         if not self._write_finished or self.is_client:
             need_delegate_close = False
             with _ExceptionLoggingContext(app_log):
                 delegate.finish()
         # If we're waiting for the application to produce an asynchronous
         # response, and we're not detached, register a close callback
         # on the stream (we didn't need one while we were reading)
         if (
             not self._finish_future.done()
             and self.stream is not None
             and not self.stream.closed()
         ):
             self.stream.set_close_callback(self._on_connection_close)
             await self._finish_future
         if self.is_client and self._disconnect_on_finish:
             self.close()
         if self.stream is None:
             return False
     except httputil.HTTPInputError as e:
         gen_log.info("Malformed HTTP message from %s: %s", self.context, e)
         if not self.is_client:
             await self.stream.write(b"HTTP/1.1 400 Bad Request\r\n\r\n")
         self.close()
         return False
     finally:
         if need_delegate_close:
             with _ExceptionLoggingContext(app_log):
                 delegate.on_connection_close()
         header_future = None  # type: ignore
         self._clear_callbacks()
     return True
Пример #9
0
    def request(self, conn, src, dst):
        keep_alive = True
        req = None
        conn = conn.copy()

        req_line = yield 
        while keep_alive and req_line is not None:
            body = ""
            headers = MultiOrderedDict()
            try:
                req = httputil.parse_request_start_line(req_line.strip())
            except httputil.HTTPInputError:
                if req_line != "":
                    self.log("HTTP Error: Malformed request start line: '{}'", req_line)
                req_line = yield
                continue
            while True:
                header_line = yield
                if header_line is None:
                    break
                if not header_line.strip():
                    break
                self.parse_header_line(headers, header_line.strip())

            if req.version == "HTTP/1.0":
                keep_alive = headers.last("connection", "").lower().strip() == "keep-alive"
            else:
                keep_alive = headers.last("connection", "").lower().strip() != "close"

            if "content-length" in headers:
                try:
                    content_length = int(headers.last("content-length"))
                except ValueError:
                    content_length = None
            else:
                content_length = None

            if req.method != "POST":
                content_length = content_length or 0

            
            if header_line is not None:
                #body += conn["lbl_buffers"][dst]
                #conn["lbl_buffers"][dst] = ""
                conn["lbl_disable"](dst)
                while len(body) < content_length or content_length is None:
                    data = yield
                    if data is None:
                        break
                    body += data

            if "content-encoding" in headers:
                conn["http_decoded"] = False
                encoding = headers.last("content-encoding")
                self.log("req encoding: {}", encoding)
                if encoding in self.DECODERS:
                    try:
                        body = self.DECODERS[encoding](body)
                        conn["http_decoded"] = True
                    except:
                        self.log("Unable to decode content '{}' len={}/{}", encoding, len(body), content_length)
            else:
                conn["http_decoded"] = True

            conn["lbl_enable"](dst)
            conn["http_headers"] = headers
            conn["http_request"] = req
            req_line = yield self.bubble(dst, conn, body)
Пример #10
0
    def _read_message(self, delegate):
        need_delegate_close = False
        try:
            header_future = self.stream.read_until_regex(
                b"\r?\n\r?\n", max_bytes=self.params.max_header_size)
            if self.params.header_timeout is None:
                header_data = yield header_future
            else:
                try:
                    header_data = yield gen.with_timeout(
                        self.stream.io_loop.time() +
                        self.params.header_timeout,
                        header_future,
                        io_loop=self.stream.io_loop)
                except gen.TimeoutError:
                    self.close()
                    raise gen.Return(False)
            start_line, headers = self._parse_headers(header_data)
            if self.is_client:
                start_line = httputil.parse_response_start_line(start_line)
                self._response_start_line = start_line
            else:
                start_line = httputil.parse_request_start_line(start_line)
                self._request_start_line = start_line
                self._request_headers = headers

            self._disconnect_on_finish = not self._can_keep_alive(
                start_line, headers)
            need_delegate_close = True
            header_future = delegate.headers_received(start_line, headers)
            if header_future is not None:
                yield header_future
            if self.stream is None:
                # We've been detached.
                need_delegate_close = False
                raise gen.Return(False)
            skip_body = False
            if self.is_client:
                if (self._request_start_line is not None
                        and self._request_start_line.method == 'HEAD'):
                    skip_body = True
                code = start_line.code
                if code == 304:
                    skip_body = True
                if code >= 100 and code < 200:
                    # TODO: client delegates will get headers_received twice
                    # in the case of a 100-continue.  Document or change?
                    yield self._read_message(delegate)
            else:
                if (headers.get("Expect") == "100-continue"
                        and not self._write_finished):
                    self.stream.write(b"HTTP/1.1 100 (Continue)\r\n\r\n")
            if not skip_body:
                body_future = self._read_body(headers, delegate)
                if body_future is not None:
                    if self._body_timeout is None:
                        yield body_future
                    else:
                        try:
                            yield gen.with_timeout(
                                self.stream.io_loop.time() +
                                self._body_timeout, body_future,
                                self.stream.io_loop)
                        except gen.TimeoutError:
                            gen_log.info("Timeout reading body from %s",
                                         self.context)
                            self.stream.close()
                            raise gen.Return(False)
            self._read_finished = True
            if not self._write_finished or self.is_client:
                need_delegate_close = False
                delegate.finish()
            # If we're waiting for the application to produce an asynchronous
            # response, and we're not detached, register a close callback
            # on the stream (we didn't need one while we were reading)
            if (not self._finish_future.done() and self.stream is not None
                    and not self.stream.closed()):
                self.stream.set_close_callback(self._on_connection_close)
                yield self._finish_future
            if self.is_client and self._disconnect_on_finish:
                self.close()
            if self.stream is None:
                raise gen.Return(False)
        except httputil.HTTPInputException as e:
            gen_log.info("Malformed HTTP message from %s: %s", self.context, e)
            self.close()
            raise gen.Return(False)
        finally:
            if need_delegate_close:
                delegate.on_connection_close()
            self._clear_callbacks()
        raise gen.Return(True)
    def _read_message(self, delegate):
        need_delegate_close = False
        try:
            header_future = self.stream.read_until_regex(
                b"\r?\n\r?\n",
                max_bytes=self.params.max_header_size)
            if self.params.header_timeout is None:
                header_data = yield header_future
            else:
                try:
                    header_data = yield gen.with_timeout(
                        self.stream.io_loop.time() + self.params.header_timeout,
                        header_future,
                        io_loop=self.stream.io_loop,
                        quiet_exceptions=iostream.StreamClosedError)
                except gen.TimeoutError:
                    self.close()
                    raise gen.Return(False)
            start_line, headers = self._parse_headers(header_data)
            if self.is_client:
                start_line = httputil.parse_response_start_line(start_line)
                self._response_start_line = start_line
            else:
                start_line = httputil.parse_request_start_line(start_line)
                self._request_start_line = start_line
                self._request_headers = headers

            self._disconnect_on_finish = not self._can_keep_alive(
                start_line, headers)
            need_delegate_close = True
            with _ExceptionLoggingContext():
                header_future = delegate.headers_received(start_line, headers)
                if header_future is not None:
                    yield header_future
            if self.stream is None:
                # We've been detached.
                need_delegate_close = False
                raise gen.Return(False)
            skip_body = False
            if self.is_client:
                if (self._request_start_line is not None and
                        self._request_start_line.method == 'HEAD'):
                    skip_body = True
                code = start_line.code
                if code == 304:
                    skip_body = True
                if code >= 100 and code < 200:
                    if ('Content-Length' in headers or
                            'Transfer-Encoding' in headers):
                        raise httputil.HTTPInputError(
                            "Response code %d cannot have body" % code)
                    yield self._read_message(delegate)
            else:
                if (headers.get("Expect") == "100-continue" and
                        not self._write_finished):
                    self.stream.write(b"HTTP/1.1 100 (Continue)\r\n\r\n")
            if not skip_body:
                body_future = self._read_body(
                    start_line.code if self.is_client else 0, headers, delegate)
                if body_future is not None:
                    if self._body_timeout is None:
                        yield body_future
                    else:
                        try:
                            yield gen.with_timeout(
                                self.stream.io_loop.time() + self._body_timeout,
                                body_future, self.stream.io_loop,
                                quiet_exceptions=iostream.StreamClosedError)
                        except gen.TimeoutError:
                            print("Timeout reading body from %s" % self.context)
                            self.stream.close()
                            raise gen.Return(False)
            self._read_finished = True
            if not self._write_finished or self.is_client:
                need_delegate_close = False
                with _ExceptionLoggingContext():
                    delegate.finish()
            if (not self._finish_future.done() and
                    self.stream is not None and
                    not self.stream.closed()):
                self.stream.set_close_callback(self._on_connection_close)
                yield self._finish_future
            if self.is_client and self._disconnect_on_finish:
                self.close()
            if self.stream is None:
                raise gen.Return(False)
        except httputil.HTTPInputError as e:
            print("Malformed HTTP message from %s: %s", (self.context, e))
            self.close()
            raise gen.Return(False)
        finally:
            if need_delegate_close:
                with _ExceptionLoggingContext():
                    delegate.on_connection_close()
            self._clear_callbacks()
        raise gen.Return(True)
    def _read_message(self, delegate):
        need_delegate_close = False
        try:
            # 消息头与消息体之间由一个空行分开
            header_future = self.stream.read_until_regex(
                b"\r?\n\r?\n",
                max_bytes=self.params.max_header_size)
            if self.params.header_timeout is None:
                header_data = yield header_future
            else:
                try:
                    header_data = yield gen.with_timeout(
                        self.stream.io_loop.time() + self.params.header_timeout,
                        header_future,
                        io_loop=self.stream.io_loop)
                except gen.TimeoutError:
                    self.close()
                    raise gen.Return(False)
            # 解析消息头,分离头字段和首行(request-line/status-line)
            start_line, headers = self._parse_headers(header_data)
            # 作为 client 解析的是 server 的 response,作为 server 解析的是 client 的 request。
            # response 与 request 的 start_line(status-line/request-line) 的字段内容不同:
            # 1. response's status-line: HTTP-Version SP Status-Code SP Reason-Phrase CRLF
            # 2. request's request-line:Method SP Request-URI SP HTTP-Version CRLF
            # start_line 的值是一个 namedtuple。
            if self.is_client:
                start_line = httputil.parse_response_start_line(start_line)
                self._response_start_line = start_line
            else:
                start_line = httputil.parse_request_start_line(start_line)
                self._request_start_line = start_line
                self._request_headers = headers

            # 非 keep-alive 的请求或响应处理完成后要关闭连接。
            self._disconnect_on_finish = not self._can_keep_alive(
                start_line, headers)
            need_delegate_close = True
            with _ExceptionLoggingContext(app_log):
                header_future = delegate.headers_received(start_line, headers)
                if header_future is not None:
                    # 如果 header_future 是一个 `Future` 实例,则要等到完成才读取 body。
                    yield header_future
            # websocket ???
            if self.stream is None:
                # We've been detached.
                need_delegate_close = False
                raise gen.Return(False)
            skip_body = False
            if self.is_client:
                # 作为 client 如果发起的是 HEAD 请求,那么 server response 应该无消息体
                if (self._request_start_line is not None and
                        self._request_start_line.method == 'HEAD'):
                    skip_body = True
                code = start_line.code
                if code == 304:
                    # 304 responses may include the content-length header
                    # but do not actually have a body.
                    # http://tools.ietf.org/html/rfc7230#section-3.3
                    skip_body = True
                if code >= 100 and code < 200:
                    # 1xx responses should never indicate the presence of
                    # a body.
                    if ('Content-Length' in headers or
                        'Transfer-Encoding' in headers):
                        raise httputil.HTTPInputError(
                            "Response code %d cannot have body" % code)
                    # TODO: client delegates will get headers_received twice
                    # in the case of a 100-continue.  Document or change?
                    yield self._read_message(delegate)
            else:
                # 100-continue 这个状态码是在 HTTP/1.1 中为了提高传输效率而设置的。当
                # client 需要 POST 较大数据给 WebServer 时,可以在发送 HTTP 请求时带上
                # Expect: 100-continue,WebServer 如果接受这个请求则应答一个
                # ``HTTP/1.1 100 (Continue)``,那么 client 就继续传输 request body,
                # 否则应答 ``HTTP/1.1 417 Expectation Failed`` client 就放弃传输剩余
                # 的数据。(注:Expect 头部域,用于指出客户端要求的特殊服务器行为采用扩展语法
                # 定义,以方便扩展。)
                if (headers.get("Expect") == "100-continue" and
                        not self._write_finished):
                    self.stream.write(b"HTTP/1.1 100 (Continue)\r\n\r\n")
            if not skip_body:
                body_future = self._read_body(
                    start_line.code if self.is_client else 0, headers, delegate)
                if body_future is not None:
                    if self._body_timeout is None:
                        yield body_future
                    else:
                        try:
                            yield gen.with_timeout(
                                self.stream.io_loop.time() + self._body_timeout,
                                body_future, self.stream.io_loop)
                        except gen.TimeoutError:
                            gen_log.info("Timeout reading body from %s",
                                         self.context)
                            self.stream.close()
                            raise gen.Return(False)
            self._read_finished = True
            # 对 client mode ,response 解析完成就调用 HTTPMessageDelegate.finish() 方法是合适的;
            # 对 server mode ,_write_finished 表示 response 是否发送完成,未完成前调用
            # HTTPMessageDelegate.finish() 方法让 delegate 执行请求响应;
            if not self._write_finished or self.is_client:
                need_delegate_close = False
                with _ExceptionLoggingContext(app_log):
                    delegate.finish()
            # If we're waiting for the application to produce an asynchronous
            # response, and we're not detached, register a close callback
            # on the stream (we didn't need one while we were reading)
            #
            # NOTE:_finish_future resolves when all data has been written and flushed
            # to the IOStream.
            #
            # hold 住执行流程,直到异步响应完成,所有数据都写入 fd,才继续后续处理,通常调用方执行 `finish` 方法
            # 设置 `_finish_future` 完成,详细见 `finish` 和 `_finish_request` 方法实现。
            if (not self._finish_future.done() and
                    self.stream is not None and
                    not self.stream.closed()):
                self.stream.set_close_callback(self._on_connection_close)
                yield self._finish_future
            # 对于 client mode,处理完响应后如果不是 keep-alive 就断开连接。
            # 对于 server mode,需要在 response 完成后才断开连接,详细见 _finish_request/finish 方法实现。
            if self.is_client and self._disconnect_on_finish:
                self.close()
            if self.stream is None:
                raise gen.Return(False)
        except httputil.HTTPInputError as e:
            gen_log.info("Malformed HTTP message from %s: %s",
                         self.context, e)
            self.close()
            raise gen.Return(False)
        finally:
            # 连接 “关闭” 前还没能结束处理请求(call HTTPMessageDelegate.finish()),则
            # call  HTTPMessageDelegate.on_connection_close()
            if need_delegate_close:
                with _ExceptionLoggingContext(app_log):
                    delegate.on_connection_close()
            self._clear_callbacks()
        raise gen.Return(True)
Пример #13
0
    def _read_message(self, delegate):
        need_delegate_close = False
        try:
            # 这一句会从 iostream 里异步地读取 header
            # 的内容,返回的是一个将会填充
            # header 内容的值的 future 对象。
            # HTTP 协议中,header 和 body 之间使用类似于 \r\n\r\n
            # 这样的被称为 CRLF 的字符串来划分,
            # 所以作者传入了下面的正则来保证刚好读完 header 的信息
            header_future = self.stream.read_until_regex(
                b"\r?\n\r?\n", max_bytes=self.params.max_header_size)
            if self.params.header_timeout is None:
                header_data = yield header_future
            else:
                # 如果设置了 timeout,则将 Future 对象转化为一个
                # gen._Timeout 对象,让 iostream 在
                # 限定时间内去填充 future 对象内的 result,否则会报超时的错。
                try:
                    header_data = yield gen.with_timeout(
                        self.stream.io_loop.time() +
                        self.params.header_timeout,
                        header_future,
                        io_loop=self.stream.io_loop,
                        quiet_exceptions=iostream.StreamClosedError)
                except gen.TimeoutError:
                    self.close()
                    raise gen.Return(False)

            # 将 header 信息转化为保存有响应信息的对象。因为客户端和服务端中
            # http 头信息的形式是不同的,所以要根据 connection 是客户端方还
            # 是服务端方来作不同的处理。
            # 客户端得到的为 start line 被叫做 status-line,结构类似于:
            # HTTP/1.1 200 OK
            # 服务端得到的 start line 被叫做
            # request-line,结构类似于:GET / HTTP/1.1
            start_line, headers = self._parse_headers(header_data)
            if self.is_client:
                start_line = httputil.parse_response_start_line(start_line)
                self._response_start_line = start_line
            else:
                start_line = httputil.parse_request_start_line(start_line)
                self._request_start_line = start_line
                self._request_headers = headers

            # 根据头信息来判断连接的保持规则。
            self._disconnect_on_finish = not self._can_keep_alive(
                start_line, headers)
            need_delegate_close = True

            # 在这个 Context 之下,如果代码抛出异常,将会把 exception 的信息
            # 用 app_log 当做日志输出
            with _ExceptionLoggingContext(app_log):
                # 将解析好的头信息交给 delegate,这时对于服务端来说,会根据
                # start line 中的 path 信息来匹配处理请求的那个 Handler。
                header_future = delegate.headers_received(start_line, headers)

                # 在 stream 的情况下,header_future 则不会是 None。
                if header_future is not None:
                    yield header_future
            if self.stream is None:
                # We've been detached.
                need_delegate_close = False
                raise gen.Return(False)

            skip_body = False

            if self.is_client:
                # 对于客户端而言如果使用这个连接的对象是客户端类型的,则进
                # 行头信息以及 HTTP 状态码来决定是否继续读取 body 数据。
                if (self._request_start_line is not None
                        and self._request_start_line.method == 'HEAD'):
                    skip_body = True
                code = start_line.code
                if code == 304:
                    # 304 responses may include the content-length header
                    # but do not actually have a body.
                    # http://tools.ietf.org/html/rfc7230#section-3.3
                    skip_body = True
                if code >= 100 and code < 200:
                    # 1xx responses should never indicate the presence of
                    # a body.
                    if ('Content-Length' in headers
                            or 'Transfer-Encoding' in headers):
                        raise httputil.HTTPInputError(
                            "Response code %d cannot have body" % code)
                    # TODO: client delegates will get headers_received twice
                    # in the case of a 100-continue.  Document or change?
                    yield self._read_message(delegate)
            else:
                # 对于服务端而言,不存在需要跳过 body 数据的情况。
                if (headers.get("Expect") == "100-continue" and
                        # 处理头中包含『Expect: 100-continue』的客户端请求。
                        not self._write_finished):
                    self.stream.write(b"HTTP/1.1 100 (Continue)\r\n\r\n")

            if not skip_body:
                body_future = self._read_body(
                    start_line.code if self.is_client else 0, headers,
                    delegate)
                if body_future is not None:
                    if self._body_timeout is None:
                        yield body_future
                    else:
                        try:
                            yield gen.with_timeout(
                                self.stream.io_loop.time() +
                                self._body_timeout,
                                body_future,
                                self.stream.io_loop,
                                quiet_exceptions=iostream.StreamClosedError)
                        except gen.TimeoutError:
                            gen_log.info("Timeout reading body from %s",
                                         self.context)
                            self.stream.close()
                            raise gen.Return(False)
            self._read_finished = True
            if not self._write_finished or self.is_client:
                # 服务端在这里便会调用到匹配到的 handler
                # 来向请求方写回返回数据。
                # 这时 _RequestDispatcher 对象中已经保有了 header 和 body 的数
                # 据,可以稍微处理一下(例如将表单数据 parse 一下)作为参数交给
                # Handler 处理了。
                need_delegate_close = False
                with _ExceptionLoggingContext(app_log):
                    delegate.finish()

            # If we're waiting for the application to produce an asynchronous
            # response, and we're not detached, register a close callback
            # on the stream (we didn't need one while we were reading)
            if (not self._finish_future.done() and self.stream is not None
                    and not self.stream.closed()):
                self.stream.set_close_callback(self._on_connection_close)
                yield self._finish_future
            if self.is_client and self._disconnect_on_finish:
                self.close()
            if self.stream is None:
                raise gen.Return(False)
        except httputil.HTTPInputError as e:
            gen_log.info("Malformed HTTP message from %s: %s", self.context, e)
            self.close()
            raise gen.Return(False)
        finally:
            if need_delegate_close:
                with _ExceptionLoggingContext(app_log):
                    delegate.on_connection_close()
            self._clear_callbacks()
        raise gen.Return(True)
Пример #14
0
Файл: http.py Проект: zeusk/lens
    def request(self, conn, src, dst):
        keep_alive = True
        req = None
        conn = conn.copy()

        req_line = yield
        while keep_alive and req_line is not None:
            body = ""
            headers = MultiOrderedDict()
            try:
                req = httputil.parse_request_start_line(req_line.strip())
            except httputil.HTTPInputError:
                if req_line != "":
                    self.log("HTTP Error: Malformed request start line: '{}'",
                             req_line)
                req_line = yield
                continue
            while True:
                header_line = yield
                if header_line is None:
                    break
                if not header_line.strip():
                    break
                self.parse_header_line(headers, header_line.strip())

            if req.version == "HTTP/1.0":
                keep_alive = headers.last("connection",
                                          "").lower().strip() == "keep-alive"
            else:
                keep_alive = headers.last("connection",
                                          "").lower().strip() != "close"

            if "content-length" in headers:
                try:
                    content_length = int(headers.last("content-length"))
                except ValueError:
                    content_length = None
            else:
                content_length = None

            if req.method != "POST":
                content_length = content_length or 0

            if header_line is not None:
                #body += conn["lbl_buffers"][dst]
                #conn["lbl_buffers"][dst] = ""
                conn["lbl_disable"](dst)
                while len(body) < content_length or content_length is None:
                    data = yield
                    if data is None:
                        break
                    body += data

            if "content-encoding" in headers:
                conn["http_decoded"] = False
                encoding = headers.last("content-encoding")
                self.log("req encoding: {}", encoding)
                if encoding in self.DECODERS:
                    try:
                        body = self.DECODERS[encoding](body)
                        conn["http_decoded"] = True
                    except:
                        self.log("Unable to decode content '{}' len={}/{}",
                                 encoding, len(body), content_length)
            else:
                conn["http_decoded"] = True

            conn["lbl_enable"](dst)
            conn["http_headers"] = headers
            conn["http_request"] = req
            req_line = yield self.bubble(dst, conn, body)
Пример #15
0
 def test_parse_request_start_line(self):
     start_line = " ".join([self.METHOD, self.PATH, self.VERSION])
     parsed_start_line = parse_request_start_line(start_line)
     self.assertEqual(parsed_start_line.method, self.METHOD)
     self.assertEqual(parsed_start_line.path, self.PATH)
     self.assertEqual(parsed_start_line.version, self.VERSION)
Пример #16
0
    def _read_message(self, delegate):
        need_delegate_close = False
        try:
            header_future = self.stream.read_until_regex(
                        b"\r?\n\r?\n",
                        max_bytes=self.params.max_header_size)
            if self.params.header_timeout is None:
                header_data = yield header_future
            else:
                try:
                    header_data = yield gen.with_timeout(
                        self.stream.io_loop.time() + self.params.header_timeout,
                        header_future,
                        io_loop=self.stream.io_loop)
                except gen.TimeoutError:
                    self.close()
                    raise gen.Return(False)
            start_line, headers = self._parse_headers(header_data)
            if self.is_client:
                start_line = httputil.parse_response_start_line(start_line)
                self._response_start_line = start_line
            else:
                start_line = httputil.parse_request_start_line(start_line)
                self._request_start_line = start_line
                self._request_headers = headers

            self._disconnect_on_finish = not self._can_keep_alive(
                start_line, headers)
            need_delegate_close = True
            header_future = delegate.headers_received(start_line, headers)
            if header_future is not None:
                yield header_future
            if self.stream is None:
                # We've been detached.
                need_delegate_close = False
                raise gen.Return(False)
            skip_body = False
            if self.is_client:
                if (self._request_start_line is not None and
                    self._request_start_line.method == 'HEAD'):
                    skip_body = True
                code = start_line.code
                if code == 304:
                    skip_body = True
                if code >= 100 and code < 200:
                    # TODO: client delegates will get headers_received twice
                    # in the case of a 100-continue.  Document or change?
                    yield self._read_message(delegate)
            else:
                if (headers.get("Expect") == "100-continue" and
                    not self._write_finished):
                    self.stream.write(b"HTTP/1.1 100 (Continue)\r\n\r\n")
            if not skip_body:
                body_future = self._read_body(headers, delegate)
                if body_future is not None:
                    if self._body_timeout is None:
                        yield body_future
                    else:
                        try:
                            yield gen.with_timeout(
                                self.stream.io_loop.time() + self._body_timeout,
                                body_future, self.stream.io_loop)
                        except gen.TimeoutError:
                            gen_log.info("Timeout reading body from %s",
                                         self.context)
                            self.stream.close()
                            raise gen.Return(False)
            self._read_finished = True
            if not self._write_finished or self.is_client:
                need_delegate_close = False
                delegate.finish()
            yield self._finish_future
            if self.is_client and self._disconnect_on_finish:
                self.close()
            if self.stream is None:
                raise gen.Return(False)
        except httputil.HTTPInputException as e:
            gen_log.info("Malformed HTTP message from %s: %s",
                         self.context, e)
            self.close()
            raise gen.Return(False)
        finally:
            if need_delegate_close:
                delegate.on_connection_close()
            self._clear_callbacks()
        raise gen.Return(True)
Пример #17
0
    def _read_message(self, delegate):
        need_delegate_close = False
        try:
            header_future = self.stream.read_until_regex(
                b"\r?\n\r?\n", max_bytes=self.params.max_header_size)
            if self.params.header_timeout is None:
                header_data = yield header_future
            else:
                try:
                    header_data = yield gen.with_timeout(
                        self.stream.io_loop.time() +
                        self.params.header_timeout,
                        header_future,
                        io_loop=self.stream.io_loop,
                        quiet_exceptions=iostream.StreamClosedError)
                except gen.TimeoutError:
                    self.close()
                    raise gen.Return(False)
            start_line, headers = self._parse_headers(header_data)
            if self.is_client:
                start_line = httputil.parse_response_start_line(start_line)
                self._response_start_line = start_line
            else:
                start_line = httputil.parse_request_start_line(start_line)
                self._request_start_line = start_line
                self._request_headers = headers

            self._disconnect_on_finish = not self._can_keep_alive(
                start_line, headers)
            need_delegate_close = True
            with _ExceptionLoggingContext(app_log):
                header_future = delegate.headers_received(start_line, headers)
                if header_future is not None:
                    yield header_future
            if self.stream is None:
                # We've been detached.
                need_delegate_close = False
                raise gen.Return(False)
            skip_body = False
            if self.is_client:
                if (self._request_start_line is not None
                        and self._request_start_line.method == 'HEAD'):
                    skip_body = True
                code = start_line.code
                if code == 304:
                    # 304 responses may include the content-length header
                    # but do not actually have a body.
                    # http://tools.ietf.org/html/rfc7230#section-3.3
                    skip_body = True
                if code >= 100 and code < 200:
                    # 1xx responses should never indicate the presence of
                    # a body.
                    if ('Content-Length' in headers
                            or 'Transfer-Encoding' in headers):
                        raise httputil.HTTPInputError(
                            "Response code %d cannot have body" % code)
                    # TODO: client delegates will get headers_received twice
                    # in the case of a 100-continue.  Document or change?
                    yield self._read_message(delegate)
            else:
                if (headers.get("Expect") == "100-continue"
                        and not self._write_finished):
                    self.stream.write(b"HTTP/1.1 100 (Continue)\r\n\r\n")
            if not skip_body:
                body_future = self._read_body(
                    start_line.code if self.is_client else 0, headers,
                    delegate)
                if body_future is not None:
                    if self._body_timeout is None:
                        yield body_future
                    else:
                        try:
                            yield gen.with_timeout(
                                self.stream.io_loop.time() +
                                self._body_timeout,
                                body_future,
                                self.stream.io_loop,
                                quiet_exceptions=iostream.StreamClosedError)
                        except gen.TimeoutError:
                            gen_log.info("Timeout reading body from %s",
                                         self.context)
                            self.stream.close()
                            raise gen.Return(False)
            self._read_finished = True
            if not self._write_finished or self.is_client:
                need_delegate_close = False
                with _ExceptionLoggingContext(app_log):
                    delegate.finish()
            # If we're waiting for the application to produce an asynchronous
            # response, and we're not detached, register a close callback
            # on the stream (we didn't need one while we were reading)
            if (not self._finish_future.done() and self.stream is not None
                    and not self.stream.closed()):
                self.stream.set_close_callback(self._on_connection_close)
                yield self._finish_future
            if self.is_client and self._disconnect_on_finish:
                self.close()
            if self.stream is None:
                raise gen.Return(False)
        except httputil.HTTPInputError as e:
            gen_log.info("Malformed HTTP message from %s: %s", self.context, e)
            self.close()
            raise gen.Return(False)
        finally:
            if need_delegate_close:
                with _ExceptionLoggingContext(app_log):
                    delegate.on_connection_close()
            self._clear_callbacks()
        raise gen.Return(True)
Пример #18
0
    def _read_message(self, delegate):
        need_delegate_close = False
        try:
            header_future = self.stream.read_until_regex(
                b"\r?\n\r?\n", max_bytes=self.params.max_header_size)
            if self.params.header_timeout is None:
                header_data = yield header_future
            else:
                try:
                    header_data = yield gen.with_timeout(
                        self.stream.io_loop.time() +
                        self.params.header_timeout,
                        header_future,
                        io_loop=self.stream.io_loop,
                        quiet_exceptions=iostream.StreamClosedError)
                except gen.TimeoutError:
                    self.close()
                    raise gen.Return(False)
            start_line, headers = self._parse_headers(header_data)
            if self.is_client:
                start_line = httputil.parse_response_start_line(start_line)
                self._response_start_line = start_line
            else:
                start_line = httputil.parse_request_start_line(start_line)
                self._request_start_line = start_line
                self._request_headers = headers

            self._disconnect_on_finish = not self._can_keep_alive(
                start_line, headers)
            need_delegate_close = True
            with _ExceptionLoggingContext():
                header_future = delegate.headers_received(start_line, headers)
                if header_future is not None:
                    yield header_future
            if self.stream is None:
                # We've been detached.
                need_delegate_close = False
                raise gen.Return(False)
            skip_body = False
            if self.is_client:
                if (self._request_start_line is not None
                        and self._request_start_line.method == 'HEAD'):
                    skip_body = True
                code = start_line.code
                if code == 304:
                    skip_body = True
                if code >= 100 and code < 200:
                    if ('Content-Length' in headers
                            or 'Transfer-Encoding' in headers):
                        raise httputil.HTTPInputError(
                            "Response code %d cannot have body" % code)
                    yield self._read_message(delegate)
            else:
                if (headers.get("Expect") == "100-continue"
                        and not self._write_finished):
                    self.stream.write(b"HTTP/1.1 100 (Continue)\r\n\r\n")
            if not skip_body:
                body_future = self._read_body(
                    start_line.code if self.is_client else 0, headers,
                    delegate)
                if body_future is not None:
                    if self._body_timeout is None:
                        yield body_future
                    else:
                        try:
                            yield gen.with_timeout(
                                self.stream.io_loop.time() +
                                self._body_timeout,
                                body_future,
                                self.stream.io_loop,
                                quiet_exceptions=iostream.StreamClosedError)
                        except gen.TimeoutError:
                            print("Timeout reading body from %s" %
                                  self.context)
                            self.stream.close()
                            raise gen.Return(False)
            self._read_finished = True
            if not self._write_finished or self.is_client:
                need_delegate_close = False
                with _ExceptionLoggingContext():
                    delegate.finish()
            if (not self._finish_future.done() and self.stream is not None
                    and not self.stream.closed()):
                self.stream.set_close_callback(self._on_connection_close)
                yield self._finish_future
            if self.is_client and self._disconnect_on_finish:
                self.close()
            if self.stream is None:
                raise gen.Return(False)
        except httputil.HTTPInputError as e:
            print("Malformed HTTP message from %s: %s", (self.context, e))
            self.close()
            raise gen.Return(False)
        finally:
            if need_delegate_close:
                with _ExceptionLoggingContext():
                    delegate.on_connection_close()
            self._clear_callbacks()
        raise gen.Return(True)