[IMP] wsgi: WSGI-to-BaseHTTPRequestHandler, there are
probably some shortcomings but the document_webdav/test/webdav_test1.yml tests pass. bzr revid: vmt@openerp.com-20110907141318-4gxmyztv1zp6sk7q
This commit is contained in:
parent
0a6fe4d1f7
commit
14a82cdf9f
|
@ -3,7 +3,7 @@ import openerp
|
||||||
bind = '127.0.0.1:8069'
|
bind = '127.0.0.1:8069'
|
||||||
pidfile = '.gunicorn.pid'
|
pidfile = '.gunicorn.pid'
|
||||||
# This is the big TODO: safely use more than a single worker.
|
# This is the big TODO: safely use more than a single worker.
|
||||||
workers = 2
|
workers = 1
|
||||||
# Some application-wide initialization is needed.
|
# Some application-wide initialization is needed.
|
||||||
on_starting = openerp.wsgi.on_starting
|
on_starting = openerp.wsgi.on_starting
|
||||||
when_ready = openerp.wsgi.when_ready
|
when_ready = openerp.wsgi.when_ready
|
||||||
|
|
185
openerp/wsgi.py
185
openerp/wsgi.py
|
@ -27,7 +27,10 @@ This module offers a WSGI interface to OpenERP.
|
||||||
|
|
||||||
from wsgiref.simple_server import make_server
|
from wsgiref.simple_server import make_server
|
||||||
from SimpleXMLRPCServer import SimpleXMLRPCDispatcher
|
from SimpleXMLRPCServer import SimpleXMLRPCDispatcher
|
||||||
|
import httplib
|
||||||
|
import urllib
|
||||||
import xmlrpclib
|
import xmlrpclib
|
||||||
|
import StringIO
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import signal
|
import signal
|
||||||
|
@ -89,6 +92,8 @@ def wsgi_xmlrpc(environ, start_response):
|
||||||
elif service == 'report':
|
elif service == 'report':
|
||||||
return xmlrpc_return(start_response, 'report', method, params)
|
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):
|
def legacy_wsgi_xmlrpc(environ, start_response):
|
||||||
if environ['REQUEST_METHOD'] == 'POST' and environ['PATH_INFO'].startswith('/xmlrpc/'):
|
if environ['REQUEST_METHOD'] == 'POST' and environ['PATH_INFO'].startswith('/xmlrpc/'):
|
||||||
length = int(environ['CONTENT_LENGTH'])
|
length = int(environ['CONTENT_LENGTH'])
|
||||||
|
@ -105,6 +110,157 @@ def wsgi_modules(environ, start_response):
|
||||||
""" WSGI handler dispatching to addons-provided entry points."""
|
""" WSGI handler dispatching to addons-provided entry points."""
|
||||||
pass
|
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.
|
# WSGI handlers provided by modules loaded with the --load command-line option.
|
||||||
module_handlers = []
|
module_handlers = []
|
||||||
|
|
||||||
|
@ -121,11 +277,12 @@ def application(environ, start_response):
|
||||||
|
|
||||||
# Try all handlers until one returns some result (i.e. not None).
|
# Try all handlers until one returns some result (i.e. not None).
|
||||||
wsgi_handlers = [
|
wsgi_handlers = [
|
||||||
wsgi_xmlrpc,
|
#wsgi_xmlrpc,
|
||||||
wsgi_jsonrpc,
|
#wsgi_jsonrpc,
|
||||||
legacy_wsgi_xmlrpc,
|
#legacy_wsgi_xmlrpc,
|
||||||
wsgi_modules,
|
#wsgi_modules,
|
||||||
] + module_handlers
|
wsgi_webdav
|
||||||
|
] #+ module_handlers
|
||||||
for handler in wsgi_handlers:
|
for handler in wsgi_handlers:
|
||||||
result = handler(environ, start_response)
|
result = handler(environ, start_response)
|
||||||
if result is None:
|
if result is None:
|
||||||
|
@ -154,11 +311,27 @@ def on_starting(server):
|
||||||
global arbiter_pid
|
global arbiter_pid
|
||||||
arbiter_pid = os.getpid() # TODO check if this is true even after replacing the executable
|
arbiter_pid = os.getpid() # TODO check if this is true even after replacing the executable
|
||||||
config = openerp.tools.config
|
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.tools.cache = kill_workers_cache
|
||||||
openerp.netsvc.init_logger()
|
openerp.netsvc.init_logger()
|
||||||
openerp.osv.osv.start_object_proxy()
|
openerp.osv.osv.start_object_proxy()
|
||||||
openerp.service.web_services.start_web_services()
|
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.
|
# Install our own signal handler on the master process.
|
||||||
def when_ready(server):
|
def when_ready(server):
|
||||||
|
|
Loading…
Reference in New Issue