Merge branch 'main' into zhiwei/bp-copy

This commit is contained in:
Adam Hopkins 2021-07-20 12:50:18 +03:00 committed by GitHub
commit d4cd897522
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 173 additions and 17 deletions

View File

@ -10,3 +10,15 @@ exclude_patterns:
- "examples/" - "examples/"
- "hack/" - "hack/"
- "scripts/" - "scripts/"
- "tests/"
checks:
argument-count:
enabled: false
file-lines:
config:
threshold: 1000
method-count:
config:
threshold: 40
complex-logic:
enabled: false

View File

@ -77,17 +77,7 @@ The goal of the project is to provide a simple way to get up and running a highl
Sponsor Sponsor
------- -------
|Try CodeStream| Check out `open collective <https://opencollective.com/sanic-org>`_ to learn more about helping to fund Sanic.
.. |Try CodeStream| image:: https://alt-images.codestream.com/codestream_logo_sanicorg.png
:target: https://codestream.com/?utm_source=github&amp;utm_campaign=sanicorg&amp;utm_medium=banner
:alt: Try CodeStream
Manage pull requests and conduct code reviews in your IDE with full source-tree context. Comment on any line, not just the diffs. Use jump-to-definition, your favorite keybindings, and code intelligence with more of your workflow.
`Learn More <https://codestream.com/?utm_source=github&amp;utm_campaign=sanicorg&amp;utm_medium=banner>`_
Thank you to our sponsor. Check out `open collective <https://opencollective.com/sanic-org>`_ to learn more about helping to fund Sanic.
Installation Installation
------------ ------------

View File

@ -21,6 +21,7 @@ from traceback import format_exc
from types import SimpleNamespace from types import SimpleNamespace
from typing import ( from typing import (
Any, Any,
AnyStr,
Awaitable, Awaitable,
Callable, Callable,
Coroutine, Coroutine,
@ -138,7 +139,7 @@ class Sanic(BaseSanic):
log_config: Optional[Dict[str, Any]] = None, log_config: Optional[Dict[str, Any]] = None,
configure_logging: bool = True, configure_logging: bool = True,
register: Optional[bool] = None, register: Optional[bool] = None,
dumps: Optional[Callable[..., str]] = None, dumps: Optional[Callable[..., AnyStr]] = None,
) -> None: ) -> None:
super().__init__(name=name) super().__init__(name=name)
@ -193,7 +194,7 @@ class Sanic(BaseSanic):
self.router.ctx.app = self self.router.ctx.app = self
if dumps: if dumps:
BaseHTTPResponse._dumps = dumps BaseHTTPResponse._dumps = dumps # type: ignore
@property @property
def loop(self): def loop(self):
@ -737,15 +738,14 @@ class Sanic(BaseSanic):
request.route = route request.route = route
if ( if (
request.stream.request_body # type: ignore request.stream
and request.stream.request_body
and not route.ctx.ignore_body and not route.ctx.ignore_body
): ):
if hasattr(handler, "is_stream"): if hasattr(handler, "is_stream"):
# Streaming handler: lift the size limit # Streaming handler: lift the size limit
request.stream.request_max_size = float( # type: ignore request.stream.request_max_size = float("inf")
"inf"
)
else: else:
# Non-streaming handler: preload body # Non-streaming handler: preload body
await request.receive_body() await request.receive_body()

View File

@ -781,6 +781,7 @@ class RouteMixin:
path={file_or_directory}, " path={file_or_directory}, "
f"relative_url={__file_uri__}" f"relative_url={__file_uri__}"
) )
raise
def _register_static( def _register_static(
self, self,

137
tests/test_http.py Normal file
View File

@ -0,0 +1,137 @@
import asyncio
import json as stdjson
from collections import namedtuple
from textwrap import dedent
from typing import AnyStr
import pytest
from sanic_testing.reusable import ReusableClient
from sanic import json, text
from sanic.app import Sanic
PORT = 1234
class RawClient:
CRLF = b"\r\n"
def __init__(self, host: str, port: int):
self.reader = None
self.writer = None
self.host = host
self.port = port
async def connect(self):
self.reader, self.writer = await asyncio.open_connection(
self.host, self.port
)
async def close(self):
self.writer.close()
await self.writer.wait_closed()
async def send(self, message: AnyStr):
if isinstance(message, str):
msg = self._clean(message).encode("utf-8")
else:
msg = message
await self._send(msg)
async def _send(self, message: bytes):
if not self.writer:
raise Exception("No open write stream")
self.writer.write(message)
async def recv(self, nbytes: int = -1) -> bytes:
if not self.reader:
raise Exception("No open read stream")
return await self.reader.read(nbytes)
def _clean(self, message: str) -> str:
return (
dedent(message)
.lstrip("\n")
.replace("\n", self.CRLF.decode("utf-8"))
)
@pytest.fixture
def test_app(app: Sanic):
app.config.KEEP_ALIVE_TIMEOUT = 1
@app.get("/")
async def base_handler(request):
return text("111122223333444455556666777788889999")
@app.post("/upload", stream=True)
async def upload_handler(request):
data = [part.decode("utf-8") async for part in request.stream]
return json(data)
return app
@pytest.fixture
def runner(test_app):
client = ReusableClient(test_app, port=PORT)
client.run()
yield client
client.stop()
@pytest.fixture
def client(runner):
client = namedtuple("Client", ("raw", "send", "recv"))
raw = RawClient(runner.host, runner.port)
runner._run(raw.connect())
def send(msg):
nonlocal runner
nonlocal raw
runner._run(raw.send(msg))
def recv(**kwargs):
nonlocal runner
nonlocal raw
method = raw.recv_until if "until" in kwargs else raw.recv
return runner._run(method(**kwargs))
yield client(raw, send, recv)
runner._run(raw.close())
def test_full_message(client):
client.send(
"""
GET / HTTP/1.1
host: localhost:7777
"""
)
response = client.recv()
assert len(response) == 140
assert b"200 OK" in response
def test_transfer_chunked(client):
client.send(
"""
POST /upload HTTP/1.1
transfer-encoding: chunked
"""
)
client.send(b"3\r\nfoo\r\n")
client.send(b"3\r\nbar\r\n")
client.send(b"0\r\n\r\n")
response = client.recv()
_, body = response.rsplit(b"\r\n\r\n", 1)
data = stdjson.loads(body)
assert data == ["foo", "bar"]

View File

@ -461,6 +461,22 @@ def test_nested_dir(app, static_file_directory):
assert response.text == "foo\n" assert response.text == "foo\n"
def test_handle_is_a_directory_error(app, static_file_directory):
error_text = "Is a directory. Access denied"
app.static("/static", static_file_directory)
@app.exception(Exception)
async def handleStaticDirError(request, exception):
if isinstance(exception, IsADirectoryError):
return text(error_text, status=403)
raise exception
request, response = app.test_client.get("/static/")
assert response.status == 403
assert response.text == error_text
def test_stack_trace_on_not_found(app, static_file_directory, caplog): def test_stack_trace_on_not_found(app, static_file_directory, caplog):
app.static("/static", static_file_directory) app.static("/static", static_file_directory)