From 80657b1c29ec9dea947426fccc1ffc7b20a6d97a Mon Sep 17 00:00:00 2001 From: Christophe Simonis Date: Mon, 24 Oct 2011 11:07:27 +0200 Subject: [PATCH] [IMP] support for jsonp bzr revid: chs@openerp.com-20111024090727-f8w5wv08ugxnrpt8 --- addons/web/__openerp__.py | 1 + addons/web/common/http.py | 137 ++++++++++++++++++++++++++----- addons/web/common/session.py | 4 +- addons/web/static/src/js/core.js | 29 +++---- 4 files changed, 132 insertions(+), 39 deletions(-) diff --git a/addons/web/__openerp__.py b/addons/web/__openerp__.py index 0afd43190d6..1cff3c7956b 100644 --- a/addons/web/__openerp__.py +++ b/addons/web/__openerp__.py @@ -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", diff --git a/addons/web/common/http.py b/addons/web/common/http.py index e23a0431a7c..ed7a5344192 100644 --- a/addons/web/common/http.py +++ b/addons/web/common/http.py @@ -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. diff --git a/addons/web/common/session.py b/addons/web/common/session.py index e8027db8ea4..06f46faa9d0 100644 --- a/addons/web/common/session.py +++ b/addons/web/common/session.py @@ -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__) diff --git a/addons/web/static/src/js/core.js b/addons/web/static/src/js/core.js index 4ed33341ea6..1b6a6302f72 100644 --- a/addons/web/static/src/js/core.js +++ b/addons/web/static/src/js/core.js @@ -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); },