[IMP] support for jsonp
authorChristophe Simonis <chs@openerp.com>
Mon, 24 Oct 2011 09:07:27 +0000 (11:07 +0200)
committerChristophe Simonis <chs@openerp.com>
Mon, 24 Oct 2011 09:07:27 +0000 (11:07 +0200)
bzr revid: chs@openerp.com-20111024090727-f8w5wv08ugxnrpt8

addons/web/__openerp__.py
addons/web/common/http.py
addons/web/common/session.py
addons/web/static/src/js/core.js

index 0afd431..1cff3c7 100644 (file)
@@ -28,6 +28,7 @@
         "static/lib/underscore/underscore.string.js",
         "static/lib/labjs/LAB.src.js",
         "static/lib/py.parse/lib/py.js",
+        "static/src/js/jq_ajax.js",
         "static/src/js/boot.js",
         "static/src/js/core.js",
         "static/src/js/dates.js",
index e23a043..ed7a534 100644 (file)
@@ -85,13 +85,23 @@ class WebRequest(object):
         self.httpresponse = None
         self.httpsession = request.session
         self.config = config
+        self.session = None
+
+    def init_session(self, session_id):
+        if self.session:
+            assert self.session.id == session_id
+            return
+
+        self.session_id = session_id or uuid.uuid4().hex
+        self.session = self.httpsession.setdefault(self.session_id, session.OpenERPSession(self.session_id))
+        self.session.config = self.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
+        session_id = self.params.pop("session_id", None)
+        self.init_session(session_id)
         self.context = self.params.pop('context', None)
         self.debug = self.params.pop('debug', False) != False
 
@@ -129,25 +139,88 @@ class JsonRequest(WebRequest):
 
     """
 
-    def dispatch(self, controller, method, requestf=None, request=None):
-        """ Calls the method asked for by the JSON-RPC2 request
+
+    def _init_jsonrpc2(self):
+        assert self.jsonrequest.get('jsonrpc') == '2.0'
+        self.init(self.jsonrequest.get("params", {}))
+        response = {"jsonrpc": "2.0" }
+        return response
+
+    def _init_jsonp(self):
+        self.init(self.jsonrequest)
+        return {}
+
+
+    def dispatch(self, controller, method):
+        """ Calls the method asked for by the JSON-RPC2 or JSONP 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
+        :returns: an utf8 encoded JSON-RPC2 or JSONP reply
         """
-        response = {"jsonrpc": "2.0" }
+
+        requestf = self.httprequest.stream
+        direct_json_request = None
+        jsonp_callback = None
+        if requestf:
+            direct_json_request = requestf.read()
+
+        if not direct_json_request:
+            params = self.httprequest.args
+            direct_json_request = params.get('r')
+            jsonp_callback = params.get('callback')
+
+        if direct_json_request:
+            try:
+                self.jsonrequest = simplejson.loads(direct_json_request, object_hook=nonliterals.non_literal_decoder)
+            except Exception, e:
+                _logger.error(e)
+                return werkzeug.exceptions.BadRequest(e)
+        else:
+            # no direct json request, try to get it from jsonp POST request
+            params = self.httprequest.args
+            rid = params.get('rid')
+            session_id = params.get('sid')
+            if session_id:
+                self.init_session(session_id)
+                stored_request = self.session.jsonp_requests.get(rid, {})
+            else:
+                stored_request = {}
+
+            jsonp_callback = stored_request.get('jsonp')
+            self.jsonrequest = stored_request.get('params', {})
+
+
+        if self.jsonrequest.get('jsonrpc') == '2.0':
+            response = self._init_jsonrpc2()
+
+            def build_response(response):
+                content = simplejson.dumps(response, cls=nonliterals.NonLiteralEncoder)
+                return werkzeug.wrappers.Response(
+                    content, headers=[('Content-Type', 'application/json'),
+                                      ('Content-Length', len(content))])
+
+
+        elif jsonp_callback:
+
+            response = self._init_jsonp()
+
+            def build_response(response):
+                content = "%s(%s);" % (\
+                            jsonp_callback,
+                            simplejson.dumps(response, cls=nonliterals.NonLiteralEncoder),
+                          )
+
+                return werkzeug.wrappers.Response(
+                    content, headers=[('Content-Type', 'application/javascript'),
+                                      ('Content-Length', len(content))])
+
+        else:
+            return werkzeug.exceptions.BadRequest()
+
         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')
@@ -188,10 +261,8 @@ class JsonRequest(WebRequest):
 
         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))])
+
+        return build_response(response)
 
 def jsonrequest(f):
     """ Decorator marking the decorated method as being a handler for a
@@ -205,8 +276,7 @@ def jsonrequest(f):
     """
     @functools.wraps(f)
     def json_handler(controller, request, config):
-        return JsonRequest(request, config).dispatch(
-            controller, f, requestf=request.stream)
+        return JsonRequest(request, config).dispatch(controller, f)
     json_handler.exposed = True
     return json_handler
 
@@ -300,7 +370,8 @@ def session_context(request, storage_path, session_cookie='sessionid'):
         # either by login process or by HTTP requests without an OpenERP
         # session id, and are generally noise
         for key, value in request.session.items():
-            if isinstance(value, session.OpenERPSession) and not value._uid:
+            if isinstance(value, session.OpenERPSession) and not value._uid and not value.jsonp_requests:
+                _logger.info('remove session %s: %r', key, value.jsonp_requests)
                 del request.session[key]
 
         # FIXME: remove this when non-literals disappear
@@ -344,6 +415,28 @@ class ControllerType(type):
 class Controller(object):
     __metaclass__ = ControllerType
 
+
+class JSONP(Controller):
+    _cp_path = '/web/jsonp'
+
+    @httprequest
+    def post(self, req, request_id, params, callback):
+        params = simplejson.loads(params, object_hook=nonliterals.non_literal_decoder)
+        params.update(
+            session_id=req.session.id,
+        )
+        params['session_id'] = req.session.id
+        req.session.jsonp_requests[request_id] = {
+            'jsonp': callback,
+            'params': params,
+            'id': request_id,
+        }
+
+        headers=[('Content-Type', 'text/plain; charset=utf-8')]
+        response = werkzeug.wrappers.Response(request_id, headers=headers)
+        return response
+
+
 class Root(object):
     """Root WSGI application for the OpenERP Web Client.
 
index e8027db..06f46fa 100644 (file)
@@ -28,7 +28,8 @@ class OpenERPSession(object):
         Used to store references to non-literal domains which need to be
         round-tripped to the client browser.
     """
-    def __init__(self):
+    def __init__(self, sid):
+        self.id = sid
         self.config = None
         self._db = False
         self._uid = False
@@ -37,6 +38,7 @@ class OpenERPSession(object):
         self.context = {}
         self.contexts_store = {}
         self.domains_store = {}
+        self.jsonp_requests = {}     # FIXME use a LRU
         
     def __getstate__(self):
         state = dict(self.__dict__)
index 4ed3334..1b6a630 100644 (file)
@@ -346,12 +346,12 @@ openerp.web.Session = openerp.web.CallbackEnabled.extend( /** @lends openerp.web
      * @param {String} [server] JSON-RPC endpoint hostname
      * @param {String} [port] JSON-RPC endpoint port
      */
-    init: function(server, port) {
+    init: function(server) {
         this._super();
-        this.server = (server == undefined) ? location.hostname : server;
-        this.port = (port == undefined) ? location.port : port;
-        this.rpc_mode = (server == location.hostname) ? "ajax" : "jsonp";
-        this.debug = (window.location.search.indexOf('?debug') !== -1);
+        var hostname = _('%s//%s').sprintf(location.protocol, location.host);
+        this.server = (server == undefined) ? hostname : server;
+        this.rpc_mode = (this.server == hostname) ? "oe-json" : "oe-jsonp";
+        this.debug = ($.deparam($.param.querystring()).debug != undefined);
         this.session_id = false;
         this.uid = false;
         this.user_context= {};
@@ -390,13 +390,9 @@ openerp.web.Session = openerp.web.CallbackEnabled.extend( /** @lends openerp.web
 
         // Call using the rpc_mode
         var deferred = $.Deferred();
-        this.rpc_ajax(url, {
-            jsonrpc: "2.0",
-            method: "call",
-            params: params,
-            id: _.uniqueId('browser-client-')
-        }).then(function () {deferred.resolve.apply(deferred, arguments);},
-                function(error) {deferred.reject(error, $.Event());});
+        this.rpc_ajax(url, params)
+            .then(function () {deferred.resolve.apply(deferred, arguments);},
+                  function(error) {deferred.reject(error, $.Event());});
         return deferred.fail(function() {
             deferred.fail(function(error, event) {
                 if (!event.isDefaultPrevented()) {
@@ -422,10 +418,11 @@ openerp.web.Session = openerp.web.CallbackEnabled.extend( /** @lends openerp.web
         var ajax = _.extend({
             type: "POST",
             url: url,
-            dataType: 'json',
+            dataType: this.rpc_mode,
             contentType: 'application/json',
-            data: JSON.stringify(payload),
-            processData: false
+            data: payload,
+            processData: false,
+            openerp: _.extend({}, this),    // need a plainObject
         }, url);
         var deferred = $.Deferred();
         $.ajax(ajax).done(function(response, textStatus, jqXHR) {
@@ -440,7 +437,7 @@ openerp.web.Session = openerp.web.CallbackEnabled.extend( /** @lends openerp.web
             }
             self.uid = false;
             self.on_session_invalid(function() {
-                self.rpc(url, payload.params,
+                self.rpc(url, payload,
                     function() {
                         deferred.resolve.apply(deferred, arguments);
                     },