[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:
Vo Minh Thu 2011-09-07 16:13:18 +02:00
parent 0a6fe4d1f7
commit 14a82cdf9f
2 changed files with 180 additions and 7 deletions

View File

@ -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

View File

@ -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):