[REF] core: HTTPocalypse (1) rationnals
This commit is the 1st commit of a comprehensive refactor of our HTTP framework. See odoo/odoo#78857 for complete historic, discussions and rationnals. Our application framework is quite complicated. There are a few explicit middlewares: `ProxyFix`, `DisableCacheMiddleware`, `SharedDataMiddleware`. There are many (13, in total) implicit ones in the form of `ir.http._dispatch` overrides. There are a few frames that don't have much values by themselves and are very much implementation details there are: `application_unproxied`, `_call_function`, `checked_call` and `EndPoint.__call__`. The ORM initialisation is quite magical, the cursor, registry and environment are all setup lazily thanks to class properties. The context and uid of the environment are aliased on the request. The abuse of properties makes it impossible to reason about where and when the environment is actually instantiated and modified. The dispatch of a request follow the next scheme: `odoo.http/Root.dispatch` -> `odoo.addons.base.models/ir.http._dispatch` -> `odoo.http/(Http|Json)Request.dispatch` -> `odoo.http/@route` -> controller. Because the request becomes http or json specialized quite late, the many ir.http override all need to parsimony try/except their code in order to manually call `_handle_exception()` uppon error, they cannot let the error bubble-up. This back-and-forth between odoo.http and ir.http is source of some headaches. Speaking about error, the `odoo.http/WebRequest._handle_exception` implementation is quite complicated, it basically craft a new exception out of the passed exception object in order to correct its traceback. If the error had been bubbled-up instead of handled by `_handle_exception()` such python hack would not have been necessary. The objectives of this refactor are about refounding the technical dept: * We want shorter error reporting; * We want simpler request and ir.http APIs; * We want better integration of all the ir.http extensions. PR: odoo#78857 Task: 2571224
This commit is contained in:
-1710
File diff suppressed because it is too large
Load Diff
@@ -1,112 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
"""
|
||||
|
||||
WSGI stack, common code.
|
||||
|
||||
"""
|
||||
|
||||
import logging
|
||||
import sys
|
||||
import threading
|
||||
import traceback
|
||||
|
||||
from xmlrpc import client as xmlrpclib
|
||||
|
||||
import werkzeug.exceptions
|
||||
import werkzeug.wrappers
|
||||
import werkzeug.serving
|
||||
|
||||
import odoo
|
||||
from odoo.tools import config
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
# XML-RPC fault codes. Some care must be taken when changing these: the
|
||||
# constants are also defined client-side and must remain in sync.
|
||||
# User code must use the exceptions defined in ``odoo.exceptions`` (not
|
||||
# create directly ``xmlrpclib.Fault`` objects).
|
||||
RPC_FAULT_CODE_CLIENT_ERROR = 1 # indistinguishable from app. error.
|
||||
RPC_FAULT_CODE_APPLICATION_ERROR = 1
|
||||
RPC_FAULT_CODE_WARNING = 2
|
||||
RPC_FAULT_CODE_ACCESS_DENIED = 3
|
||||
RPC_FAULT_CODE_ACCESS_ERROR = 4
|
||||
|
||||
def xmlrpc_handle_exception_int(e):
|
||||
if isinstance(e, odoo.exceptions.RedirectWarning):
|
||||
fault = xmlrpclib.Fault(RPC_FAULT_CODE_WARNING, str(e))
|
||||
elif isinstance(e, odoo.exceptions.AccessError):
|
||||
fault = xmlrpclib.Fault(RPC_FAULT_CODE_ACCESS_ERROR, str(e))
|
||||
elif isinstance(e, odoo.exceptions.AccessDenied):
|
||||
fault = xmlrpclib.Fault(RPC_FAULT_CODE_ACCESS_DENIED, str(e))
|
||||
elif isinstance(e, odoo.exceptions.UserError):
|
||||
fault = xmlrpclib.Fault(RPC_FAULT_CODE_WARNING, str(e))
|
||||
else:
|
||||
info = sys.exc_info()
|
||||
# Which one is the best ?
|
||||
formatted_info = "".join(traceback.format_exception(*info))
|
||||
#formatted_info = odoo.tools.exception_to_unicode(e) + '\n' + info
|
||||
fault = xmlrpclib.Fault(RPC_FAULT_CODE_APPLICATION_ERROR, formatted_info)
|
||||
|
||||
return xmlrpclib.dumps(fault, allow_none=None)
|
||||
|
||||
def xmlrpc_handle_exception_string(e):
|
||||
if isinstance(e, odoo.exceptions.RedirectWarning):
|
||||
fault = xmlrpclib.Fault('warning -- Warning\n\n' + str(e), '')
|
||||
elif isinstance(e, odoo.exceptions.MissingError):
|
||||
fault = xmlrpclib.Fault('warning -- MissingError\n\n' + str(e), '')
|
||||
elif isinstance(e, odoo.exceptions.AccessError):
|
||||
fault = xmlrpclib.Fault('warning -- AccessError\n\n' + str(e), '')
|
||||
elif isinstance(e, odoo.exceptions.AccessDenied):
|
||||
fault = xmlrpclib.Fault('AccessDenied', str(e))
|
||||
elif isinstance(e, odoo.exceptions.UserError):
|
||||
fault = xmlrpclib.Fault('warning -- UserError\n\n' + str(e), '')
|
||||
#InternalError
|
||||
else:
|
||||
info = sys.exc_info()
|
||||
formatted_info = "".join(traceback.format_exception(*info))
|
||||
fault = xmlrpclib.Fault(odoo.tools.exception_to_unicode(e), formatted_info)
|
||||
|
||||
return xmlrpclib.dumps(fault, allow_none=None, encoding=None)
|
||||
|
||||
def application_unproxied(environ, start_response):
|
||||
""" WSGI entry point."""
|
||||
# cleanup db/uid trackers - they're set at HTTP dispatch in
|
||||
# web.session.OpenERPSession.send() and at RPC dispatch in
|
||||
# odoo.service.web_services.objects_proxy.dispatch().
|
||||
# /!\ The cleanup cannot be done at the end of this `application`
|
||||
# method because werkzeug still produces relevant logging afterwards
|
||||
if hasattr(threading.current_thread(), 'uid'):
|
||||
del threading.current_thread().uid
|
||||
if hasattr(threading.current_thread(), 'dbname'):
|
||||
del threading.current_thread().dbname
|
||||
if hasattr(threading.current_thread(), 'url'):
|
||||
del threading.current_thread().url
|
||||
|
||||
result = odoo.http.root(environ, start_response)
|
||||
if result is not None:
|
||||
return result
|
||||
|
||||
# We never returned from the loop.
|
||||
return werkzeug.exceptions.NotFound("No handler found.\n")(environ, start_response)
|
||||
|
||||
try:
|
||||
# werkzeug >= 0.15
|
||||
from werkzeug.middleware.proxy_fix import ProxyFix as ProxyFix_
|
||||
# 0.15 also supports port and prefix, but 0.14 only forwarded for, proto
|
||||
# and host so replicate that
|
||||
ProxyFix = lambda app: ProxyFix_(app, x_for=1, x_proto=1, x_host=1)
|
||||
except ImportError:
|
||||
# werkzeug < 0.15
|
||||
from werkzeug.contrib.fixers import ProxyFix
|
||||
|
||||
def application(environ, start_response):
|
||||
# FIXME: is checking for the presence of HTTP_X_FORWARDED_HOST really useful?
|
||||
# we're ignoring the user configuration, and that means we won't
|
||||
# support the standardised Forwarded header once werkzeug supports
|
||||
# it
|
||||
if config['proxy_mode'] and 'HTTP_X_FORWARDED_HOST' in environ:
|
||||
return ProxyFix(application_unproxied)(environ, start_response)
|
||||
else:
|
||||
return application_unproxied(environ, start_response)
|
||||
Reference in New Issue
Block a user