Perf improv (#2074)
* handler improvements for performance * Resovle tests * Linting * Add tests
This commit is contained in:
parent
8a2ea626c6
commit
15a8b5c894
25
sanic/app.py
25
sanic/app.py
|
@ -676,27 +676,23 @@ class Sanic(BaseSanic):
|
||||||
response = None
|
response = None
|
||||||
try:
|
try:
|
||||||
# Fetch handler from router
|
# Fetch handler from router
|
||||||
(
|
route, handler, kwargs = self.router.get(
|
||||||
route,
|
request.path, request.method, request.headers.get("host")
|
||||||
handler,
|
)
|
||||||
kwargs,
|
|
||||||
) = self.router.get(request)
|
|
||||||
|
|
||||||
request._match_info = kwargs
|
request._match_info = kwargs
|
||||||
request.route = route
|
request.route = route
|
||||||
request.name = route.name
|
|
||||||
request.uri_template = f"/{route.path}"
|
|
||||||
request.endpoint = request.name
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
request.stream
|
request.stream.request_body # type: ignore
|
||||||
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("inf")
|
request.stream.request_max_size = float( # type: ignore
|
||||||
|
"inf"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
# Non-streaming handler: preload body
|
# Non-streaming handler: preload body
|
||||||
await request.receive_body()
|
await request.receive_body()
|
||||||
|
@ -730,8 +726,7 @@ class Sanic(BaseSanic):
|
||||||
if response:
|
if response:
|
||||||
response = await request.respond(response)
|
response = await request.respond(response)
|
||||||
else:
|
else:
|
||||||
if request.stream:
|
response = request.stream.response # type: ignore
|
||||||
response = request.stream.response
|
|
||||||
# Make sure that response is finished / run StreamingHTTP callback
|
# Make sure that response is finished / run StreamingHTTP callback
|
||||||
|
|
||||||
if isinstance(response, BaseHTTPResponse):
|
if isinstance(response, BaseHTTPResponse):
|
||||||
|
@ -757,9 +752,9 @@ class Sanic(BaseSanic):
|
||||||
):
|
):
|
||||||
request.app = self
|
request.app = self
|
||||||
if not getattr(handler, "__blueprintname__", False):
|
if not getattr(handler, "__blueprintname__", False):
|
||||||
request.endpoint = handler.__name__
|
request._name = handler.__name__
|
||||||
else:
|
else:
|
||||||
request.endpoint = (
|
request._name = (
|
||||||
getattr(handler, "__blueprintname__", "") + handler.__name__
|
getattr(handler, "__blueprintname__", "") + handler.__name__
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -86,15 +86,14 @@ class Request:
|
||||||
"_remote_addr",
|
"_remote_addr",
|
||||||
"_socket",
|
"_socket",
|
||||||
"_match_info",
|
"_match_info",
|
||||||
|
"_name",
|
||||||
"app",
|
"app",
|
||||||
"body",
|
"body",
|
||||||
"conn_info",
|
"conn_info",
|
||||||
"ctx",
|
"ctx",
|
||||||
"endpoint",
|
|
||||||
"head",
|
"head",
|
||||||
"headers",
|
"headers",
|
||||||
"method",
|
"method",
|
||||||
"name",
|
|
||||||
"parsed_args",
|
"parsed_args",
|
||||||
"parsed_not_grouped_args",
|
"parsed_not_grouped_args",
|
||||||
"parsed_files",
|
"parsed_files",
|
||||||
|
@ -106,7 +105,6 @@ class Request:
|
||||||
"route",
|
"route",
|
||||||
"stream",
|
"stream",
|
||||||
"transport",
|
"transport",
|
||||||
"uri_template",
|
|
||||||
"version",
|
"version",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -124,6 +122,7 @@ class Request:
|
||||||
# TODO: Content-Encoding detection
|
# TODO: Content-Encoding detection
|
||||||
self._parsed_url = parse_url(url_bytes)
|
self._parsed_url = parse_url(url_bytes)
|
||||||
self._id: Optional[Union[uuid.UUID, str, int]] = None
|
self._id: Optional[Union[uuid.UUID, str, int]] = None
|
||||||
|
self._name: Optional[str] = None
|
||||||
self.app = app
|
self.app = app
|
||||||
|
|
||||||
self.headers = headers
|
self.headers = headers
|
||||||
|
@ -136,7 +135,6 @@ class Request:
|
||||||
self.body = b""
|
self.body = b""
|
||||||
self.conn_info: Optional[ConnInfo] = None
|
self.conn_info: Optional[ConnInfo] = None
|
||||||
self.ctx = SimpleNamespace()
|
self.ctx = SimpleNamespace()
|
||||||
self.name: Optional[str] = None
|
|
||||||
self.parsed_forwarded: Optional[Options] = None
|
self.parsed_forwarded: Optional[Options] = None
|
||||||
self.parsed_json = None
|
self.parsed_json = None
|
||||||
self.parsed_form = None
|
self.parsed_form = None
|
||||||
|
@ -147,12 +145,10 @@ class Request:
|
||||||
self.parsed_not_grouped_args: DefaultDict[
|
self.parsed_not_grouped_args: DefaultDict[
|
||||||
Tuple[bool, bool, str, str], List[Tuple[str, str]]
|
Tuple[bool, bool, str, str], List[Tuple[str, str]]
|
||||||
] = defaultdict(list)
|
] = defaultdict(list)
|
||||||
self.uri_template: Optional[str] = None
|
|
||||||
self.request_middleware_started = False
|
self.request_middleware_started = False
|
||||||
self._cookies: Optional[Dict[str, str]] = None
|
self._cookies: Optional[Dict[str, str]] = None
|
||||||
self._match_info: Dict[str, Any] = {}
|
self._match_info: Dict[str, Any] = {}
|
||||||
self.stream: Optional[Http] = None
|
self.stream: Optional[Http] = None
|
||||||
self.endpoint: Optional[str] = None
|
|
||||||
self.route: Optional[Route] = None
|
self.route: Optional[Route] = None
|
||||||
self._protocol = None
|
self._protocol = None
|
||||||
|
|
||||||
|
@ -207,6 +203,22 @@ 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 name(self):
|
||||||
|
if self._name:
|
||||||
|
return self._name
|
||||||
|
elif self.route:
|
||||||
|
return self.route.name
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def endpoint(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def uri_template(self):
|
||||||
|
return f"/{self.route.path}"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def protocol(self):
|
def protocol(self):
|
||||||
if not self._protocol:
|
if not self._protocol:
|
||||||
|
|
|
@ -11,7 +11,6 @@ from sanic_routing.route import Route # type: ignore
|
||||||
from sanic.constants import HTTP_METHODS
|
from sanic.constants import HTTP_METHODS
|
||||||
from sanic.exceptions import MethodNotSupported, NotFound, SanicException
|
from sanic.exceptions import MethodNotSupported, NotFound, SanicException
|
||||||
from sanic.models.handler_types import RouteHandler
|
from sanic.models.handler_types import RouteHandler
|
||||||
from sanic.request import Request
|
|
||||||
|
|
||||||
|
|
||||||
ROUTER_CACHE_SIZE = 1024
|
ROUTER_CACHE_SIZE = 1024
|
||||||
|
@ -27,16 +26,11 @@ class Router(BaseRouter):
|
||||||
DEFAULT_METHOD = "GET"
|
DEFAULT_METHOD = "GET"
|
||||||
ALLOWED_METHODS = HTTP_METHODS
|
ALLOWED_METHODS = HTTP_METHODS
|
||||||
|
|
||||||
# Putting the lru_cache on Router.get() performs better for the benchmarsk
|
|
||||||
# at tests/benchmark/test_route_resolution_benchmark.py
|
|
||||||
# However, overall application performance is significantly improved
|
|
||||||
# with the lru_cache on this method.
|
|
||||||
@lru_cache(maxsize=ROUTER_CACHE_SIZE)
|
|
||||||
def _get(
|
def _get(
|
||||||
self, path, method, host
|
self, path: str, method: str, host: Optional[str]
|
||||||
) -> Tuple[Route, RouteHandler, Dict[str, Any]]:
|
) -> Tuple[Route, RouteHandler, Dict[str, Any]]:
|
||||||
try:
|
try:
|
||||||
route, handler, params = self.resolve(
|
return self.resolve(
|
||||||
path=path,
|
path=path,
|
||||||
method=method,
|
method=method,
|
||||||
extra={"host": host},
|
extra={"host": host},
|
||||||
|
@ -50,14 +44,9 @@ class Router(BaseRouter):
|
||||||
allowed_methods=e.allowed_methods,
|
allowed_methods=e.allowed_methods,
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
@lru_cache(maxsize=ROUTER_CACHE_SIZE)
|
||||||
route,
|
|
||||||
handler,
|
|
||||||
params,
|
|
||||||
)
|
|
||||||
|
|
||||||
def get( # type: ignore
|
def get( # type: ignore
|
||||||
self, request: Request
|
self, path: str, method: str, host: Optional[str]
|
||||||
) -> Tuple[Route, RouteHandler, Dict[str, Any]]:
|
) -> Tuple[Route, RouteHandler, Dict[str, Any]]:
|
||||||
"""
|
"""
|
||||||
Retrieve a `Route` object containg the details about how to handle
|
Retrieve a `Route` object containg the details about how to handle
|
||||||
|
@ -69,9 +58,7 @@ class Router(BaseRouter):
|
||||||
correct response
|
correct response
|
||||||
:rtype: Tuple[ Route, RouteHandler, Dict[str, Any]]
|
:rtype: Tuple[ Route, RouteHandler, Dict[str, Any]]
|
||||||
"""
|
"""
|
||||||
return self._get(
|
return self._get(path, method, host)
|
||||||
request.path, request.method, request.headers.get("host")
|
|
||||||
)
|
|
||||||
|
|
||||||
def add( # type: ignore
|
def add( # type: ignore
|
||||||
self,
|
self,
|
||||||
|
|
|
@ -23,18 +23,21 @@ class TestSanicRouteResolution:
|
||||||
)
|
)
|
||||||
router, simple_routes = sanic_router(route_details=simple_routes)
|
router, simple_routes = sanic_router(route_details=simple_routes)
|
||||||
route_to_call = choice(simple_routes)
|
route_to_call = choice(simple_routes)
|
||||||
|
request = Request(
|
||||||
|
"/{}".format(route_to_call[-1]).encode(),
|
||||||
|
{"host": "localhost"},
|
||||||
|
"v1",
|
||||||
|
route_to_call[0],
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
|
||||||
result = benchmark.pedantic(
|
result = benchmark.pedantic(
|
||||||
router.get,
|
router.get,
|
||||||
(
|
(
|
||||||
Request(
|
request.path,
|
||||||
"/{}".format(route_to_call[-1]).encode(),
|
request.method,
|
||||||
{"host": "localhost"},
|
request.headers.get("host"),
|
||||||
"v1",
|
|
||||||
route_to_call[0],
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
iterations=1000,
|
iterations=1000,
|
||||||
rounds=1000,
|
rounds=1000,
|
||||||
|
@ -56,18 +59,21 @@ class TestSanicRouteResolution:
|
||||||
)
|
)
|
||||||
|
|
||||||
print("{} -> {}".format(route_to_call[-1], url))
|
print("{} -> {}".format(route_to_call[-1], url))
|
||||||
|
request = Request(
|
||||||
|
"/{}".format(url).encode(),
|
||||||
|
{"host": "localhost"},
|
||||||
|
"v1",
|
||||||
|
route_to_call[0],
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
|
||||||
result = benchmark.pedantic(
|
result = benchmark.pedantic(
|
||||||
router.get,
|
router.get,
|
||||||
(
|
(
|
||||||
Request(
|
request.path,
|
||||||
"/{}".format(url).encode(),
|
request.method,
|
||||||
{"host": "localhost"},
|
request.headers.get("host"),
|
||||||
"v1",
|
|
||||||
route_to_call[0],
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
iterations=1000,
|
iterations=1000,
|
||||||
rounds=1000,
|
rounds=1000,
|
||||||
|
|
|
@ -35,6 +35,27 @@ def test_request_id_defaults_uuid():
|
||||||
assert request.id == request.id == request._id
|
assert request.id == request.id == request._id
|
||||||
|
|
||||||
|
|
||||||
|
def test_name_none():
|
||||||
|
request = Request(b"/", {}, None, "GET", None, None)
|
||||||
|
|
||||||
|
assert request.name is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_name_from_route():
|
||||||
|
request = Request(b"/", {}, None, "GET", None, None)
|
||||||
|
route = Mock()
|
||||||
|
request.route = route
|
||||||
|
|
||||||
|
assert request.name == route.name
|
||||||
|
|
||||||
|
|
||||||
|
def test_name_from_set():
|
||||||
|
request = Request(b"/", {}, None, "GET", None, None)
|
||||||
|
request._name = "foo"
|
||||||
|
|
||||||
|
assert request.name == "foo"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"request_id,expected_type",
|
"request_id,expected_type",
|
||||||
(
|
(
|
||||||
|
|
|
@ -166,7 +166,9 @@ def test_matching(path, headers, expected):
|
||||||
request = Request(path, headers, None, "GET", None, app)
|
request = Request(path, headers, None, "GET", None, app)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
app.router.get(request=request)
|
app.router.get(
|
||||||
|
request.path, request.method, request.headers.get("host")
|
||||||
|
)
|
||||||
except NotFound:
|
except NotFound:
|
||||||
response = 404
|
response = 404
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|
Loading…
Reference in New Issue
Block a user