[REF] core: HTTPocalypse (7) http dispatcher

This commit is the 7th commit of a comprehensive refactor of our HTTP
framework. See odoo/odoo#78857 for complete historic, discussions and
rationnals.

PR: odoo#78857
Task: 2571224
See also: [REF] core: HTTPocalypse (6) serve db-free routes
This commit is contained in:
Julien Castiaux
2022-02-24 13:30:48 +00:00
parent f4b0370943
commit d7e054d669
+121 -4
View File
@@ -203,7 +203,8 @@ DEFAULT_SESSION = {
'profile_params': None,
}
...
# The request mimetypes that transport JSON in their body.
JSON_MIMETYPES = ('application/json', 'application/json-rpc')
# The @route arguments to propagate from the decorated method to the
# routing rule.
@@ -475,7 +476,8 @@ def route(route=None, **routing):
_logger.warning("%s called ignoring args %s", fname, params_ko)
result = endpoint(self, *args, **params_ok)
...
if routing['type'] == 'http': # _generate_routing_rules() ensures type is set
return Response.load(result)
return result
route_wrapper.original_routing = routing
@@ -683,8 +685,82 @@ class Response(werkzeug.wrappers.Response):
In addition to the :class:`werkzeug.wrappers.Response` parameters,
this class's constructor can take the following additional
parameters for QWeb Lazy Rendering.
:param basestring template: template to render
:param dict qcontext: Rendering context to use
:param int uid: User id to use for the ir.ui.view render call,
``None`` to use the request's user (the default)
these attributes are available as parameters on the Response object
and can be altered at any time before rendering
Also exposes all the attributes and methods of
:class:`werkzeug.wrappers.Response`.
"""
...
default_mimetype = 'text/html'
def __init__(self, *args, **kw):
template = kw.pop('template', None)
qcontext = kw.pop('qcontext', None)
uid = kw.pop('uid', None)
super().__init__(*args, **kw)
self.set_default(template, qcontext, uid)
@classmethod
def load(cls, result, fname="<function>"):
"""
Convert the return value of an endpoint into a Response.
:param result: The endpoint return value to load the Response from.
:type result: Union[Response, werkzeug.wrappers.BaseResponse,
werkzeug.exceptions.HTTPException, str, bytes, NoneType]
:param str fname: The endpoint function name wherefrom the
result emanated, used for logging.
:returns: The created :class:`~odoo.http.Response`.
:rtype: Response
:raises TypeError: When ``result`` type is none of the above-
mentioned type.
"""
if isinstance(result, Response):
return result
if isinstance(result, werkzeug.exceptions.HTTPException):
_logger.warning("%s returns an HTTPException instead of raising it.", fname)
raise result
if isinstance(result, werkzeug.wrappers.BaseResponse):
response = cls.force_type(result)
response.set_default()
return response
if isinstance(result, (bytes, str, type(None))):
return cls(result)
raise TypeError(f"{fname} returns an invalid value: {result}")
def set_default(self, template=None, qcontext=None, uid=None):
self.template = template
self.qcontext = qcontext or dict()
self.qcontext['response_template'] = self.template
self.uid = uid
@property
def is_qweb(self):
return self.template is not None
def render(self):
""" Renders the Response's template, returns the result. """
self.qcontext['request'] = request
return request.env["ir.ui.view"]._render_template(self.template, self.qcontext)
def flatten(self):
"""
Forces the rendering of the response's template, sets the result
as response body and unsets :attr:`.template`
"""
if self.template:
self.response.append(self.render())
self.template = None
class FutureResponse:
@@ -791,6 +867,23 @@ class Request:
except (ValueError, KeyError):
return DEFAULT_LANG
def get_http_params(self):
"""
Extract key=value pairs from the query string and the forms
present in the body (both application/x-www-form-urlencoded and
multipart/form-data).
:returns: The merged key-value pairs.
:rtype: dict
"""
params = {
**self.httprequest.args,
**self.httprequest.form,
**self.httprequest.files
}
params.pop('session_id', None)
return params
...
def _inject_future_response(self, response):
@@ -945,7 +1038,31 @@ class Dispatcher(ABC):
class HttpDispatcher(Dispatcher):
routing_type = 'http'
...
@classmethod
def is_compatible_with(cls, request):
return request.httprequest.mimetype not in JSON_MIMETYPES
def dispatch(self, endpoint, args):
"""
Perform http-related actions such as deserializing the request
body and query-string and checking cors/csrf while dispatching a
request to a ``type='http'`` route.
See :meth:`~odoo.http.Response.load`: method for the compatible
endpoint return types.
"""
self.request.params = dict(self.request.get_http_params(), **args)
...
if self.request.db:
...
else:
return endpoint(**self.request.params)
def handle_error(self, exc):
...
class JsonRPCDispatcher(Dispatcher):