From 18382b165d346cdaeda854b1d6be01a359ac8ff6 Mon Sep 17 00:00:00 2001 From: Julien Castiaux Date: Thu, 10 Feb 2022 12:40:23 +0000 Subject: [PATCH] [REF] core: HTTPocalypse (8) json dispatcher This commit is the 8th 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 --- odoo/http.py | 79 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 78 insertions(+), 1 deletion(-) diff --git a/odoo/http.py b/odoo/http.py index 0b26b315c09..9da9e10d4d7 100644 --- a/odoo/http.py +++ b/odoo/http.py @@ -1067,7 +1067,84 @@ class HttpDispatcher(Dispatcher): class JsonRPCDispatcher(Dispatcher): routing_type = 'json' - ... + + def __init__(self, request): + super().__init__(request) + self.jsonrequest = {} + + @classmethod + def is_compatible_with(cls, request): + return request.httprequest.mimetype in JSON_MIMETYPES + + def dispatch(self, endpoint, args): + """ + `JSON-RPC 2 `_ over HTTP. + + Our implementation differs from the specification on two points: + + 1. The ``method`` member of the JSON-RPC request payload is + ignored as the HTTP path is already used to route the request + to the controller. + 2. We only support parameter structures by-name, i.e. the + ``params`` member of the JSON-RPC request payload MUST be a + JSON Object and not a JSON Array. + + In addition, it is possible to pass a context that replaces + the session context via a special ``context`` argument that is + removed prior to calling the endpoint. + + Sucessful request:: + + --> {"jsonrpc": "2.0", "method": "call", "params": {"context": {}, "arg1": "val1" }, "id": null} + + <-- {"jsonrpc": "2.0", "result": { "res1": "val1" }, "id": null} + + Request producing a error:: + + --> {"jsonrpc": "2.0", "method": "call", "params": {"context": {}, "arg1": "val1" }, "id": null} + + <-- {"jsonrpc": "2.0", "error": {"code": 1, "message": "End user error message.", "data": {"code": "codestring", "debug": "traceback" } }, "id": null} + + """ + httprequest = self.request.httprequest + body = httprequest.get_data().decode(httprequest.charset) + try: + self.jsonrequest = json.loads(body) + except ValueError: + _logger.info('%s: Invalid JSON data\n%s', httprequest.path, + body) + raise werkzeug.exceptions.BadRequest(f"Invalid JSON data:\n{body}") + + self.request.params = dict(self.jsonrequest.get('params', {}), **args) + ctx = self.request.params.pop('context', None) + if ctx is not None and self.request.db: + self.request.update_env(context=ctx) + + if self.request.db: + ... + else: + result = endpoint(**self.request.params) + return self._response(result) + + def handle_error(self, exc): + ... + + def _response(self, result=None, error=None): + request_id = self.jsonrequest.get('id') + status = 200 + response = {'jsonrpc': '2.0', 'id': request_id} + if error is not None: + response['error'] = error + status = error.pop('http_status', 200) + if result is not None: + response['result'] = result + + body = json.dumps(response, default=date_utils.json_default) + + return Response(body, status=status, headers=[ + ('Content-Type', 'application/json'), + ('Content-Length', len(body)), + ]) # =========================================================