Merge branch 'main' into zhiwei/bp-copy

This commit is contained in:
Zhiwei 2021-07-13 12:51:48 -07:00 committed by GitHub
commit 47ebddaee5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 120 additions and 15 deletions

View File

@ -30,6 +30,7 @@ from typing import (
List, List,
Optional, Optional,
Set, Set,
Tuple,
Type, Type,
Union, Union,
) )
@ -411,7 +412,13 @@ class Sanic(BaseSanic):
self.websocket_enabled = enable self.websocket_enabled = enable
def blueprint(self, blueprint, **options): def blueprint(
self,
blueprint: Union[
Blueprint, List[Blueprint], Tuple[Blueprint], BlueprintGroup
],
**options: Any,
):
"""Register a blueprint on the application. """Register a blueprint on the application.
:param blueprint: Blueprint object or (list, tuple) thereof :param blueprint: Blueprint object or (list, tuple) thereof
@ -869,7 +876,7 @@ class Sanic(BaseSanic):
*, *,
debug: bool = False, debug: bool = False,
auto_reload: Optional[bool] = None, auto_reload: Optional[bool] = None,
ssl: Union[dict, SSLContext, None] = None, ssl: Union[Dict[str, str], SSLContext, None] = None,
sock: Optional[socket] = None, sock: Optional[socket] = None,
workers: int = 1, workers: int = 1,
protocol: Optional[Type[Protocol]] = None, protocol: Optional[Type[Protocol]] = None,
@ -999,7 +1006,7 @@ class Sanic(BaseSanic):
port: Optional[int] = None, port: Optional[int] = None,
*, *,
debug: bool = False, debug: bool = False,
ssl: Union[dict, SSLContext, None] = None, ssl: Union[Dict[str, str], SSLContext, None] = None,
sock: Optional[socket] = None, sock: Optional[socket] = None,
protocol: Type[Protocol] = None, protocol: Type[Protocol] = None,
backlog: int = 100, backlog: int = 100,

View File

@ -181,10 +181,10 @@ class Blueprint(BaseSanic):
@staticmethod @staticmethod
def group( def group(
*blueprints, *blueprints: Union[Blueprint, BlueprintGroup],
url_prefix="", url_prefix: Optional[str] = None,
version=None, version: Optional[Union[int, str, float]] = None,
strict_slashes=None, strict_slashes: Optional[bool] = None,
version_prefix: str = "/v", version_prefix: str = "/v",
): ):
""" """

View File

@ -31,6 +31,7 @@ class NotFound(SanicException):
""" """
status_code = 404 status_code = 404
quiet = True
class InvalidUsage(SanicException): class InvalidUsage(SanicException):
@ -39,6 +40,7 @@ class InvalidUsage(SanicException):
""" """
status_code = 400 status_code = 400
quiet = True
class MethodNotSupported(SanicException): class MethodNotSupported(SanicException):
@ -47,6 +49,7 @@ class MethodNotSupported(SanicException):
""" """
status_code = 405 status_code = 405
quiet = True
def __init__(self, message, method, allowed_methods): def __init__(self, message, method, allowed_methods):
super().__init__(message) super().__init__(message)
@ -70,6 +73,7 @@ class ServiceUnavailable(SanicException):
""" """
status_code = 503 status_code = 503
quiet = True
class URLBuildError(ServerError): class URLBuildError(ServerError):
@ -101,6 +105,7 @@ class RequestTimeout(SanicException):
""" """
status_code = 408 status_code = 408
quiet = True
class PayloadTooLarge(SanicException): class PayloadTooLarge(SanicException):
@ -109,6 +114,7 @@ class PayloadTooLarge(SanicException):
""" """
status_code = 413 status_code = 413
quiet = True
class HeaderNotFound(InvalidUsage): class HeaderNotFound(InvalidUsage):
@ -117,6 +123,7 @@ class HeaderNotFound(InvalidUsage):
""" """
status_code = 400 status_code = 400
quiet = True
class ContentRangeError(SanicException): class ContentRangeError(SanicException):
@ -125,6 +132,7 @@ class ContentRangeError(SanicException):
""" """
status_code = 416 status_code = 416
quiet = True
def __init__(self, message, content_range): def __init__(self, message, content_range):
super().__init__(message) super().__init__(message)
@ -137,6 +145,7 @@ class HeaderExpectationFailed(SanicException):
""" """
status_code = 417 status_code = 417
quiet = True
class Forbidden(SanicException): class Forbidden(SanicException):
@ -145,6 +154,7 @@ class Forbidden(SanicException):
""" """
status_code = 403 status_code = 403
quiet = True
class InvalidRangeType(ContentRangeError): class InvalidRangeType(ContentRangeError):
@ -153,6 +163,7 @@ class InvalidRangeType(ContentRangeError):
""" """
status_code = 416 status_code = 416
quiet = True
class PyFileError(Exception): class PyFileError(Exception):
@ -196,6 +207,7 @@ class Unauthorized(SanicException):
""" """
status_code = 401 status_code = 401
quiet = True
def __init__(self, message, status_code=None, scheme=None, **kwargs): def __init__(self, message, status_code=None, scheme=None, **kwargs):
super().__init__(message, status_code) super().__init__(message, status_code)

View File

@ -486,20 +486,24 @@ class Http:
self.keep_alive = False self.keep_alive = False
raise InvalidUsage("Bad chunked encoding") raise InvalidUsage("Bad chunked encoding")
del buf[: pos + 2]
if size <= 0: if size <= 0:
self.request_body = None self.request_body = None
# Because we are leaving one CRLF in the buffer, we manually
# reset the buffer here
self.recv_buffer = bytearray()
if size < 0: if size < 0:
self.keep_alive = False self.keep_alive = False
raise InvalidUsage("Bad chunked encoding") raise InvalidUsage("Bad chunked encoding")
# Consume CRLF, chunk size 0 and the two CRLF that follow
pos += 4
# Might need to wait for the final CRLF
while len(buf) < pos:
await self._receive_more()
del buf[:pos]
return None return None
# Remove CRLF, chunk size and the CRLF that follows
del buf[: pos + 2]
self.request_bytes_left = size self.request_bytes_left = size
self.request_bytes += size self.request_bytes += size

View File

@ -83,7 +83,7 @@ ujson = "ujson>=1.35" + env_dependency
uvloop = "uvloop>=0.5.3" + env_dependency uvloop = "uvloop>=0.5.3" + env_dependency
types_ujson = "types-ujson" + env_dependency types_ujson = "types-ujson" + env_dependency
requirements = [ requirements = [
"sanic-routing==0.7.0", "sanic-routing~=0.7",
"httptools>=0.0.10", "httptools>=0.0.10",
uvloop, uvloop,
ujson, ujson,
@ -93,7 +93,7 @@ requirements = [
] ]
tests_require = [ tests_require = [
"sanic-testing>=0.6.0", "sanic-testing>=0.7.0b1",
"pytest==5.2.1", "pytest==5.2.1",
"coverage==5.3", "coverage==5.3",
"gunicorn==20.0.4", "gunicorn==20.0.4",

82
tests/test_pipelining.py Normal file
View File

@ -0,0 +1,82 @@
from httpx import AsyncByteStream
from sanic_testing.reusable import ReusableClient
from sanic.response import json
def test_no_body_requests(app):
@app.get("/")
async def handler(request):
return json(
{
"request_id": str(request.id),
"connection_id": id(request.conn_info),
}
)
client = ReusableClient(app, port=1234)
with client:
_, response1 = client.get("/")
_, response2 = client.get("/")
assert response1.status == response2.status == 200
assert response1.json["request_id"] != response2.json["request_id"]
assert response1.json["connection_id"] == response2.json["connection_id"]
def test_json_body_requests(app):
@app.post("/")
async def handler(request):
return json(
{
"request_id": str(request.id),
"connection_id": id(request.conn_info),
"foo": request.json.get("foo"),
}
)
client = ReusableClient(app, port=1234)
with client:
_, response1 = client.post("/", json={"foo": True})
_, response2 = client.post("/", json={"foo": True})
assert response1.status == response2.status == 200
assert response1.json["foo"] is response2.json["foo"] is True
assert response1.json["request_id"] != response2.json["request_id"]
assert response1.json["connection_id"] == response2.json["connection_id"]
def test_streaming_body_requests(app):
@app.post("/", stream=True)
async def handler(request):
data = [part.decode("utf-8") async for part in request.stream]
return json(
{
"request_id": str(request.id),
"connection_id": id(request.conn_info),
"data": data,
}
)
data = ["hello", "world"]
class Data(AsyncByteStream):
def __init__(self, data):
self.data = data
async def __aiter__(self):
for value in self.data:
yield value.encode("utf-8")
client = ReusableClient(app, port=1234)
with client:
_, response1 = client.post("/", data=Data(data))
_, response2 = client.post("/", data=Data(data))
assert response1.status == response2.status == 200
assert response1.json["data"] == response2.json["data"] == data
assert response1.json["request_id"] != response2.json["request_id"]
assert response1.json["connection_id"] == response2.json["connection_id"]

View File

@ -471,7 +471,7 @@ def test_stack_trace_on_not_found(app, static_file_directory, caplog):
assert response.status == 404 assert response.status == 404
assert counter[logging.INFO] == 5 assert counter[logging.INFO] == 5
assert counter[logging.ERROR] == 1 assert counter[logging.ERROR] == 0
def test_no_stack_trace_on_not_found(app, static_file_directory, caplog): def test_no_stack_trace_on_not_found(app, static_file_directory, caplog):