summaryrefslogtreecommitdiff
path: root/addons/web/common/dispatch.py
diff options
context:
space:
mode:
authorAntony Lesuisse <al@openerp.com>2011-10-05 19:58:26 +0200
committerAntony Lesuisse <al@openerp.com>2011-10-05 19:58:26 +0200
commit0d79dca93f85312deafe5df32d87dfe0c887d495 (patch)
treef46d488ab1a2c51c211170055cc53c5877f70292 /addons/web/common/dispatch.py
parentcaec244322bfe3f7f4d68c5d80554ffcbffdbd94 (diff)
[IMP] cleanup of web.common
bzr revid: al@openerp.com-20111005175826-7fzk3wesvz198kpm
Diffstat (limited to 'addons/web/common/dispatch.py')
-rw-r--r--addons/web/common/dispatch.py428
1 files changed, 0 insertions, 428 deletions
diff --git a/addons/web/common/dispatch.py b/addons/web/common/dispatch.py
deleted file mode 100644
index cb998caf891..00000000000
--- a/addons/web/common/dispatch.py
+++ /dev/null
@@ -1,428 +0,0 @@
-#!/usr/bin/python
-from __future__ import with_statement
-
-import functools
-import logging
-import urllib
-import os
-import pprint
-import sys
-import traceback
-import uuid
-import xmlrpclib
-
-import simplejson
-import werkzeug.datastructures
-import werkzeug.exceptions
-import werkzeug.utils
-import werkzeug.wrappers
-import werkzeug.wsgi
-
-import ast
-import nonliterals
-import http
-import session
-import openerplib
-
-__all__ = ['Root', 'jsonrequest', 'httprequest', 'Controller',
- 'WebRequest', 'JsonRequest', 'HttpRequest']
-
-_logger = logging.getLogger(__name__)
-
-#-----------------------------------------------------------
-# Globals (wont move into a pool)
-#-----------------------------------------------------------
-
-addons_module = {}
-addons_manifest = {}
-controllers_class = {}
-controllers_object = {}
-controllers_path = {}
-
-#----------------------------------------------------------
-# OpenERP Web RequestHandler
-#----------------------------------------------------------
-class WebRequest(object):
- """ Parent class for all OpenERP Web request types, mostly deals with
- initialization and setup of the request object (the dispatching itself has
- to be handled by the subclasses)
-
- :param request: a wrapped werkzeug Request object
- :type request: :class:`werkzeug.wrappers.BaseRequest`
- :param config: configuration object
-
- .. attribute:: httprequest
-
- the original :class:`werkzeug.wrappers.Request` object provided to the
- request
-
- .. attribute:: httpsession
-
- a :class:`~collections.Mapping` holding the HTTP session data for the
- current http session
-
- .. attribute:: config
-
- config parameter provided to the request object
-
- .. attribute:: params
-
- :class:`~collections.Mapping` of request parameters, not generally
- useful as they're provided directly to the handler method as keyword
- arguments
-
- .. attribute:: session_id
-
- opaque identifier for the :class:`session.OpenERPSession` instance of
- the current request
-
- .. attribute:: session
-
- :class:`~session.OpenERPSession` instance for the current request
-
- .. attribute:: context
-
- :class:`~collections.Mapping` of context values for the current request
-
- .. attribute:: debug
-
- ``bool``, indicates whether the debug mode is active on the client
- """
- def __init__(self, request, config):
- self.httprequest = request
- self.httpresponse = None
- self.httpsession = request.session
- self.config = config
-
- def init(self, params):
- self.params = dict(params)
- # OpenERP session setup
- self.session_id = self.params.pop("session_id", None) or uuid.uuid4().hex
- self.session = self.httpsession.setdefault(self.session_id, session.OpenERPSession())
- self.session.config = self.config
- self.context = self.params.pop('context', None)
- self.debug = self.params.pop('debug', False) != False
-
-class JsonRequest(WebRequest):
- """ JSON-RPC2 over HTTP.
-
- Sucessful request::
-
- --> {"jsonrpc": "2.0",
- "method": "call",
- "params": {"session_id": "SID",
- "context": {},
- "arg1": "val1" },
- "id": null}
-
- <-- {"jsonrpc": "2.0",
- "result": { "res1": "val1" },
- "id": null}
-
- Request producing a error::
-
- --> {"jsonrpc": "2.0",
- "method": "call",
- "params": {"session_id": "SID",
- "context": {},
- "arg1": "val1" },
- "id": null}
-
- <-- {"jsonrpc": "2.0",
- "error": {"code": 1,
- "message": "End user error message.",
- "data": {"code": "codestring",
- "debug": "traceback" } },
- "id": null}
-
- """
-
- def dispatch(self, controller, method, requestf=None, request=None):
- """ Calls the method asked for by the JSON-RPC2 request
-
- :param controller: the instance of the controller which received the request
- :param method: the method which received the request
- :param requestf: a file-like object containing an encoded JSON-RPC2 request
- :param request: a JSON-RPC2 request
-
- :returns: an utf8 encoded JSON-RPC2 reply
- """
- response = {"jsonrpc": "2.0" }
- error = None
- try:
- # Read POST content or POST Form Data named "request"
- if requestf:
- self.jsonrequest = simplejson.load(requestf, object_hook=nonliterals.non_literal_decoder)
- else:
- self.jsonrequest = simplejson.loads(request, object_hook=nonliterals.non_literal_decoder)
- self.init(self.jsonrequest.get("params", {}))
- if _logger.isEnabledFor(logging.DEBUG):
- _logger.debug("--> %s.%s\n%s", controller.__class__.__name__, method.__name__, pprint.pformat(self.jsonrequest))
- response['id'] = self.jsonrequest.get('id')
- response["result"] = method(controller, self, **self.params)
- except openerplib.AuthenticationError:
- error = {
- 'code': 100,
- 'message': "OpenERP Session Invalid",
- 'data': {
- 'type': 'session_invalid',
- 'debug': traceback.format_exc()
- }
- }
- except xmlrpclib.Fault, e:
- error = {
- 'code': 200,
- 'message': "OpenERP Server Error",
- 'data': {
- 'type': 'server_exception',
- 'fault_code': e.faultCode,
- 'debug': "Client %s\nServer %s" % (
- "".join(traceback.format_exception("", None, sys.exc_traceback)), e.faultString)
- }
- }
- except Exception:
- logging.getLogger(__name__ + '.JSONRequest.dispatch').exception\
- ("An error occured while handling a json request")
- error = {
- 'code': 300,
- 'message': "OpenERP WebClient Error",
- 'data': {
- 'type': 'client_exception',
- 'debug': "Client %s" % traceback.format_exc()
- }
- }
- if error:
- response["error"] = error
-
- if _logger.isEnabledFor(logging.DEBUG):
- _logger.debug("<--\n%s", pprint.pformat(response))
- content = simplejson.dumps(response, cls=nonliterals.NonLiteralEncoder)
- return werkzeug.wrappers.Response(
- content, headers=[('Content-Type', 'application/json'),
- ('Content-Length', len(content))])
-
-def jsonrequest(f):
- """ Decorator marking the decorated method as being a handler for a
- JSON-RPC request (the exact request path is specified via the
- ``$(Controller._cp_path)/$methodname`` combination.
-
- If the method is called, it will be provided with a :class:`JsonRequest`
- instance and all ``params`` sent during the JSON-RPC request, apart from
- the ``session_id``, ``context`` and ``debug`` keys (which are stripped out
- beforehand)
- """
- @functools.wraps(f)
- def json_handler(controller, request, config):
- return JsonRequest(request, config).dispatch(
- controller, f, requestf=request.stream)
- json_handler.exposed = True
- return json_handler
-
-class HttpRequest(WebRequest):
- """ Regular GET/POST request
- """
- def dispatch(self, controller, method):
- params = dict(self.httprequest.args)
- params.update(self.httprequest.form)
- params.update(self.httprequest.files)
- self.init(params)
- akw = {}
- for key, value in self.httprequest.args.iteritems():
- if isinstance(value, basestring) and len(value) < 1024:
- akw[key] = value
- else:
- akw[key] = type(value)
- _logger.debug("%s --> %s.%s %r", self.httprequest.method, controller.__class__.__name__, method.__name__, akw)
- r = method(controller, self, **self.params)
- if self.debug or 1:
- if isinstance(r, werkzeug.wrappers.BaseResponse):
- _logger.debug('<-- %s', r)
- else:
- _logger.debug("<-- size: %s", len(r))
- return r
-
- def make_response(self, data, headers=None, cookies=None):
- """ Helper for non-HTML responses, or HTML responses with custom
- response headers or cookies.
-
- While handlers can just return the HTML markup of a page they want to
- send as a string if non-HTML data is returned they need to create a
- complete response object, or the returned data will not be correctly
- interpreted by the clients.
-
- :param basestring data: response body
- :param headers: HTTP headers to set on the response
- :type headers: ``[(name, value)]``
- :param collections.Mapping cookies: cookies to set on the client
- """
- response = werkzeug.wrappers.Response(data, headers=headers)
- if cookies:
- for k, v in cookies.iteritems():
- response.set_cookie(k, v)
- return response
-
- def not_found(self, description=None):
- """ Helper for 404 response, return its result from the method
- """
- return werkzeug.exceptions.NotFound(description)
-
-def httprequest(f):
- """ Decorator marking the decorated method as being a handler for a
- normal HTTP request (the exact request path is specified via the
- ``$(Controller._cp_path)/$methodname`` combination.
-
- If the method is called, it will be provided with a :class:`HttpRequest`
- instance and all ``params`` sent during the request (``GET`` and ``POST``
- merged in the same dictionary), apart from the ``session_id``, ``context``
- and ``debug`` keys (which are stripped out beforehand)
- """
- @functools.wraps(f)
- def http_handler(controller, request, config):
- return HttpRequest(request, config).dispatch(controller, f)
- http_handler.exposed = True
- return http_handler
-
-class ControllerType(type):
- def __init__(cls, name, bases, attrs):
- super(ControllerType, cls).__init__(name, bases, attrs)
- controllers_class["%s.%s" % (cls.__module__, cls.__name__)] = cls
-
-class Controller(object):
- __metaclass__ = ControllerType
-
-class Root(object):
- """Root WSGI application for the OpenERP Web Client.
-
- :param options: mandatory initialization options object, must provide
- the following attributes:
-
- ``server_host`` (``str``)
- hostname of the OpenERP server to dispatch RPC to
- ``server_port`` (``int``)
- RPC port of the OpenERP server
- ``serve_static`` (``bool | None``)
- whether this application should serve the various
- addons's static files
- ``storage_path`` (``str``)
- filesystem path where HTTP session data will be stored
- ``dbfilter`` (``str``)
- only used in case the list of databases is requested
- by the server, will be filtered by this pattern
- """
- def __init__(self, options):
- self.root = '/web/webclient/home'
- self.config = options
-
- if self.config.backend == 'local':
- conn = openerplib.get_connector(protocol='local')
- else:
- conn = openerplib.get_connector(hostname=self.config.server_host,
- port=self.config.server_port)
- self.config.connector = conn
-
- self.session_cookie = 'sessionid'
- self.addons = {}
-
- static_dirs = self._load_addons()
- if options.serve_static:
- self.dispatch = werkzeug.wsgi.SharedDataMiddleware(
- self.dispatch, static_dirs)
-
- if options.session_storage:
- if not os.path.exists(options.session_storage):
- os.mkdir(options.session_storage, 0700)
- self.session_storage = options.session_storage
-
- def __call__(self, environ, start_response):
- """ Handle a WSGI request
- """
- return self.dispatch(environ, start_response)
-
- def dispatch(self, environ, start_response):
- """
- Performs the actual WSGI dispatching for the application, may be
- wrapped during the initialization of the object.
-
- Call the object directly.
- """
- request = werkzeug.wrappers.Request(environ)
- request.parameter_storage_class = werkzeug.datastructures.ImmutableDict
-
- if request.path == '/':
- params = urllib.urlencode(dict(request.args, debug=''))
- return werkzeug.utils.redirect(self.root + '?' + params, 301)(
- environ, start_response)
- elif request.path == '/mobile':
- return werkzeug.utils.redirect(
- '/web_mobile/static/src/web_mobile.html', 301)(environ, start_response)
-
- handler = self.find_handler(*(request.path.split('/')[1:]))
-
- if not handler:
- response = werkzeug.exceptions.NotFound()
- else:
- with http.session(request, self.session_storage, self.session_cookie) as session:
- result = handler(
- request, self.config)
-
- if isinstance(result, basestring):
- response = werkzeug.wrappers.Response(
- result, headers=[('Content-Type', 'text/html; charset=utf-8'),
- ('Content-Length', len(result))])
- else:
- response = result
-
- response.set_cookie(self.session_cookie, session.sid)
-
- return response(environ, start_response)
-
- def _load_addons(self):
- """
- Loads all addons at the specified addons path, returns a mapping of
- static URLs to the corresponding directories
- """
- statics = {}
- for addons_path in self.config.addons_path:
- if addons_path not in sys.path:
- sys.path.insert(0, addons_path)
- for module in os.listdir(addons_path):
- if module not in addons_module:
- manifest_path = os.path.join(addons_path, module, '__openerp__.py')
- path_static = os.path.join(addons_path, module, 'static')
- if os.path.isfile(manifest_path) and os.path.isdir(path_static):
- manifest = ast.literal_eval(open(manifest_path).read())
- manifest['addons_path'] = addons_path
- _logger.info("Loading %s", module)
- m = __import__(module)
- addons_module[module] = m
- addons_manifest[module] = manifest
- statics['/%s/static' % module] = path_static
- for k, v in controllers_class.items():
- if k not in controllers_object:
- o = v()
- controllers_object[k] = o
- if hasattr(o, '_cp_path'):
- controllers_path[o._cp_path] = o
- return statics
-
- def find_handler(self, *l):
- """
- Tries to discover the controller handling the request for the path
- specified by the provided parameters
-
- :param l: path sections to a controller or controller method
- :returns: a callable matching the path sections, or ``None``
- :rtype: ``Controller | None``
- """
- if len(l) > 1:
- for i in range(len(l), 1, -1):
- ps = "/" + "/".join(l[0:i])
- if ps in controllers_path:
- c = controllers_path[ps]
- rest = l[i:] or ['index']
- meth = rest[0]
- m = getattr(c, meth)
- if getattr(m, 'exposed', False):
- _logger.debug("Dispatching to %s %s %s", ps, c, meth)
- return m
- return None