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
Changes from 1 commit
ffb5cfe
7655d0e
0bde825
4a9c107
120245c
666210b
2cd8904
9da2e74
4caa403
e737b82
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 commentThe reason will be displayed to describe this comment to others. Learn more. Should we have a |
||
uvloop==0.8.1 |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -199,10 +199,12 @@ def write(self, *args): | |
payload = b'HTTP/1.1 %a %b\r\n' % ( | ||
self.response.status.value, self.response.status.phrase.encode()) | ||
if not isinstance(self.response.body, bytes): | ||
self.response.body = self.response.body.encode() | ||
self.response.body = str(self.response.body).encode() | ||
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 commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
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 commentThe reason will be displayed to describe this comment to others. Learn more.
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). |
||
self.response.headers['Content-Type'] = 'text/html' | ||
for key, value in self.response.headers.items(): | ||
payload += b'%b: %b\r\n' % (key.encode(), str(value).encode()) | ||
payload += b'\r\n%b' % self.response.body | ||
|
@@ -242,7 +244,7 @@ async def shutdown(self): | |
async def __call__(self, request: Request, response: Response): | ||
try: | ||
if not await self.hook('request', request, response): | ||
params, handler = self.dispatch(request) | ||
params, handler = await self.dispatch(request) | ||
await handler(request, response, **params) | ||
except Exception as error: | ||
await self.on_error(request, response, error) | ||
|
@@ -268,22 +270,25 @@ async def on_error(self, request: Request, response: Response, error): | |
def factory(self): | ||
return self.Protocol(self) | ||
|
||
def route(self, path: str, methods: list=None): | ||
def route(self, path: str, methods: list=None, **extras): | ||
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 commentThe reason will be displayed to describe this comment to others. Learn more. That way we are mixing methods and extra in the same space. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||
handlers.update(extras) | ||
self.routes.add(path, **handlers) | ||
return func | ||
|
||
return wrapper | ||
|
||
def dispatch(self, request: Request): | ||
handlers, params = self.routes.match(request.path) | ||
if request.method not in handlers: | ||
async def dispatch(self, request: Request): | ||
payload, params = self.routes.match(request.path) | ||
await self.hook('dispatch', request, payload) | ||
if request.method not in payload: | ||
raise HttpError(HTTPStatus.METHOD_NOT_ALLOWED) | ||
request.kwargs.update(params) | ||
return params, handlers[request.method] | ||
return params, payload[request.method] | ||
|
||
def listen(self, name: str): | ||
def wrapper(func): | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,6 +5,8 @@ | |
from pathlib import Path | ||
from traceback import print_exc | ||
|
||
from mimetype_match import get_best_match | ||
|
||
from . import HttpError | ||
|
||
|
||
|
@@ -48,6 +50,15 @@ async def handle_options(request, response): | |
return request.method == 'OPTIONS' | ||
|
||
|
||
def content_negociation(app): | ||
|
||
@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 commentThe reason will be displayed to describe this comment to others. Learn more. I wonder if the return value of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Other thought: should the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
It's hard to guess without a use-case, I'd say let's wait for it to appear.
Not exactly, it can be a bit different so it's only for the check.
It can be a bit different, for instance the client asks for |
||
raise HttpError(HTTPStatus.NOT_ACCEPTABLE) | ||
|
||
|
||
def traceback(app): | ||
|
||
@app.listen('error') | ||
|
@@ -108,8 +119,8 @@ async def serve(request, response, path): | |
content_type, encoding = mimetypes.guess_type(str(abspath)) | ||
with abspath.open('rb') as source: | ||
response.body = source.read() | ||
response.headers['Content-Type'] = (content_type | ||
or 'application/octet-stream') | ||
response.headers['Content-Type'] = (content_type or | ||
'application/octet-stream') | ||
|
||
@app.listen('startup') | ||
async def register_route(): | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,15 @@ | |
import pytest | ||
|
||
|
||
class Transport: | ||
|
||
def write(self, data): | ||
... | ||
|
||
def close(self): | ||
... | ||
|
||
|
||
class Client: | ||
|
||
# Default content type for request body encoding, change it to your own | ||
|
@@ -36,12 +45,15 @@ async def request(self, path, method='GET', body=b'', headers=None, | |
headers['Content-Type'] = content_type | ||
body, headers = self.encode_body(body, headers) | ||
protocol = self.app.factory() | ||
protocol.connection_made(Transport()) | ||
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 commentThe 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 commentThe reason will be displayed to describe this comment to others. Learn more. Is that this urgent? #lazyme |
||
protocol.write() | ||
return protocol.response | ||
|
||
async def get(self, path, **kwargs): | ||
return await self.request(path, method='GET', **kwargs) | ||
|
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?