[REF] first shot at converting everything to werkzeug

bzr revid: xmo@openerp.com-20110902085853-38y28g2x0d13p8ne
This commit is contained in:
Xavier Morel 2011-09-02 10:58:53 +02:00
parent 6e739caf10
commit 80d0859837
5 changed files with 177 additions and 133 deletions

View File

@ -1,5 +1,6 @@
#!/usr/bin/python
import urllib
from __future__ import with_statement
import functools
import logging
import os
@ -8,11 +9,15 @@ import traceback
import uuid
import xmlrpclib
import cherrypy
import cherrypy.lib.static
import simplejson
import werkzeug.exceptions
import werkzeug.utils
import werkzeug.wrappers
import werkzeug.wsgi
import ast
import nonliterals
import http
# import backendlocal as backend
import backendrpc as backend
@ -40,27 +45,28 @@ controllers_path = {}
#----------------------------------------------------------
# OpenERP Web RequestHandler
#----------------------------------------------------------
class CherryPyRequest(object):
class WebRequest(object):
""" CherryPy request handling
"""
def init(self,params):
self.params = params
def __init__(self, request, config):
# Move cherrypy thread local objects to attributes
self.applicationsession = applicationsession
self.httprequest = cherrypy.request
self.httpresponse = cherrypy.response
self.httpsession = cherrypy.session
self.httpsession_id = "cookieid"
self.httprequest = request
self.httpresponse = None
self.httpsession = request.session
self.config = config
# Request attributes
def init(self, params):
self.params = dict(params)
# OpenERP session setup
self.session_id = self.params.pop("session_id", None) or uuid.uuid4().hex
host = cherrypy.config['openerp.server.host']
port = cherrypy.config['openerp.server.port']
self.session = self.httpsession.setdefault(self.session_id, backend.OpenERPSession(host, port))
# Request attributes
self.session = self.httpsession.setdefault(
self.session_id, backend.OpenERPSession(
self.config.server_host, self.config.server_port))
self.context = self.params.pop('context', None)
self.debug = self.params.pop('debug',False) != False
self.debug = self.params.pop('debug', False) != False
class JsonRequest(CherryPyRequest):
class JsonRequest(WebRequest):
""" JSON-RPC2 over HTTP.
Sucessful request::
@ -138,8 +144,8 @@ class JsonRequest(CherryPyRequest):
}
}
except Exception:
cherrypy.log("An error occured while handling a json request",
severity=logging.ERROR, traceback=True)
logging.getLogger('openerp.JSONRequest.dispatch').exception\
("An error occured while handling a json request")
error = {
'code': 300,
'message': "OpenERP WebClient Error",
@ -156,42 +162,55 @@ class JsonRequest(CherryPyRequest):
print
content = simplejson.dumps(response, cls=nonliterals.NonLiteralEncoder)
cherrypy.response.headers['Content-Type'] = 'application/json'
cherrypy.response.headers['Content-Length'] = len(content)
return content
return werkzeug.wrappers.Response(
content, headers=[('Content-Type', 'application/json'),
('Content-Length', len(content))])
def jsonrequest(f):
@cherrypy.expose
@functools.wraps(f)
def json_handler(controller):
return JsonRequest().dispatch(controller, f, requestf=cherrypy.request.body)
def json_handler(controller, request, config):
return JsonRequest(request, config).dispatch(
controller, f, requestf=request.stream)
json_handler.exposed = True
return json_handler
class HttpRequest(CherryPyRequest):
class HttpRequest(WebRequest):
""" Regular GET/POST request
"""
def dispatch(self, controller, method, **kw):
self.init(kw)
def dispatch(self, controller, method):
self.init(self.httprequest.args)
akw = {}
for key in kw.keys():
if isinstance(kw[key], basestring) and len(kw[key]) < 1024:
akw[key] = kw[key]
for key, value in self.httprequest.args.iteritems():
if isinstance(value, basestring) and len(value) < 1024:
akw[key] = value
else:
akw[key] = type(kw[key])
akw[key] = type(value)
if self.debug or 1:
print "%s --> %s.%s %r" % (self.httprequest.method, controller.__class__.__name__, method.__name__, akw)
r = method(controller, self, **kw)
r = method(controller, self, **self.params)
if self.debug or 1:
print "<--", 'size:', len(r)
if isinstance(r, werkzeug.wrappers.BaseResponse):
print '<--', r
else:
print "<--", 'size:', len(r)
print
return r
def make_response(self, data, headers=None, cookies=None):
response = werkzeug.wrappers.Response(data, headers=headers)
if cookies:
for k, v in cookies.iteritems():
response.set_cookie(k, v)
return response
def not_found(self, description=None):
return werkzeug.exceptions.NotFound(description)
def httprequest(f):
# check cleaner wrapping:
# functools.wraps(f)(lambda x: JsonRequest().dispatch(x, f))
def http_handler(controller,*l, **kw):
return HttpRequest().dispatch(controller, f, **kw)
http_handler.exposed = 1
@functools.wraps(f)
def http_handler(controller, request, config):
return HttpRequest(request, config).dispatch(controller, f)
http_handler.exposed = True
return http_handler
#-----------------------------------------------------------
@ -207,41 +226,82 @@ class Controller(object):
__metaclass__ = ControllerType
class Root(object):
def __init__(self):
def __init__(self, options):
self.config = options
self.session_cookie = 'sessionid'
self.addons = {}
self._load_addons()
static_dirs = self._load_addons()
if options.serve_static:
self.dispatch = werkzeug.wsgi.SharedDataMiddleware(
self.dispatch, static_dirs)
if options.session_storage:
if not os.path.exists(options.session_storage):
os.mkdir(options.session_storage, 0700)
self.session_storage = options.session_storage
def __call__(self, environ, start_response):
return self.dispatch(environ, start_response)
def dispatch(self, environ, start_response):
request = werkzeug.wrappers.Request(environ)
if request.path == '/':
return werkzeug.utils.redirect(
'/base/webclient/home', 301)(environ, start_response)
elif request.path == '/mobile':
return werkzeug.utils.redirect(
'/web_mobile/static/src/web_mobile.html', 301)(
environ, start_response)
handler = self.find_handler(*(request.path.split('/')[1:]))
if not handler:
response = werkzeug.exceptions.NotFound()
else:
with http.session(request, self.session_storage, self.session_cookie) as session:
result = handler(
request, self.config)
if isinstance(result, werkzeug.wrappers.Response):
response = result
else:
response = werkzeug.wrappers.Response(
result, headers=[('Content-Type', 'text/html; charset=utf-8'),
('Content-Length', len(result))])
response.set_cookie(self.session_cookie, session.sid)
return response(environ, start_response)
def _load_addons(self):
statics = {}
if path_addons not in sys.path:
sys.path.insert(0, path_addons)
for i in os.listdir(path_addons):
if i not in addons_module:
manifest_path = os.path.join(path_addons, i, '__openerp__.py')
for module in os.listdir(path_addons):
if module not in addons_module:
manifest_path = os.path.join(path_addons, module, '__openerp__.py')
if os.path.isfile(manifest_path):
manifest = eval(open(manifest_path).read())
print "Loading", i
m = __import__(i)
addons_module[i] = m
addons_manifest[i] = manifest
manifest = ast.literal_eval(open(manifest_path).read())
print "Loading", module
m = __import__(module)
addons_module[module] = m
addons_manifest[module] = manifest
statics['/%s/static' % module] = \
os.path.join(path_addons, module, 'static')
for k, v in controllers_class.items():
if k not in controllers_object:
o = v()
controllers_object[k] = o
if hasattr(o, '_cp_path'):
controllers_path[o._cp_path] = o
return statics
def default(self, *l, **kw):
print "default",l,kw
# handle static files
if len(l) > 2 and l[1] == 'static':
# sanitize path
p = os.path.normpath(os.path.join(*l))
p2 = os.path.join(path_addons, p)
print "p",p
print "p2",p2
return cherrypy.lib.static.serve_file(p2)
elif len(l) > 1:
def find_handler(self, *l):
if len(l) > 1:
for i in range(len(l), 1, -1):
ps = "/" + "/".join(l[0:i])
if ps in controllers_path:
@ -249,19 +309,7 @@ class Root(object):
rest = l[i:] or ['index']
meth = rest[0]
m = getattr(c, meth)
if getattr(m, 'exposed', 0):
print "Calling", ps, c, meth, m
return m(**kw)
raise cherrypy.NotFound('/' + '/'.join(l))
elif l and l[0] == 'mobile':
#for the mobile web client we are supposed to use a different url to just add '/mobile'
raise cherrypy.HTTPRedirect('/web_mobile/static/src/web_mobile.html', 301)
else:
if kw:
qs = '?' + urllib.urlencode(kw)
else:
qs = ''
raise cherrypy.HTTPRedirect('/base/webclient/home' + qs, 301)
default.exposed = True
#
if getattr(m, 'exposed', False):
print "Dispatching to", ps, c, meth, m
return m
return None

View File

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
import contextlib
import werkzeug.contrib.sessions
STORES = {}
@contextlib.contextmanager
def session(request, storage_path, session_cookie='sessionid'):
session_store = STORES.get(storage_path)
if not session_store:
session_store = werkzeug.contrib.sessions.FilesystemSessionStore(
storage_path)
STORES[storage_path] = session_store
sid = request.cookies.get(session_cookie)
if sid:
request.session = session_store.get(sid)
else:
request.session = session_store.new()
yield request.session
session_store.save(request.session)

View File

@ -53,29 +53,29 @@ class DispatcherTest(unittest2.TestCase):
def test_default_redirect(self):
self.assertRaises(
cherrypy.HTTPRedirect,
openerpweb.openerpweb.Root().default)
openerpweb.openerpweb.Root().find_handler)
def test_serve_static_missing(self):
self.assertRaises(
cherrypy.NotFound,
openerpweb.openerpweb.Root().default,
openerpweb.openerpweb.Root().find_handler,
'does-not-exist', 'static', 'bar')
def test_serve_controller_missing(self):
self.assertRaises(
cherrypy.NotFound,
openerpweb.openerpweb.Root().default,
openerpweb.openerpweb.Root().find_handler,
'controller', 'does', 'not', 'exist')
def test_find_controller_method(self):
openerpweb.openerpweb.Root().default(
openerpweb.openerpweb.Root().find_handler(
'some', 'controller', 'path', 'method')
self.mock_method.assert_called_once_with()
def test_find_controller_index(self):
openerpweb.openerpweb.Root().default(
openerpweb.openerpweb.Root().find_handler(
'some', 'controller', 'path')
self.mock_index.assert_called_once_with()
def test_nested_paths(self):
openerpweb.openerpweb.Root().default(
openerpweb.openerpweb.Root().find_handler(
'some', 'other', 'controller', '2')
self.mock_index.assert_called_once_with()

View File

@ -13,8 +13,6 @@ import time
from xml.etree import ElementTree
from cStringIO import StringIO
import cherrypy
import base.common as openerpweb
import base.common.ast
import base.common.nonliterals
@ -130,19 +128,17 @@ class WebClient(openerpweb.Controller):
@openerpweb.httprequest
def css(self, req, mods='base'):
req.httpresponse.headers['Content-Type'] = 'text/css'
files = manifest_glob(mods.split(','), 'css')
content,timestamp = concat_files(files)
# TODO request set the Date of last modif and Etag
return content
return req.make_response(content, [('Content-Type', 'text/css')])
@openerpweb.httprequest
def js(self, req, mods='base'):
req.httpresponse.headers['Content-Type'] = 'application/javascript'
files = manifest_glob(mods.split(','), 'js')
content,timestamp = concat_files(files)
# TODO request set the Date of last modif and Etag
return content
return req.make_response(content, [('Content-Type', 'application/javascript')])
@openerpweb.httprequest
def home(self, req, s_action=None, **kw):
@ -208,7 +204,7 @@ class Database(openerpweb.Controller):
dbs = proxy.list()
h = req.httprequest.headers['Host'].split(':')[0]
d = h.split('.')[0]
r = cherrypy.config['openerp.dbfilter'].replace('%h', h).replace('%d', d)
r = req.config.dbfilter.replace('%h', h).replace('%d', d)
dbs = [i for i in dbs if re.match(r, i)]
return {"db_list": dbs}
@ -253,11 +249,11 @@ class Database(openerpweb.Controller):
try:
db_dump = base64.decodestring(
req.session.proxy("db").dump(backup_pwd, backup_db))
req.httpresponse.headers['Content-Type'] = "application/octet-stream; charset=binary"
req.httpresponse.headers['Content-Disposition'] = 'attachment; filename="' + backup_db + '.dump"'
req.httpresponse.cookie['fileToken'] = token
req.httpresponse.cookie['fileToken']['path'] = '/'
return db_dump
return req.make_response(db_dump,
[('Content-Type', 'application/octet-stream; charset=binary'),
('Content-Disposition', 'attachment; filename="' + backup_db + '.dump"')],
{'fileToken': token}
)
except xmlrpclib.Fault, e:
if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied':
return 'Backup Database|' + e.faultCode
@ -985,18 +981,17 @@ class Binary(openerpweb.Controller):
res = Model.read([int(id)], [field, fieldname], context)[0]
filecontent = res.get(field, '')
if not filecontent:
raise cherrypy.NotFound
return req.not_found()
else:
req.httpresponse.headers['Content-Type'] = 'application/octet-stream'
filename = '%s_%s' % (model.replace('.', '_'), id)
if fieldname:
filename = res.get(fieldname, '') or filename
req.httpresponse.headers['Content-Disposition'] = 'attachment; filename=' + filename
return base64.decodestring(filecontent)
return req.make_response(filecontent,
[('Content-Type', 'application/octet-stream'),
('Content-Disposition', 'attachment; filename=' + filename)])
@openerpweb.httprequest
def upload(self, req, callback, ufile=None):
cherrypy.response.timeout = 500
headers = {}
for key, val in req.httprequest.headers.iteritems():
headers[key.lower()] = val
@ -1023,7 +1018,6 @@ class Binary(openerpweb.Controller):
@openerpweb.httprequest
def upload_attachment(self, req, callback, model, id, ufile=None):
cherrypy.response.timeout = 500
context = req.session.eval_context(req.context)
Model = req.session.model('ir.attachment')
try:

View File

@ -1,25 +1,22 @@
#!/usr/bin/env python
import optparse
import os
import signal
import sys
import tempfile
import cherrypy
import cherrypy.lib.static
import werkzeug.serving
optparser = optparse.OptionParser()
optparser.add_option("-p", "--port", dest="socket_port", default=8002,
help="listening port", type="int", metavar="NUMBER")
optparser.add_option("-s", "--session-path", dest="tools.sessions.storage_path",
default=os.path.join(tempfile.gettempdir(), "cpsessions"),
optparser.add_option("-s", "--session-path", dest="session_storage",
default=os.path.join(tempfile.gettempdir(), "oe-sessions"),
help="directory used for session storage", metavar="DIR")
optparser.add_option("--server-host", dest="openerp.server.host",
optparser.add_option("--server-host", dest="server_host",
default='127.0.0.1', help="OpenERP server hostname", metavar="HOST")
optparser.add_option("--server-port", dest="openerp.server.port", default=8069,
optparser.add_option("--server-port", dest="server_port", default=8069,
help="OpenERP server port", type="int", metavar="NUMBER")
optparser.add_option("--db-filter", dest="openerp.dbfilter", default='.*',
optparser.add_option("--db-filter", dest="dbfilter", default='.*',
help="Filter listed database", metavar="REGEXP")
path_root = os.path.dirname(os.path.abspath(__file__))
@ -29,34 +26,14 @@ if path_addons not in sys.path:
import base
def main(options):
# change the timezone of the program to the OpenERP server's assumed timezone
os.environ["TZ"] = "UTC"
DEFAULT_CONFIG = {
'server.socket_host': '0.0.0.0',
'tools.sessions.on': True,
'tools.sessions.storage_type': 'file',
'tools.sessions.timeout': 60
}
cherrypy.config.update(config=DEFAULT_CONFIG)
if os.path.exists(os.path.join(path_root,'openerp-web.cfg')):
cherrypy.config.update(os.path.join(path_root,'openerp-web.cfg'))
if os.path.exists(os.path.expanduser('~/.openerp_webrc')):
cherrypy.config.update(os.path.expanduser('~/.openerp_webrc'))
cherrypy.config.update(options)
if not os.path.exists(cherrypy.config['tools.sessions.storage_path']):
os.makedirs(cherrypy.config['tools.sessions.storage_path'], 0700)
return cherrypy.tree.mount(base.common.Root())
if __name__ == "__main__":
(options, args) = optparser.parse_args(sys.argv[1:])
o = dict((k, v) for k, v in vars(options).iteritems() if v is not None)
options.serve_static = True
os.environ["TZ"] = "UTC"
app = base.common.Root(options)
werkzeug.serving.run_simple(
'0.0.0.0', options.socket_port, main(o),
'0.0.0.0', options.socket_port, app,
use_reloader=True, threaded=True)