Add raw header info to request object (#2032)
This commit is contained in:
parent
c41d7136e8
commit
a733d32715
|
@ -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"
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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 (
|
||||||
|
|
|
@ -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]]:
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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"
|
||||||
|
|
Loading…
Reference in New Issue
Block a user