[MAJOR IMP] Rewrite the http/RPC engine and let HTTP/1.1 features.
This patch attempts a major change in the structure of the XML-RPC framework. There is one http server, capable of multiple services (except XML-RPC). That server could handle authentication, and is also HTTP/1.1 capable, which means it supports **persistent connections** ! At this commit, the old behaviour of the XML-RPC protocol is merely working. The netsvc.Service is split, so expect wizard/report breakages. External modules (koo) also break with this API. The net-svc code is crippled and gone FTM. bzr revid: p_christ@hol.gr-20090829152346-7i1iiqs8skdddamq
This commit is contained in:
parent
8558374c2b
commit
4abaf2763e
302
bin/netsvc.py
302
bin/netsvc.py
|
@ -37,17 +37,23 @@ import time
|
|||
import xmlrpclib
|
||||
import release
|
||||
|
||||
SERVICES = {}
|
||||
GROUPS = {}
|
||||
|
||||
class Service(object):
|
||||
""" Base class for *Local* services
|
||||
|
||||
Functionality here is trusted, no authentication.
|
||||
"""
|
||||
_services = {}
|
||||
def __init__(self, name, audience=''):
|
||||
SERVICES[name] = self
|
||||
Service._services[name] = self
|
||||
self.__name = name
|
||||
self._methods = {}
|
||||
|
||||
def joinGroup(self, name):
|
||||
GROUPS.setdefault(name, {})[self.__name] = self
|
||||
raise Exception("No group for local services")
|
||||
#GROUPS.setdefault(name, {})[self.__name] = self
|
||||
|
||||
def service_exist(self,name):
|
||||
return Service._services.has_key(name)
|
||||
|
||||
def exportMethod(self, method):
|
||||
if callable(method):
|
||||
|
@ -59,11 +65,16 @@ class Service(object):
|
|||
else:
|
||||
raise
|
||||
|
||||
class LocalService(Service):
|
||||
class LocalService(object):
|
||||
""" Proxy for local services.
|
||||
|
||||
Any instance of this class will behave like the single instance
|
||||
of Service(name)
|
||||
"""
|
||||
def __init__(self, name):
|
||||
self.__name = name
|
||||
try:
|
||||
self._service = SERVICES[name]
|
||||
self._service = Service._services[name]
|
||||
for method_name, method_definition in self._service._methods.items():
|
||||
setattr(self, method_name, method_definition)
|
||||
except KeyError, keyError:
|
||||
|
@ -72,8 +83,42 @@ class LocalService(Service):
|
|||
def __call__(self, method, *params):
|
||||
return getattr(self, method)(*params)
|
||||
|
||||
def service_exist(name):
|
||||
return SERVICES.get(name, False)
|
||||
class ExportService(object):
|
||||
""" Proxy for exported services.
|
||||
|
||||
All methods here should take an AuthProxy as their first parameter. It
|
||||
will be appended by the calling framework.
|
||||
|
||||
Note that this class has no direct proxy, capable of calling
|
||||
eservice.method(). Rather, the proxy should call
|
||||
dispatch(method,auth,params)
|
||||
"""
|
||||
|
||||
_services = {}
|
||||
_groups = {}
|
||||
|
||||
def __init__(self, name, audience=''):
|
||||
ExportService._services[name] = self
|
||||
self.__name = name
|
||||
|
||||
def joinGroup(self, name):
|
||||
ExportService._groups.setdefault(name, {})[self.__name] = self
|
||||
|
||||
@classmethod
|
||||
def getService(cls,name):
|
||||
return cls._services[name]
|
||||
|
||||
def dispatch(self, method, auth, params):
|
||||
pass
|
||||
|
||||
def new_dispatch(self,method,auth,params):
|
||||
pass
|
||||
|
||||
def abortResponse(self, error, description, origin, details):
|
||||
if not tools.config['debug_mode']:
|
||||
raise Exception("%s -- %s\n\n%s"%(origin, description, details))
|
||||
else:
|
||||
raise
|
||||
|
||||
LOG_NOTSET = 'notset'
|
||||
LOG_DEBUG_RPC = 'debug_rpc'
|
||||
|
@ -244,10 +289,46 @@ class Agent(object):
|
|||
|
||||
import traceback
|
||||
|
||||
class xmlrpc(object):
|
||||
class RpcGateway(object):
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
class Server:
|
||||
""" Generic interface for all servers with an event loop etc.
|
||||
Override this to impement http, net-rpc etc. servers.
|
||||
|
||||
Servers here must have threaded behaviour. start() must not block,
|
||||
there is no run().
|
||||
"""
|
||||
__is_started = False
|
||||
__servers = []
|
||||
|
||||
def __init__(self):
|
||||
if Server.__is_started:
|
||||
raise Exception('All instances of servers must be inited before the startAll()')
|
||||
Server.__servers.append(self)
|
||||
|
||||
def start(self):
|
||||
print "called stub Server.start"
|
||||
pass
|
||||
|
||||
def stop(self):
|
||||
print "called stub Server.stop"
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def startAll(cls):
|
||||
if cls.__is_started:
|
||||
return
|
||||
print "Starting %d services" % len(cls.__servers)
|
||||
for srv in cls.__servers:
|
||||
srv.start()
|
||||
cls.__is_started = True
|
||||
|
||||
@classmethod
|
||||
def quitAll(cls):
|
||||
if not cls.__is_started:
|
||||
return
|
||||
for srv in cls.__servers:
|
||||
srv.stop()
|
||||
cls.__is_started = False
|
||||
|
||||
|
||||
class OpenERPDispatcherException(Exception):
|
||||
def __init__(self, exception, traceback):
|
||||
|
@ -264,7 +345,11 @@ class OpenERPDispatcher:
|
|||
self.log('service', service_name)
|
||||
self.log('method', method)
|
||||
self.log('params', params)
|
||||
result = LocalService(service_name)(method, *params)
|
||||
if hasattr(self,'auth_provider'):
|
||||
auth = self.auth_provider
|
||||
else:
|
||||
auth = None
|
||||
result = ExportService.getService(service_name).dispatch(method, auth, params)
|
||||
self.log('result', result)
|
||||
return result
|
||||
except Exception, e:
|
||||
|
@ -279,193 +364,4 @@ class OpenERPDispatcher:
|
|||
pdb.post_mortem(tb[2])
|
||||
raise OpenERPDispatcherException(e, tb_s)
|
||||
|
||||
class GenericXMLRPCRequestHandler(OpenERPDispatcher):
|
||||
def _dispatch(self, method, params):
|
||||
try:
|
||||
service_name = self.path.split("/")[-1]
|
||||
return self.dispatch(service_name, method, params)
|
||||
except OpenERPDispatcherException, e:
|
||||
raise xmlrpclib.Fault(tools.exception_to_unicode(e.exception), e.traceback)
|
||||
|
||||
class SSLSocket(object):
|
||||
def __init__(self, socket):
|
||||
if not hasattr(socket, 'sock_shutdown'):
|
||||
from OpenSSL import SSL
|
||||
ctx = SSL.Context(SSL.SSLv23_METHOD)
|
||||
ctx.use_privatekey_file(tools.config['secure_pkey_file'])
|
||||
ctx.use_certificate_file(tools.config['secure_cert_file'])
|
||||
|
||||
self.socket = SSL.Connection(ctx, socket)
|
||||
else:
|
||||
self.socket = socket
|
||||
|
||||
def shutdown(self, how):
|
||||
return self.socket.sock_shutdown(how)
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self.socket, name)
|
||||
|
||||
class SimpleXMLRPCRequestHandler(GenericXMLRPCRequestHandler, SimpleXMLRPCServer.SimpleXMLRPCRequestHandler):
|
||||
rpc_paths = map(lambda s: '/xmlrpc/%s' % s, SERVICES.keys())
|
||||
|
||||
class SecureXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
|
||||
def setup(self):
|
||||
self.connection = SSLSocket(self.request)
|
||||
self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
|
||||
self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
|
||||
|
||||
class SimpleThreadedXMLRPCServer(SocketServer.ThreadingMixIn, SimpleXMLRPCServer.SimpleXMLRPCServer):
|
||||
encoding = None
|
||||
allow_none = False
|
||||
|
||||
def server_bind(self):
|
||||
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
SimpleXMLRPCServer.SimpleXMLRPCServer.server_bind(self)
|
||||
|
||||
def handle_error(self, request, client_address):
|
||||
""" Override the error handler
|
||||
"""
|
||||
import traceback
|
||||
Logger().notifyChannel("init", LOG_ERROR,"Server error in request from %s:\n%s" %
|
||||
(client_address,traceback.format_exc()))
|
||||
|
||||
class SecureThreadedXMLRPCServer(SimpleThreadedXMLRPCServer):
|
||||
def __init__(self, server_address, HandlerClass, logRequests=1):
|
||||
SimpleThreadedXMLRPCServer.__init__(self, server_address, HandlerClass, logRequests)
|
||||
self.socket = SSLSocket(socket.socket(self.address_family, self.socket_type))
|
||||
self.server_bind()
|
||||
self.server_activate()
|
||||
|
||||
def handle_error(self, request, client_address):
|
||||
""" Override the error handler
|
||||
"""
|
||||
import traceback
|
||||
e_type, e_value, e_traceback = sys.exc_info()
|
||||
Logger().notifyChannel("init", LOG_ERROR,"SSL Request handler error in request from %s: %s\n%s" %
|
||||
(client_address,str(e_type),traceback.format_exc()))
|
||||
|
||||
class HttpDaemon(threading.Thread):
|
||||
def __init__(self, interface, port, secure=False):
|
||||
threading.Thread.__init__(self)
|
||||
self.__port = port
|
||||
self.__interface = interface
|
||||
self.secure = bool(secure)
|
||||
handler_class = (SimpleXMLRPCRequestHandler, SecureXMLRPCRequestHandler)[self.secure]
|
||||
server_class = (SimpleThreadedXMLRPCServer, SecureThreadedXMLRPCServer)[self.secure]
|
||||
|
||||
if self.secure:
|
||||
from OpenSSL.SSL import Error as SSLError
|
||||
else:
|
||||
class SSLError(Exception): pass
|
||||
try:
|
||||
self.server = server_class((interface, port), handler_class, 0)
|
||||
except SSLError, e:
|
||||
Logger().notifyChannel('xml-rpc-ssl', LOG_CRITICAL, "Can not load the certificate and/or the private key files")
|
||||
sys.exit(1)
|
||||
except Exception, e:
|
||||
Logger().notifyChannel('xml-rpc', LOG_CRITICAL, "Error occur when starting the server daemon: %s" % (e,))
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def attach(self, path, gw):
|
||||
pass
|
||||
|
||||
def stop(self):
|
||||
self.running = False
|
||||
if os.name != 'nt':
|
||||
self.server.socket.shutdown( hasattr(socket, 'SHUT_RDWR') and socket.SHUT_RDWR or 2 )
|
||||
self.server.socket.close()
|
||||
|
||||
def run(self):
|
||||
self.server.register_introspection_functions()
|
||||
|
||||
self.running = True
|
||||
while self.running:
|
||||
self.server.handle_request()
|
||||
return True
|
||||
|
||||
# If the server need to be run recursively
|
||||
#
|
||||
#signal.signal(signal.SIGALRM, self.my_handler)
|
||||
#signal.alarm(6)
|
||||
#while True:
|
||||
# self.server.handle_request()
|
||||
#signal.alarm(0) # Disable the alarm
|
||||
|
||||
import tiny_socket
|
||||
class TinySocketClientThread(threading.Thread, OpenERPDispatcher):
|
||||
def __init__(self, sock, threads):
|
||||
threading.Thread.__init__(self)
|
||||
self.sock = sock
|
||||
self.threads = threads
|
||||
|
||||
def run(self):
|
||||
import select
|
||||
self.running = True
|
||||
try:
|
||||
ts = tiny_socket.mysocket(self.sock)
|
||||
except:
|
||||
self.sock.close()
|
||||
self.threads.remove(self)
|
||||
return False
|
||||
while self.running:
|
||||
try:
|
||||
msg = ts.myreceive()
|
||||
except:
|
||||
self.sock.close()
|
||||
self.threads.remove(self)
|
||||
return False
|
||||
try:
|
||||
result = self.dispatch(msg[0], msg[1], msg[2:])
|
||||
ts.mysend(result)
|
||||
except OpenERPDispatcherException, e:
|
||||
new_e = Exception(tools.exception_to_unicode(e.exception)) # avoid problems of pickeling
|
||||
ts.mysend(new_e, exception=True, traceback=e.traceback)
|
||||
|
||||
self.sock.close()
|
||||
self.threads.remove(self)
|
||||
return True
|
||||
|
||||
def stop(self):
|
||||
self.running = False
|
||||
|
||||
|
||||
class TinySocketServerThread(threading.Thread):
|
||||
def __init__(self, interface, port, secure=False):
|
||||
threading.Thread.__init__(self)
|
||||
self.__port = port
|
||||
self.__interface = interface
|
||||
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
self.socket.bind((self.__interface, self.__port))
|
||||
self.socket.listen(5)
|
||||
self.threads = []
|
||||
|
||||
def run(self):
|
||||
import select
|
||||
try:
|
||||
self.running = True
|
||||
while self.running:
|
||||
(clientsocket, address) = self.socket.accept()
|
||||
ct = TinySocketClientThread(clientsocket, self.threads)
|
||||
self.threads.append(ct)
|
||||
ct.start()
|
||||
self.socket.close()
|
||||
except Exception, e:
|
||||
self.socket.close()
|
||||
return False
|
||||
|
||||
def stop(self):
|
||||
self.running = False
|
||||
for t in self.threads:
|
||||
t.stop()
|
||||
try:
|
||||
if hasattr(socket, 'SHUT_RDWR'):
|
||||
self.socket.shutdown(socket.SHUT_RDWR)
|
||||
else:
|
||||
self.socket.shutdown(2)
|
||||
self.socket.close()
|
||||
except:
|
||||
return False
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
|
@ -152,35 +152,22 @@ if tools.config["stop_after_init"]:
|
|||
|
||||
|
||||
#----------------------------------------------------------
|
||||
# Launch Server
|
||||
# Launch Servers
|
||||
#----------------------------------------------------------
|
||||
|
||||
if tools.config['xmlrpc']:
|
||||
port = int(tools.config['port'])
|
||||
interface = tools.config["interface"]
|
||||
secure = tools.config["secure"]
|
||||
import service.http_server
|
||||
|
||||
httpd = netsvc.HttpDaemon(interface, port, secure)
|
||||
service.http_server.init_servers()
|
||||
service.http_server.init_xmlrpc()
|
||||
|
||||
xml_gw = netsvc.xmlrpc.RpcGateway('web-services')
|
||||
httpd.attach("/xmlrpc", xml_gw)
|
||||
logger.notifyChannel("web-services", netsvc.LOG_INFO,
|
||||
"starting XML-RPC%s services, port %s" %
|
||||
((tools.config['secure'] and ' Secure' or ''), port))
|
||||
|
||||
#
|
||||
#if tools.config["soap"]:
|
||||
# soap_gw = netsvc.xmlrpc.RpcGateway('web-services')
|
||||
# httpd.attach("/soap", soap_gw )
|
||||
# logger.notifyChannel("web-services", netsvc.LOG_INFO, 'starting SOAP services, port '+str(port))
|
||||
#
|
||||
|
||||
if tools.config['netrpc']:
|
||||
netport = int(tools.config['netport'])
|
||||
netinterface = tools.config["netinterface"]
|
||||
tinySocket = netsvc.TinySocketServerThread(netinterface, netport, False)
|
||||
logger.notifyChannel("web-services", netsvc.LOG_INFO,
|
||||
"starting NET-RPC service, port %d" % (netport,))
|
||||
# *-* TODO
|
||||
#if tools.config['netrpc']:
|
||||
#netport = int(tools.config['netport'])
|
||||
#netinterface = tools.config["netinterface"]
|
||||
#tinySocket = netsvc.TinySocketServerThread(netinterface, netport, False)
|
||||
#logger.notifyChannel("web-services", netsvc.LOG_INFO,
|
||||
#"starting NET-RPC service, port %d" % (netport,))
|
||||
|
||||
LST_SIGNALS = ['SIGINT', 'SIGTERM']
|
||||
if os.name == 'posix':
|
||||
|
@ -196,11 +183,8 @@ def handler(signum, _):
|
|||
:param signum: the signal number
|
||||
:param _:
|
||||
"""
|
||||
if tools.config['netrpc']:
|
||||
tinySocket.stop()
|
||||
if tools.config['xmlrpc']:
|
||||
httpd.stop()
|
||||
netsvc.Agent.quit()
|
||||
netsvc.Server.quitAll()
|
||||
if tools.config['pidfile']:
|
||||
os.unlink(tools.config['pidfile'])
|
||||
logger.notifyChannel('shutdown', netsvc.LOG_INFO,
|
||||
|
@ -217,16 +201,14 @@ if tools.config['pidfile']:
|
|||
fd.write(pidtext)
|
||||
fd.close()
|
||||
|
||||
|
||||
netsvc.Server.startAll()
|
||||
|
||||
logger.notifyChannel("web-services", netsvc.LOG_INFO,
|
||||
'the server is running, waiting for connections...')
|
||||
|
||||
if tools.config['netrpc']:
|
||||
tinySocket.start()
|
||||
if tools.config['xmlrpc']:
|
||||
httpd.start()
|
||||
|
||||
while True:
|
||||
time.sleep(1)
|
||||
time.sleep(60)
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
|
|
|
@ -86,7 +86,6 @@ class osv_pool(netsvc.Service):
|
|||
self._init = True
|
||||
self._init_parent = {}
|
||||
netsvc.Service.__init__(self, 'object_proxy', audience='')
|
||||
self.joinGroup('web-services')
|
||||
self.exportMethod(self.obj_list)
|
||||
self.exportMethod(self.exec_workflow)
|
||||
self.exportMethod(self.execute)
|
||||
|
|
|
@ -49,7 +49,7 @@ def toxml(val):
|
|||
|
||||
class report_int(netsvc.Service):
|
||||
def __init__(self, name, audience='*'):
|
||||
assert not netsvc.service_exist(name), 'The report "%s" already exist!' % name
|
||||
assert not self.service_exist(name), 'The report "%s" already exist!' % name
|
||||
super(report_int, self).__init__(name, audience)
|
||||
if name[0:7]<>'report.':
|
||||
raise Exception, 'ConceptionError, bad report name, should start with "report."'
|
||||
|
@ -57,7 +57,7 @@ class report_int(netsvc.Service):
|
|||
self.id = 0
|
||||
self.name2 = '.'.join(name.split('.')[1:])
|
||||
self.title = None
|
||||
self.joinGroup('report')
|
||||
#self.joinGroup('report')
|
||||
self.exportMethod(self.create)
|
||||
|
||||
def create(self, cr, uid, ids, datas, context=None):
|
||||
|
@ -239,8 +239,9 @@ def register_all(db):
|
|||
cr.execute("SELECT * FROM ir_act_report_xml WHERE auto=%s ORDER BY id", (True,))
|
||||
result = cr.dictfetchall()
|
||||
cr.close()
|
||||
svcs = netsvc.Service._services
|
||||
for r in result:
|
||||
if netsvc.service_exist('report.'+r['report_name']):
|
||||
if svcs.has_key('report.'+r['report_name']):
|
||||
continue
|
||||
if r['report_rml'] or r['report_rml_content_data']:
|
||||
report_sxw('report.'+r['report_name'], r['model'],
|
||||
|
|
|
@ -0,0 +1,211 @@
|
|||
# -*- encoding: utf-8 -*-
|
||||
|
||||
#
|
||||
# Copyright P. Christeas <p_christ@hol.gr> 2008,2009
|
||||
#
|
||||
#
|
||||
# WARNING: This program as such is intended to be used by professional
|
||||
# programmers who take the whole responsability of assessing all potential
|
||||
# consequences resulting from its eventual inadequacies and bugs
|
||||
# End users who are looking for a ready-to-use solution with commercial
|
||||
# garantees and support are strongly adviced to contract a Free Software
|
||||
# Service Company
|
||||
#
|
||||
# This program is Free Software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
###############################################################################
|
||||
|
||||
""" This file contains instance of the http server.
|
||||
|
||||
|
||||
"""
|
||||
from websrv_lib import *
|
||||
import netsvc
|
||||
import threading
|
||||
import tools
|
||||
import os
|
||||
import socket
|
||||
import xmlrpclib
|
||||
|
||||
from SimpleXMLRPCServer import SimpleXMLRPCDispatcher
|
||||
|
||||
try:
|
||||
import fcntl
|
||||
except ImportError:
|
||||
fcntl = None
|
||||
|
||||
class ThreadedHTTPServer(ConnThreadingMixIn, SimpleXMLRPCDispatcher, HTTPServer):
|
||||
""" A threaded httpd server, with all the necessary functionality for us.
|
||||
|
||||
It also inherits the xml-rpc dispatcher, so that some xml-rpc functions
|
||||
will be available to the request handler
|
||||
"""
|
||||
encoding = None
|
||||
allow_none = False
|
||||
allow_reuse_address = 1
|
||||
_send_traceback_header = False
|
||||
i = 0
|
||||
|
||||
def __init__(self, addr, requestHandler,
|
||||
logRequests=True, allow_none=False, encoding=None, bind_and_activate=True):
|
||||
self.logRequests = logRequests
|
||||
|
||||
SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding)
|
||||
HTTPServer.__init__(self, addr, requestHandler, bind_and_activate)
|
||||
|
||||
# [Bug #1222790] If possible, set close-on-exec flag; if a
|
||||
# method spawns a subprocess, the subprocess shouldn't have
|
||||
# the listening socket open.
|
||||
if fcntl is not None and hasattr(fcntl, 'FD_CLOEXEC'):
|
||||
flags = fcntl.fcntl(self.fileno(), fcntl.F_GETFD)
|
||||
flags |= fcntl.FD_CLOEXEC
|
||||
fcntl.fcntl(self.fileno(), fcntl.F_SETFD, flags)
|
||||
|
||||
def handle_error(self, request, client_address):
|
||||
""" Override the error handler
|
||||
"""
|
||||
import traceback
|
||||
netsvc.Logger().notifyChannel("init", netsvc.LOG_ERROR,"Server error in request from %s:\n%s" %
|
||||
(client_address,traceback.format_exc()))
|
||||
|
||||
class MultiHandler2(MultiHTTPHandler):
|
||||
def log_message(self, format, *args):
|
||||
netsvc.Logger().notifyChannel('http',netsvc.LOG_DEBUG,format % args)
|
||||
|
||||
|
||||
class SecureMultiHandler2(SecureMultiHTTPHandler):
|
||||
def log_message(self, format, *args):
|
||||
netsvc.Logger().notifyChannel('https',netsvc.LOG_DEBUG,format % args)
|
||||
|
||||
class HttpDaemon(threading.Thread, netsvc.Server):
|
||||
def __init__(self, interface, port):
|
||||
threading.Thread.__init__(self)
|
||||
netsvc.Server.__init__(self)
|
||||
self.__port = port
|
||||
self.__interface = interface
|
||||
|
||||
try:
|
||||
self.server = ThreadedHTTPServer((interface, port), MultiHandler2)
|
||||
self.server.vdirs = []
|
||||
self.server.logRequests = True
|
||||
except Exception, e:
|
||||
netsvc.Logger().notifyChannel('httpd', netsvc.LOG_CRITICAL, "Error occur when starting the server daemon: %s" % (e,))
|
||||
raise
|
||||
|
||||
|
||||
def attach(self, path, gw):
|
||||
pass
|
||||
|
||||
def stop(self):
|
||||
self.running = False
|
||||
if os.name != 'nt':
|
||||
self.server.socket.shutdown( hasattr(socket, 'SHUT_RDWR') and socket.SHUT_RDWR or 2 )
|
||||
self.server.socket.close()
|
||||
|
||||
def run(self):
|
||||
#self.server.register_introspection_functions()
|
||||
|
||||
self.running = True
|
||||
while self.running:
|
||||
self.server.handle_request()
|
||||
return True
|
||||
|
||||
class HttpSDaemon(threading.Thread, netsvc.Server):
|
||||
def __init__(self, interface, port):
|
||||
threading.Thread.__init__(self)
|
||||
netsvc.Server.__init__(self)
|
||||
self.__port = port
|
||||
self.__interface = interface
|
||||
|
||||
from ssl import SSLError
|
||||
|
||||
try:
|
||||
self.server = ThreadedHTTPServer((interface, port), SecureMultiHandler2)
|
||||
self.server.vdirs = []
|
||||
self.server.logRequests = True
|
||||
except SSLError, e:
|
||||
netsvc.Logger().notifyChannel('httpd-ssl', netsvc.LOG_CRITICAL, "Can not load the certificate and/or the private key files")
|
||||
raise
|
||||
except Exception, e:
|
||||
netsvc.Logger().notifyChannel('httpd-ssl', netsvc.LOG_CRITICAL, "Error occur when starting the server daemon: %s" % (e,))
|
||||
raise
|
||||
|
||||
httpd = None
|
||||
httpsd = None
|
||||
|
||||
def init_servers():
|
||||
global httpd, httpsd
|
||||
if tools.config.get_misc('httpd','enable', True):
|
||||
print "creating a httpd"
|
||||
httpd = HttpDaemon(tools.config.get_misc('httpd','interface', ''), \
|
||||
tools.config.get_misc('httpd','port', 8069))
|
||||
|
||||
if tools.config.get_misc('httpsd','enable', False):
|
||||
print "creating a httpsd"
|
||||
httpsd = HttpSDaemon(tools.config.get_misc('httpsd','interface', ''), \
|
||||
tools.config.get_misc('httpsd','port', 8071))
|
||||
|
||||
def reg_http_service(hts, secure_only = False):
|
||||
""" Register some handler to httpd.
|
||||
hts must be an HTTPDir
|
||||
"""
|
||||
global httpd, httpsd
|
||||
if not isinstance(hts, HTTPDir):
|
||||
raise Exception("Wrong class for http service")
|
||||
|
||||
if httpd and not secure_only:
|
||||
httpd.server.vdirs.append(hts)
|
||||
|
||||
if httpsd:
|
||||
httpsd.server.vdirs.append(hts)
|
||||
|
||||
if (not httpd) and (not httpsd):
|
||||
netsvc.Logger().notifyChannel('httpd',netsvc.LOG_WARNING,"No httpd available to register service %s",hts.path)
|
||||
return
|
||||
|
||||
import SimpleXMLRPCServer
|
||||
class XMLRPCRequestHandler(netsvc.OpenERPDispatcher,SimpleXMLRPCServer.SimpleXMLRPCRequestHandler):
|
||||
rpc_paths = []
|
||||
protocol_version = 'HTTP/1.1'
|
||||
def _dispatch(self, method, params):
|
||||
try:
|
||||
service_name = self.path.split("/")[-1]
|
||||
return self.dispatch(service_name, method, params)
|
||||
except netsvc.OpenERPDispatcherException, e:
|
||||
raise xmlrpclib.Fault(tools.exception_to_unicode(e.exception), e.traceback)
|
||||
|
||||
def log_message(self, format, *args):
|
||||
netsvc.Logger().notifyChannel('xmlrpc',netsvc.LOG_DEBUG_RPC,format % args)
|
||||
|
||||
def handle(self):
|
||||
pass
|
||||
|
||||
def finish(self):
|
||||
pass
|
||||
|
||||
def setup(self):
|
||||
self.connection = dummyconn()
|
||||
if not len(XMLRPCRequestHandler.rpc_paths):
|
||||
XMLRPCRequestHandler.rpc_paths = map(lambda s: '/%s' % s, netsvc.ExportService._services.keys())
|
||||
pass
|
||||
|
||||
|
||||
def init_xmlrpc():
|
||||
if not tools.config.get_misc('xmlrpc','enable', True):
|
||||
return
|
||||
reg_http_service(HTTPDir('/xmlrpc/',XMLRPCRequestHandler))
|
||||
# Example of http file serving:
|
||||
# reg_http_service(HTTPDir('/test/',HTTPHandler))
|
||||
print "Started XML-RPC"
|
||||
#eof
|
|
@ -41,28 +41,36 @@ import tools
|
|||
import locale
|
||||
logging.basicConfig()
|
||||
|
||||
class db(netsvc.Service):
|
||||
class db(netsvc.ExportService):
|
||||
def __init__(self, name="db"):
|
||||
netsvc.Service.__init__(self, name)
|
||||
netsvc.ExportService.__init__(self, name)
|
||||
self.joinGroup("web-services")
|
||||
self.exportMethod(self.create)
|
||||
self.exportMethod(self.get_progress)
|
||||
self.exportMethod(self.drop)
|
||||
self.exportMethod(self.dump)
|
||||
self.exportMethod(self.restore)
|
||||
self.exportMethod(self.rename)
|
||||
self.exportMethod(self.list)
|
||||
self.exportMethod(self.list_lang)
|
||||
self.exportMethod(self.change_admin_password)
|
||||
self.exportMethod(self.server_version)
|
||||
self.exportMethod(self.migrate_databases)
|
||||
self.actions = {}
|
||||
self.id = 0
|
||||
self.id_protect = threading.Semaphore()
|
||||
|
||||
self._pg_psw_env_var_is_set = False # on win32, pg_dump need the PGPASSWORD env var
|
||||
|
||||
def create(self, password, db_name, demo, lang, user_password='admin'):
|
||||
def dispatch(self, method, auth, params):
|
||||
if method in [ 'create', 'get_progress', 'drop', 'dump',
|
||||
'restore', 'rename',
|
||||
'change_admin_password', 'migrate_databases' ]:
|
||||
passwd = params[0]
|
||||
params = params[1:]
|
||||
security.check_super(password)
|
||||
elif method in [ 'db_exist', 'list', 'list_lang', 'server_version' ]:
|
||||
# params = params
|
||||
# No security check for these methods
|
||||
pass
|
||||
else:
|
||||
raise KeyError("Method not found: %s" % method)
|
||||
fn = getattr(self, 'exp_'+method)
|
||||
return fn(*params)
|
||||
|
||||
def new_dispatch(self,method,auth,params):
|
||||
pass
|
||||
|
||||
def exp_create(self, db_name, demo, lang, user_password='admin'):
|
||||
security.check_super(password)
|
||||
self.id_protect.acquire()
|
||||
self.id += 1
|
||||
|
@ -131,8 +139,7 @@ class db(netsvc.Service):
|
|||
self.actions[id]['thread'] = create_thread
|
||||
return id
|
||||
|
||||
def get_progress(self, password, id):
|
||||
security.check_super(password)
|
||||
def exp_get_progress(self, id):
|
||||
if self.actions[id]['thread'].isAlive():
|
||||
# return addons.init_progress[db_name]
|
||||
return (min(self.actions[id].get('progress', 0),0.95), [])
|
||||
|
@ -147,8 +154,7 @@ class db(netsvc.Service):
|
|||
del self.actions[id]
|
||||
raise Exception, e
|
||||
|
||||
def drop(self, password, db_name):
|
||||
security.check_super(password)
|
||||
def exp_drop(self, db_name):
|
||||
sql_db.close_db(db_name)
|
||||
logger = netsvc.Logger()
|
||||
|
||||
|
@ -180,8 +186,7 @@ class db(netsvc.Service):
|
|||
if os.name == 'nt' and self._pg_psw_env_var_is_set:
|
||||
os.environ['PGPASSWORD'] = ''
|
||||
|
||||
def dump(self, password, db_name):
|
||||
security.check_super(password)
|
||||
def exp_dump(self, db_name):
|
||||
logger = netsvc.Logger()
|
||||
|
||||
self._set_pg_psw_env_var()
|
||||
|
@ -210,7 +215,7 @@ class db(netsvc.Service):
|
|||
|
||||
return base64.encodestring(data)
|
||||
|
||||
def restore(self, password, db_name, data):
|
||||
def exp_restore(self, db_name, data):
|
||||
security.check_super(password)
|
||||
logger = netsvc.Logger()
|
||||
|
||||
|
@ -261,8 +266,7 @@ class db(netsvc.Service):
|
|||
|
||||
return True
|
||||
|
||||
def rename(self, password, old_name, new_name):
|
||||
security.check_super(password)
|
||||
def exp_rename(self, old_name, new_name):
|
||||
sql_db.close_db(old_name)
|
||||
logger = netsvc.Logger()
|
||||
|
||||
|
@ -288,14 +292,14 @@ class db(netsvc.Service):
|
|||
sql_db.close_db('template1')
|
||||
return True
|
||||
|
||||
def db_exist(self, db_name):
|
||||
def exp_db_exist(self, db_name):
|
||||
try:
|
||||
db = sql_db.db_connect(db_name)
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
|
||||
def list(self):
|
||||
def exp_list(self):
|
||||
db = sql_db.db_connect('template1')
|
||||
cr = db.cursor()
|
||||
try:
|
||||
|
@ -321,27 +325,25 @@ class db(netsvc.Service):
|
|||
res.sort()
|
||||
return res
|
||||
|
||||
def change_admin_password(self, old_password, new_password):
|
||||
security.check_super(old_password)
|
||||
def exp_change_admin_password(self, new_password):
|
||||
tools.config['admin_passwd'] = new_password
|
||||
tools.config.save()
|
||||
return True
|
||||
|
||||
def list_lang(self):
|
||||
def exp_list_lang(self):
|
||||
return tools.scan_languages()
|
||||
|
||||
def server_version(self):
|
||||
def exp_server_version(self):
|
||||
""" Return the version of the server
|
||||
Used by the client to verify the compatibility with its own version
|
||||
"""
|
||||
return release.version
|
||||
|
||||
def migrate_databases(self, password, databases):
|
||||
def exp_migrate_databases(self,databases):
|
||||
|
||||
from osv.orm import except_orm
|
||||
from osv.osv import except_osv
|
||||
|
||||
security.check_super(password)
|
||||
l = netsvc.Logger()
|
||||
for db in databases:
|
||||
try:
|
||||
|
@ -360,64 +362,74 @@ class db(netsvc.Service):
|
|||
return True
|
||||
db()
|
||||
|
||||
class common(netsvc.Service):
|
||||
class _ObjectService(netsvc.ExportService):
|
||||
"A common base class for those who have fn(db, uid, password,...) "
|
||||
|
||||
def common_dispatch(self, method, auth, params):
|
||||
(db, uid, passwd ) = params[0:3]
|
||||
params = params[3:]
|
||||
security.check(db,uid,passwd)
|
||||
cr = pooler.get_db(db).cursor()
|
||||
fn = getattr(self, 'exp_'+method)
|
||||
res = fn(cr, uid, *params)
|
||||
cr.commit()
|
||||
cr.close()
|
||||
return res
|
||||
|
||||
class common(_ObjectService):
|
||||
def __init__(self,name="common"):
|
||||
netsvc.Service.__init__(self,name)
|
||||
_ObjectService.__init__(self,name)
|
||||
self.joinGroup("web-services")
|
||||
self.exportMethod(self.ir_get)
|
||||
self.exportMethod(self.ir_set)
|
||||
self.exportMethod(self.ir_del)
|
||||
self.exportMethod(self.about)
|
||||
self.exportMethod(self.login)
|
||||
self.exportMethod(self.logout)
|
||||
self.exportMethod(self.timezone_get)
|
||||
self.exportMethod(self.get_available_updates)
|
||||
self.exportMethod(self.get_migration_scripts)
|
||||
self.exportMethod(self.get_server_environment)
|
||||
self.exportMethod(self.login_message)
|
||||
self.exportMethod(self.set_loglevel)
|
||||
|
||||
def ir_set(self, db, uid, password, keys, args, name, value, replace=True, isobject=False):
|
||||
security.check(db, uid, password)
|
||||
cr = pooler.get_db(db).cursor()
|
||||
def dispatch(self, method, auth, params):
|
||||
logger = netsvc.Logger()
|
||||
if method in [ 'ir_set','ir_del', 'ir_get' ]:
|
||||
return self.common_dispatch(method,auth,params)
|
||||
if method == 'login':
|
||||
# At this old dispatcher, we do NOT update the auth proxy
|
||||
res = security.login(params[0], params[1], params[2])
|
||||
msg = res and 'successful login' or 'bad login or password'
|
||||
# TODO log the client ip address..
|
||||
logger.notifyChannel("web-service", netsvc.LOG_INFO, "%s from '%s' using database '%s'" % (msg, params[1], params[0].lower()))
|
||||
return res or False
|
||||
elif method == 'logout':
|
||||
if auth:
|
||||
auth.logout(params[1])
|
||||
logger.notifyChannel("web-service", netsvc.LOG_INFO,'Logout %s from database %s'%(login,db))
|
||||
return True
|
||||
elif method in ['about', 'timezone_get', 'get_server_environment', 'login_message']:
|
||||
pass
|
||||
elif method in ['get_available_updates', 'get_migration_scripts', 'set_loglevel']:
|
||||
passwd = params[0]
|
||||
params = params[1:]
|
||||
security.check_super(passwd)
|
||||
else:
|
||||
raise Exception("Method not found: %s" % method)
|
||||
|
||||
fn = getattr(self, 'exp_'+method)
|
||||
return fn(*params)
|
||||
|
||||
|
||||
def new_dispatch(self,method,auth,params):
|
||||
pass
|
||||
|
||||
def exp_ir_set(self, cr, uid, keys, args, name, value, replace=True, isobject=False):
|
||||
res = ir.ir_set(cr,uid, keys, args, name, value, replace, isobject)
|
||||
cr.commit()
|
||||
cr.close()
|
||||
return res
|
||||
|
||||
def ir_del(self, db, uid, password, id):
|
||||
security.check(db, uid, password)
|
||||
cr = pooler.get_db(db).cursor()
|
||||
def exp_ir_del(self, cr, uid, id):
|
||||
res = ir.ir_del(cr,uid, id)
|
||||
cr.commit()
|
||||
cr.close()
|
||||
return res
|
||||
|
||||
def ir_get(self, db, uid, password, keys, args=None, meta=None, context=None):
|
||||
def exp_ir_get(self, cr, uid, keys, args=None, meta=None, context=None):
|
||||
if not args:
|
||||
args=[]
|
||||
if not context:
|
||||
context={}
|
||||
security.check(db, uid, password)
|
||||
cr = pooler.get_db(db).cursor()
|
||||
res = ir.ir_get(cr,uid, keys, args, meta, context)
|
||||
cr.commit()
|
||||
cr.close()
|
||||
return res
|
||||
|
||||
def login(self, db, login, password):
|
||||
res = security.login(db, login, password)
|
||||
logger = netsvc.Logger()
|
||||
msg = res and 'successful login' or 'bad login or password'
|
||||
logger.notifyChannel("web-service", netsvc.LOG_INFO, "%s from '%s' using database '%s'" % (msg, login, db.lower()))
|
||||
return res or False
|
||||
|
||||
def logout(self, db, login, password):
|
||||
logger = netsvc.Logger()
|
||||
logger.notifyChannel("web-service", netsvc.LOG_INFO,'Logout %s from database %s'%(login,db))
|
||||
return True
|
||||
|
||||
def about(self, extended=False):
|
||||
def exp_about(self, extended=False):
|
||||
"""Return information about the OpenERP Server.
|
||||
|
||||
@param extended: if True then return version info
|
||||
|
@ -437,12 +449,11 @@ GNU Public Licence.
|
|||
return info, release.version
|
||||
return info
|
||||
|
||||
def timezone_get(self, db, login, password):
|
||||
def exp_timezone_get(self, db, login, password):
|
||||
return time.tzname[0]
|
||||
|
||||
|
||||
def get_available_updates(self, password, contract_id, contract_password):
|
||||
security.check_super(password)
|
||||
def exp_get_available_updates(self, contract_id, contract_password):
|
||||
import tools.maintenance as tm
|
||||
try:
|
||||
rc = tm.remote_contract(contract_id, contract_password)
|
||||
|
@ -455,8 +466,7 @@ GNU Public Licence.
|
|||
self.abortResponse(1, 'Migration Error', 'warning', str(e))
|
||||
|
||||
|
||||
def get_migration_scripts(self, password, contract_id, contract_password):
|
||||
security.check_super(password)
|
||||
def exp_get_migration_scripts(self, contract_id, contract_password):
|
||||
l = netsvc.Logger()
|
||||
import tools.maintenance as tm
|
||||
try:
|
||||
|
@ -528,7 +538,7 @@ GNU Public Licence.
|
|||
l.notifyChannel('migration', netsvc.LOG_ERROR, tb_s)
|
||||
raise
|
||||
|
||||
def get_server_environment(self):
|
||||
def exp_get_server_environment(self):
|
||||
os_lang = '.'.join( [x for x in locale.getdefaultlocale() if x] )
|
||||
if not os_lang:
|
||||
os_lang = 'NOT SET'
|
||||
|
@ -553,42 +563,36 @@ GNU Public Licence.
|
|||
return environment
|
||||
|
||||
|
||||
def login_message(self):
|
||||
def exp_login_message(self):
|
||||
return tools.config.get('login_message', False)
|
||||
|
||||
def set_loglevel(self, password, loglevel):
|
||||
security.check_super(password)
|
||||
def exp_set_loglevel(self,loglevel):
|
||||
l = netsvc.Logger()
|
||||
l.set_loglevel(int(loglevel))
|
||||
return True
|
||||
|
||||
common()
|
||||
|
||||
class objects_proxy(netsvc.Service):
|
||||
class objects_proxy(netsvc.ExportService):
|
||||
def __init__(self, name="object"):
|
||||
netsvc.Service.__init__(self,name)
|
||||
netsvc.ExportService.__init__(self,name)
|
||||
self.joinGroup('web-services')
|
||||
self.exportMethod(self.execute)
|
||||
self.exportMethod(self.exec_workflow)
|
||||
self.exportMethod(self.obj_list)
|
||||
|
||||
def exec_workflow(self, db, uid, passwd, object, method, id):
|
||||
security.check(db, uid, passwd)
|
||||
service = netsvc.LocalService("object_proxy")
|
||||
res = service.exec_workflow(db, uid, object, method, id)
|
||||
return res
|
||||
def dispatch(self, method, auth, params):
|
||||
(db, uid, passwd ) = params[0:3]
|
||||
params = params[3:]
|
||||
if method not in ['execute','exec_workflow','obj_list']:
|
||||
raise KeyError("Method not supported %s" % method)
|
||||
security.check(db,uid,passwd)
|
||||
ls = netsvc.LocalService('object_proxy')
|
||||
fn = getattr(ls, method)
|
||||
res = fn(db, uid, *params)
|
||||
return res
|
||||
|
||||
def execute(self, db, uid, passwd, object, method, *args):
|
||||
security.check(db, uid, passwd)
|
||||
service = netsvc.LocalService("object_proxy")
|
||||
res = service.execute(db, uid, object, method, *args)
|
||||
return res
|
||||
|
||||
def new_dispatch(self,method,auth,params):
|
||||
pass
|
||||
|
||||
def obj_list(self, db, uid, passwd):
|
||||
security.check(db, uid, passwd)
|
||||
service = netsvc.LocalService("object_proxy")
|
||||
res = service.obj_list()
|
||||
return res
|
||||
objects_proxy()
|
||||
|
||||
|
||||
|
@ -603,26 +607,36 @@ objects_proxy()
|
|||
# Wizard datas: {}
|
||||
# TODO: change local request to OSE request/reply pattern
|
||||
#
|
||||
class wizard(netsvc.Service):
|
||||
class wizard(netsvc.ExportService):
|
||||
def __init__(self, name='wizard'):
|
||||
netsvc.Service.__init__(self,name)
|
||||
netsvc.ExportService.__init__(self,name)
|
||||
self.joinGroup('web-services')
|
||||
self.exportMethod(self.execute)
|
||||
self.exportMethod(self.create)
|
||||
self.id = 0
|
||||
self.wiz_datas = {}
|
||||
self.wiz_name = {}
|
||||
self.wiz_uid = {}
|
||||
|
||||
def dispatch(self, method, auth, params):
|
||||
(db, uid, passwd ) = params[0:3]
|
||||
params = params[3:]
|
||||
if method not in ['execute','create']:
|
||||
raise KeyError("Method not supported %s" % method)
|
||||
security.check(db,uid,passwd)
|
||||
fn = getattr(self, 'exp_'+method)
|
||||
res = fn(ls, db, uid, *params)
|
||||
return res
|
||||
|
||||
def new_dispatch(self,method,auth,params):
|
||||
pass
|
||||
|
||||
def _execute(self, db, uid, wiz_id, datas, action, context):
|
||||
self.wiz_datas[wiz_id].update(datas)
|
||||
wiz = netsvc.LocalService('wizard.'+self.wiz_name[wiz_id])
|
||||
return wiz.execute(db, uid, self.wiz_datas[wiz_id], action, context)
|
||||
|
||||
def create(self, db, uid, passwd, wiz_name, datas=None):
|
||||
def exp_create(self, db, uid, wiz_name, datas=None):
|
||||
if not datas:
|
||||
datas={}
|
||||
security.check(db, uid, passwd)
|
||||
#FIXME: this is not thread-safe
|
||||
self.id += 1
|
||||
self.wiz_datas[self.id] = {}
|
||||
|
@ -630,10 +644,9 @@ class wizard(netsvc.Service):
|
|||
self.wiz_uid[self.id] = uid
|
||||
return self.id
|
||||
|
||||
def execute(self, db, uid, passwd, wiz_id, datas, action='init', context=None):
|
||||
def exp_execute(self, db, uid, wiz_id, datas, action='init', context=None):
|
||||
if not context:
|
||||
context={}
|
||||
security.check(db, uid, passwd)
|
||||
|
||||
if wiz_id in self.wiz_uid:
|
||||
if self.wiz_uid[wiz_id] == uid:
|
||||
|
@ -657,22 +670,33 @@ class ExceptionWithTraceback(Exception):
|
|||
self.traceback = tb
|
||||
self.args = (msg, tb)
|
||||
|
||||
class report_spool(netsvc.Service):
|
||||
class report_spool(netsvc.ExportService):
|
||||
def __init__(self, name='report'):
|
||||
netsvc.Service.__init__(self, name)
|
||||
netsvc.ExportService.__init__(self, name)
|
||||
self.joinGroup('web-services')
|
||||
self.exportMethod(self.report)
|
||||
self.exportMethod(self.report_get)
|
||||
self._reports = {}
|
||||
self.id = 0
|
||||
self.id_protect = threading.Semaphore()
|
||||
|
||||
def report(self, db, uid, passwd, object, ids, datas=None, context=None):
|
||||
def dispatch(self, method, auth, params):
|
||||
(db, uid, passwd ) = params[0:3]
|
||||
params = params[3:]
|
||||
if method not in ['report','report_get']:
|
||||
raise KeyError("Method not supported %s" % method)
|
||||
security.check(db,uid,passwd)
|
||||
fn = getattr(self, 'exp_' + method)
|
||||
res = fn(db, uid, *params)
|
||||
return res
|
||||
|
||||
|
||||
def new_dispatch(self,method,auth,params):
|
||||
pass
|
||||
|
||||
def exp_report(self, db, uid, object, ids, datas=None, context=None):
|
||||
if not datas:
|
||||
datas={}
|
||||
if not context:
|
||||
context={}
|
||||
security.check(db, uid, passwd)
|
||||
|
||||
self.id_protect.acquire()
|
||||
self.id += 1
|
||||
|
@ -728,8 +752,7 @@ class report_spool(netsvc.Service):
|
|||
del self._reports[report_id]
|
||||
return res
|
||||
|
||||
def report_get(self, db, uid, passwd, report_id):
|
||||
security.check(db, uid, passwd)
|
||||
def exp_report_get(self, db, uid, report_id):
|
||||
|
||||
if report_id in self._reports:
|
||||
if self._reports[report_id]['uid'] == uid:
|
||||
|
|
|
@ -0,0 +1,357 @@
|
|||
# -*- encoding: utf-8 -*-
|
||||
|
||||
#
|
||||
# Copyright P. Christeas <p_christ@hol.gr> 2008,2009
|
||||
#
|
||||
#
|
||||
# WARNING: This program as such is intended to be used by professional
|
||||
# programmers who take the whole responsability of assessing all potential
|
||||
# consequences resulting from its eventual inadequacies and bugs
|
||||
# End users who are looking for a ready-to-use solution with commercial
|
||||
# garantees and support are strongly adviced to contract a Free Software
|
||||
# Service Company
|
||||
#
|
||||
# This program is Free Software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
###############################################################################
|
||||
|
||||
""" Framework for generic http servers
|
||||
|
||||
"""
|
||||
|
||||
|
||||
class AuthRequiredExc(Exception):
|
||||
def __init__(self,atype,realm):
|
||||
Exception.__init__(self)
|
||||
self.atype = atype
|
||||
self.realm = realm
|
||||
|
||||
class AuthRejectedExc(Exception):
|
||||
pass
|
||||
|
||||
class AuthProvider:
|
||||
def __init__(self,realm):
|
||||
self.realm = realm
|
||||
|
||||
def setupAuth(self, multi,handler):
|
||||
""" Attach an AuthProxy object to handler
|
||||
"""
|
||||
pass
|
||||
|
||||
def authenticate(self, user, passwd, client_address):
|
||||
#if user == 'user' and passwd == 'password':
|
||||
# return (user, passwd)
|
||||
#else:
|
||||
return False
|
||||
|
||||
class BasicAuthProvider(AuthProvider):
|
||||
def setupAuth(self, multi, handler):
|
||||
if not multi.sec_realms.has_key(self.realm):
|
||||
multi.sec_realms[self.realm] = BasicAuthProxy(self)
|
||||
|
||||
|
||||
class AuthProxy:
|
||||
""" This class will hold authentication information for a handler,
|
||||
i.e. a connection
|
||||
"""
|
||||
def __init__(self, provider):
|
||||
self.provider = provider
|
||||
|
||||
def checkRequest(self,handler,path = '/'):
|
||||
""" Check if we are allowed to process that request
|
||||
"""
|
||||
pass
|
||||
|
||||
import base64
|
||||
class BasicAuthProxy(AuthProxy):
|
||||
""" Require basic authentication..
|
||||
"""
|
||||
def __init__(self,provider):
|
||||
AuthProxy.__init__(self,provider)
|
||||
self.auth_creds = None
|
||||
self.auth_tries = 0
|
||||
|
||||
def checkRequest(self,handler,path = '/'):
|
||||
if self.auth_creds:
|
||||
return True
|
||||
auth_str = handler.headers.get('Authorization',False)
|
||||
if auth_str and auth_str.startswith('Basic '):
|
||||
auth_str=auth_str[len('Basic '):]
|
||||
(user,passwd) = base64.decodestring(auth_str).split(':')
|
||||
print "Found user=\"%s\", passwd=\"%s\"" %(user,passwd)
|
||||
self.auth_creds = self.provider.authenticate(user,passwd,handler.client_address)
|
||||
if self.auth_creds:
|
||||
return True
|
||||
if self.auth_tries > 5:
|
||||
raise AuthRejectedExc("Authorization failed.")
|
||||
self.auth_tries += 1
|
||||
raise AuthRequiredExc(atype = 'Basic', realm=self.provider.realm)
|
||||
|
||||
|
||||
from BaseHTTPServer import *
|
||||
|
||||
from SimpleHTTPServer import SimpleHTTPRequestHandler
|
||||
class HTTPHandler(SimpleHTTPRequestHandler):
|
||||
def __init__(self,request, client_address, server):
|
||||
SimpleHTTPRequestHandler.__init__(self,request,client_address,server)
|
||||
# print "Handler for %s inited" % str(client_address)
|
||||
self.protocol_version = 'HTTP/1.1'
|
||||
self.connection = dummyconn()
|
||||
|
||||
def handle(self):
|
||||
""" Classes here should NOT handle inside their constructor
|
||||
"""
|
||||
pass
|
||||
|
||||
def finish(self):
|
||||
pass
|
||||
|
||||
def setup(self):
|
||||
pass
|
||||
|
||||
class HTTPDir:
|
||||
""" A dispatcher class, like a virtual folder in httpd
|
||||
"""
|
||||
def __init__(self,path,handler, auth_provider = None):
|
||||
self.path = path
|
||||
self.handler = handler
|
||||
self.auth_provider = auth_provider
|
||||
|
||||
def matches(self, request):
|
||||
""" Test if some request matches us. If so, return
|
||||
the matched path. """
|
||||
if request.startswith(self.path):
|
||||
return self.path
|
||||
return False
|
||||
|
||||
class noconnection:
|
||||
""" a class to use instead of the real connection
|
||||
"""
|
||||
def makefile(self, mode, bufsize):
|
||||
return None
|
||||
|
||||
class dummyconn:
|
||||
def shutdown(self, tru):
|
||||
pass
|
||||
|
||||
import SocketServer
|
||||
class MultiHTTPHandler(BaseHTTPRequestHandler):
|
||||
""" this is a multiple handler, that will dispatch each request
|
||||
to a nested handler, iff it matches
|
||||
|
||||
The handler will also have *one* dict of authentication proxies,
|
||||
groupped by their realm.
|
||||
"""
|
||||
|
||||
protocol_version = "HTTP/1.1"
|
||||
|
||||
def __init__(self, request, client_address, server):
|
||||
self.in_handlers = {}
|
||||
self.sec_realms = {}
|
||||
SocketServer.StreamRequestHandler.__init__(self,request,client_address,server)
|
||||
self.log_message("MultiHttpHandler init for %s" %(str(client_address)))
|
||||
|
||||
def _handle_one_foreign(self,fore, path, auth_provider):
|
||||
""" This method overrides the handle_one_request for *children*
|
||||
handlers. It is required, since the first line should not be
|
||||
read again..
|
||||
|
||||
"""
|
||||
fore.raw_requestline = "%s %s %s\n" % (self.command, path, self.version)
|
||||
if not fore.parse_request(): # An error code has been sent, just exit
|
||||
return
|
||||
self.request_version = fore.request_version
|
||||
if auth_provider and auth_provider.realm:
|
||||
try:
|
||||
self.sec_realms[auth_provider.realm].checkRequest(fore,path)
|
||||
except AuthRequiredExc,ae:
|
||||
if self.request_version != 'HTTP/1.1':
|
||||
self.log_error("Cannot require auth at %s",self.request_version)
|
||||
self.send_error(401)
|
||||
return
|
||||
self.send_response(401,'Authorization required')
|
||||
self.send_header('WWW-Authenticate','%s realm="%s"' % (ae.atype,ae.realm))
|
||||
self.send_header('Content-Type','text/html')
|
||||
self.send_header('Content-Length','0')
|
||||
self.end_headers()
|
||||
#self.wfile.write("\r\n")
|
||||
return
|
||||
except AuthRejectedExc,e:
|
||||
self.send_error(401,e.args[0])
|
||||
self.close_connection = 1
|
||||
return
|
||||
mname = 'do_' + fore.command
|
||||
if not hasattr(fore, mname):
|
||||
fore.send_error(501, "Unsupported method (%r)" % fore.command)
|
||||
return
|
||||
fore.close_connection = 0
|
||||
method = getattr(fore, mname)
|
||||
method()
|
||||
if fore.close_connection:
|
||||
# print "Closing connection because of handler"
|
||||
self.close_connection = fore.close_connection
|
||||
|
||||
def parse_rawline(self):
|
||||
"""Parse a request (internal).
|
||||
|
||||
The request should be stored in self.raw_requestline; the results
|
||||
are in self.command, self.path, self.request_version and
|
||||
self.headers.
|
||||
|
||||
Return True for success, False for failure; on failure, an
|
||||
error is sent back.
|
||||
|
||||
"""
|
||||
self.command = None # set in case of error on the first line
|
||||
self.request_version = version = self.default_request_version
|
||||
self.close_connection = 1
|
||||
requestline = self.raw_requestline
|
||||
if requestline[-2:] == '\r\n':
|
||||
requestline = requestline[:-2]
|
||||
elif requestline[-1:] == '\n':
|
||||
requestline = requestline[:-1]
|
||||
self.requestline = requestline
|
||||
words = requestline.split()
|
||||
if len(words) == 3:
|
||||
[command, path, version] = words
|
||||
if version[:5] != 'HTTP/':
|
||||
self.send_error(400, "Bad request version (%r)" % version)
|
||||
return False
|
||||
try:
|
||||
base_version_number = version.split('/', 1)[1]
|
||||
version_number = base_version_number.split(".")
|
||||
# RFC 2145 section 3.1 says there can be only one "." and
|
||||
# - major and minor numbers MUST be treated as
|
||||
# separate integers;
|
||||
# - HTTP/2.4 is a lower version than HTTP/2.13, which in
|
||||
# turn is lower than HTTP/12.3;
|
||||
# - Leading zeros MUST be ignored by recipients.
|
||||
if len(version_number) != 2:
|
||||
raise ValueError
|
||||
version_number = int(version_number[0]), int(version_number[1])
|
||||
except (ValueError, IndexError):
|
||||
self.send_error(400, "Bad request version (%r)" % version)
|
||||
return False
|
||||
if version_number >= (1, 1):
|
||||
self.close_connection = 0
|
||||
if version_number >= (2, 0):
|
||||
self.send_error(505,
|
||||
"Invalid HTTP Version (%s)" % base_version_number)
|
||||
return False
|
||||
elif len(words) == 2:
|
||||
[command, path] = words
|
||||
self.close_connection = 1
|
||||
if command != 'GET':
|
||||
self.send_error(400,
|
||||
"Bad HTTP/0.9 request type (%r)" % command)
|
||||
return False
|
||||
elif not words:
|
||||
return False
|
||||
else:
|
||||
self.send_error(400, "Bad request syntax (%r)" % requestline)
|
||||
return False
|
||||
self.request_version = version
|
||||
self.command, self.path, self.version = command, path, version
|
||||
return True
|
||||
|
||||
def handle_one_request(self):
|
||||
"""Handle a single HTTP request.
|
||||
Dispatch to the correct handler.
|
||||
"""
|
||||
self.request.setblocking(True)
|
||||
self.raw_requestline = self.rfile.readline()
|
||||
if not self.raw_requestline:
|
||||
self.close_connection = 1
|
||||
# self.log_message("no requestline, connection closed?")
|
||||
return
|
||||
if not self.parse_rawline():
|
||||
self.log_message("Could not parse rawline.")
|
||||
return
|
||||
# self.parse_request(): # Do NOT parse here. the first line should be the only
|
||||
for vdir in self.server.vdirs:
|
||||
p = vdir.matches(self.path)
|
||||
if p == False:
|
||||
continue
|
||||
npath = self.path[len(p):]
|
||||
if not npath.startswith('/'):
|
||||
npath = '/' + npath
|
||||
|
||||
if not self.in_handlers.has_key(p):
|
||||
self.in_handlers[p] = vdir.handler(noconnection(),self.client_address,self.server)
|
||||
if vdir.auth_provider:
|
||||
vdir.auth_provider.setupAuth(self, self.in_handlers[p])
|
||||
hnd = self.in_handlers[p]
|
||||
hnd.rfile = self.rfile
|
||||
hnd.wfile = self.wfile
|
||||
self.rlpath = self.raw_requestline
|
||||
self._handle_one_foreign(hnd,npath, vdir.auth_provider)
|
||||
# print "Handled, closing = ", self.close_connection
|
||||
return
|
||||
# if no match:
|
||||
self.send_error(404, "Path not found: %s" % self.path)
|
||||
return
|
||||
|
||||
|
||||
class SecureMultiHTTPHandler(MultiHTTPHandler):
|
||||
def setup(self):
|
||||
import ssl
|
||||
self.connection = ssl.wrap_socket(self.request,
|
||||
server_side=True,
|
||||
certfile="server.cert",
|
||||
keyfile="server.key",
|
||||
ssl_version=ssl.PROTOCOL_SSLv23)
|
||||
self.rfile = self.connection.makefile('rb', self.rbufsize)
|
||||
self.wfile = self.connection.makefile('wb', self.wbufsize)
|
||||
self.log_message("Secure %s connection from %s",self.connection.cipher(),self.client_address)
|
||||
|
||||
import threading
|
||||
class ConnThreadingMixIn:
|
||||
"""Mix-in class to handle each _connection_ in a new thread.
|
||||
|
||||
This is necessary for persistent connections, where multiple
|
||||
requests should be handled synchronously at each connection, but
|
||||
multiple connections can run in parallel.
|
||||
"""
|
||||
|
||||
# Decides how threads will act upon termination of the
|
||||
# main process
|
||||
daemon_threads = False
|
||||
|
||||
def _handle_request_noblock(self):
|
||||
"""Start a new thread to process the request."""
|
||||
t = threading.Thread(target = self._handle_request2)
|
||||
print "request came, handling in new thread",t
|
||||
if self.daemon_threads:
|
||||
t.setDaemon (1)
|
||||
t.start()
|
||||
|
||||
def _handle_request2(self):
|
||||
"""Handle one request, without blocking.
|
||||
|
||||
I assume that select.select has returned that the socket is
|
||||
readable before this function was called, so there should be
|
||||
no risk of blocking in get_request().
|
||||
"""
|
||||
try:
|
||||
request, client_address = self.get_request()
|
||||
except socket.error:
|
||||
return
|
||||
if self.verify_request(request, client_address):
|
||||
try:
|
||||
self.process_request(request, client_address)
|
||||
except:
|
||||
self.handle_error(request, client_address)
|
||||
self.close_request(request)
|
||||
|
||||
#eof
|
|
@ -473,8 +473,8 @@ def trans_generate(lang, modules, dbname=None):
|
|||
push_translation(module, 'view', encode(obj.model), 0, t)
|
||||
elif model=='ir.actions.wizard':
|
||||
service_name = 'wizard.'+encode(obj.wiz_name)
|
||||
if netsvc.SERVICES.get(service_name):
|
||||
obj2 = netsvc.SERVICES[service_name]
|
||||
if netsvc.Service._services.get(service_name):
|
||||
obj2 = netsvc.Service._services[service_name]
|
||||
for state_name, state_def in obj2.states.iteritems():
|
||||
if 'result' in state_def:
|
||||
result = state_def['result']
|
||||
|
|
|
@ -44,7 +44,7 @@ class interface(netsvc.Service):
|
|||
states = {}
|
||||
|
||||
def __init__(self, name):
|
||||
assert not netsvc.service_exist('wizard.'+name), 'The wizard "%s" already exists!'%name
|
||||
assert not self.service_exist('wizard.'+name), 'The wizard "%s" already exists!'%name
|
||||
super(interface, self).__init__('wizard.'+name)
|
||||
self.exportMethod(self.execute)
|
||||
self.wiz_name = name
|
||||
|
|
Loading…
Reference in New Issue