diff --git a/gunicorn.conf.py b/gunicorn.conf.py index 4ab6aa85748..0c1709dbed5 100644 --- a/gunicorn.conf.py +++ b/gunicorn.conf.py @@ -3,7 +3,7 @@ import openerp bind = '127.0.0.1:8069' pidfile = '.gunicorn.pid' # This is the big TODO: safely use more than a single worker. -workers = 2 +workers = 1 # Some application-wide initialization is needed. on_starting = openerp.wsgi.on_starting when_ready = openerp.wsgi.when_ready diff --git a/openerp/wsgi.py b/openerp/wsgi.py index cdd28e9c8b7..422d4a7f9c3 100644 --- a/openerp/wsgi.py +++ b/openerp/wsgi.py @@ -27,7 +27,10 @@ This module offers a WSGI interface to OpenERP. from wsgiref.simple_server import make_server from SimpleXMLRPCServer import SimpleXMLRPCDispatcher +import httplib +import urllib import xmlrpclib +import StringIO import os import signal @@ -89,6 +92,8 @@ def wsgi_xmlrpc(environ, start_response): elif service == 'report': return xmlrpc_return(start_response, 'report', method, params) + # TODO the body has been read, need to raise an exception (not return None). + def legacy_wsgi_xmlrpc(environ, start_response): if environ['REQUEST_METHOD'] == 'POST' and environ['PATH_INFO'].startswith('/xmlrpc/'): length = int(environ['CONTENT_LENGTH']) @@ -105,6 +110,157 @@ def wsgi_modules(environ, start_response): """ WSGI handler dispatching to addons-provided entry points.""" pass +def wsgi_webdav(environ, start_response): + if environ['REQUEST_METHOD'] == 'OPTIONS' and environ['PATH_INFO'] == '*': + return return_options(start_response) + if environ['PATH_INFO'].startswith('/webdav'): # TODO depends on config + environ['PATH_INFO'] = '/' + environ['PATH_INFO'][len('/webdav'):] + return wsgi_to_http(environ, start_response) + +def return_options(start_response): + # TODO Microsoft specifi header, see websrv_lib do_OPTIONS + options = [('DAV', '1 2'), ('Allow', 'GET HEAD PROPFIND OPTIONS REPORT')] + start_response("200 OK", [('Content-Length', str(0))] + options) + return [] + +webdav = None + +def wsgi_to_http(environ, start_response): + """ + Forward a WSGI request to a BaseHTTPRequestHandler. + + This code is adapted from wbsrv_lib.MultiHTTPHandler._handle_one_foreign(). + It is a temporary solution: the HTTP sub-handlers (in particular the + document_webdav addon) have to be WSGIfied. + """ + global webdav + # Make sure the addons are loaded in the registry, so they have a chance + # to register themselves in the 'service' layer. + openerp.pooler.get_db_and_pool('xx', update_module=[], pooljobs=False) + + scheme = environ['wsgi.url_scheme'] + + headers = {} + for key, value in environ.items(): + if key.startswith('HTTP_'): + key = key[5:].replace('_', '-').title() + headers[key] = value + if key == 'CONTENT_LENGTH': + key = key.replace('_', '-').title() + headers[key] = value + if environ.get('Content-Type'): + headers['Content-Type'] = environ['Content-Type'] + + path = urllib.quote(environ.get('PATH_INFO', '')) + if environ.get('QUERY_STRING'): + path += '?' + environ['QUERY_STRING'] + + class Dummy(): + pass + server = Dummy() + server.server_name = environ['SERVER_NAME'] + server.server_port = int(environ['SERVER_PORT']) + con = openerp.service.websrv_lib.noconnection(environ['gunicorn.socket']) # None + fore = webdav.handler(openerp.service.websrv_lib.noconnection(con), environ['REMOTE_ADDR'], server) + + # let's pretend we are a Multi handler + class M(): + def __init__(self): + self.sec_realms = {} + self.shared_headers = [] + self.shared_response = '' + self.shared_body = '' + def send_error(self, code, msg): + self.shared_response = str(code) + ' ' + msg + def send_response(self, code, msg): + self.shared_response = str(code) + ' ' + msg + def send_header(self, a, b): + self.shared_headers.append((a, b)) + def end_headers(self, *args, **kwargs): + pass + + multi = M() + + webdav.auth_provider.setupAuth(multi, fore) + + request_version = 'HTTP/1.1' # TODO + fore.wfile = StringIO.StringIO() + fore.rfile = environ['wsgi.input'] + fore.headers = headers + fore.command = environ['REQUEST_METHOD'] + fore.path = path + fore.request_version = request_version + fore.close_connection = 1 + + fore.raw_requestline = "%s %s %s\n" % (environ['REQUEST_METHOD'], path, request_version) + fore.requestline = fore.raw_requestline + + from openerp.service.websrv_lib import AuthRequiredExc, AuthRejectedExc + + def go(): + auth_provider = webdav.auth_provider + if auth_provider and auth_provider.realm: + try: + multi.sec_realms[auth_provider.realm].checkRequest(fore, path) + except AuthRequiredExc, ae: + # Darwin 9.x.x webdav clients will report "HTTP/1.0" to us, while they support (and need) the + # authorisation features of HTTP/1.1 + if request_version != 'HTTP/1.1' and ('Darwin/9.' not in fore.headers.get('User-Agent', '')): + print 'self.log_error("Cannot require auth at %s", self.request_version)' + multi.send_error(403) + return + #self._get_ignore_body(fore) # consume any body that came, not loose sync with input + multi.send_response(401,'Authorization required') + multi.send_header('WWW-Authenticate','%s realm="%s"' % (ae.atype,ae.realm)) + multi.send_header('Connection', 'keep-alive') + multi.send_header('Content-Type','text/html') + multi.send_header('Content-Length', 4) # len(self.auth_required_msg)) + multi.end_headers() + #self.wfile.write(self.auth_required_msg) + multi.shared_body = 'Blah' + return + except AuthRejectedExc,e: + print '("Rejected auth: %s" % e.args[0])' + multi.send_error(403,e.args[0]) + return + mname = 'do_' + fore.command + if not hasattr(fore, mname): + if fore.command == 'OPTIONS': + return return_options(start_response) + multi.send_error(501, "Unsupported method (%r)" % fore.command) + return + method = getattr(fore, mname) + try: + method() + if hasattr(fore, '_flush'): + fore._flush() + response = fore.wfile.getvalue() + class DummySocket(StringIO.StringIO): + """ + This is used to provide a StringIO to httplib.HTTPResponse + which, instead of taking a file object, expects a socket and + uses its makefile() method. + """ + def makefile(self, *args, **kw): + return self + response = httplib.HTTPResponse(DummySocket(response)) + response.begin() + response_headers = response.getheaders() + body = response.read() + start_response(str(response.status) + ' ' + response.reason, response_headers) + return [body] + except (AuthRejectedExc, AuthRequiredExc): + raise + except Exception, e: + multi.send_error(500, "Internal error") + return + res = go() + if res is None: + start_response(multi.shared_response, multi.shared_headers) + return [multi.shared_body] + else: + return res + # WSGI handlers provided by modules loaded with the --load command-line option. module_handlers = [] @@ -121,11 +277,12 @@ def application(environ, start_response): # Try all handlers until one returns some result (i.e. not None). wsgi_handlers = [ - wsgi_xmlrpc, - wsgi_jsonrpc, - legacy_wsgi_xmlrpc, - wsgi_modules, - ] + module_handlers + #wsgi_xmlrpc, + #wsgi_jsonrpc, + #legacy_wsgi_xmlrpc, + #wsgi_modules, + wsgi_webdav + ] #+ module_handlers for handler in wsgi_handlers: result = handler(environ, start_response) if result is None: @@ -154,11 +311,27 @@ def on_starting(server): global arbiter_pid arbiter_pid = os.getpid() # TODO check if this is true even after replacing the executable config = openerp.tools.config - config['addons_path'] = '/home/openerp/repos/addons/trunk-xmlrpc-no-osv-memory' # need a config file + config['addons_path'] = '/home/openerp/repos/addons/trunk-xmlrpc' # need a config file + #config['log_level'] = 10 # debug #openerp.tools.cache = kill_workers_cache openerp.netsvc.init_logger() openerp.osv.osv.start_object_proxy() openerp.service.web_services.start_web_services() + test_in_thread() + +def test_in_thread(): + def f(): + import time + time.sleep(2) + print ">>>> test thread" + cr = openerp.sql_db.db_connect('xx').cursor() + module_name = 'document_webdav' + fp = openerp.tools.file_open('/home/openerp/repos/addons/trunk-xmlrpc/document_webdav/test/webdav_test1.yml') + openerp.tools.convert_yaml_import(cr, module_name, fp, {}, 'update', True) + cr.close() + print "<<<< test thread" + import threading + threading.Thread(target=f).start() # Install our own signal handler on the master process. def when_ready(server):