[IMP] models: add check for common conversion error in field definitions
[odoo/odoo.git] / openerp / http.py
index 3bc2bc3..eddbcb6 100644 (file)
@@ -5,6 +5,7 @@
 import ast
 import collections
 import contextlib
 import ast
 import collections
 import contextlib
+import datetime
 import errno
 import functools
 import getpass
 import errno
 import functools
 import getpass
@@ -21,8 +22,10 @@ import time
 import traceback
 import urlparse
 import warnings
 import traceback
 import urlparse
 import warnings
+from zlib import adler32
 
 import babel.core
 
 import babel.core
+import psutil
 import psycopg2
 import simplejson
 import werkzeug.contrib.sessions
 import psycopg2
 import simplejson
 import werkzeug.contrib.sessions
@@ -32,13 +35,19 @@ import werkzeug.local
 import werkzeug.routing
 import werkzeug.wrappers
 import werkzeug.wsgi
 import werkzeug.routing
 import werkzeug.wrappers
 import werkzeug.wsgi
+from werkzeug.wsgi import wrap_file
 
 import openerp
 
 import openerp
+from openerp import SUPERUSER_ID
 from openerp.service import security, model as service_model
 from openerp.service import security, model as service_model
-import openerp.tools
+from openerp.tools.func import lazy_property
+from openerp.tools import ustr
 
 _logger = logging.getLogger(__name__)
 
 
 _logger = logging.getLogger(__name__)
 
+# 1 week cache for statics as advised by Google Page Speed
+STATIC_CACHE = 60 * 60 * 24 * 7
+
 #----------------------------------------------------------
 # RequestHandler
 #----------------------------------------------------------
 #----------------------------------------------------------
 # RequestHandler
 #----------------------------------------------------------
@@ -50,6 +59,76 @@ request = _request_stack()
     A global proxy that always redirect to the current request object.
 """
 
     A global proxy that always redirect to the current request object.
 """
 
+def replace_request_password(args):
+    # password is always 3rd argument in a request, we replace it in RPC logs
+    # so it's easier to forward logs for diagnostics/debugging purposes...
+    if len(args) > 2:
+        args = list(args)
+        args[2] = '*'
+    return tuple(args)
+
+# don't trigger debugger for those exceptions, they carry user-facing warnings
+# and indications, they're not necessarily indicative of anything being
+# *broken*
+NO_POSTMORTEM = (openerp.osv.orm.except_orm,
+                 openerp.exceptions.AccessError,
+                 openerp.exceptions.AccessDenied,
+                 openerp.exceptions.Warning,
+                 openerp.exceptions.RedirectWarning)
+def dispatch_rpc(service_name, method, params):
+    """ Handle a RPC call.
+
+    This is pure Python code, the actual marshalling (from/to XML-RPC) is done
+    in a upper layer.
+    """
+    try:
+        rpc_request = logging.getLogger(__name__ + '.rpc.request')
+        rpc_response = logging.getLogger(__name__ + '.rpc.response')
+        rpc_request_flag = rpc_request.isEnabledFor(logging.DEBUG)
+        rpc_response_flag = rpc_response.isEnabledFor(logging.DEBUG)
+        if rpc_request_flag or rpc_response_flag:
+            start_time = time.time()
+            start_rss, start_vms = 0, 0
+            start_rss, start_vms = psutil.Process(os.getpid()).get_memory_info()
+            if rpc_request and rpc_response_flag:
+                openerp.netsvc.log(rpc_request, logging.DEBUG, '%s.%s' % (service_name, method), replace_request_password(params))
+
+        threading.current_thread().uid = None
+        threading.current_thread().dbname = None
+        if service_name == 'common':
+            dispatch = openerp.service.common.dispatch
+        elif service_name == 'db':
+            dispatch = openerp.service.db.dispatch
+        elif service_name == 'object':
+            dispatch = openerp.service.model.dispatch
+        elif service_name == 'report':
+            dispatch = openerp.service.report.dispatch
+        else:
+            dispatch = openerp.service.wsgi_server.rpc_handlers.get(service_name)
+        result = dispatch(method, params)
+
+        if rpc_request_flag or rpc_response_flag:
+            end_time = time.time()
+            end_rss, end_vms = 0, 0
+            end_rss, end_vms = psutil.Process(os.getpid()).get_memory_info()
+            logline = '%s.%s time:%.3fs mem: %sk -> %sk (diff: %sk)' % (service_name, method, end_time - start_time, start_vms / 1024, end_vms / 1024, (end_vms - start_vms)/1024)
+            if rpc_response_flag:
+                openerp.netsvc.log(rpc_response, logging.DEBUG, logline, result)
+            else:
+                openerp.netsvc.log(rpc_request, logging.DEBUG, logline, replace_request_password(params), depth=1)
+
+        return result
+    except NO_POSTMORTEM:
+        raise
+    except openerp.exceptions.DeferredException, e:
+        _logger.exception(openerp.tools.exception_to_unicode(e))
+        openerp.tools.debugger.post_mortem(openerp.tools.config, e.traceback)
+        raise
+    except Exception, e:
+        _logger.exception(openerp.tools.exception_to_unicode(e))
+        openerp.tools.debugger.post_mortem(openerp.tools.config, sys.exc_info())
+        raise
+
 def local_redirect(path, query=None, keep_hash=False, forward_debug=True, code=303):
     url = path
     if not query:
 def local_redirect(path, query=None, keep_hash=False, forward_debug=True, code=303):
     url = path
     if not query:
@@ -73,68 +152,33 @@ def redirect_with_hash(url, code=303):
     return "<html><head><script>window.location = '%s' + location.hash;</script></head></html>" % url
 
 class WebRequest(object):
     return "<html><head><script>window.location = '%s' + location.hash;</script></head></html>" % url
 
 class WebRequest(object):
-    """ Parent class for all OpenERP Web request types, mostly deals with
+    """ Parent class for all Odoo Web request types, mostly deals with
     initialization and setup of the request object (the dispatching itself has
     to be handled by the subclasses)
 
     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 httprequest: a wrapped werkzeug Request object
+    :type httprequest: :class:`werkzeug.wrappers.BaseRequest`
 
     .. attribute:: httprequest
 
         the original :class:`werkzeug.wrappers.Request` object provided to the
         request
 
 
     .. attribute:: httprequest
 
         the original :class:`werkzeug.wrappers.Request` object provided to the
         request
 
-    .. attribute:: httpsession
-
-        .. deprecated:: 8.0
-
-        Use ``self.session`` instead.
-
     .. attribute:: params
 
         :class:`~collections.Mapping` of request parameters, not generally
         useful as they're provided directly to the handler method as keyword
         arguments
     .. 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
-
-        a :class:`OpenERPSession` holding the HTTP session data for the
-        current http session
-
-    .. attribute:: context
-
-        :class:`~collections.Mapping` of context values for the current request
-
-    .. attribute:: db
-
-        ``str``, the name of the database linked to the current request. Can be ``None``
-        if the current request uses the ``none`` authentication.
-
-    .. attribute:: uid
-
-        ``int``, the id of the user related to the current request. Can be ``None``
-        if the current request uses the ``none`` authenticatoin.
     """
     def __init__(self, httprequest):
         self.httprequest = httprequest
         self.httpresponse = None
         self.httpsession = httprequest.session
     """
     def __init__(self, httprequest):
         self.httprequest = httprequest
         self.httpresponse = None
         self.httpsession = httprequest.session
-        self.session = httprequest.session
-        self.session_id = httprequest.session.sid
         self.disable_db = False
         self.uid = None
         self.disable_db = False
         self.uid = None
-        self.func = None
-        self.func_arguments = {}
+        self.endpoint = None
         self.auth_method = None
         self.auth_method = None
-        self._cr_cm = None
         self._cr = None
         self._cr = None
-        self.func_request_type = None
 
         # prevents transaction commit, use when you catch an exception during handling
         self._failed = None
 
         # prevents transaction commit, use when you catch an exception during handling
         self._failed = None
@@ -145,34 +189,47 @@ class WebRequest(object):
             threading.current_thread().dbname = self.db
         if self.session.uid:
             threading.current_thread().uid = self.session.uid
             threading.current_thread().dbname = self.db
         if self.session.uid:
             threading.current_thread().uid = self.session.uid
-        self.context = dict(self.session.context)
-        self.lang = self.context["lang"]
 
 
-    @property
-    def registry(self):
+    @lazy_property
+    def env(self):
         """
         """
-        The registry to the database linked to this request. Can be ``None`` if the current request uses the
-        ``none'' authentication.
+        The :class:`~openerp.api.Environment` bound to current request.
         """
         """
-        return openerp.modules.registry.RegistryManager.get(self.db) if self.db else None
+        return openerp.api.Environment(self.cr, self.uid, self.context)
 
 
-    @property
-    def db(self):
+    @lazy_property
+    def context(self):
         """
         """
-        The registry to the database linked to this request. Can be ``None`` if the current request uses the
-        ``none'' authentication.
+        :class:`~collections.Mapping` of context values for the current
+        request
         """
         """
-        return self.session.db if not self.disable_db else None
+        return dict(self.session.context)
+
+    @lazy_property
+    def lang(self):
+        return self.context["lang"]
+
+    @lazy_property
+    def session(self):
+        """
+        a :class:`OpenERPSession` holding the HTTP session data for the
+        current http session
+        """
+        return self.httprequest.session
 
     @property
     def cr(self):
         """
 
     @property
     def cr(self):
         """
-        The cursor initialized for the current method call. If the current request uses the ``none`` authentication
-        trying to access this property will raise an exception.
+        :class:`~openerp.sql_db.Cursor` initialized for the current method
+        call.
+
+        Accessing the cursor when the current request uses the ``none``
+        authentication will raise an exception.
         """
         """
-        # some magic to lazy create the cr
+        # can not be a lazy_property because manual rollback in _call_function
+        # if already set (?)
         if not self._cr:
         if not self._cr:
-            self._cr = self.registry.db.cursor()
+            self._cr = self.registry.cursor()
         return self._cr
 
     def __enter__(self):
         return self._cr
 
     def __enter__(self):
@@ -181,25 +238,22 @@ class WebRequest(object):
 
     def __exit__(self, exc_type, exc_value, traceback):
         _request_stack.pop()
 
     def __exit__(self, exc_type, exc_value, traceback):
         _request_stack.pop()
+
         if self._cr:
             if exc_type is None and not self._failed:
                 self._cr.commit()
         if self._cr:
             if exc_type is None and not self._failed:
                 self._cr.commit()
-            else:
-                # just to be explicit - happens at close() anyway
-                self._cr.rollback()
             self._cr.close()
         # just to be sure no one tries to re-use the request
         self.disable_db = True
         self.uid = None
 
             self._cr.close()
         # just to be sure no one tries to re-use the request
         self.disable_db = True
         self.uid = None
 
-    def set_handler(self, func, arguments, auth):
+    def set_handler(self, endpoint, arguments, auth):
         # is this needed ?
         arguments = dict((k, v) for k, v in arguments.iteritems()
                          if not k.startswith("_ignored_"))
 
         # is this needed ?
         arguments = dict((k, v) for k, v in arguments.iteritems()
                          if not k.startswith("_ignored_"))
 
-        self.func = func
-        self.func_request_type = func.routing['type']
-        self.func_arguments = arguments
+        endpoint.arguments = arguments
+        self.endpoint = endpoint
         self.auth_method = auth
 
 
         self.auth_method = auth
 
 
@@ -208,18 +262,23 @@ class WebRequest(object):
            to abitrary responses. Anything returned (except None) will
            be used as response.""" 
         self._failed = exception # prevent tx commit
            to abitrary responses. Anything returned (except None) will
            be used as response.""" 
         self._failed = exception # prevent tx commit
+        if not isinstance(exception, NO_POSTMORTEM):
+            openerp.tools.debugger.post_mortem(
+                openerp.tools.config, sys.exc_info())
         raise
 
     def _call_function(self, *args, **kwargs):
         request = self
         raise
 
     def _call_function(self, *args, **kwargs):
         request = self
-        if self.func_request_type != self._request_type:
-            raise Exception("%s, %s: Function declared as capable of handling request of type '%s' but called with a request of type '%s'" \
-                % (self.func, self.httprequest.path, self.func_request_type, self._request_type))
+        if self.endpoint.routing['type'] != self._request_type:
+            msg = "%s, %s: Function declared as capable of handling request of type '%s' but called with a request of type '%s'"
+            params = (self.endpoint.original, self.httprequest.path, self.endpoint.routing['type'], self._request_type)
+            _logger.error(msg, *params)
+            raise werkzeug.exceptions.BadRequest(msg % params)
 
 
-        kwargs.update(self.func_arguments)
+        kwargs.update(self.endpoint.arguments)
 
         # Backward for 7.0
 
         # Backward for 7.0
-        if getattr(self.func.method, '_first_arg_is_req', False):
+        if self.endpoint.first_arg_is_req:
             args = (request,) + args
 
         # Correct exception handling and concurency retry
             args = (request,) + args
 
         # Correct exception handling and concurency retry
@@ -229,14 +288,16 @@ class WebRequest(object):
             # case, the request cursor is unusable. Rollback transaction to create a new one.
             if self._cr:
                 self._cr.rollback()
             # case, the request cursor is unusable. Rollback transaction to create a new one.
             if self._cr:
                 self._cr.rollback()
-            return self.func(*a, **kw)
+            return self.endpoint(*a, **kw)
 
         if self.db:
             return checked_call(self.db, *args, **kwargs)
 
         if self.db:
             return checked_call(self.db, *args, **kwargs)
-        return self.func(*args, **kwargs)
+        return self.endpoint(*args, **kwargs)
 
     @property
     def debug(self):
 
     @property
     def debug(self):
+        """ Indicates whether the current request is in "debug" mode
+        """
         return 'debug' in self.httprequest.args
 
     @contextlib.contextmanager
         return 'debug' in self.httprequest.args
 
     @contextlib.contextmanager
@@ -244,24 +305,72 @@ class WebRequest(object):
         warnings.warn('please use request.registry and request.cr directly', DeprecationWarning)
         yield (self.registry, self.cr)
 
         warnings.warn('please use request.registry and request.cr directly', DeprecationWarning)
         yield (self.registry, self.cr)
 
+    @lazy_property
+    def session_id(self):
+        """
+        opaque identifier for the :class:`OpenERPSession` instance of
+        the current request
+
+        .. deprecated:: 8.0
+
+            Use the ``sid`` attribute on :attr:`.session`
+        """
+        return self.session.sid
+
+    @property
+    def registry(self):
+        """
+        The registry to the database linked to this request. Can be ``None``
+        if the current request uses the ``none`` authentication.
+
+        .. deprecated:: 8.0
+
+            use :attr:`.env`
+        """
+        return openerp.modules.registry.RegistryManager.get(self.db) if self.db else None
+
+    @property
+    def db(self):
+        """
+        The database linked to this request. Can be ``None``
+        if the current request uses the ``none`` authentication.
+        """
+        return self.session.db if not self.disable_db else None
+
+    @lazy_property
+    def httpsession(self):
+        """ HTTP session data
+
+        .. deprecated:: 8.0
+
+            Use :attr:`.session` instead.
+        """
+        return self.session
+
 def route(route=None, **kw):
     """
 def route(route=None, **kw):
     """
-    Decorator marking the decorated method as being a handler for requests. The method must be part of a subclass
-    of ``Controller``.
-
-    :param route: string or array. The route part that will determine which http requests will match the decorated
-    method. Can be a single string or an array of strings. See werkzeug's routing documentation for the format of
-    route expression ( http://werkzeug.pocoo.org/docs/routing/ ).
+    Decorator marking the decorated method as being a handler for
+    requests. The method must be part of a subclass of ``Controller``.
+
+    :param route: string or array. The route part that will determine which
+                  http requests will match the decorated method. Can be a
+                  single string or an array of strings. See werkzeug's routing
+                  documentation for the format of route expression (
+                  http://werkzeug.pocoo.org/docs/routing/ ).
     :param type: The type of request, can be ``'http'`` or ``'json'``.
     :param auth: The type of authentication method, can on of the following:
 
     :param type: The type of request, can be ``'http'`` or ``'json'``.
     :param auth: The type of authentication method, can on of the following:
 
-        * ``user``: The user must be authenticated and the current request will perform using the rights of the
-        user.
-        * ``admin``: The user may not be authenticated and the current request will perform using the admin user.
-        * ``none``: The method is always active, even if there is no database. Mainly used by the framework and
-        authentication modules. There request code will not have any facilities to access the database nor have any
-        configuration indicating the current database nor the current user.
-    :param methods: A sequence of http methods this route applies to. If not specified, all methods are allowed.
+                 * ``user``: The user must be authenticated and the current request
+                   will perform using the rights of the user.
+                 * ``admin``: The user may not be authenticated and the current request
+                   will perform using the admin user.
+                 * ``none``: The method is always active, even if there is no
+                   database. Mainly used by the framework and authentication
+                   modules. There request code will not have any facilities to access
+                   the database nor have any configuration indicating the current
+                   database nor the current user.
+    :param methods: A sequence of http methods this route applies to. If not
+                    specified, all methods are allowed.
     :param cors: The Access-Control-Allow-Origin cors directive value.
     """
     routing = kw.copy()
     :param cors: The Access-Control-Allow-Origin cors directive value.
     """
     routing = kw.copy()
@@ -273,12 +382,35 @@ def route(route=None, **kw):
             else:
                 routes = [route]
             routing['routes'] = routes
             else:
                 routes = [route]
             routing['routes'] = routes
-        f.routing = routing
-        return f
+        @functools.wraps(f)
+        def response_wrap(*args, **kw):
+            response = f(*args, **kw)
+            if isinstance(response, Response) or f.routing_type == 'json':
+                return response
+            elif isinstance(response, werkzeug.wrappers.BaseResponse):
+                response = Response.force_type(response)
+                response.set_default()
+                return response
+            elif isinstance(response, basestring):
+                return Response(response)
+            else:
+                _logger.warn("<function %s.%s> returns an invalid response type for an http request" % (f.__module__, f.__name__))
+            return response
+        response_wrap.routing = routing
+        response_wrap.original_func = f
+        return response_wrap
     return decorator
 
 class JsonRequest(WebRequest):
     return decorator
 
 class JsonRequest(WebRequest):
-    """ JSON-RPC2 over HTTP.
+    """ Request handler for `JSON-RPC 2
+    <http://www.jsonrpc.org/specification>`_ over HTTP
+
+    * ``method`` is ignored
+    * ``params`` must be a JSON object (not an array) and is passed as keyword
+      arguments to the handler method
+    * the handler method's result is returned as JSON-RPC ``result`` and
+      wrapped in the `JSON-RPC Response
+      <http://www.jsonrpc.org/specification#response_object>`_
 
     Sucessful request::
 
 
     Sucessful request::
 
@@ -342,7 +474,13 @@ class JsonRequest(WebRequest):
             request = self.httprequest.stream.read()
 
         # Read POST content or POST Form Data named "request"
             request = self.httprequest.stream.read()
 
         # Read POST content or POST Form Data named "request"
-        self.jsonrequest = simplejson.loads(request)
+        try:
+            self.jsonrequest = simplejson.loads(request)
+        except simplejson.JSONDecodeError:
+            msg = 'Invalid JSON data: %r' % (request,)
+            _logger.error('%s: %s', self.httprequest.path, msg)
+            raise werkzeug.exceptions.BadRequest(msg)
+
         self.params = dict(self.jsonrequest.get("params", {}))
         self.context = self.params.pop('context', dict(self.session.context))
 
         self.params = dict(self.jsonrequest.get("params", {}))
         self.context = self.params.pop('context', dict(self.session.context))
 
@@ -350,7 +488,7 @@ class JsonRequest(WebRequest):
         response = {
             'jsonrpc': '2.0',
             'id': self.jsonrequest.get('id')
         response = {
             'jsonrpc': '2.0',
             'id': self.jsonrequest.get('id')
-        }
+            }
         if error is not None:
             response['error'] = error
         if result is not None:
         if error is not None:
             response['error'] = error
         if result is not None:
@@ -367,31 +505,33 @@ class JsonRequest(WebRequest):
             mime = 'application/json'
             body = simplejson.dumps(response)
 
             mime = 'application/json'
             body = simplejson.dumps(response)
 
-        return werkzeug.wrappers.Response(
+        return Response(
                     body, headers=[('Content-Type', mime),
                                    ('Content-Length', len(body))])
 
     def _handle_exception(self, exception):
         """Called within an except block to allow converting exceptions
                     body, headers=[('Content-Type', mime),
                                    ('Content-Length', len(body))])
 
     def _handle_exception(self, exception):
         """Called within an except block to allow converting exceptions
-           to abitrary responses. Anything returned (except None) will
+           to arbitrary responses. Anything returned (except None) will
            be used as response."""
         try:
             return super(JsonRequest, self)._handle_exception(exception)
         except Exception:
            be used as response."""
         try:
             return super(JsonRequest, self)._handle_exception(exception)
         except Exception:
-            _logger.exception("Exception during JSON request handling.")
+            if not isinstance(exception, (openerp.exceptions.Warning, SessionExpiredException)):
+                _logger.exception("Exception during JSON request handling.")
             error = {
                     'code': 200,
             error = {
                     'code': 200,
-                    'message': "OpenERP Server Error",
+                    'message': "Odoo Server Error",
                     'data': serialize_exception(exception)
             }
             if isinstance(exception, AuthenticationError):
                 error['code'] = 100
                     'data': serialize_exception(exception)
             }
             if isinstance(exception, AuthenticationError):
                 error['code'] = 100
-                error['message'] = "OpenERP Session Invalid"
+                error['message'] = "Odoo Session Invalid"
+            if isinstance(exception, SessionExpiredException):
+                error['code'] = 100
+                error['message'] = "Odoo Session Expired"
             return self._json_response(error=error)
 
     def dispatch(self):
             return self._json_response(error=error)
 
     def dispatch(self):
-        """ Calls the method asked for by the JSON-RPC2 or JSONP request
-        """
         if self.jsonp_handler:
             return self.jsonp_handler()
         try:
         if self.jsonp_handler:
             return self.jsonp_handler()
         try:
@@ -404,7 +544,7 @@ def serialize_exception(e):
     tmp = {
         "name": type(e).__module__ + "." + type(e).__name__ if type(e).__module__ else type(e).__name__,
         "debug": traceback.format_exc(),
     tmp = {
         "name": type(e).__module__ + "." + type(e).__name__ if type(e).__module__ else type(e).__name__,
         "debug": traceback.format_exc(),
-        "message": u"%s" % e,
+        "message": ustr(e),
         "arguments": to_jsonable(e.args),
     }
     if isinstance(e, openerp.osv.osv.except_osv):
         "arguments": to_jsonable(e.args),
     }
     if isinstance(e, openerp.osv.osv.except_osv):
@@ -428,13 +568,12 @@ def to_jsonable(o):
         for k, v in o.items():
             tmp[u"%s" % k] = to_jsonable(v)
         return tmp
         for k, v in o.items():
             tmp[u"%s" % k] = to_jsonable(v)
         return tmp
-    return u"%s" % o
+    return ustr(o)
 
 def jsonrequest(f):
     """ 
         .. deprecated:: 8.0
 
 def jsonrequest(f):
     """ 
         .. deprecated:: 8.0
-
-        Use the ``route()`` decorator instead.
+            Use the :func:`~openerp.http.route` decorator instead.
     """
     base = f.__name__.lstrip('/')
     if f.__name__ == "index":
     """
     base = f.__name__.lstrip('/')
     if f.__name__ == "index":
@@ -442,7 +581,23 @@ def jsonrequest(f):
     return route([base, base + "/<path:_ignored_path>"], type="json", auth="user", combine=True)(f)
 
 class HttpRequest(WebRequest):
     return route([base, base + "/<path:_ignored_path>"], type="json", auth="user", combine=True)(f)
 
 class HttpRequest(WebRequest):
-    """ Regular GET/POST request
+    """ Handler for the ``http`` request type.
+
+    matched routing parameters, query string parameters, form_ parameters
+    and files are passed to the handler method as keyword arguments.
+
+    In case of name conflict, routing parameters have priority.
+
+    The handler method's result can be:
+
+    * a falsy value, in which case the HTTP response will be an
+      `HTTP 204`_ (No Content)
+    * a werkzeug Response object, which is returned as-is
+    * a ``str`` or ``unicode``, will be wrapped in a Response object and
+      interpreted as HTML
+
+    .. _form: http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.2
+    .. _HTTP 204: http://tools.ietf.org/html/rfc7231#section-6.3.5
     """
     _request_type = "http"
 
     """
     _request_type = "http"
 
@@ -460,29 +615,26 @@ class HttpRequest(WebRequest):
            be used as response."""
         try:
             return super(HttpRequest, self)._handle_exception(exception)
            be used as response."""
         try:
             return super(HttpRequest, self)._handle_exception(exception)
-        except Exception, e:
-            if isinstance(e, werkzeug.exceptions.HTTPException):
-                return e
-            raise
+        except SessionExpiredException:
+            if not request.params.get('noredirect'):
+                query = werkzeug.urls.url_encode({
+                    'redirect': request.httprequest.url,
+                })
+                return werkzeug.utils.redirect('/web/login?%s' % query)
+        except werkzeug.exceptions.HTTPException, e:
+            return e
 
     def dispatch(self):
 
     def dispatch(self):
-        # TODO: refactor this correctly. This is a quick fix for pos demo.
-        if request.httprequest.method == 'OPTIONS' and request.func and request.func.routing.get('cors'):
-            response = werkzeug.wrappers.Response(status=200)
-            response.headers.set('Access-Control-Allow-Origin', request.func.routing['cors'])
-            methods = 'GET, POST'
-            if request.func_request_type == 'json':
-                methods = 'POST'
-            elif request.func.routing.get('methods'):
-                methods = ', '.join(request.func.routing['methods'])
-            response.headers.set('Access-Control-Allow-Methods', methods)
-            response.headers.set('Access-Control-Max-Age',60*60*24)
-            response.headers.set('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept')
-            return response
+        if request.httprequest.method == 'OPTIONS' and request.endpoint and request.endpoint.routing.get('cors'):
+            headers = {
+                'Access-Control-Max-Age': 60 * 60 * 24,
+                'Access-Control-Allow-Headers': 'Origin, X-Requested-With, Content-Type, Accept'
+            }
+            return Response(status=200, headers=headers)
 
         r = self._call_function(**self.params)
         if not r:
 
         r = self._call_function(**self.params)
         if not r:
-            r = werkzeug.wrappers.Response(status=204)  # no content
+            r = Response(status=204)  # no content
         return r
 
     def make_response(self, data, headers=None, cookies=None):
         return r
 
     def make_response(self, data, headers=None, cookies=None):
@@ -499,14 +651,34 @@ class HttpRequest(WebRequest):
         :type headers: ``[(name, value)]``
         :param collections.Mapping cookies: cookies to set on the client
         """
         :type headers: ``[(name, value)]``
         :param collections.Mapping cookies: cookies to set on the client
         """
-        response = werkzeug.wrappers.Response(data, headers=headers)
+        response = Response(data, headers=headers)
         if cookies:
             for k, v in cookies.iteritems():
                 response.set_cookie(k, v)
         return response
 
         if cookies:
             for k, v in cookies.iteritems():
                 response.set_cookie(k, v)
         return response
 
+    def render(self, template, qcontext=None, lazy=True, **kw):
+        """ Lazy render of a QWeb template.
+
+        The actual rendering of the given template will occur at then end of
+        the dispatching. Meanwhile, the template and/or qcontext can be
+        altered or even replaced by a static response.
+
+        :param basestring template: template to render
+        :param dict qcontext: Rendering context to use
+        :param bool lazy: whether the template rendering should be deferred
+                          until the last possible moment
+        :param kw: forwarded to werkzeug's Response object
+        """
+        response = Response(template=template, qcontext=qcontext, **kw)
+        if not lazy:
+            return response.render()
+        return response
+
     def not_found(self, description=None):
     def not_found(self, description=None):
-        """ Helper for 404 response, return its result from the method
+        """ Shortcut for a `HTTP 404
+        <http://tools.ietf.org/html/rfc7231#section-6.5.4>`_ (Not Found)
+        response
         """
         return werkzeug.exceptions.NotFound(description)
 
         """
         return werkzeug.exceptions.NotFound(description)
 
@@ -514,7 +686,7 @@ def httprequest(f):
     """ 
         .. deprecated:: 8.0
 
     """ 
         .. deprecated:: 8.0
 
-        Use the ``route()`` decorator instead.
+        Use the :func:`~openerp.http.route` decorator instead.
     """
     base = f.__name__.lstrip('/')
     if f.__name__ == "index":
     """
     base = f.__name__.lstrip('/')
     if f.__name__ == "index":
@@ -534,8 +706,18 @@ class ControllerType(type):
 
         # flag old-style methods with req as first argument
         for k, v in attrs.items():
 
         # flag old-style methods with req as first argument
         for k, v in attrs.items():
-            if inspect.isfunction(v):
-                spec = inspect.getargspec(v)
+            if inspect.isfunction(v) and hasattr(v, 'original_func'):
+                # Set routing type on original functions
+                routing_type = v.routing.get('type')
+                parent = [claz for claz in bases if isinstance(claz, ControllerType) and hasattr(claz, k)]
+                parent_routing_type = getattr(parent[0], k).original_func.routing_type if parent else routing_type or 'http'
+                if routing_type is not None and routing_type is not parent_routing_type:
+                    routing_type = parent_routing_type
+                    _logger.warn("Subclass re-defines <function %s.%s.%s> with different type than original."
+                                    " Will use original type: %r" % (cls.__module__, cls.__name__, k, parent_routing_type))
+                v.original_func.routing_type = routing_type or parent_routing_type
+
+                spec = inspect.getargspec(v.original_func)
                 first_arg = spec.args[1] if len(spec.args) >= 2 else None
                 if first_arg in ["req", "request"]:
                     v._first_arg_is_req = True
                 first_arg = spec.args[1] if len(spec.args) >= 2 else None
                 if first_arg in ["req", "request"]:
                     v._first_arg_is_req = True
@@ -559,40 +741,63 @@ class Controller(object):
 class EndPoint(object):
     def __init__(self, method, routing):
         self.method = method
 class EndPoint(object):
     def __init__(self, method, routing):
         self.method = method
+        self.original = getattr(method, 'original_func', method)
         self.routing = routing
         self.routing = routing
+        self.arguments = {}
+
+    @property
+    def first_arg_is_req(self):
+        # Backward for 7.0
+        return getattr(self.method, '_first_arg_is_req', False)
+
     def __call__(self, *args, **kw):
         return self.method(*args, **kw)
 
 def routing_map(modules, nodb_only, converters=None):
     routing_map = werkzeug.routing.Map(strict_slashes=False, converters=converters)
     def __call__(self, *args, **kw):
         return self.method(*args, **kw)
 
 def routing_map(modules, nodb_only, converters=None):
     routing_map = werkzeug.routing.Map(strict_slashes=False, converters=converters)
+
+    def get_subclasses(klass):
+        def valid(c):
+            return c.__module__.startswith('openerp.addons.') and c.__module__.split(".")[2] in modules
+        subclasses = klass.__subclasses__()
+        result = []
+        for subclass in subclasses:
+            if valid(subclass):
+                result.extend(get_subclasses(subclass))
+        if not result and valid(klass):
+            result = [klass]
+        return result
+
+    uniq = lambda it: collections.OrderedDict((id(x), x) for x in it).values()
+
     for module in modules:
         if module not in controllers_per_module:
             continue
 
         for _, cls in controllers_per_module[module]:
     for module in modules:
         if module not in controllers_per_module:
             continue
 
         for _, cls in controllers_per_module[module]:
-            subclasses = cls.__subclasses__()
-            subclasses = [c for c in subclasses if c.__module__.startswith('openerp.addons.') and c.__module__.split(".")[2] in modules]
+            subclasses = uniq(c for c in get_subclasses(cls) if c is not cls)
             if subclasses:
                 name = "%s (extended by %s)" % (cls.__name__, ', '.join(sub.__name__ for sub in subclasses))
                 cls = type(name, tuple(reversed(subclasses)), {})
 
             o = cls()
             if subclasses:
                 name = "%s (extended by %s)" % (cls.__name__, ', '.join(sub.__name__ for sub in subclasses))
                 cls = type(name, tuple(reversed(subclasses)), {})
 
             o = cls()
-            members = inspect.getmembers(o)
-            for mk, mv in members:
-                if inspect.ismethod(mv) and hasattr(mv, 'routing'):
+            members = inspect.getmembers(o, inspect.ismethod)
+            for _, mv in members:
+                if hasattr(mv, 'routing'):
                     routing = dict(type='http', auth='user', methods=None, routes=None)
                     methods_done = list()
                     routing = dict(type='http', auth='user', methods=None, routes=None)
                     methods_done = list()
+                    # update routing attributes from subclasses(auth, methods...)
                     for claz in reversed(mv.im_class.mro()):
                         fn = getattr(claz, mv.func_name, None)
                         if fn and hasattr(fn, 'routing') and fn not in methods_done:
                             methods_done.append(fn)
                             routing.update(fn.routing)
                     for claz in reversed(mv.im_class.mro()):
                         fn = getattr(claz, mv.func_name, None)
                         if fn and hasattr(fn, 'routing') and fn not in methods_done:
                             methods_done.append(fn)
                             routing.update(fn.routing)
-                    if not nodb_only or nodb_only == (routing['auth'] == "none"):
+                    if not nodb_only or routing['auth'] == "none":
                         assert routing['routes'], "Method %r has not route defined" % mv
                         endpoint = EndPoint(mv, routing)
                         for url in routing['routes']:
                             if routing.get("combine", False):
                         assert routing['routes'], "Method %r has not route defined" % mv
                         endpoint = EndPoint(mv, routing)
                         for url in routing['routes']:
                             if routing.get("combine", False):
-                                # deprecated
+                                # deprecated v7 declaration
                                 url = o._cp_path.rstrip('/') + '/' + url.lstrip('/')
                                 if url.endswith("/") and len(url) > 1:
                                     url = url[: -1]
                                 url = o._cp_path.rstrip('/') + '/' + url.lstrip('/')
                                 if url.endswith("/") and len(url) > 1:
                                     url = url[: -1]
@@ -612,7 +817,7 @@ class SessionExpiredException(Exception):
 class Service(object):
     """
         .. deprecated:: 8.0
 class Service(object):
     """
         .. deprecated:: 8.0
-        Use ``openerp.netsvc.dispatch_rpc()`` instead.
+            Use :func:`dispatch_rpc` instead.
     """
     def __init__(self, session, service_name):
         self.session = session
     """
     def __init__(self, session, service_name):
         self.session = session
@@ -620,14 +825,14 @@ class Service(object):
 
     def __getattr__(self, method):
         def proxy_method(*args):
 
     def __getattr__(self, method):
         def proxy_method(*args):
-            result = openerp.netsvc.dispatch_rpc(self.service_name, method, args)
+            result = dispatch_rpc(self.service_name, method, args)
             return result
         return proxy_method
 
 class Model(object):
     """
         .. deprecated:: 8.0
             return result
         return proxy_method
 
 class Model(object):
     """
         .. deprecated:: 8.0
-        Use the resistry and cursor in ``openerp.addons.web.http.request`` instead.
+            Use the registry and cursor in :data:`request` instead.
     """
     def __init__(self, session, model):
         self.session = session
     """
     def __init__(self, session, model):
         self.session = session
@@ -643,9 +848,9 @@ class Model(object):
                 or self.session.uid != request.uid:
                 raise Exception("Trying to use Model with badly configured database or user.")
                 
                 or self.session.uid != request.uid:
                 raise Exception("Trying to use Model with badly configured database or user.")
                 
-            mod = request.registry.get(self.model)
             if method.startswith('_'):
                 raise Exception("Access denied")
             if method.startswith('_'):
                 raise Exception("Access denied")
+            mod = request.registry[self.model]
             meth = getattr(mod, method)
             cr = request.cr
             result = meth(cr, request.uid, *args, **kw)
             meth = getattr(mod, method)
             cr = request.cr
             result = meth(cr, request.uid, *args, **kw)
@@ -680,10 +885,12 @@ class OpenERPSession(werkzeug.contrib.sessions.Session):
 
     def authenticate(self, db, login=None, password=None, uid=None):
         """
 
     def authenticate(self, db, login=None, password=None, uid=None):
         """
-        Authenticate the current user with the given db, login and password. If successful, store
-        the authentication parameters in the current session and request.
+        Authenticate the current user with the given db, login and
+        password. If successful, store the authentication parameters in the
+        current session and request.
 
 
-        :param uid: If not None, that user id will be used instead the login to authenticate the user.
+        :param uid: If not None, that user id will be used instead the login
+                    to authenticate the user.
         """
 
         if uid is None:
         """
 
         if uid is None:
@@ -693,7 +900,7 @@ class OpenERPSession(werkzeug.contrib.sessions.Session):
                 HTTP_HOST=wsgienv['HTTP_HOST'],
                 REMOTE_ADDR=wsgienv['REMOTE_ADDR'],
             )
                 HTTP_HOST=wsgienv['HTTP_HOST'],
                 REMOTE_ADDR=wsgienv['REMOTE_ADDR'],
             )
-            uid = openerp.netsvc.dispatch_rpc('common', 'authenticate', [db, login, password, env])
+            uid = dispatch_rpc('common', 'authenticate', [db, login, password, env])
         else:
             security.check(db, uid, password)
         self.db = db
         else:
             security.check(db, uid, password)
         self.db = db
@@ -708,9 +915,9 @@ class OpenERPSession(werkzeug.contrib.sessions.Session):
 
     def check_security(self):
         """
 
     def check_security(self):
         """
-        Chech the current authentication parameters to know if those are still valid. This method
-        should be called at each request. If the authentication fails, a ``SessionExpiredException``
-        is raised.
+        Check the current authentication parameters to know if those are still
+        valid. This method should be called at each request. If the
+        authentication fails, a :exc:`SessionExpiredException` is raised.
         """
         if not self.db or not self.uid:
             raise SessionExpiredException("Session expired")
         """
         if not self.db or not self.uid:
             raise SessionExpiredException("Session expired")
@@ -731,9 +938,8 @@ class OpenERPSession(werkzeug.contrib.sessions.Session):
 
     def get_context(self):
         """
 
     def get_context(self):
         """
-        Re-initializes the current user's session context (based on
-        his preferences) by calling res.users.get_context() with the old
-        context.
+        Re-initializes the current user's session context (based on his
+        preferences) by calling res.users.get_context() with the old context.
 
         :returns: the new context
         """
 
         :returns: the new context
         """
@@ -766,8 +972,8 @@ class OpenERPSession(werkzeug.contrib.sessions.Session):
     # Deprecated to be removed in 9
 
     """
     # Deprecated to be removed in 9
 
     """
-        Damn properties for retro-compatibility. All of that is deprecated, all
-        of that.
+        Damn properties for retro-compatibility. All of that is deprecated,
+        all of that.
     """
     @property
     def _db(self):
     """
     @property
     def _db(self):
@@ -797,21 +1003,21 @@ class OpenERPSession(werkzeug.contrib.sessions.Session):
     def send(self, service_name, method, *args):
         """
         .. deprecated:: 8.0
     def send(self, service_name, method, *args):
         """
         .. deprecated:: 8.0
-        Use ``openerp.netsvc.dispatch_rpc()`` instead.
+            Use :func:`dispatch_rpc` instead.
         """
         """
-        return openerp.netsvc.dispatch_rpc(service_name, method, args)
+        return dispatch_rpc(service_name, method, args)
 
     def proxy(self, service):
         """
         .. deprecated:: 8.0
 
     def proxy(self, service):
         """
         .. deprecated:: 8.0
-        Use ``openerp.netsvc.dispatch_rpc()`` instead.
+            Use :func:`dispatch_rpc` instead.
         """
         return Service(self, service)
 
     def assert_valid(self, force=False):
         """
         .. deprecated:: 8.0
         """
         return Service(self, service)
 
     def assert_valid(self, force=False):
         """
         .. deprecated:: 8.0
-        Use ``check_security()`` instead.
+            Use :meth:`check_security` instead.
 
         Ensures this session is valid (logged into the openerp server)
         """
 
         Ensures this session is valid (logged into the openerp server)
         """
@@ -825,7 +1031,7 @@ class OpenERPSession(werkzeug.contrib.sessions.Session):
     def ensure_valid(self):
         """
         .. deprecated:: 8.0
     def ensure_valid(self):
         """
         .. deprecated:: 8.0
-        Use ``check_security()`` instead.
+            Use :meth:`check_security` instead.
         """
         if self.uid:
             try:
         """
         if self.uid:
             try:
@@ -836,7 +1042,7 @@ class OpenERPSession(werkzeug.contrib.sessions.Session):
     def execute(self, model, func, *l, **d):
         """
         .. deprecated:: 8.0
     def execute(self, model, func, *l, **d):
         """
         .. deprecated:: 8.0
-        Use the resistry and cursor in ``openerp.addons.web.http.request`` instead.
+            Use the registry and cursor in :data:`request` instead.
         """
         model = self.model(model)
         r = getattr(model, func)(*l, **d)
         """
         model = self.model(model)
         r = getattr(model, func)(*l, **d)
@@ -845,7 +1051,7 @@ class OpenERPSession(werkzeug.contrib.sessions.Session):
     def exec_workflow(self, model, id, signal):
         """
         .. deprecated:: 8.0
     def exec_workflow(self, model, id, signal):
         """
         .. deprecated:: 8.0
-        Use the resistry and cursor in ``openerp.addons.web.http.request`` instead.
+            Use the registry and cursor in :data:`request` instead.
         """
         self.assert_valid()
         r = self.proxy('object').exec_workflow(self.db, self.uid, self.password, model, signal, id)
         """
         self.assert_valid()
         r = self.proxy('object').exec_workflow(self.db, self.uid, self.password, model, signal, id)
@@ -854,7 +1060,7 @@ class OpenERPSession(werkzeug.contrib.sessions.Session):
     def model(self, model):
         """
         .. deprecated:: 8.0
     def model(self, model):
         """
         .. deprecated:: 8.0
-        Use the resistry and cursor in ``openerp.addons.web.http.request`` instead.
+            Use the registry and cursor in :data:`request` instead.
 
         Get an RPC proxy for the object ``model``, bound to this session.
 
 
         Get an RPC proxy for the object ``model``, bound to this session.
 
@@ -921,19 +1127,65 @@ mimetypes.add_type('application/font-woff', '.woff')
 mimetypes.add_type('application/vnd.ms-fontobject', '.eot')
 mimetypes.add_type('application/x-font-ttf', '.ttf')
 
 mimetypes.add_type('application/vnd.ms-fontobject', '.eot')
 mimetypes.add_type('application/x-font-ttf', '.ttf')
 
-class LazyResponse(werkzeug.wrappers.Response):
-    """ Lazy werkzeug response.
-    API not yet frozen"""
+class Response(werkzeug.wrappers.Response):
+    """ Response object passed through controller route chain.
+
+    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(Response, self).__init__(*args, **kw)
+        self.set_default(template, qcontext, uid)
+
+    def set_default(self, template=None, qcontext=None, uid=None):
+        self.template = template
+        self.qcontext = qcontext or dict()
+        self.uid = uid
+        # Support for Cross-Origin Resource Sharing
+        if request.endpoint and 'cors' in request.endpoint.routing:
+            self.headers.set('Access-Control-Allow-Origin', request.endpoint.routing['cors'])
+            methods = 'GET, POST'
+            if request.endpoint.routing['type'] == 'json':
+                methods = 'POST'
+            elif request.endpoint.routing.get('methods'):
+                methods = ', '.join(request.endpoint.routing['methods'])
+            self.headers.set('Access-Control-Allow-Methods', methods)
+
+    @property
+    def is_qweb(self):
+        return self.template is not None
 
 
-    def __init__(self, callback, status_code=None, **kwargs):
-        super(LazyResponse, self).__init__(mimetype='text/html')
-        if status_code:
-            self.status_code = status_code
-        self.callback = callback
-        self.params = kwargs
-    def process(self):
-        response = self.callback(**self.params)
-        self.response.append(response)
+    def render(self):
+        """ Renders the Response's template, returns the result
+        """
+        view_obj = request.registry["ir.ui.view"]
+        uid = self.uid or request.uid or openerp.SUPERUSER_ID
+        return view_obj.render(
+            request.cr, uid, self.template, self.qcontext,
+            context=request.context)
+
+    def flatten(self):
+        """ Forces the rendering of the response's template, sets the result
+        as response body and unsets :attr:`.template`
+        """
+        self.response.append(self.render())
+        self.template = None
 
 class DisableCacheMiddleware(object):
     def __init__(self, app):
 
 class DisableCacheMiddleware(object):
     def __init__(self, app):
@@ -957,51 +1209,36 @@ class DisableCacheMiddleware(object):
             start_response(status, new_headers)
         return self.app(environ, start_wrapped)
 
             start_response(status, new_headers)
         return self.app(environ, start_wrapped)
 
-def session_path():
-    try:
-        import pwd
-        username = pwd.getpwuid(os.geteuid()).pw_name
-    except ImportError:
-        try:
-            username = getpass.getuser()
-        except Exception:
-            username = "unknown"
-    path = os.path.join(tempfile.gettempdir(), "oe-sessions-" + username)
-    try:
-        os.mkdir(path, 0700)
-    except OSError as exc:
-        if exc.errno == errno.EEXIST:
-            # directory exists: ensure it has the correct permissions
-            # this will fail if the directory is not owned by the current user
-            os.chmod(path, 0700)
-        else:
-            raise
-    return path
-
 class Root(object):
     """Root WSGI application for the OpenERP Web Client.
     """
     def __init__(self):
 class Root(object):
     """Root WSGI application for the OpenERP Web Client.
     """
     def __init__(self):
+        self._loaded = False
+
+    @lazy_property
+    def session_store(self):
         # Setup http sessions
         # Setup http sessions
-        path = session_path()
+        path = openerp.tools.config.session_dir
         _logger.debug('HTTP sessions stored in: %s', path)
         _logger.debug('HTTP sessions stored in: %s', path)
-        self.session_store = werkzeug.contrib.sessions.FilesystemSessionStore(path, session_class=OpenERPSession)
-
-        # TODO should we move this to ir.http so that only configured modules are served ?
-        _logger.info("HTTP Configuring static files")
-        self.load_addons()
+        return werkzeug.contrib.sessions.FilesystemSessionStore(path, session_class=OpenERPSession)
 
 
+    @lazy_property
+    def nodb_routing_map(self):
         _logger.info("Generating nondb routing")
         _logger.info("Generating nondb routing")
-        self.nodb_routing_map = routing_map([''] + openerp.conf.server_wide_modules, True)
+        return routing_map([''] + openerp.conf.server_wide_modules, True)
 
     def __call__(self, environ, start_response):
         """ Handle a WSGI request
         """
 
     def __call__(self, environ, start_response):
         """ Handle a WSGI request
         """
+        if not self._loaded:
+            self._loaded = True
+            self.load_addons()
         return self.dispatch(environ, start_response)
 
     def load_addons(self):
         return self.dispatch(environ, start_response)
 
     def load_addons(self):
-        """ Load all addons from addons patch containg static files and
+        """ Load all addons from addons path containing static files and
         controllers and configure them.  """
         controllers and configure them.  """
+        # TODO should we move this to ir.http so that only configured modules are served ?
         statics = {}
 
         for addons_path in openerp.modules.module.ad_paths:
         statics = {}
 
         for addons_path in openerp.modules.module.ad_paths:
@@ -1015,11 +1252,15 @@ class Root(object):
                         _logger.debug("Loading %s", module)
                         if 'openerp.addons' in sys.modules:
                             m = __import__('openerp.addons.' + module)
                         _logger.debug("Loading %s", module)
                         if 'openerp.addons' in sys.modules:
                             m = __import__('openerp.addons.' + module)
+                        else:
+                            m = None
                         addons_module[module] = m
                         addons_manifest[module] = manifest
                         statics['/%s/static' % module] = path_static
 
                         addons_module[module] = m
                         addons_manifest[module] = manifest
                         statics['/%s/static' % module] = path_static
 
-        app = werkzeug.wsgi.SharedDataMiddleware(self.dispatch, statics)
+        if statics:
+            _logger.info("HTTP Configuring static files")
+        app = werkzeug.wsgi.SharedDataMiddleware(self.dispatch, statics, cache_timeout=STATIC_CACHE)
         self.dispatch = DisableCacheMiddleware(app)
 
     def setup_session(self, httprequest):
         self.dispatch = DisableCacheMiddleware(app)
 
     def setup_session(self, httprequest):
@@ -1062,15 +1303,15 @@ class Root(object):
         # deduce type of request
         if httprequest.args.get('jsonp'):
             return JsonRequest(httprequest)
         # deduce type of request
         if httprequest.args.get('jsonp'):
             return JsonRequest(httprequest)
-        if httprequest.mimetype == "application/json":
+        if httprequest.mimetype in ("application/json", "application/json-rpc"):
             return JsonRequest(httprequest)
         else:
             return HttpRequest(httprequest)
 
     def get_response(self, httprequest, result, explicit_session):
             return JsonRequest(httprequest)
         else:
             return HttpRequest(httprequest)
 
     def get_response(self, httprequest, result, explicit_session):
-        if isinstance(result, LazyResponse):
+        if isinstance(result, Response) and result.is_qweb:
             try:
             try:
-                result.process()
+                result.flatten()
             except(Exception), e:
                 if request.db:
                     result = request.registry['ir.http']._handle_exception(e)
             except(Exception), e:
                 if request.db:
                     result = request.registry['ir.http']._handle_exception(e)
@@ -1078,7 +1319,7 @@ class Root(object):
                     raise
 
         if isinstance(result, basestring):
                     raise
 
         if isinstance(result, basestring):
-            response = werkzeug.wrappers.Response(result, mimetype='text/html')
+            response = Response(result, mimetype='text/html')
         else:
             response = result
 
         else:
             response = result
 
@@ -1093,16 +1334,6 @@ class Root(object):
         if not explicit_session and hasattr(response, 'set_cookie'):
             response.set_cookie('session_id', httprequest.session.sid, max_age=90 * 24 * 60 * 60)
 
         if not explicit_session and hasattr(response, 'set_cookie'):
             response.set_cookie('session_id', httprequest.session.sid, max_age=90 * 24 * 60 * 60)
 
-        # Support for Cross-Origin Resource Sharing
-        if request.func and 'cors' in request.func.routing:
-            response.headers.set('Access-Control-Allow-Origin', request.func.routing['cors'])
-            methods = 'GET, POST'
-            if request.func_request_type == 'json':
-                methods = 'POST'
-            elif request.func.routing.get('methods'):
-                methods = ', '.join(request.func.routing['methods'])
-            response.headers.set('Access-Control-Allow-Methods', methods)
-
         return response
 
     def dispatch(self, environ, start_response):
         return response
 
     def dispatch(self, environ, start_response):
@@ -1120,7 +1351,10 @@ class Root(object):
             request = self.get_request(httprequest)
 
             def _dispatch_nodb():
             request = self.get_request(httprequest)
 
             def _dispatch_nodb():
-                func, arguments = self.nodb_routing_map.bind_to_environ(request.httprequest.environ).match()
+                try:
+                    func, arguments = self.nodb_routing_map.bind_to_environ(request.httprequest.environ).match()
+                except werkzeug.exceptions.HTTPException, e:
+                    return request._handle_exception(e)
                 request.set_handler(func, arguments, "none")
                 result = request.dispatch()
                 return result
                 request.set_handler(func, arguments, "none")
                 result = request.dispatch()
                 return result
@@ -1132,10 +1366,11 @@ class Root(object):
                     try:
                         with openerp.tools.mute_logger('openerp.sql_db'):
                             ir_http = request.registry['ir.http']
                     try:
                         with openerp.tools.mute_logger('openerp.sql_db'):
                             ir_http = request.registry['ir.http']
-                    except psycopg2.OperationalError:
-                        # psycopg2 error. At this point, that means the
-                        # database probably does not exists anymore. Log the
-                        # user out and fall back to nodb
+                    except (AttributeError, psycopg2.OperationalError):
+                        # psycopg2 error or attribute error while constructing
+                        # the registry. That means the database probably does
+                        # not exists anymore or the code doesnt match the db.
+                        # Log the user out and fall back to nodb
                         request.session.logout()
                         result = _dispatch_nodb()
                     else:
                         request.session.logout()
                         result = _dispatch_nodb()
                     else:
@@ -1156,13 +1391,15 @@ class Root(object):
         return request.registry['ir.http'].routing_map()
 
 def db_list(force=False, httprequest=None):
         return request.registry['ir.http'].routing_map()
 
 def db_list(force=False, httprequest=None):
-    dbs = openerp.netsvc.dispatch_rpc("db", "list", [force])
+    dbs = dispatch_rpc("db", "list", [force])
     return db_filter(dbs, httprequest=httprequest)
 
 def db_filter(dbs, httprequest=None):
     httprequest = httprequest or request.httprequest
     return db_filter(dbs, httprequest=httprequest)
 
 def db_filter(dbs, httprequest=None):
     httprequest = httprequest or request.httprequest
-    h = httprequest.environ['HTTP_HOST'].split(':')[0]
-    d = h.split('.')[0]
+    h = httprequest.environ.get('HTTP_HOST', '').split(':')[0]
+    d, _, r = h.partition('.')
+    if d == "www" and r:
+        d = r.partition('.')[0]
     r = openerp.tools.config['dbfilter'].replace('%h', h).replace('%d', d)
     dbs = [i for i in dbs if re.match(r, i)]
     return dbs
     r = openerp.tools.config['dbfilter'].replace('%h', h).replace('%d', d)
     dbs = [i for i in dbs if re.match(r, i)]
     return dbs
@@ -1187,12 +1424,108 @@ def db_monodb(httprequest=None):
     if db_session in dbs:
         return db_session
 
     if db_session in dbs:
         return db_session
 
-    # if dbfilters was specified when launching the server and there is
-    # only one possible db, we take that one
-    if openerp.tools.config['dbfilter'] != ".*" and len(dbs) == 1:
+    # if there is only one possible db, we take that one
+    if len(dbs) == 1:
         return dbs[0]
     return None
 
         return dbs[0]
     return None
 
+def send_file(filepath_or_fp, mimetype=None, as_attachment=False, filename=None, mtime=None,
+              add_etags=True, cache_timeout=STATIC_CACHE, conditional=True):
+    """This is a modified version of Flask's send_file()
+
+    Sends the contents of a file to the client. This will use the
+    most efficient method available and configured.  By default it will
+    try to use the WSGI server's file_wrapper support.
+
+    By default it will try to guess the mimetype for you, but you can
+    also explicitly provide one.  For extra security you probably want
+    to send certain files as attachment (HTML for instance).  The mimetype
+    guessing requires a `filename` or an `attachment_filename` to be
+    provided.
+
+    Please never pass filenames to this function from user sources without
+    checking them first.
+
+    :param filepath_or_fp: the filename of the file to send.
+                           Alternatively a file object might be provided
+                           in which case `X-Sendfile` might not work and
+                           fall back to the traditional method.  Make sure
+                           that the file pointer is positioned at the start
+                           of data to send before calling :func:`send_file`.
+    :param mimetype: the mimetype of the file if provided, otherwise
+                     auto detection happens.
+    :param as_attachment: set to `True` if you want to send this file with
+                          a ``Content-Disposition: attachment`` header.
+    :param filename: the filename for the attachment if it differs from the file's filename or
+                     if using file object without 'name' attribute (eg: E-tags with StringIO).
+    :param mtime: last modification time to use for contitional response.
+    :param add_etags: set to `False` to disable attaching of etags.
+    :param conditional: set to `False` to disable conditional responses.
+
+    :param cache_timeout: the timeout in seconds for the headers.
+    """
+    if isinstance(filepath_or_fp, (str, unicode)):
+        if not filename:
+            filename = os.path.basename(filepath_or_fp)
+        file = open(filepath_or_fp, 'rb')
+        if not mtime:
+            mtime = os.path.getmtime(filepath_or_fp)
+    else:
+        file = filepath_or_fp
+        if not filename:
+            filename = getattr(file, 'name', None)
+
+    file.seek(0, 2)
+    size = file.tell()
+    file.seek(0)
+
+    if mimetype is None and filename:
+        mimetype = mimetypes.guess_type(filename)[0]
+    if mimetype is None:
+        mimetype = 'application/octet-stream'
+
+    headers = werkzeug.datastructures.Headers()
+    if as_attachment:
+        if filename is None:
+            raise TypeError('filename unavailable, required for sending as attachment')
+        headers.add('Content-Disposition', 'attachment', filename=filename)
+        headers['Content-Length'] = size
+
+    data = wrap_file(request.httprequest.environ, file)
+    rv = Response(data, mimetype=mimetype, headers=headers,
+                                    direct_passthrough=True)
+
+    if isinstance(mtime, str):
+        try:
+            server_format = openerp.tools.misc.DEFAULT_SERVER_DATETIME_FORMAT
+            mtime = datetime.datetime.strptime(mtime.split('.')[0], server_format)
+        except Exception:
+            mtime = None
+    if mtime is not None:
+        rv.last_modified = mtime
+
+    rv.cache_control.public = True
+    if cache_timeout:
+        rv.cache_control.max_age = cache_timeout
+        rv.expires = int(time.time() + cache_timeout)
+
+    if add_etags and filename and mtime:
+        rv.set_etag('odoo-%s-%s-%s' % (
+            mtime,
+            size,
+            adler32(
+                filename.encode('utf-8') if isinstance(filename, unicode)
+                else filename
+            ) & 0xffffffff
+        ))
+        if conditional:
+            rv = rv.make_conditional(request.httprequest)
+            # make sure we don't send x-sendfile for servers that
+            # ignore the 304 status code for x-sendfile.
+            if rv.status_code == 304:
+                rv.headers.pop('x-sendfile', None)
+    return rv
+
 #----------------------------------------------------------
 # RPC controller
 #----------------------------------------------------------
 #----------------------------------------------------------
 # RPC controller
 #----------------------------------------------------------
@@ -1201,18 +1534,15 @@ class CommonController(Controller):
     @route('/jsonrpc', type='json', auth="none")
     def jsonrpc(self, service, method, args):
         """ Method used by client APIs to contact OpenERP. """
     @route('/jsonrpc', type='json', auth="none")
     def jsonrpc(self, service, method, args):
         """ Method used by client APIs to contact OpenERP. """
-        return openerp.netsvc.dispatch_rpc(service, method, args)
+        return dispatch_rpc(service, method, args)
 
     @route('/gen_session_id', type='json', auth="none")
     def gen_session_id(self):
         nsession = root.session_store.new()
         return nsession.sid
 
 
     @route('/gen_session_id', type='json', auth="none")
     def gen_session_id(self):
         nsession = root.session_store.new()
         return nsession.sid
 
-root = None
-
-def wsgi_postload():
-    global root
-    root = Root()
-    openerp.wsgi.register_wsgi_handler(root)
+# register main wsgi handler
+root = Root()
+openerp.service.wsgi_server.register_wsgi_handler(root)
 
 # vim:et:ts=4:sw=4:
 
 # vim:et:ts=4:sw=4: