Match to the new ASGI-HTTP spec.

This commit is contained in:
Andrew Godwin 2017-11-28 17:28:35 -08:00
parent 7fb3e9a167
commit 20ff8fec28
4 changed files with 98 additions and 34 deletions

View File

@ -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

View File

@ -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,

View File

@ -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):

View File

@ -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(