Merge branch 'zhiwei/bp-copy' of https://github.com/ChihweiLHBird/sanic into zhiwei/bp-copy
This commit is contained in:
commit
176db5f8a4
13
sanic/app.py
13
sanic/app.py
@ -30,6 +30,7 @@ from typing import (
|
||||
List,
|
||||
Optional,
|
||||
Set,
|
||||
Tuple,
|
||||
Type,
|
||||
Union,
|
||||
)
|
||||
@ -411,7 +412,13 @@ class Sanic(BaseSanic):
|
||||
|
||||
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.
|
||||
|
||||
:param blueprint: Blueprint object or (list, tuple) thereof
|
||||
@ -869,7 +876,7 @@ class Sanic(BaseSanic):
|
||||
*,
|
||||
debug: bool = False,
|
||||
auto_reload: Optional[bool] = None,
|
||||
ssl: Union[dict, SSLContext, None] = None,
|
||||
ssl: Union[Dict[str, str], SSLContext, None] = None,
|
||||
sock: Optional[socket] = None,
|
||||
workers: int = 1,
|
||||
protocol: Optional[Type[Protocol]] = None,
|
||||
@ -999,7 +1006,7 @@ class Sanic(BaseSanic):
|
||||
port: Optional[int] = None,
|
||||
*,
|
||||
debug: bool = False,
|
||||
ssl: Union[dict, SSLContext, None] = None,
|
||||
ssl: Union[Dict[str, str], SSLContext, None] = None,
|
||||
sock: Optional[socket] = None,
|
||||
protocol: Type[Protocol] = None,
|
||||
backlog: int = 100,
|
||||
|
@ -182,10 +182,10 @@ class Blueprint(BaseSanic):
|
||||
|
||||
@staticmethod
|
||||
def group(
|
||||
*blueprints,
|
||||
url_prefix="",
|
||||
version=None,
|
||||
strict_slashes=None,
|
||||
*blueprints: Union[Blueprint, BlueprintGroup],
|
||||
url_prefix: Optional[str] = None,
|
||||
version: Optional[Union[int, str, float]] = None,
|
||||
strict_slashes: Optional[bool] = None,
|
||||
version_prefix: str = "/v",
|
||||
):
|
||||
"""
|
||||
|
@ -31,6 +31,7 @@ class NotFound(SanicException):
|
||||
"""
|
||||
|
||||
status_code = 404
|
||||
quiet = True
|
||||
|
||||
|
||||
class InvalidUsage(SanicException):
|
||||
@ -39,6 +40,7 @@ class InvalidUsage(SanicException):
|
||||
"""
|
||||
|
||||
status_code = 400
|
||||
quiet = True
|
||||
|
||||
|
||||
class MethodNotSupported(SanicException):
|
||||
@ -47,6 +49,7 @@ class MethodNotSupported(SanicException):
|
||||
"""
|
||||
|
||||
status_code = 405
|
||||
quiet = True
|
||||
|
||||
def __init__(self, message, method, allowed_methods):
|
||||
super().__init__(message)
|
||||
@ -70,6 +73,7 @@ class ServiceUnavailable(SanicException):
|
||||
"""
|
||||
|
||||
status_code = 503
|
||||
quiet = True
|
||||
|
||||
|
||||
class URLBuildError(ServerError):
|
||||
@ -101,6 +105,7 @@ class RequestTimeout(SanicException):
|
||||
"""
|
||||
|
||||
status_code = 408
|
||||
quiet = True
|
||||
|
||||
|
||||
class PayloadTooLarge(SanicException):
|
||||
@ -109,6 +114,7 @@ class PayloadTooLarge(SanicException):
|
||||
"""
|
||||
|
||||
status_code = 413
|
||||
quiet = True
|
||||
|
||||
|
||||
class HeaderNotFound(InvalidUsage):
|
||||
@ -117,6 +123,7 @@ class HeaderNotFound(InvalidUsage):
|
||||
"""
|
||||
|
||||
status_code = 400
|
||||
quiet = True
|
||||
|
||||
|
||||
class ContentRangeError(SanicException):
|
||||
@ -125,6 +132,7 @@ class ContentRangeError(SanicException):
|
||||
"""
|
||||
|
||||
status_code = 416
|
||||
quiet = True
|
||||
|
||||
def __init__(self, message, content_range):
|
||||
super().__init__(message)
|
||||
@ -137,6 +145,7 @@ class HeaderExpectationFailed(SanicException):
|
||||
"""
|
||||
|
||||
status_code = 417
|
||||
quiet = True
|
||||
|
||||
|
||||
class Forbidden(SanicException):
|
||||
@ -145,6 +154,7 @@ class Forbidden(SanicException):
|
||||
"""
|
||||
|
||||
status_code = 403
|
||||
quiet = True
|
||||
|
||||
|
||||
class InvalidRangeType(ContentRangeError):
|
||||
@ -153,6 +163,7 @@ class InvalidRangeType(ContentRangeError):
|
||||
"""
|
||||
|
||||
status_code = 416
|
||||
quiet = True
|
||||
|
||||
|
||||
class PyFileError(Exception):
|
||||
@ -196,6 +207,7 @@ class Unauthorized(SanicException):
|
||||
"""
|
||||
|
||||
status_code = 401
|
||||
quiet = True
|
||||
|
||||
def __init__(self, message, status_code=None, scheme=None, **kwargs):
|
||||
super().__init__(message, status_code)
|
||||
|
@ -486,20 +486,24 @@ class Http:
|
||||
self.keep_alive = False
|
||||
raise InvalidUsage("Bad chunked encoding")
|
||||
|
||||
del buf[: pos + 2]
|
||||
|
||||
if size <= 0:
|
||||
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:
|
||||
self.keep_alive = False
|
||||
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
|
||||
|
||||
# Remove CRLF, chunk size and the CRLF that follows
|
||||
del buf[: pos + 2]
|
||||
|
||||
self.request_bytes_left = size
|
||||
self.request_bytes += size
|
||||
|
||||
|
4
setup.py
4
setup.py
@ -83,7 +83,7 @@ ujson = "ujson>=1.35" + env_dependency
|
||||
uvloop = "uvloop>=0.5.3" + env_dependency
|
||||
types_ujson = "types-ujson" + env_dependency
|
||||
requirements = [
|
||||
"sanic-routing==0.7.0",
|
||||
"sanic-routing~=0.7",
|
||||
"httptools>=0.0.10",
|
||||
uvloop,
|
||||
ujson,
|
||||
@ -93,7 +93,7 @@ requirements = [
|
||||
]
|
||||
|
||||
tests_require = [
|
||||
"sanic-testing>=0.6.0",
|
||||
"sanic-testing>=0.7.0b1",
|
||||
"pytest==5.2.1",
|
||||
"coverage==5.3",
|
||||
"gunicorn==20.0.4",
|
||||
|
82
tests/test_pipelining.py
Normal file
82
tests/test_pipelining.py
Normal 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"]
|
@ -471,7 +471,7 @@ def test_stack_trace_on_not_found(app, static_file_directory, caplog):
|
||||
|
||||
assert response.status == 404
|
||||
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):
|
||||
|
Loading…
x
Reference in New Issue
Block a user