-
-
Notifications
You must be signed in to change notification settings - Fork 951
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add request_max_size option #2175
Add request_max_size option #2175
Conversation
This can be a base for the next guard - request timeout. |
It is still possible to read unlimited data in mounted apps and in the middleware. But, I think, it is a developer's responsibility in this case. |
@@ -61,6 +61,7 @@ def __init__( | |||
on_startup: typing.Optional[typing.Sequence[typing.Callable]] = None, | |||
on_shutdown: typing.Optional[typing.Sequence[typing.Callable]] = None, | |||
lifespan: typing.Optional[Lifespan["AppType"]] = None, | |||
request_max_size: int = 2621440, # 2.5mb |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Like in Django
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you permalink where you got this from in a comment below here just for the record? I'd also be okay with a code comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does flask do?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Kludex Flask depends on the configuration option or on 'content-length' header. Otherwise, reads nothing.
https://flask.palletsprojects.com/en/2.3.x/config/#MAX_CONTENT_LENGTH
https://flask.palletsprojects.com/en/2.3.x/patterns/fileuploads/#improving-uploads
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
body = message.get("body", b"") | ||
bytes_read += len(body) | ||
if bytes_read > max_body_size: | ||
raise HTTPException( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the exception good enough?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah fine by me
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please follow the same logic as above.
if "app" in scope:
raise HTTPException(status_code=413, "Request Entity Too Large")
else:
response = PlainTextResponse("Request Entity Too Large", status_code=404)
@@ -78,6 +79,7 @@ def __init__( | |||
) | |||
self.user_middleware = [] if middleware is None else list(middleware) | |||
self.middleware_stack: typing.Optional[ASGIApp] = None | |||
self.request_max_size = request_max_size |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might be a good idea to start making these attributes private.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Currently, all members of Starlette are public. I would not like to introduce a new style here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not true:
starlette/starlette/datastructures.py
Line 61 in 2168e47
self._url = url |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am referring to the Starlette
class itself
https://github.com/encode/starlette/blob/master/starlette/applications.py#L71
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair enough. I still think a lot of those shouldn't have been public in the first place and just because they are doesn't mean we should make this public as well.
body = message.get("body", b"") | ||
bytes_read += len(body) | ||
if bytes_read > max_body_size: | ||
raise HTTPException( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah fine by me
…lette into request-body-limit
A couple things to consider @alex-oleshkevich:
|
It can be a middleware from #2328 that automatically gets applied globally and per-route. And be reused anywhere what can accept a middleware (like Mount). |
That sounds good to me. It’ll be two middleware’s right? One to set the values (or override them), and another to enforce them. |
Yes, like that. One middleware in the |
It may be worth to have a shared code to use in app, route and middleware. |
Yeah I was thinking we make two middlewares, let's call them def __init__(self, ..., request_max_size_bytes: int | None = 1234) -> None:
if request_max_size_bytes:
self.app = RequestSizeLimitConfigurationMiddleware(self.app, request_max_size_bytes=request_max_size_bytes) And then in def request_response(
func: typing.Callable[[Request], typing.Union[typing.Awaitable[Response], Response]],
max_request_size: int | None = 1234,
) -> ASGIApp:
"""
Takes a function or coroutine `func(request) -> response`,
and returns an ASGI application.
"""
async def app(scope: Scope, receive: Receive, send: Send) -> None:
request = Request(scope, receive, send)
async def app(scope: Scope, receive: Receive, send: Send) -> None:
if is_async_callable(func):
response = await func(request)
else:
response = await run_in_threadpool(func, request)
await response(scope, receive, send)
await wrap_app_handling_exceptions(app, request)(scope, receive, send)
if max_request_size is not None:
app = RequestSizeLimitEnforcementMiddleware(app, max_request_size=max_request_size)
return app |
@Kludex what should we do with this ticket? It is stale and can be closed. |
This PR adds an option to limit request body size. This is a mitigation of some DoS attacks.
How it works
There are two places where you can define the limit: app instance and route. When defined on the app, the limit becomes global and guards all endpoints. The developer still can overwrite this value on per-route bases (for example on file upload routes).
Refs #2155