diff --git a/daphne/cli.py b/daphne/cli.py index 2c606a8..99e2454 100755 --- a/daphne/cli.py +++ b/daphne/cli.py @@ -1,7 +1,6 @@ import sys import argparse import logging -import importlib from .server import Server from .endpoints import build_endpoint_description_strings from .access import AccessLogGenerator diff --git a/daphne/http_protocol.py b/daphne/http_protocol.py index 3170d24..7f7fa89 100755 --- a/daphne/http_protocol.py +++ b/daphne/http_protocol.py @@ -198,7 +198,7 @@ class WebRequest(http.Request): """ if "type" not in message: raise ValueError("Message has no type defined") - if message["type"] == "http.response": + if message["type"] == "http.response.start": if self._response_started: raise ValueError("HTTP response has already been started") self._response_started = True @@ -213,33 +213,31 @@ class WebRequest(http.Request): header = header.encode("latin1") self.responseHeaders.addRawHeader(header, value) logger.debug("HTTP %s response started for %s", message["status"], self.client_addr) - elif message["type"] == "http.response.hunk": + elif message["type"] == "http.response.content": if not self._response_started: - raise ValueError("HTTP response has not yet been started but got %s" % message["type"]) + raise ValueError("HTTP response has not yet been started but got %s" % message["type"])# Write out body + http.Request.write(self, message.get("content", b"")) + + # End if there's no more content + if not message.get("more_content", False): + self.finish() + logger.debug("HTTP response complete for %s", self.client_addr) + try: + self.server.log_action("http", "complete", { + "path": self.uri.decode("ascii"), + "status": self.code, + "method": self.method.decode("ascii"), + "client": "%s:%s" % tuple(self.client_addr) if self.client_addr else None, + "time_taken": self.duration(), + "size": self.sentLength, + }) + except Exception as e: + logging.error(traceback.format_exc()) + else: + logger.debug("HTTP response chunk for %s", self.client_addr) else: raise ValueError("Cannot handle message type %s!" % message["type"]) - # Write out body - http.Request.write(self, message.get("content", b"")) - - # End if there's no more content - if not message.get("more_content", False): - self.finish() - logger.debug("HTTP response complete for %s", self.client_addr) - try: - self.server.log_action("http", "complete", { - "path": self.uri.decode("ascii"), - "status": self.code, - "method": self.method.decode("ascii"), - "client": "%s:%s" % tuple(self.client_addr) if self.client_addr else None, - "time_taken": self.duration(), - "size": self.sentLength, - }) - except Exception as e: - logging.error(traceback.format_exc()) - else: - logger.debug("HTTP response chunk for %s", self.client_addr) - def handle_exception(self, exception): """ Called by the server when our application tracebacks @@ -298,12 +296,14 @@ class WebRequest(http.Request): Responds with a server-level error page (very basic) """ self.handle_reply({ - "type": "http.response", + "type": "http.response.start", "status": status, - "status_text": status_text, "headers": [ (b"Content-Type", b"text/html; charset=utf-8"), ], + }) + self.handle_reply({ + "type": "http.response.content", "content": (self.error_template % { "title": six.text_type(status) + " " + status_text.decode("ascii"), "body": body, diff --git a/tests/http_base.py b/tests/http_base.py index 13267a6..74be5eb 100644 --- a/tests/http_base.py +++ b/tests/http_base.py @@ -158,7 +158,10 @@ class DaphneTestCase(unittest.TestCase): body=body, headers=headers, xff=xff, - responses=[{"type": "http.response", "status": 200, "content": b"OK"}], + responses=[ + {"type": "http.response.start", "status": 200}, + {"type": "http.response.content", "content": b"OK"}, + ], ) return scope, messages @@ -247,7 +250,6 @@ class DaphneTestCase(unittest.TestCase): frame += b"\0\0\0\0" # Payload frame += value - print("sending %r" % frame) sock.sendall(frame) def receive_from_socket(self, sock, length, timeout=1): diff --git a/tests/test_http_response.py b/tests/test_http_response.py index 4da453b..957b7c6 100644 --- a/tests/test_http_response.py +++ b/tests/test_http_response.py @@ -27,8 +27,11 @@ class TestHTTPResponse(DaphneTestCase): """ response = self.run_daphne_response([ { - "type": "http.response", + "type": "http.response.start", "status": 200, + }, + { + "type": "http.response.content", "content": b"hello world", }, ]) @@ -45,7 +48,10 @@ class TestHTTPResponse(DaphneTestCase): with self.assertRaises(ValueError): self.run_daphne_response([ { - "type": "http.response", + "type": "http.response.start", + }, + { + "type": "http.response.content", "content": b"hello world", }, ]) @@ -56,14 +62,65 @@ class TestHTTPResponse(DaphneTestCase): """ response = self.run_daphne_response([ { - "type": "http.response", + "type": "http.response.start", "status": 201, + }, + { + "type": "http.response.content", "content": b"i made a thing!", }, ]) self.assertEqual(response.status, 201) self.assertEqual(response.read(), b"i made a thing!") + def test_chunked_response(self): + """ + Tries sending a response in multiple parts. + """ + response = self.run_daphne_response([ + { + "type": "http.response.start", + "status": 201, + }, + { + "type": "http.response.content", + "content": b"chunk 1 ", + "more_content": True, + }, + { + "type": "http.response.content", + "content": b"chunk 2", + }, + ]) + self.assertEqual(response.status, 201) + self.assertEqual(response.read(), b"chunk 1 chunk 2") + + def test_chunked_response_empty(self): + """ + Tries sending a response in multiple parts and an empty end. + """ + response = self.run_daphne_response([ + { + "type": "http.response.start", + "status": 201, + }, + { + "type": "http.response.content", + "content": b"chunk 1 ", + "more_content": True, + }, + { + "type": "http.response.content", + "content": b"chunk 2", + "more_content": True, + }, + { + "type": "http.response.content", + }, + ]) + self.assertEqual(response.status, 201) + self.assertEqual(response.read(), b"chunk 1 chunk 2") + @given(body=http_strategies.http_body()) @settings(max_examples=5, deadline=2000) def test_body(self, body): @@ -72,8 +129,11 @@ class TestHTTPResponse(DaphneTestCase): """ response = self.run_daphne_response([ { - "type": "http.response", + "type": "http.response.start", "status": 200, + }, + { + "type": "http.response.content", "content": body, }, ]) @@ -86,10 +146,13 @@ class TestHTTPResponse(DaphneTestCase): # The ASGI spec requires us to lowercase our header names response = self.run_daphne_response([ { - "type": "http.response", + "type": "http.response.start", "status": 200, "headers": self.normalize_headers(headers), }, + { + "type": "http.response.content", + }, ]) # Check headers in a sensible way. Ignore transfer-encoding. self.assertEqual(