[IMP] support for jsonp

bzr revid: chs@openerp.com-20111024090727-f8w5wv08ugxnrpt8
This commit is contained in:
Christophe Simonis 2011-10-24 11:07:27 +02:00
parent 89a48aa9a5
commit 80657b1c29
4 changed files with 132 additions and 39 deletions

View 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",

View 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.

View 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__)

View 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);
},