Add raw header info to request object (#2032)

This commit is contained in:
Adam Hopkins 2021-03-03 16:33:34 +02:00 committed by GitHub
parent c41d7136e8
commit a733d32715
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 57 additions and 15 deletions

View File

@ -1 +1,2 @@
HTTP_METHODS = ("GET", "POST", "PUT", "HEAD", "OPTIONS", "PATCH", "DELETE") HTTP_METHODS = ("GET", "POST", "PUT", "HEAD", "OPTIONS", "PATCH", "DELETE")
DEFAULT_HTTP_CONTENT_TYPE = "application/octet-stream"

View File

@ -189,8 +189,9 @@ class Http:
# Parse header content # Parse header content
try: try:
raw_headers = buf[:pos].decode(errors="surrogateescape") head = buf[:pos]
reqline, *raw_headers = raw_headers.split("\r\n") raw_headers = head.decode(errors="surrogateescape")
reqline, *split_headers = raw_headers.split("\r\n")
method, self.url, protocol = reqline.split(" ") method, self.url, protocol = reqline.split(" ")
if protocol == "HTTP/1.1": if protocol == "HTTP/1.1":
@ -204,7 +205,7 @@ class Http:
request_body = False request_body = False
headers = [] headers = []
for name, value in (h.split(":", 1) for h in raw_headers): for name, value in (h.split(":", 1) for h in split_headers):
name, value = h = name.lower(), value.lstrip() name, value = h = name.lower(), value.lstrip()
if name in ("content-length", "transfer-encoding"): if name in ("content-length", "transfer-encoding"):
@ -223,6 +224,7 @@ class Http:
request = self.protocol.request_class( request = self.protocol.request_class(
url_bytes=self.url.encode(), url_bytes=self.url.encode(),
headers=headers_instance, headers=headers_instance,
head=bytes(head),
version=protocol[5:], version=protocol[5:],
method=method, method=method,
transport=self.protocol.transport, transport=self.protocol.transport,

View File

@ -11,7 +11,7 @@ from urllib.parse import unquote
from sanic_routing.route import Route # type: ignore from sanic_routing.route import Route # type: ignore
from sanic.compat import stat_async from sanic.compat import stat_async
from sanic.constants import HTTP_METHODS from sanic.constants import DEFAULT_HTTP_CONTENT_TYPE, HTTP_METHODS
from sanic.exceptions import ( from sanic.exceptions import (
ContentRangeError, ContentRangeError,
FileNotFound, FileNotFound,
@ -689,7 +689,7 @@ class RouteMixin:
content_type = ( content_type = (
content_type content_type
or guess_type(file_path)[0] or guess_type(file_path)[0]
or "application/octet-stream" or DEFAULT_HTTP_CONTENT_TYPE
) )
if "charset=" not in content_type and ( if "charset=" not in content_type and (

View File

@ -31,6 +31,7 @@ from urllib.parse import parse_qs, parse_qsl, unquote, urlunparse
from httptools import parse_url # type: ignore from httptools import parse_url # type: ignore
from sanic.compat import CancelledErrors, Header from sanic.compat import CancelledErrors, Header
from sanic.constants import DEFAULT_HTTP_CONTENT_TYPE
from sanic.exceptions import InvalidUsage from sanic.exceptions import InvalidUsage
from sanic.headers import ( from sanic.headers import (
Options, Options,
@ -49,12 +50,6 @@ try:
except ImportError: except ImportError:
from json import loads as json_loads # type: ignore from json import loads as json_loads # type: ignore
DEFAULT_HTTP_CONTENT_TYPE = "application/octet-stream"
# HTTP/1.1: https://www.w3.org/Protocols/rfc2616/rfc2616-sec7.html#sec7.2.1
# > If the media type remains unknown, the recipient SHOULD treat it
# > as type "application/octet-stream"
class RequestParameters(dict): class RequestParameters(dict):
""" """
@ -95,6 +90,7 @@ class Request:
"conn_info", "conn_info",
"ctx", "ctx",
"endpoint", "endpoint",
"head",
"headers", "headers",
"method", "method",
"name", "name",
@ -121,6 +117,7 @@ class Request:
method: str, method: str,
transport: TransportProtocol, transport: TransportProtocol,
app: Sanic, app: Sanic,
head: bytes = b"",
): ):
self.raw_url = url_bytes self.raw_url = url_bytes
# TODO: Content-Encoding detection # TODO: Content-Encoding detection
@ -132,6 +129,7 @@ class Request:
self.version = version self.version = version
self.method = method self.method = method
self.transport = transport self.transport = transport
self.head = head
# Init but do not inhale # Init but do not inhale
self.body = b"" self.body = b""
@ -207,6 +205,16 @@ class Request:
if not self.body: if not self.body:
self.body = b"".join([data async for data in self.stream]) self.body = b"".join([data async for data in self.stream])
@property
def raw_headers(self):
_, headers = self.head.split(b"\r\n", 1)
return bytes(headers)
@property
def request_line(self):
reqline, _ = self.head.split(b"\r\n", 1)
return bytes(reqline)
@property @property
def id(self) -> Optional[Union[uuid.UUID, str, int]]: def id(self) -> Optional[Union[uuid.UUID, str, int]]:
""" """

View File

@ -17,6 +17,7 @@ from urllib.parse import quote_plus
from warnings import warn from warnings import warn
from sanic.compat import Header, open_async from sanic.compat import Header, open_async
from sanic.constants import DEFAULT_HTTP_CONTENT_TYPE
from sanic.cookies import CookieJar from sanic.cookies import CookieJar
from sanic.helpers import has_message_body, remove_entity_headers from sanic.helpers import has_message_body, remove_entity_headers
from sanic.http import Http from sanic.http import Http
@ -297,7 +298,7 @@ def raw(
body: Optional[AnyStr], body: Optional[AnyStr],
status: int = 200, status: int = 200,
headers: Optional[Dict[str, str]] = None, headers: Optional[Dict[str, str]] = None,
content_type: str = "application/octet-stream", content_type: str = DEFAULT_HTTP_CONTENT_TYPE,
) -> HTTPResponse: ) -> HTTPResponse:
""" """
Returns response object without encoding the body. Returns response object without encoding the body.

View File

@ -2,11 +2,9 @@ from unittest.mock import Mock
import pytest import pytest
from sanic import Sanic, headers from sanic import headers, text
from sanic.compat import Header
from sanic.exceptions import PayloadTooLarge from sanic.exceptions import PayloadTooLarge
from sanic.http import Http from sanic.http import Http
from sanic.request import Request
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -85,3 +83,35 @@ async def test_header_size_exceeded():
with pytest.raises(PayloadTooLarge): with pytest.raises(PayloadTooLarge):
await http.http1_request_header() await http.http1_request_header()
def test_raw_headers(app):
app.route("/")(lambda _: text(""))
request, _ = app.test_client.get(
"/",
headers={
"FOO": "bar",
"Host": "example.com",
"User-Agent": "Sanic-Testing",
},
)
assert request.raw_headers == (
b"Host: example.com\r\nAccept: */*\r\nAccept-Encoding: gzip, "
b"deflate\r\nConnection: keep-alive\r\nUser-Agent: "
b"Sanic-Testing\r\nFOO: bar"
)
def test_request_line(app):
app.route("/")(lambda _: text(""))
request, _ = app.test_client.get(
"/",
headers={
"FOO": "bar",
"Host": "example.com",
"User-Agent": "Sanic-Testing",
},
)
assert request.request_line == b"GET / HTTP/1.1"