async def read_data(self) -> None: if self.connection.they_are_waiting_for_100_continue: await self.asend( h11.InformationalResponse(status_code=100, headers=self.response_headers())) data = await self.stream.receive_some(MAX_RECV) self.connection.receive_data(data)
async def receive(self): if self.waiting_for_100_continue and not self.transport.is_closing(): event = h11.InformationalResponse( status_code=100, headers=[], reason="Continue" ) output = self.conn.send(event) self.transport.write(output) self.waiting_for_100_continue = False if not self.disconnected and not self.response_complete: self.flow.resume_reading() await self.message_event.wait() self.message_event.clear() if self.disconnected or self.response_complete: message = {"type": "http.disconnect"} else: message = { "type": "http.request", "body": self.body, "more_body": self.more_body, } self.body = b"" return message
async def stream_send(self, event: StreamEvent) -> None: if isinstance(event, Response): if event.status_code >= 200: await self._send_h11_event( h11.Response( headers=chain(event.headers, self.config.response_headers("h11")), status_code=event.status_code, )) else: await self._send_h11_event( h11.InformationalResponse( headers=chain(event.headers, self.config.response_headers("h11")), status_code=event.status_code, )) elif isinstance(event, Body): await self._send_h11_event(h11.Data(data=event.data)) elif isinstance(event, EndBody): await self._send_h11_event(h11.EndOfMessage()) elif isinstance(event, Data): await self.send(RawData(data=event.data)) elif isinstance(event, EndData): pass elif isinstance(event, StreamClosed): await self._maybe_recycle()
def _handle_events(self) -> None: if self.connection.they_are_waiting_for_100_continue: self._send( h11.InformationalResponse(status_code=100, headers=(('Date', ''), ('Server', ''))), ) while True: try: event = self.connection.next_event() except h11.ProtocolError: self._handle_error() self.transport.close() else: if isinstance(event, h11.Request): headers = CIMultiDict() for name, value in event.headers: headers.add(name.decode().title(), value.decode()) self.streams[0] = Stream(self.loop) self.handle_request( 0, event.method.decode().upper(), event.target.decode(), headers, ) elif isinstance(event, h11.EndOfMessage): self.streams[0].complete() elif isinstance(event, h11.Data): self.streams[0].append(event.data) elif event is h11.NEED_DATA: break if self.connection.our_state is h11.MUST_CLOSE: self.transport.close() elif self.connection.our_state is h11.DONE: self.connection.start_next_cycle()
def _handle_events(self) -> None: while True: if self.connection.they_are_waiting_for_100_continue: self._send( h11.InformationalResponse(status_code=100, headers=self.response_headers()), ) try: event = self.connection.next_event() except h11.RemoteProtocolError: self._handle_error() self.close() break else: if isinstance(event, h11.Request): headers = CIMultiDict() if event.http_version < b'1.1': headers.setdefault('host', self.app.config['SERVER_NAME'] or '') for name, value in event.headers: headers.add(name.decode().title(), value.decode()) if 'Upgrade' in headers: self._handle_upgrade_request(headers, event) break self.handle_request( 0, event.method.decode().upper(), event.target.decode(), headers, ) elif isinstance(event, h11.EndOfMessage): self.streams[0].complete() elif isinstance(event, h11.Data): self.streams[0].append(event.data) elif event is h11.NEED_DATA or event is h11.PAUSED: break elif isinstance(event, h11.ConnectionClosed): break if self.connection.our_state is h11.MUST_CLOSE: self.close()
async def handle_connection(self) -> None: try: # Loop over the requests in order of receipt (either # pipelined or due to keep-alive). while True: with trio.fail_after(self.config.keep_alive_timeout): request = await self.read_request() self.raise_if_upgrade(request, self.connection.trailing_data[0]) async with trio.open_nursery() as nursery: nursery.start_soon(self.handle_request, request) await self.read_body() await self.recycle_or_close() except (trio.BrokenResourceError, trio.ClosedResourceError): await self.asgi_put({"type": "http.disconnect"}) await self.aclose() except (trio.TooSlowError, MustCloseError): await self.aclose() except H2CProtocolRequired as error: await self.asend( h11.InformationalResponse(status_code=101, headers=[(b"upgrade", b"h2c")] + self.response_headers())) raise error except WrongProtocolError: raise # Do not close the connection
async def _check_protocol(self, event: h11.Request) -> None: upgrade_value = "" has_body = False for name, value in event.headers: sanitised_name = name.decode("latin1").strip().lower() if sanitised_name == "upgrade": upgrade_value = value.decode("latin1").strip() elif sanitised_name in {"content-length", "transfer-encoding"}: has_body = True # h2c Upgrade requests with a body are a pain as the body must # be fully recieved in HTTP/1.1 before the upgrade response # and HTTP/2 takes over, so Hypercorn ignores the upgrade and # responds in HTTP/1.1. Use a preflight OPTIONS request to # initiate the upgrade if really required (or just use h2). if upgrade_value.lower() == "h2c" and not has_body: await self._send_h11_event( h11.InformationalResponse( status_code=101, headers=self.config.response_headers("h11") + [(b"connection", b"upgrade"), (b"upgrade", b"h2c")], )) raise H2CProtocolRequired(self.connection.trailing_data[0], event) elif event.method == b"PRI" and event.target == b"*" and event.http_version == b"2.0": raise H2ProtocolAssumed(b"PRI * HTTP/2.0\r\n\r\n" + self.connection.trailing_data[0])
def accept(self, event, subprotocol=None): request = event.h11request request_headers = _normed_header_dict(request.headers) nonce = request_headers[b'sec-websocket-key'] accept_token = self._generate_accept_token(nonce) headers = { b"Upgrade": b'WebSocket', b"Connection": b'Upgrade', b"Sec-WebSocket-Accept": accept_token, } if subprotocol is not None: if subprotocol not in event.proposed_subprotocols: raise ValueError( "unexpected subprotocol {!r}".format(subprotocol)) headers[b'Sec-WebSocket-Protocol'] = subprotocol extensions = request_headers.get(b'sec-websocket-extensions', None) if extensions: accepts = self._extension_accept(extensions) if accepts: headers[b"Sec-WebSocket-Extensions"] = accepts response = h11.InformationalResponse(status_code=101, headers=headers.items()) self._outgoing += self._upgrade_connection.send(response) self._proto = FrameProtocol(self.client, self.extensions) self._state = ConnectionState.OPEN
async def _read_from_peer(self): if self.conn.they_are_waiting_for_100_continue: go_ahead = h11.InformationalResponse(status_code=100, headers=self.basic_headers()) await self.send(go_ahead) data = await self.stream.receive_some(4096) self.conn.receive_data(data)
def _accept(self, event): # type: (AcceptConnection) -> None request_headers = normed_header_dict( self._initiating_request.extra_headers) nonce = request_headers[b"sec-websocket-key"] accept_token = generate_accept_token(nonce) headers = [ (b"Upgrade", b"WebSocket"), (b"Connection", b"Upgrade"), (b"Sec-WebSocket-Accept", accept_token), ] if event.subprotocol is not None: if event.subprotocol not in self._initiating_request.subprotocols: raise LocalProtocolError("unexpected subprotocol {}".format( event.subprotocol)) headers.append((b"Sec-WebSocket-Protocol", event.subprotocol)) if event.extensions: accepts = handshake_extensions(self._initiating_request.extensions, event.extensions) if accepts: headers.append((b"Sec-WebSocket-Extensions", accepts)) response = h11.InformationalResponse(status_code=101, headers=headers + event.extra_headers) self._connection = Connection( ConnectionType.CLIENT if self.client else ConnectionType.SERVER, event.extensions, ) self._state = ConnectionState.OPEN return self._h11_connection.send(response)
async def _handle_events(self) -> None: while True: if self.connection.they_are_waiting_for_100_continue: await self._send_h11_event( h11.InformationalResponse( status_code=100, headers=self.config.response_headers("h11") ) ) if self.connection.our_state in {h11.DONE, h11.CLOSED, h11.MUST_CLOSE}: return try: event = self.connection.next_event() except h11.RemoteProtocolError: if self.connection.our_state in {h11.IDLE, h11.SEND_RESPONSE}: await self._send_error_response(400) await self.send(Closed()) break else: if isinstance(event, h11.Request): await self._check_protocol(event) await self._create_stream(event) elif isinstance(event, h11.Data): await self.stream.handle(Body(stream_id=STREAM_ID, data=event.data)) elif isinstance(event, h11.EndOfMessage): await self.stream.handle(EndBody(stream_id=STREAM_ID)) elif isinstance(event, Data): # WebSocket pass through await self.stream.handle(event) elif event is h11.PAUSED: await self.send(Updated()) await self.can_read.clear() await self.can_read.wait() elif isinstance(event, h11.ConnectionClosed) or event is h11.NEED_DATA: break
def _make_handshake( response_status, response_headers, subprotocols=None, extensions=None, auto_accept_key=True, ): client = WSConnection(CLIENT) server = h11.Connection(h11.SERVER) server.receive_data( client.send( Request( host="localhost", target="/", subprotocols=subprotocols or [], extensions=extensions or [], ) ) ) request = server.next_event() if auto_accept_key: full_request_headers = normed_header_dict(request.headers) response_headers.append( ( b"Sec-WebSocket-Accept", generate_accept_token(full_request_headers[b"sec-websocket-key"]), ) ) response = h11.InformationalResponse( status_code=response_status, headers=response_headers ) client.receive_data(server.send(response)) return list(client.events())
def test_connection_send_state() -> None: client = WSConnection(CLIENT) assert client.state is ConnectionState.CONNECTING server = h11.Connection(h11.SERVER) server.receive_data(client.send(Request( host="localhost", target="/", ))) headers = normed_header_dict(server.next_event().headers) response = h11.InformationalResponse( status_code=101, headers=[ (b"connection", b"Upgrade"), (b"upgrade", b"WebSocket"), ( b"Sec-WebSocket-Accept", generate_accept_token(headers[b"sec-websocket-key"]), ), ], ) client.receive_data(server.send(response)) assert len(list(client.events())) == 1 assert client.state is ConnectionState.OPEN # type: ignore # https://github.com/python/mypy/issues/9005 with pytest.raises(LocalProtocolError) as excinfo: client.send(Request(host="localhost", target="/")) client.receive_data(b"foobar") assert len(list(client.events())) == 1
def handle_events(self) -> None: # Called on receipt of data or after recycling the connection while True: if self.connection.they_are_waiting_for_100_continue: self.send( h11.InformationalResponse(status_code=100, headers=self.response_headers())) try: event = self.connection.next_event() except h11.RemoteProtocolError: self.send(self.error_response(400)) self.send(h11.EndOfMessage()) self.app_queue.put_nowait({"type": "http.disconnect"}) self.close() break else: if isinstance(event, h11.Request): self.stop_keep_alive_timeout() try: self.raise_if_upgrade(event, self.connection.trailing_data[0]) except H2CProtocolRequired as error: self.send( h11.InformationalResponse( status_code=101, headers=[(b"upgrade", b"h2c")] + self.response_headers(), )) raise error self.task = self.loop.create_task( self.handle_request(event)) self.task.add_done_callback(self.recycle_or_close) elif isinstance(event, h11.EndOfMessage): self.app_queue.put_nowait({ "type": "http.request", "body": b"", "more_body": False }) elif isinstance(event, h11.Data): self.app_queue.put_nowait({ "type": "http.request", "body": event.data, "more_body": True }) elif (isinstance(event, h11.ConnectionClosed) or event is h11.NEED_DATA or event is h11.PAUSED): break
def handle_upgrade(self, event: h11.Request): upgrade_value = None for name, value in self.headers: if name == b"upgrade": upgrade_value = value.lower() break if upgrade_value == b"websocket" and self.ws_protocol_class: self.connections.discard(self) output = [event.method, b" ", event.target, b" HTTP/1.1\r\n"] for name, value in self.headers: output += [name, b": ", value, b"\r\n"] output.append(b"\r\n") protocol = self.ws_protocol_class( config=self.config, server_state=self.server_state, on_connection_lost=self.on_connection_lost ) protocol.connection_made(self.transport) protocol.data_received(b"".join(output)) self.transport.set_protocol(protocol) elif upgrade_value == b"h2c": self.connections.discard(self) self.transport.write( self.conn.send( h11.InformationalResponse( status_code=101, headers=self.headers ) ) ) protocol = self.h2_protocol_class( config=self.config, server_state=self.server_state, on_connection_lost=self.on_connection_lost, _loop=self.loop ) protocol.handle_upgrade_from_h11(self.transport, event, self.headers) self.transport.set_protocol(protocol) else: msg = "Unsupported upgrade request." self.logger.warning(msg) reason = STATUS_PHRASES[400] headers = [ (b"content-type", b"text/plain; charset=utf-8"), (b"connection", b"close"), ] event = h11.Response(status_code=400, headers=headers, reason=reason) output = self.conn.send(event) self.transport.write(output) event = h11.Data(data=b"Unsupported upgrade request.") output = self.conn.send(event) self.transport.write(output) event = h11.EndOfMessage() output = self.conn.send(event) self.transport.write(output) self.transport.close()
async def _read(self): if self.conn.they_are_waiting_for_100_continue: go_ahead = h11.InformationalResponse(status_code=100) await self.writer.write(go_ahead) try: data = await self.reader.read(1000000) except ConnectionError: data = b'' self.conn.receive_data(data)
async def _read_from_peer(self): if self._conn.they_are_waiting_for_100_continue: go_ahead = h11.InformationalResponse(status_code=100, headers=self.basic_headers()) await self.send_h11(go_ahead) try: data = await self.socket.recv(settings.MAX_RECV) except ConnectionError: data = b"" self._conn.receive_data(data)
async def _read_from_peer(self) -> None: if self.conn.they_are_waiting_for_100_continue: go_ahead = h11.InformationalResponse(status_code=100, headers=self.basic_headers()) await self.send(go_ahead) try: data = await self.stream.receive(MAX_RECV) except (ConnectionError, EndOfStream): data = b"" self.conn.receive_data(data)
async def _read(self): if self.conn.they_are_waiting_for_100_continue: go_ahead = h11.InformationalResponse( status_code=100, headers=self.server.create_headers()) await self.send(go_ahead) try: data = await self.sock.recv(self.server.max_recv) except ConnectionError: data = b'' self.conn.receive_data(data)
async def _read_from_peer(self): if self.conn.they_are_waiting_for_100_continue: go_ahead = h11.InformationalResponse(status_code=100, headers=self.basic_headers()) await self.send(go_ahead) try: data = await self.sock.recv(MAX_RECV) except ConnectionError: # They've stopped listening. Not much we can do about it here. data = b"" self.conn.receive_data(data)
def test_handshake() -> None: response, nonce = _make_handshake([]) response.headers = sorted(response.headers) # For test determinism assert response == h11.InformationalResponse( status_code=101, headers=[ (b"connection", b"Upgrade"), (b"sec-websocket-accept", generate_accept_token(nonce)), (b"upgrade", b"WebSocket"), ], )
def accept(self, event, subprotocol=None): request = event.h11request request_headers = _normed_header_dict(request.headers) nonce = request_headers[b'sec-websocket-key'] accept_token = self._generate_accept_token(nonce) headers = { b"Upgrade": b'WebSocket', b"Connection": b'Upgrade', b"Sec-WebSocket-Accept": accept_token, } if subprotocol is not None: if subprotocol not in event.proposed_subprotocols: raise ValueError( "unexpected subprotocol {!r}".format(subprotocol)) headers[b'Sec-WebSocket-Protocol'] = subprotocol extensions = request_headers.get(b'sec-websocket-extensions', None) accepts = {} if extensions is not None: offers = _split_comma_header(extensions) for offer in offers: offer = offer.decode('ascii') name = offer.split(';', 1)[0].strip() for extension in self.extensions: if extension.name == name: accept = extension.accept(self, offer) if accept is True: accepts[extension.name] = True elif accept: accepts[extension.name] = accept.encode('ascii') if accepts: extensions = [] for name, params in accepts.items(): if params is True: extensions.append(name.encode('ascii')) else: # py34 annoyance: doesn't support bytestring formatting params = params.decode("ascii") extensions.append( ('%s; %s' % (name, params)).encode("ascii")) headers[b"Sec-WebSocket-Extensions"] = b', '.join(extensions) response = h11.InformationalResponse(status_code=101, headers=headers.items()) self._outgoing += self._upgrade_connection.send(response) self._proto = FrameProtocol(self.client, self.extensions) self._state = ConnectionState.OPEN
def _handle_upgrade_request(self, headers: CIMultiDict, event: h11.Request) -> None: self._timeout_handle.cancel() connection_tokens = headers.get('connection', '').lower().split(',') if ( any(token == 'upgrade' for token in connection_tokens) and headers.get('upgrade', '').lower() == 'websocket' ): raise WebsocketProtocolRequired(event) elif headers.get('upgrade', '').lower() == 'h2c': self._send(h11.InformationalResponse( status_code=101, headers=[('upgrade', 'h2c')] + self.response_headers(), )) raise H2CProtocolRequired(event) else: self._handle_error() self.close()
def server_handshake(self, subprotocols=None, extensions=None, **kwargs): headers = { 'upgrade': 'websocket', 'connection': 'upgrade', 'sec-websocket-accept': secondary_nonce_creator(self.nonce), 'sec-websocket-version': '13' } if subprotocols: headers['sec-websocket-protocol'] = self._addon_header_str_ifier( self.subprotocols) if extensions: headers['sec-websocket-extensions'] = self._addon_header_str_ifier( self.extensions) if kwargs: headers.update(kwargs) return h11.InformationalResponse(status_code=101, reason='Switching Protocols', headers=headers.items())
async def from_h11_connection(cls, h11_conn, upgrade_settings): """ Take a h11 connection and complete the upgrade Returns an initiated h2 connection. """ # Finish h11 part of the connection resp = h11.InformationalResponse( status_code=101, headers=[("Upgrade", "h2c")], ) await h11_conn.send(resp) instance = cls(h11_conn.socket) instance._conn.initiate_upgrade_connection(settings_header=upgrade_settings) await instance.sendall() return instance
def accept(self, event): request = event.h11request request_headers = dict(request.headers) nonce = request_headers[b'sec-websocket-key'] accept_token = self._generate_accept_token(nonce) headers = { b"Upgrade": b'WebSocket', b"Connection": b'Upgrade', b"Sec-WebSocket-Accept": accept_token, b"Sec-WebSocket-Version": self.version, } extensions = request_headers.get(b'sec-websocket-extensions', None) accepts = {} if extensions: offers = [e.strip() for e in extensions.split(b',')] for offer in offers: offer = offer.decode('ascii') name = offer.split(';', 1)[0].strip() for extension in self.extensions: if extension.name == name: accept = extension.accept(self, offer) if accept is True: accepts[extension.name] = True elif accept: accepts[extension.name] = accept.encode('ascii') if accepts: extensions = [] for name, params in accepts.items(): name = name.encode('ascii') if params is True: extensions.append(name) else: extensions.append(b'%s; %s' % (name, params)) headers[b"Sec-WebSocket-Extensions"] = b', '.join(extensions) response = h11.InformationalResponse(status_code=101, headers=headers.items()) self._outgoing += self._upgrade_connection.send(response) self._state = ConnectionState.OPEN
def _accept(self, event: AcceptConnection) -> bytes: # _accept is always called after _process_connection_request. assert self._initiating_request is not None request_headers = normed_header_dict( self._initiating_request.extra_headers) nonce = request_headers[b"sec-websocket-key"] accept_token = generate_accept_token(nonce) headers = [ (b"Upgrade", b"WebSocket"), (b"Connection", b"Upgrade"), (b"Sec-WebSocket-Accept", accept_token), ] if event.subprotocol is not None: if event.subprotocol not in self._initiating_request.subprotocols: raise LocalProtocolError( f"unexpected subprotocol {event.subprotocol}") headers.append( (b"Sec-WebSocket-Protocol", event.subprotocol.encode("ascii"))) if event.extensions: accepts = server_extensions_handshake( cast(Sequence[str], self._initiating_request.extensions), event.extensions, ) if accepts: headers.append((b"Sec-WebSocket-Extensions", accepts)) response = h11.InformationalResponse(status_code=101, headers=headers + event.extra_headers) self._connection = Connection( ConnectionType.CLIENT if self.client else ConnectionType.SERVER, event.extensions, ) self._state = ConnectionState.OPEN return self._h11_connection.send(response)
def _handle_upgrade_request(self, headers: CIMultiDict, event: h11.Request) -> None: self._keep_alive_timeout_handle.cancel() connection_tokens = headers.get('connection', '').lower().split(',') if (any(token.strip() == 'upgrade' for token in connection_tokens) and headers.get('upgrade', '').lower() == 'websocket' and event.method.decode().upper() == 'GET'): raise WebsocketProtocolRequired(event) # h2c Upgrade requests with a body are a pain as the body must # be fully recieved in HTTP/1.1 before the upgrade response # and HTTP/2 takes over, so Quart ignores the upgrade and # responds in HTTP/1.1. Use a preflight OPTIONS request to # initiate the upgrade if really required (or just use h2). elif (headers.get('upgrade', '').lower() == 'h2c' and 'Content-Length' not in headers and 'Transfer-Encoding' not in headers): self._send( h11.InformationalResponse( status_code=101, headers=[('upgrade', 'h2c')] + self.response_headers(), ), ) raise H2CProtocolRequired(event)
def maybe_upgrade_request(self, event: h11.Request) -> None: upgrade_value = '' connection_value = '' has_body = False for name, value in event.headers: sanitised_name = name.decode().lower() if sanitised_name == 'upgrade': upgrade_value = value.decode().strip() elif sanitised_name == 'connection': connection_value = value.decode().strip() elif sanitised_name == 'content-length': has_body = True elif sanitised_name == 'transfer-encoding': has_body = True connection_tokens = connection_value.lower().split(',') if ( any(token.strip() == 'upgrade' for token in connection_tokens) and upgrade_value.lower() == 'websocket' and event.method.decode().upper() == 'GET' ): self.stop_keep_alive_timeout() raise WebsocketProtocolRequired(event) # h2c Upgrade requests with a body are a pain as the body must # be fully recieved in HTTP/1.1 before the upgrade response # and HTTP/2 takes over, so Hypercorn ignores the upgrade and # responds in HTTP/1.1. Use a preflight OPTIONS request to # initiate the upgrade if really required (or just use h2). elif upgrade_value.lower() == 'h2c' and not has_body: self.stop_keep_alive_timeout() self.send( h11.InformationalResponse( status_code=101, headers=[(b'upgrade', b'h2c')] + self.response_headers(), ), ) raise H2CProtocolRequired(event)
def handle_events(self) -> None: while True: if self.connection.they_are_waiting_for_100_continue: self.send( h11.InformationalResponse(status_code=100, headers=self.response_headers()), ) try: event = self.connection.next_event() except h11.RemoteProtocolError: self.handle_error() self.close() break else: if isinstance(event, h11.Request): self.maybe_upgrade_request(event) # Raises on upgrade self.handle_request(event) elif isinstance(event, h11.EndOfMessage): self.app_queue.put_nowait({ 'type': 'http.request', 'body': b'', 'more_body': False, }) elif isinstance(event, h11.Data): self.app_queue.put_nowait({ 'type': 'http.request', 'body': event.data, 'more_body': True, }) elif ( isinstance(event, h11.ConnectionClosed) or event is h11.NEED_DATA or event is h11.PAUSED ): break if self.connection.our_state is h11.MUST_CLOSE: self.close()