async def send(self, message: Message) -> None: """ Awaited by the application to send ASGI `http` events. """ message_type = message["type"] self.logger.info("%s: '%s' event received from application.", self.state, message_type) if (self.state is HTTPCycleState.REQUEST and message_type == "http.response.start"): self.response["statusCode"] = message["status"] headers: typing.Dict[str, str] = {} multi_value_headers: typing.Dict[str, typing.List[str]] = {} for key, value in message.get("headers", []): lower_key = key.decode().lower() if lower_key in multi_value_headers: multi_value_headers[lower_key].append(value.decode()) elif lower_key in headers: multi_value_headers[lower_key] = [ headers.pop(lower_key), value.decode(), ] else: headers[lower_key] = value.decode() self.response["headers"] = headers if multi_value_headers: self.response["multiValueHeaders"] = multi_value_headers self.state = HTTPCycleState.RESPONSE elif (self.state is HTTPCycleState.RESPONSE and message_type == "http.response.body"): body = message.get("body", b"") more_body = message.get("more_body", False) # The body must be completely read before returning the response. self.body += body if not more_body: body = self.body # Check if a binary response should be returned based on the mime type # or content encoding. mimetype, _ = cgi.parse_header(self.response["headers"].get( "content-type", "text/plain")) if (mimetype not in self.text_mime_types and not mimetype.startswith("text/") ) or self.response["headers"].get("content-encoding") in [ "gzip", "br" ]: body = base64.b64encode(body) self.response["isBase64Encoded"] = True self.response["body"] = body.decode() self.state = HTTPCycleState.COMPLETE await self.app_queue.put({"type": "http.disconnect"}) else: raise UnexpectedMessage( f"{self.state}: Unexpected '{message_type}' event received.")
async def send(self, message: Message) -> None: """ Awaited by the application to send ASGI `websocket` events. """ message_type = message["type"] self.logger.info( "%s: '%s' event received from application.", self.state, message_type ) if self.state is WebSocketCycleState.HANDSHAKE and message_type in ( "websocket.accept", "websocket.close", ): # API Gateway handles the WebSocket client handshake in the connect event, # and it cannot be negotiated by the application directly. The handshake # behaviour is simulated to allow the application to accept or reject the # the client connection. This process does not support subprotocols. if message_type == "websocket.accept": await self.app_queue.put( {"type": "websocket.receive", "bytes": None, "text": self.body} ) elif message_type == "websocket.close": self.state = WebSocketCycleState.CLOSED raise WebSocketClosed elif ( self.state is WebSocketCycleState.RESPONSE and message_type == "websocket.send" ): # Message data sent from the application is posted to the WebSocket client # in API Gateway using an API call. message_text = message.get("text", "") self.websocket.post_to_connection(message_text.encode()) await self.app_queue.put({"type": "websocket.disconnect", "code": "1000"}) elif ( self.state is WebSocketCycleState.DISCONNECTING and message_type == "websocket.close" ): # ASGI connection is closing, however the WebSocket client in API Gateway # will persist and be used in future application ASGI connections until the # client disconnects or the application rejects a handshake. self.state = WebSocketCycleState.CLOSED else: raise UnexpectedMessage( f"{self.state}: Unexpected '{message_type}' event received." )
async def send(self, message: Message) -> None: """ Awaited by the application to send ASGI `lifespan` events. """ message_type = message["type"] self.logger.info("%s: '%s' event received from application.", self.state, message_type) if self.state is LifespanCycleState.CONNECTING: if self.lifespan == "on": raise LifespanFailure( "Lifespan connection failed during startup and lifespan is 'on'." ) # If a message is sent before the startup event is received by the # application, then assume that lifespan is unsupported. self.state = LifespanCycleState.UNSUPPORTED raise LifespanUnsupported("Lifespan protocol appears unsupported.") if message_type not in ( "lifespan.startup.complete", "lifespan.shutdown.complete", "lifespan.startup.failed", "lifespan.shutdown.failed", ): self.state = LifespanCycleState.FAILED raise UnexpectedMessage( f"Unexpected '{message_type}' event received.") if self.state is LifespanCycleState.STARTUP: if message_type == "lifespan.startup.complete": self.startup_event.set() elif message_type == "lifespan.startup.failed": self.state = LifespanCycleState.FAILED self.startup_event.set() message = message.get("message", "") raise LifespanFailure(f"Lifespan startup failure. {message}") elif self.state is LifespanCycleState.SHUTDOWN: if message_type == "lifespan.shutdown.complete": self.shutdown_event.set() elif message_type == "lifespan.shutdown.failed": self.state = LifespanCycleState.FAILED self.shutdown_event.set() message = message.get("message", "") raise LifespanFailure(f"Lifespan shutdown failure. {message}")
async def send(self, message: Message) -> None: """ Awaited by the application to send ASGI `http` events. """ message_type = message["type"] self.logger.info("%s: '%s' event received from application.", self.state, message_type) if (self.state is HTTPCycleState.REQUEST and message_type == "http.response.start"): self.response["statusCode"] = message["status"] self.response["headers"] = { k.decode().lower(): v.decode() for k, v in message.get("headers", []) } self.state = HTTPCycleState.RESPONSE elif (self.state is HTTPCycleState.RESPONSE and message_type == "http.response.body"): body = message.get("body", b"") more_body = message.get("more_body", False) # The body must be completely read before returning the response. self.body += body if not more_body: body = self.body mimetype, _ = cgi.parse_header(self.response["headers"].get( "content-type", "text/plain")) # Check if a binary response should be returned based on the mime type # or content encoding. if (mimetype not in self.text_mime_types and not mimetype.startswith("text/")) or self.response[ "headers"].get("content-encoding") == "gzip": body = base64.b64encode(body) self.response["isBase64Encoded"] = True self.response["body"] = body.decode() self.state = HTTPCycleState.COMPLETE await self.app_queue.put({"type": "http.disconnect"}) else: raise UnexpectedMessage( f"{self.state}: Unexpected '{message_type}' event received.")
async def send(self, message: Message) -> None: """ Awaited by the application to send ASGI `http` events. """ message_type = message["type"] self.logger.info("%s: '%s' event received from application.", self.state, message_type) if (self.state is HTTPCycleState.REQUEST and message_type == "http.response.start"): self.response["statusCode"] = message["status"] headers: typing.Dict[str, str] = {} multi_value_headers: typing.Dict[str, typing.List[str]] = {} cookies: typing.List[str] = [] event = self.scope["aws.event"] # ELB if "elb" in event["requestContext"]: for key, value in message.get("headers", []): lower_key = key.decode().lower() if lower_key in multi_value_headers: multi_value_headers[lower_key].append(value.decode()) else: multi_value_headers[lower_key] = [value.decode()] if "multiValueHeaders" not in event: # If there are multiple occurrences of headers, create case-mutated # variations: https://github.com/logandk/serverless-wsgi/issues/11 for key, values in multi_value_headers.items(): if len(values) > 1: for value, cased_key in zip( values, all_casings(key)): headers[cased_key] = value elif len(values) == 1: headers[key] = values[0] multi_value_headers = {} # API Gateway else: for key, value in message.get("headers", []): lower_key = key.decode().lower() if event.get( "version") == "2.0" and lower_key == "set-cookie": cookies.append(value.decode()) elif lower_key in multi_value_headers: multi_value_headers[lower_key].append(value.decode()) elif lower_key in headers: multi_value_headers[lower_key] = [ headers.pop(lower_key), value.decode(), ] else: headers[lower_key] = value.decode() self.response["headers"] = headers if multi_value_headers: self.response["multiValueHeaders"] = multi_value_headers if cookies: self.response["cookies"] = cookies self.state = HTTPCycleState.RESPONSE elif (self.state is HTTPCycleState.RESPONSE and message_type == "http.response.body"): body = message.get("body", b"") more_body = message.get("more_body", False) # The body must be completely read before returning the response. self.body.write(body) if not more_body: body = self.body.getvalue() self.body.close() # Check if a binary response should be returned based on the mime type # or content encoding. mimetype, _ = cgi.parse_header(self.response["headers"].get( "content-type", "text/plain")) if (mimetype not in self.text_mime_types and not mimetype.startswith("text/") ) or self.response["headers"].get("content-encoding") in [ "gzip", "br" ]: body = base64.b64encode(body) self.response["isBase64Encoded"] = True self.response["body"] = body.decode() self.state = HTTPCycleState.COMPLETE await self.app_queue.put({"type": "http.disconnect"}) else: raise UnexpectedMessage( f"{self.state}: Unexpected '{message_type}' event received.")