Exemple #1
0
 def wait(self, event: events.Event) -> layer.CommandGenerator[None]:
     """
     We wait for the current flow to be finished before parsing the next message,
     as we may want to upgrade to WebSocket or plain TCP before that.
     """
     assert self.stream_id
     if isinstance(event, events.DataReceived):
         return
     elif isinstance(event, events.ConnectionClosed):
         # for practical purposes, we assume that a peer which sent at least a FIN
         # is not interested in any more data from us, see
         # see https://github.com/httpwg/http-core/issues/22
         if event.connection.state is not ConnectionState.CLOSED:
             yield commands.CloseConnection(event.connection)
         yield ReceiveHttp(
             self.ReceiveProtocolError(
                 self.stream_id,
                 f"Client disconnected.",
                 code=status_codes.CLIENT_CLOSED_REQUEST))
     else:  # pragma: no cover
         raise AssertionError(f"Unexpected event: {event}")
Exemple #2
0
    def send(self, event: HttpEvent) -> layer.CommandGenerator[None]:
        assert event.stream_id == self.stream_id
        if isinstance(event, ResponseHeaders):
            self.response = response = event.response

            if response.is_http2:
                response = response.copy()
                # Convert to an HTTP/1 response.
                response.http_version = "HTTP/1.1"
                # not everyone supports empty reason phrases, so we better make up one.
                response.reason = status_codes.RESPONSES.get(
                    response.status_code, "")
                # Shall we set a Content-Length header here if there is none?
                # For now, let's try to modify as little as possible.

            raw = http1.assemble_response_head(response)
            yield commands.SendData(self.conn, raw)
        elif isinstance(event, ResponseData):
            assert self.response
            if "chunked" in self.response.headers.get("transfer-encoding",
                                                      "").lower():
                raw = b"%x\r\n%s\r\n" % (len(event.data), event.data)
            else:
                raw = event.data
            if raw:
                yield commands.SendData(self.conn, raw)
        elif isinstance(event, ResponseEndOfMessage):
            assert self.response
            if "chunked" in self.response.headers.get("transfer-encoding",
                                                      "").lower():
                yield commands.SendData(self.conn, b"0\r\n\r\n")
            yield from self.mark_done(response=True)
        elif isinstance(event, ResponseProtocolError):
            if not self.response:
                resp = http.make_error_response(event.code, event.message)
                raw = http1.assemble_response(resp)
                yield commands.SendData(self.conn, raw)
            yield commands.CloseConnection(self.conn)
        else:
            raise AssertionError(f"Unexpected event: {event}")
Exemple #3
0
 def mark_done(self,
               *,
               request: bool = False,
               response: bool = False) -> layer.CommandGenerator[None]:
     if request:
         self.request_done = True
     if response:
         self.response_done = True
     if self.request_done and self.response_done:
         assert self.request
         assert self.response
         if should_make_pipe(self.request, self.response):
             yield from self.make_pipe()
             return
         connection_done = (
             http1_sansio.expected_http_body_size(self.request,
                                                  self.response) == -1
             or http1.connection_close(self.request.http_version,
                                       self.request.headers)
             or http1.connection_close(self.response.http_version,
                                       self.response.headers)
             # If we proxy HTTP/2 to HTTP/1, we only use upstream connections for one request.
             # This simplifies our connection management quite a bit as we can rely on
             # the proxyserver's max-connection-per-server throttling.
             or (self.request.is_http2 and isinstance(self, Http1Client)))
         if connection_done:
             yield commands.CloseConnection(self.conn)
             self.state = self.done
             return
         self.request_done = self.response_done = False
         self.request = self.response = None
         if isinstance(self, Http1Server):
             self.stream_id += 2
         else:
             self.stream_id = None
         self.state = self.read_headers
         if self.buf:
             yield from self.state(events.DataReceived(self.conn, b""))
Exemple #4
0
def test_dataclasses(tconn):
    assert repr(commands.SendData(tconn, b"foo"))
    assert repr(commands.OpenConnection(tconn))
    assert repr(commands.CloseConnection(tconn))
    assert repr(commands.GetSocket(tconn))
    assert repr(commands.Log("hello", "info"))
Exemple #5
0
    def relay_messages(
            self,
            event: events.ConnectionEvent) -> layer.CommandGenerator[None]:
        from_client = event.connection == self.context.client
        from_str = 'client' if from_client else 'server'
        if from_client:
            src_ws = self.client_ws
            dst_ws = self.server_ws
        else:
            src_ws = self.server_ws
            dst_ws = self.client_ws

        if isinstance(event, events.DataReceived):
            src_ws.receive_data(event.data)
        elif isinstance(event, events.ConnectionClosed):
            src_ws.receive_data(None)
        else:  # pragma: no cover
            raise AssertionError(f"Unexpected event: {event}")

        for ws_event in src_ws.events():
            if isinstance(ws_event, wsproto.events.Message):
                src_ws.frame_buf.append(ws_event.data)

                if ws_event.message_finished:
                    if isinstance(ws_event, wsproto.events.TextMessage):
                        frame_type = Opcode.TEXT
                        content = "".join(src_ws.frame_buf)  # type: ignore
                    else:
                        frame_type = Opcode.BINARY
                        content = b"".join(src_ws.frame_buf)  # type: ignore

                    fragmentizer = Fragmentizer(src_ws.frame_buf)
                    src_ws.frame_buf.clear()

                    message = websocket.WebSocketMessage(
                        frame_type, from_client, content)
                    self.flow.messages.append(message)
                    yield WebsocketMessageHook(self.flow)

                    assert not message.killed  # this is deprecated, instead we should have .content set to emptystr.

                    for msg in fragmentizer(message.content):
                        yield dst_ws.send2(msg)

            elif isinstance(ws_event,
                            (wsproto.events.Ping, wsproto.events.Pong)):
                yield commands.Log(
                    f"Received WebSocket {ws_event.__class__.__name__.lower()} from {from_str} "
                    f"(payload: {bytes(ws_event.payload)!r})")
                yield dst_ws.send2(ws_event)
            elif isinstance(ws_event, wsproto.events.CloseConnection):
                self.flow.close_sender = from_str
                self.flow.close_code = ws_event.code
                self.flow.close_reason = ws_event.reason

                for ws in [self.server_ws, self.client_ws]:
                    if ws.state in {
                            ConnectionState.OPEN,
                            ConnectionState.REMOTE_CLOSING
                    }:
                        # response == original event, so no need to differentiate here.
                        yield ws.send2(ws_event)
                    yield commands.CloseConnection(ws.conn)
                if ws_event.code in {1000, 1001, 1005}:
                    yield WebsocketEndHook(self.flow)
                else:
                    self.flow.error = flow.Error(
                        f"WebSocket Error: {format_close_event(ws_event)}")
                    yield WebsocketErrorHook(self.flow)
                self._handle_event = self.done
            else:  # pragma: no cover
                raise AssertionError(f"Unexpected WebSocket event: {ws_event}")
Exemple #6
0
 def send_close(self, half_close: bool) -> layer.CommandGenerator[None]:
     yield commands.CloseConnection(self.tunnel_connection,
                                    half_close=half_close)
Exemple #7
0
 def on_handshake_error(self, err: str) -> layer.CommandGenerator[None]:
     """Called if either receive_handshake_data returns an error or we receive a close during handshake."""
     yield commands.CloseConnection(self.tunnel_connection)
Exemple #8
0
 def _handle_event(self,
                   event: events.Event) -> layer.CommandGenerator[None]:
     if isinstance(event, events.DataReceived):
         yield commands.SendData(event.connection, event.data.lower())
     if isinstance(event, events.ConnectionClosed):
         yield commands.CloseConnection(event.connection)