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 sys
import argparse import argparse
import logging import logging
import importlib
from .server import Server from .server import Server
from .endpoints import build_endpoint_description_strings from .endpoints import build_endpoint_description_strings
from .access import AccessLogGenerator from .access import AccessLogGenerator

View File

@ -198,7 +198,7 @@ class WebRequest(http.Request):
""" """
if "type" not in message: if "type" not in message:
raise ValueError("Message has no type defined") raise ValueError("Message has no type defined")
if message["type"] == "http.response": if message["type"] == "http.response.start":
if self._response_started: if self._response_started:
raise ValueError("HTTP response has already been started") raise ValueError("HTTP response has already been started")
self._response_started = True self._response_started = True
@ -213,33 +213,31 @@ class WebRequest(http.Request):
header = header.encode("latin1") header = header.encode("latin1")
self.responseHeaders.addRawHeader(header, value) self.responseHeaders.addRawHeader(header, value)
logger.debug("HTTP %s response started for %s", message["status"], self.client_addr) 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: 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: else:
raise ValueError("Cannot handle message type %s!" % message["type"]) 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): def handle_exception(self, exception):
""" """
Called by the server when our application tracebacks 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) Responds with a server-level error page (very basic)
""" """
self.handle_reply({ self.handle_reply({
"type": "http.response", "type": "http.response.start",
"status": status, "status": status,
"status_text": status_text,
"headers": [ "headers": [
(b"Content-Type", b"text/html; charset=utf-8"), (b"Content-Type", b"text/html; charset=utf-8"),
], ],
})
self.handle_reply({
"type": "http.response.content",
"content": (self.error_template % { "content": (self.error_template % {
"title": six.text_type(status) + " " + status_text.decode("ascii"), "title": six.text_type(status) + " " + status_text.decode("ascii"),
"body": body, "body": body,

View File

@ -158,7 +158,10 @@ class DaphneTestCase(unittest.TestCase):
body=body, body=body,
headers=headers, headers=headers,
xff=xff, 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 return scope, messages
@ -247,7 +250,6 @@ class DaphneTestCase(unittest.TestCase):
frame += b"\0\0\0\0" frame += b"\0\0\0\0"
# Payload # Payload
frame += value frame += value
print("sending %r" % frame)
sock.sendall(frame) sock.sendall(frame)
def receive_from_socket(self, sock, length, timeout=1): def receive_from_socket(self, sock, length, timeout=1):

View File

@ -27,8 +27,11 @@ class TestHTTPResponse(DaphneTestCase):
""" """
response = self.run_daphne_response([ response = self.run_daphne_response([
{ {
"type": "http.response", "type": "http.response.start",
"status": 200, "status": 200,
},
{
"type": "http.response.content",
"content": b"hello world", "content": b"hello world",
}, },
]) ])
@ -45,7 +48,10 @@ class TestHTTPResponse(DaphneTestCase):
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
self.run_daphne_response([ self.run_daphne_response([
{ {
"type": "http.response", "type": "http.response.start",
},
{
"type": "http.response.content",
"content": b"hello world", "content": b"hello world",
}, },
]) ])
@ -56,14 +62,65 @@ class TestHTTPResponse(DaphneTestCase):
""" """
response = self.run_daphne_response([ response = self.run_daphne_response([
{ {
"type": "http.response", "type": "http.response.start",
"status": 201, "status": 201,
},
{
"type": "http.response.content",
"content": b"i made a thing!", "content": b"i made a thing!",
}, },
]) ])
self.assertEqual(response.status, 201) self.assertEqual(response.status, 201)
self.assertEqual(response.read(), b"i made a thing!") 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()) @given(body=http_strategies.http_body())
@settings(max_examples=5, deadline=2000) @settings(max_examples=5, deadline=2000)
def test_body(self, body): def test_body(self, body):
@ -72,8 +129,11 @@ class TestHTTPResponse(DaphneTestCase):
""" """
response = self.run_daphne_response([ response = self.run_daphne_response([
{ {
"type": "http.response", "type": "http.response.start",
"status": 200, "status": 200,
},
{
"type": "http.response.content",
"content": body, "content": body,
}, },
]) ])
@ -86,10 +146,13 @@ class TestHTTPResponse(DaphneTestCase):
# The ASGI spec requires us to lowercase our header names # The ASGI spec requires us to lowercase our header names
response = self.run_daphne_response([ response = self.run_daphne_response([
{ {
"type": "http.response", "type": "http.response.start",
"status": 200, "status": 200,
"headers": self.normalize_headers(headers), "headers": self.normalize_headers(headers),
}, },
{
"type": "http.response.content",
},
]) ])
# Check headers in a sensible way. Ignore transfer-encoding. # Check headers in a sensible way. Ignore transfer-encoding.
self.assertEqual( self.assertEqual(