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 content_negociation
extension
#21
Conversation
To reject unacceptable client requests based on the `Accept` header
roll/__init__.py
Outdated
if 'Content-Length' not in self.response.headers: | ||
length = len(self.response.body) | ||
self.response.headers['Content-Length'] = length | ||
if 'Content-Type' not in self.response.headers: |
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'm not sure this really serve a use case. Plus it costs a not in
for each request (not a big deal but…).
I'd better let the user explicitly set it.
Or at least have a DEFAULT_CONTENT_TYPE
property, so it can be changed.
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 set a default.
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.
After a second thought, I really think this can be treacherous. IMHO Content-Type should be set explicitly (or using dedicated shortcuts like json
).
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.
Any HTTP/1.1 message containing an entity-body SHOULD include a
Content-Type header field defining the media type of that body. If
and only if the media type is not given by a Content-Type field, the
recipient MAY attempt to guess the media type via inspection of its
content and/or the name extension(s) of the URI used to identify the
resource. If the media type remains unknown, the recipient SHOULD
treat it as type "application/octet-stream".
I really think as a web framework we should set a default, if it's easy to override both per view and for the whole app.
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.
SHOULD
Means it's valid without ;)
But my point is that we are implicitly setting a value, and we have only one chance over X that this value is useful. So I think setting the correct value, per view, is the role of the user, not the one of the framework (which is blind on this topic).
If you really want to be educational, raise if the header is not found (but that would not be correct given the header is not mandatory as per the HTTP spec you quoted above) ;)
roll/__init__.py
Outdated
if methods is None: | ||
methods = ['GET'] | ||
|
||
def wrapper(func): | ||
self.routes.add(path, **{m: func for m in methods}) | ||
handlers = {method: func for method in methods} |
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.
That way we are mixing methods and extra in the same space.
Maybe what we should do: add a has_route
method to autoroute
, so we can make the payload merge our-selves, and thus have a handlers
key.
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 agree that it may be the role of autoroutes to deal with that.
roll/extensions.py
Outdated
@app.listen('dispatch') | ||
async def reject_unacceptable_requests(request, payload): | ||
accept = request.headers.get('Accept', '*/*') | ||
if get_best_match(accept, payload['accepts']) is None: |
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 wonder if the return value of get_best_match
should not be saved in some way?
I guess at some point the value of Accept
header should be used to define which format to be used as response body?
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.
Other thought: should the route
key be accepts
, given at the end this is what the view can serve as content type, no?
So accepts
seems to me the point of view of the request.
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 wonder if the return value of get_best_match should not be saved in some way?
It's hard to guess without a use-case, I'd say let's wait for it to appear.
I guess at some point the value of Accept header should be used to define which format to be used as response body?
Not exactly, it can be a bit different so it's only for the check.
at the end this is what the view can serve as content type, no?
It can be a bit different, for instance the client asks for Accept: application/vnd.example.resource+json; version=2
and the server returns Content-Type: application/vnd.example.resource+json; version=2.1.3
protocol.on_message_begin() | ||
protocol.on_url(path.encode()) | ||
protocol.request.body = body | ||
protocol.request.method = method | ||
protocol.request.headers = headers | ||
return await self.app(protocol.request, protocol.response) | ||
await self.app(protocol.request, protocol.response) |
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 suggest to extract this change to a separate commit. IMHO this should be merged right away.
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 that this urgent? #lazyme
requirements.txt
Outdated
@@ -1,3 +1,4 @@ | |||
autoroutes==0.2.0 | |||
httptools==0.0.9 | |||
mimetype-match==1.0.4 |
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.
Should we have a requirements_ext.txt
, or should we just document the fact that this package needs be installed for the content_negotiation
use?
We could easily check in the extension call that the module is installed, and raise if not.
docs/reference.md
Outdated
@@ -240,3 +251,12 @@ Fired in case of error, can be at each request. | |||
Use it to customize HTTP error formatting for instance. | |||
|
|||
Receives `request`, `response` and `error` parameters. | |||
|
|||
|
|||
### dispatch |
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.
Another option: attach the route payload to the request, and let the users use the request
hook instead of adding a new one?
98a9346
to
4a9c107
Compare
roll/__init__.py
Outdated
if 'Content-Length' not in self.response.headers: | ||
length = len(self.response.body) | ||
self.response.headers['Content-Length'] = length | ||
if 'Content-Type' not in self.response.headers: |
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.
After a second thought, I really think this can be treacherous. IMHO Content-Type should be set explicitly (or using dedicated shortcuts like json
).
roll/__init__.py
Outdated
@@ -270,7 +275,7 @@ def __init__(self): | |||
def factory(self): | |||
return self.Protocol(self) | |||
|
|||
def route(self, path: str, methods: list=None, **extras): | |||
def route(self, path: str, methods: list=None, **extras: dict): |
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 **extras
be something else than a dict?
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.
Good question, I guess no, it's more as a matter of consistency in my experiment to annotate all parameters but it's quite useless… (for now!)
roll/__init__.py
Outdated
params, handler = await self.dispatch(request) | ||
if request.method not in request.routing: | ||
raise HttpError(HTTPStatus.METHOD_NOT_ALLOWED) | ||
request.kwargs.update(params) |
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.
We may merge routing
(the route payload) and kwargs
(the route dynamic variables, if any) at this point. Maybe not in the same dict, in case of key conflicts?
Maybe a route
object (perf arlert), with two properties payload
and vars
?
roll/__init__.py
Outdated
request.kwargs.update(params) | ||
handler = request.routing[request.method] | ||
await handler(request, response, **params) | ||
request.kwargs.update(request.route.vars) |
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.
You can drop request.kwargs
at this point :)
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.
And renaming it to extras
in slots for extra usage? 🙃
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.
Nah! :)
You really want to do request['user']
or request['session']
not request.extras['user']
… ;)
I'll push something in that direction tomorrow :)
To reject unacceptable client requests based on the
Accept
header