async def send(self, message): message_type = message["type"] if self.flow.write_paused and not self.disconnected: await self.flow.drain() if self.disconnected: return if self.gunicorn_atoms is not None: self.gunicorn_atoms.on_asgi_message(message) if not self.response_started: # Sending response status line and headers if message_type != "http.response.start": msg = "Expected ASGI message 'http.response.start', but got '%s'." raise RuntimeError(msg % message_type) self.response_started = True self.waiting_for_100_continue = False status_code = message["status"] headers = self.default_headers + list(message.get("headers", [])) if CLOSE_HEADER in self.scope[ "headers"] and CLOSE_HEADER not in headers: headers = headers + [CLOSE_HEADER] if self.access_log and self.gunicorn_log is None: self.access_logger.info( '%s - "%s %s HTTP/%s" %d', get_client_addr(self.scope), self.scope["method"], get_path_with_query_string(self.scope), self.scope["http_version"], status_code, ) # Write response status line and headers content = [STATUS_LINE[status_code]] for name, value in headers: if HEADER_RE.search(name): raise RuntimeError("Invalid HTTP header name.") if HEADER_VALUE_RE.search(value): raise RuntimeError("Invalid HTTP header value.") name = name.lower() if name == b"content-length" and self.chunked_encoding is None: self.expected_content_length = int(value.decode()) self.chunked_encoding = False elif name == b"transfer-encoding" and value.lower( ) == b"chunked": self.expected_content_length = 0 self.chunked_encoding = True elif name == b"connection" and value.lower() == b"close": self.keep_alive = False content.extend([name, b": ", value, b"\r\n"]) if (self.chunked_encoding is None and self.scope["method"] != "HEAD" and status_code not in (204, 304)): # Neither content-length nor transfer-encoding specified self.chunked_encoding = True content.append(b"transfer-encoding: chunked\r\n") content.append(b"\r\n") self.transport.write(b"".join(content)) elif not self.response_complete: # Sending response body if message_type != "http.response.body": msg = "Expected ASGI message 'http.response.body', but got '%s'." raise RuntimeError(msg % message_type) body = message.get("body", b"") more_body = message.get("more_body", False) # Write response body if self.scope["method"] == "HEAD": self.expected_content_length = 0 elif self.chunked_encoding: if body: content = [b"%x\r\n" % len(body), body, b"\r\n"] else: content = [] if not more_body: content.append(b"0\r\n\r\n") self.transport.write(b"".join(content)) else: num_bytes = len(body) if num_bytes > self.expected_content_length: raise RuntimeError( "Response content longer than Content-Length") else: self.expected_content_length -= num_bytes self.transport.write(body) # Handle response completion if not more_body: if self.expected_content_length != 0: raise RuntimeError( "Response content shorter than Content-Length") self.response_complete = True self.scope["response_end_time"] = time.monotonic() if self.gunicorn_log is not None: try: self.gunicorn_log.access_log.info( self.gunicorn_log.cfg.access_log_format, self.gunicorn_atoms, ) except: # noqa self.gunicorn_log.error(traceback.format_exc()) self.message_event.set() if not self.keep_alive: self.transport.close() self.on_response() else: # Response already sent msg = "Unexpected ASGI message '%s' sent, after response already completed." raise RuntimeError(msg % message_type)
async def send(self, message): message_type = message["type"] if self.flow.write_paused and not self.disconnected: await self.flow.drain() if self.disconnected: return if message_type == "http.response.push": push_stream_id = self.conn.get_next_available_stream_id() headers = [(b":authority", self.host), (b":method", b"GET"), (b":path", message["path"].encode("ascii")), (b":scheme", self.scheme)] + message["headers"] try: self.conn.push_stream(stream_id=self.stream_id, promised_stream_id=push_stream_id, request_headers=headers) self.transport.write(self.conn.data_to_send()) except h2.exceptions.ProtocolError: self.logger.debug("h2 protocol error.", exc_info=True) else: event = h2.events.RequestReceived() event.stream_id = push_stream_id event.headers = headers self.new_request(event) elif not self.response_started: # Sending response status line and headers if message_type != "http.response.start": msg = "Expected ASGI message 'http.response.start', but got '%s'." raise RuntimeError(msg % message_type) self.response_started = True status_code = message["status"] headers = ([(":status", str(status_code))] + self.default_headers + message.get("headers", [])) if self.access_log_enabled: self.access_logger.info( '%s - "%s %s HTTP/%s" %d', get_client_addr(self.scope), self.scope["method"], get_path_with_query_string(self.scope), self.scope["http_version"], status_code, extra={ "status_code": status_code, "scope": self.scope }, ) self.conn.send_headers(self.stream_id, headers, end_stream=False) self.transport.write(self.conn.data_to_send()) elif not self.response_completed: if message_type == "http.response.body": more_body = message.get("more_body", False) if self.scope["method"] == "HEAD": body = b"" else: body = message.get("body", b"") self.conn.send_data(self.stream_id, body, end_stream=not more_body) self.transport.write(self.conn.data_to_send()) if not more_body: self.response_completed = True else: msg = "Got unexpected ASGI message '%s'." raise RuntimeError(msg % message_type) if self.response_completed: self.on_response(self.stream_id)
async def send(self, message): message_type = message["type"] if self.flow.write_paused and not self.disconnected: await self.flow.drain() if self.disconnected: return if not self.response_started: # Sending response status line and headers if message_type != "http.response.start": msg = "Expected ASGI message 'http.response.start', but got '%s'." raise RuntimeError(msg % message_type) self.response_started = True self.waiting_for_100_continue = False status_code = message["status"] headers = self.default_headers + message.get("headers", []) if self.access_log: self.access_logger.info( '%s - "%s %s HTTP/%s" %d', get_client_addr(self.scope), self.scope["method"], get_path_with_query_string(self.scope), self.scope["http_version"], status_code, extra={ "status_code": status_code, "scope": self.scope }, ) # Write response status line and headers reason = STATUS_PHRASES[status_code] event = h11.Response(status_code=status_code, headers=headers, reason=reason) output = self.conn.send(event) self.transport.write(output) elif not self.response_complete: # Sending response body if message_type != "http.response.body": msg = "Expected ASGI message 'http.response.body', but got '%s'." raise RuntimeError(msg % message_type) body = message.get("body", b"") more_body = message.get("more_body", False) # Write response body if self.scope["method"] == "HEAD": event = h11.Data(data=b"") else: event = h11.Data(data=body) output = self.conn.send(event) self.transport.write(output) # Handle response completion if not more_body: self.response_complete = True event = h11.EndOfMessage() output = self.conn.send(event) self.transport.write(output) else: # Response already sent msg = "Unexpected ASGI message '%s' sent, after response already completed." raise RuntimeError(msg % message_type) if self.response_complete: if self.conn.our_state is h11.MUST_CLOSE or not self.keep_alive: event = h11.ConnectionClosed() self.conn.send(event) self.transport.close() self.on_response()
async def send(self, message): message_type = message["type"] if self.flow.write_paused and not self.disconnected: await self.flow.drain() if self.disconnected: return if self.gunicorn_atoms is not None: self.gunicorn_atoms.on_asgi_message(message) if not self.response_started: # Sending response status line and headers if message_type != "http.response.start": msg = "Expected ASGI message 'http.response.start', but got '%s'." raise RuntimeError(msg % message_type) self.response_started = True self.waiting_for_100_continue = False status_code = message["status"] headers = self.default_headers + message.get("headers", []) if CLOSE_HEADER in self.scope[ "headers"] and CLOSE_HEADER not in headers: headers = headers + [CLOSE_HEADER] if self.access_log and self.gunicorn_log is None: self.access_logger.info( '%s - "%s %s HTTP/%s" %d', get_client_addr(self.scope), self.scope["method"], get_path_with_query_string(self.scope), self.scope["http_version"], status_code, ) # Write response status line and headers reason = STATUS_PHRASES[status_code] event = h11.Response(status_code=status_code, headers=headers, reason=reason) output = self.conn.send(event) self.transport.write(output) elif not self.response_complete: # Sending response body if message_type != "http.response.body": msg = "Expected ASGI message 'http.response.body', but got '%s'." raise RuntimeError(msg % message_type) body = message.get("body", b"") more_body = message.get("more_body", False) # Write response body if self.scope["method"] == "HEAD": event = h11.Data(data=b"") else: event = h11.Data(data=body) output = self.conn.send(event) self.transport.write(output) # Handle response completion if not more_body: self.response_complete = True self.message_event.set() event = h11.EndOfMessage() output = self.conn.send(event) self.scope["response_end_time"] = time.monotonic() if self.gunicorn_log is not None: try: self.gunicorn_log.access_log.info( self.gunicorn_log.cfg.access_log_format, self.gunicorn_atoms, ) except: # noqa self.gunicorn_log.error(traceback.format_exc()) self.transport.write(output) else: # Response already sent msg = "Unexpected ASGI message '%s' sent, after response already completed." raise RuntimeError(msg % message_type) if self.response_complete: if self.conn.our_state is h11.MUST_CLOSE or not self.keep_alive: event = h11.ConnectionClosed() self.conn.send(event) self.transport.close() self.on_response()
def test_get_client_addr(scope, expected_client): assert get_client_addr(scope) == expected_client