[MERGE] /openerp/6.1 xmlrpc compliant
bzr revid: al@openerp.com-20110928220539-5q8eujk1l0q43d4x
This commit is contained in:
commit
c71979e6ae
|
@ -35,6 +35,7 @@ from osv import fields,osv
|
|||
from osv.orm import browse_record
|
||||
from service import security
|
||||
from tools.translate import _
|
||||
import openerp.exceptions
|
||||
|
||||
class groups(osv.osv):
|
||||
_name = "res.groups"
|
||||
|
@ -437,14 +438,14 @@ class users(osv.osv):
|
|||
if passwd == tools.config['admin_passwd']:
|
||||
return True
|
||||
else:
|
||||
raise security.ExceptionNoTb('AccessDenied')
|
||||
raise openerp.exceptions.AccessDenied()
|
||||
|
||||
def check(self, db, uid, passwd):
|
||||
"""Verifies that the given (uid, password) pair is authorized for the database ``db`` and
|
||||
raise an exception if it is not."""
|
||||
if not passwd:
|
||||
# empty passwords disallowed for obvious security reasons
|
||||
raise security.ExceptionNoTb('AccessDenied')
|
||||
raise openerp.exceptions.AccessDenied()
|
||||
if self._uid_cache.get(db, {}).get(uid) == passwd:
|
||||
return
|
||||
cr = pooler.get_db(db).cursor()
|
||||
|
@ -453,7 +454,7 @@ class users(osv.osv):
|
|||
(int(uid), passwd, True))
|
||||
res = cr.fetchone()[0]
|
||||
if not res:
|
||||
raise security.ExceptionNoTb('AccessDenied')
|
||||
raise openerp.exceptions.AccessDenied()
|
||||
if self._uid_cache.has_key(db):
|
||||
ulist = self._uid_cache[db]
|
||||
ulist[uid] = passwd
|
||||
|
@ -470,7 +471,7 @@ class users(osv.osv):
|
|||
cr.execute('SELECT id FROM res_users WHERE id=%s AND password=%s', (uid, passwd))
|
||||
res = cr.fetchone()
|
||||
if not res:
|
||||
raise security.ExceptionNoTb('Bad username or password')
|
||||
raise openerp.exceptions.AccessDenied()
|
||||
return res[0]
|
||||
finally:
|
||||
cr.close()
|
||||
|
@ -481,7 +482,7 @@ class users(osv.osv):
|
|||
password is not used to authenticate requests.
|
||||
|
||||
:return: True
|
||||
:raise: security.ExceptionNoTb when old password is wrong
|
||||
:raise: openerp.exceptions.AccessDenied when old password is wrong
|
||||
:raise: except_osv when new password is not set or empty
|
||||
"""
|
||||
self.check(cr.dbname, uid, old_passwd)
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2011 OpenERP s.a. (<http://openerp.com>).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
""" OpenERP core exceptions.
|
||||
|
||||
This module defines a few exception types. Those types are understood by the
|
||||
RPC layer. Any other exception type bubbling until the RPC layer will be
|
||||
treated as a 'Server error'.
|
||||
|
||||
"""
|
||||
|
||||
class Warning(Exception):
|
||||
pass
|
||||
|
||||
class AccessDenied(Exception):
|
||||
""" Login/password error. No message, no traceback. """
|
||||
def __init__(self):
|
||||
super(AccessDenied, self).__init__('AccessDenied.')
|
||||
self.traceback = ('', '', '')
|
||||
|
||||
class AccessError(Exception):
|
||||
""" Access rights error. """
|
||||
|
||||
class DeferredException(Exception):
|
||||
""" Exception object holding a traceback for asynchronous reporting.
|
||||
|
||||
Some RPC calls (database creation and report generation) happen with
|
||||
an initial request followed by multiple, polling requests. This class
|
||||
is used to store the possible exception occuring in the thread serving
|
||||
the first request, and is then sent to a polling request.
|
||||
|
||||
('Traceback' is misleading, this is really a exc_info() triple.)
|
||||
"""
|
||||
def __init__(self, msg, tb):
|
||||
self.message = msg
|
||||
self.traceback = tb
|
||||
self.args = (msg, tb)
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
@ -37,6 +37,7 @@ from pprint import pformat
|
|||
# TODO modules that import netsvc only for things from loglevels must be changed to use loglevels.
|
||||
from loglevels import *
|
||||
import tools
|
||||
import openerp.exceptions
|
||||
|
||||
def close_socket(sock):
|
||||
""" Closes a socket instance cleanly
|
||||
|
@ -60,11 +61,12 @@ def close_socket(sock):
|
|||
#.apidoc title: Common Services: netsvc
|
||||
#.apidoc module-mods: member-order: bysource
|
||||
|
||||
def abort_response(error, description, origin, details):
|
||||
if not tools.config['debug_mode']:
|
||||
raise Exception("%s -- %s\n\n%s"%(origin, description, details))
|
||||
def abort_response(dummy_1, description, dummy_2, details):
|
||||
# TODO Replace except_{osv,orm} with these directly.
|
||||
if description == 'AccessError':
|
||||
raise openerp.exceptions.AccessError(details)
|
||||
else:
|
||||
raise
|
||||
raise openerp.exceptions.Warning(details)
|
||||
|
||||
class Service(object):
|
||||
""" Base class for *Local* services
|
||||
|
@ -96,12 +98,9 @@ def LocalService(name):
|
|||
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)
|
||||
dispatch(method, params)
|
||||
"""
|
||||
|
||||
_services = {}
|
||||
|
@ -118,7 +117,7 @@ class ExportService(object):
|
|||
|
||||
# Dispatch a RPC call w.r.t. the method name. The dispatching
|
||||
# w.r.t. the service (this class) is done by OpenERPDispatcher.
|
||||
def dispatch(self, method, auth, params):
|
||||
def dispatch(self, method, params):
|
||||
raise Exception("stub dispatch at %s" % self.__name)
|
||||
|
||||
BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE, _NOTHING, DEFAULT = range(10)
|
||||
|
@ -371,11 +370,6 @@ class Server:
|
|||
def _close_socket(self):
|
||||
close_socket(self.socket)
|
||||
|
||||
class OpenERPDispatcherException(Exception):
|
||||
def __init__(self, exception, traceback):
|
||||
self.exception = exception
|
||||
self.traceback = traceback
|
||||
|
||||
def replace_request_password(args):
|
||||
# password is always 3rd argument in a request, we replace it in RPC logs
|
||||
# so it's easier to forward logs for diagnostics/debugging purposes...
|
||||
|
@ -393,7 +387,7 @@ def log(title, msg, channel=logging.DEBUG_RPC, depth=None, fn=""):
|
|||
logger.log(channel, indent+line)
|
||||
indent=indent_after
|
||||
|
||||
def dispatch_rpc(service_name, method, params, auth):
|
||||
def dispatch_rpc(service_name, method, params):
|
||||
""" Handle a RPC call.
|
||||
|
||||
This is pure Python code, the actual marshalling (from/to XML-RPC or
|
||||
|
@ -408,7 +402,7 @@ def dispatch_rpc(service_name, method, params, auth):
|
|||
_log('service', tuple(replace_request_password(params)), depth=None, fn='%s.%s'%(service_name,method))
|
||||
if logger.isEnabledFor(logging.DEBUG_RPC):
|
||||
start_time = time.time()
|
||||
result = ExportService.getService(service_name).dispatch(method, auth, params)
|
||||
result = ExportService.getService(service_name).dispatch(method, params)
|
||||
if logger.isEnabledFor(logging.DEBUG_RPC):
|
||||
end_time = time.time()
|
||||
if not logger.isEnabledFor(logging.DEBUG_RPC_ANSWER):
|
||||
|
@ -416,13 +410,24 @@ def dispatch_rpc(service_name, method, params, auth):
|
|||
_log('execution time', '%.3fs' % (end_time - start_time), channel=logging.DEBUG_RPC_ANSWER)
|
||||
_log('result', result, channel=logging.DEBUG_RPC_ANSWER)
|
||||
return result
|
||||
except openerp.exceptions.AccessError:
|
||||
raise
|
||||
except openerp.exceptions.AccessDenied:
|
||||
raise
|
||||
except openerp.exceptions.Warning:
|
||||
raise
|
||||
except openerp.exceptions.DeferredException, e:
|
||||
_log('exception', tools.exception_to_unicode(e))
|
||||
post_mortem(e.traceback)
|
||||
raise
|
||||
except Exception, e:
|
||||
_log('exception', tools.exception_to_unicode(e))
|
||||
tb = getattr(e, 'traceback', sys.exc_info())
|
||||
tb_s = "".join(traceback.format_exception(*tb))
|
||||
if tools.config['debug_mode'] and isinstance(tb[2], types.TracebackType):
|
||||
import pdb
|
||||
pdb.post_mortem(tb[2])
|
||||
raise OpenERPDispatcherException(e, tb_s)
|
||||
post_mortem(sys.exc_info())
|
||||
raise
|
||||
|
||||
def post_mortem(info):
|
||||
if tools.config['debug_mode'] and isinstance(info[2], types.TracebackType):
|
||||
import pdb
|
||||
pdb.post_mortem(info[2])
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
|
@ -32,13 +32,10 @@ import openerp.sql_db as sql_db
|
|||
from openerp.tools.func import wraps
|
||||
from openerp.tools.translate import translate
|
||||
from openerp.osv.orm import MetaModel, Model, TransientModel, AbstractModel
|
||||
import openerp.exceptions
|
||||
|
||||
class except_osv(Exception):
|
||||
def __init__(self, name, value, exc_type='warning'):
|
||||
self.name = name
|
||||
self.exc_type = exc_type
|
||||
self.value = value
|
||||
self.args = (exc_type, name)
|
||||
# For backward compatibility
|
||||
except_osv = openerp.exceptions.Warning
|
||||
|
||||
service = None
|
||||
|
||||
|
@ -122,7 +119,7 @@ class object_proxy():
|
|||
self.logger.debug("AccessError", exc_info=True)
|
||||
netsvc.abort_response(1, inst.name, 'warning', inst.value)
|
||||
except except_osv, inst:
|
||||
netsvc.abort_response(1, inst.name, inst.exc_type, inst.value)
|
||||
netsvc.abort_response(1, inst.name, 'warning', inst.value)
|
||||
except IntegrityError, inst:
|
||||
osv_pool = pooler.get_pool(dbname)
|
||||
for key in osv_pool._sql_error.keys():
|
||||
|
|
|
@ -57,7 +57,6 @@ def start_services():
|
|||
|
||||
# Initialize the HTTP stack.
|
||||
#http_server.init_servers()
|
||||
#http_server.init_xmlrpc()
|
||||
#http_server.init_static_http()
|
||||
netrpc_server.init_servers()
|
||||
|
||||
|
@ -67,7 +66,6 @@ def start_services():
|
|||
# Start the top-level servers threads (normally HTTP, HTTPS, and NETRPC).
|
||||
openerp.netsvc.Server.startAll()
|
||||
|
||||
|
||||
# Start the WSGI server.
|
||||
openerp.wsgi.start_server()
|
||||
|
||||
|
|
|
@ -64,53 +64,6 @@ try:
|
|||
except ImportError:
|
||||
class SSLError(Exception): pass
|
||||
|
||||
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, proto='http',
|
||||
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)
|
||||
|
||||
self.numThreads = 0
|
||||
self.proto = proto
|
||||
self.__threadno = 0
|
||||
|
||||
# [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
|
||||
"""
|
||||
|
||||
logging.getLogger("init").exception("Server error in request from %s:" % (client_address,))
|
||||
|
||||
def _mark_start(self, thread):
|
||||
self.numThreads += 1
|
||||
|
||||
def _mark_end(self, thread):
|
||||
self.numThreads -= 1
|
||||
|
||||
|
||||
def _get_next_name(self):
|
||||
self.__threadno += 1
|
||||
return 'http-client-%d' % self.__threadno
|
||||
class HttpLogHandler:
|
||||
""" helper class for uniform log handling
|
||||
Please define self._logger at each class that is derived from this
|
||||
|
@ -129,136 +82,6 @@ class HttpLogHandler:
|
|||
def log_request(self, code='-', size='-'):
|
||||
self._logger.log(netsvc.logging.DEBUG_RPC, '"%s" %s %s',
|
||||
self.requestline, str(code), str(size))
|
||||
|
||||
class MultiHandler2(HttpLogHandler, MultiHTTPHandler):
|
||||
_logger = logging.getLogger('http')
|
||||
|
||||
|
||||
class SecureMultiHandler2(HttpLogHandler, SecureMultiHTTPHandler):
|
||||
_logger = logging.getLogger('https')
|
||||
|
||||
def getcert_fnames(self):
|
||||
tc = tools.config
|
||||
fcert = tc.get('secure_cert_file', 'server.cert')
|
||||
fkey = tc.get('secure_pkey_file', 'server.key')
|
||||
return (fcert,fkey)
|
||||
|
||||
class BaseHttpDaemon(threading.Thread, netsvc.Server):
|
||||
_RealProto = '??'
|
||||
|
||||
def __init__(self, interface, port, handler):
|
||||
threading.Thread.__init__(self, name='%sDaemon-%d'%(self._RealProto, port))
|
||||
netsvc.Server.__init__(self)
|
||||
self.__port = port
|
||||
self.__interface = interface
|
||||
|
||||
try:
|
||||
self.server = ThreadedHTTPServer((interface, port), handler, proto=self._RealProto)
|
||||
self.server.logRequests = True
|
||||
self.server.timeout = self._busywait_timeout
|
||||
logging.getLogger("web-services").info(
|
||||
"starting %s service at %s port %d" %
|
||||
(self._RealProto, interface or '0.0.0.0', port,))
|
||||
except Exception, e:
|
||||
logging.getLogger("httpd").exception("Error occured when starting the server daemon.")
|
||||
raise
|
||||
|
||||
@property
|
||||
def socket(self):
|
||||
return self.server.socket
|
||||
|
||||
def attach(self, path, gw):
|
||||
pass
|
||||
|
||||
def stop(self):
|
||||
self.running = False
|
||||
self._close_socket()
|
||||
|
||||
def run(self):
|
||||
self.running = True
|
||||
while self.running:
|
||||
try:
|
||||
self.server.handle_request()
|
||||
except (socket.error, select.error), e:
|
||||
if self.running or e.args[0] != errno.EBADF:
|
||||
raise
|
||||
return True
|
||||
|
||||
def stats(self):
|
||||
res = "%sd: " % self._RealProto + ((self.running and "running") or "stopped")
|
||||
if self.server:
|
||||
res += ", %d threads" % (self.server.numThreads,)
|
||||
return res
|
||||
|
||||
# No need for these two classes: init_server() below can initialize correctly
|
||||
# directly the BaseHttpDaemon class.
|
||||
class HttpDaemon(BaseHttpDaemon):
|
||||
_RealProto = 'HTTP'
|
||||
def __init__(self, interface, port):
|
||||
super(HttpDaemon, self).__init__(interface, port,
|
||||
handler=MultiHandler2)
|
||||
|
||||
class HttpSDaemon(BaseHttpDaemon):
|
||||
_RealProto = 'HTTPS'
|
||||
def __init__(self, interface, port):
|
||||
try:
|
||||
super(HttpSDaemon, self).__init__(interface, port,
|
||||
handler=SecureMultiHandler2)
|
||||
except SSLError, e:
|
||||
logging.getLogger('httpsd').exception( \
|
||||
"Can not load the certificate and/or the private key files")
|
||||
raise
|
||||
|
||||
httpd = None
|
||||
httpsd = None
|
||||
|
||||
def init_servers():
|
||||
global httpd, httpsd
|
||||
if tools.config.get('xmlrpc'):
|
||||
httpd = HttpDaemon(tools.config.get('xmlrpc_interface', ''),
|
||||
int(tools.config.get('xmlrpc_port', 8069)))
|
||||
|
||||
if tools.config.get('xmlrpcs'):
|
||||
httpsd = HttpSDaemon(tools.config.get('xmlrpcs_interface', ''),
|
||||
int(tools.config.get('xmlrpcs_port', 8071)))
|
||||
|
||||
import SimpleXMLRPCServer
|
||||
class XMLRPCRequestHandler(FixSendError,HttpLogHandler,SimpleXMLRPCServer.SimpleXMLRPCRequestHandler):
|
||||
rpc_paths = []
|
||||
protocol_version = 'HTTP/1.1'
|
||||
_logger = logging.getLogger('xmlrpc')
|
||||
|
||||
def _dispatch(self, method, params):
|
||||
try:
|
||||
service_name = self.path.split("/")[-1]
|
||||
auth = getattr(self, 'auth_provider', None)
|
||||
return netsvc.dispatch_rpc(service_name, method, params, auth)
|
||||
except netsvc.OpenERPDispatcherException, e:
|
||||
raise xmlrpclib.Fault(tools.exception_to_unicode(e.exception), e.traceback)
|
||||
|
||||
def handle(self):
|
||||
pass
|
||||
|
||||
def finish(self):
|
||||
pass
|
||||
|
||||
def setup(self):
|
||||
self.connection = dummyconn()
|
||||
self.rpc_paths = map(lambda s: '/%s' % s, netsvc.ExportService._services.keys())
|
||||
|
||||
|
||||
def init_xmlrpc():
|
||||
if tools.config.get('xmlrpc', False):
|
||||
# Example of http file serving:
|
||||
# reg_http_service('/test/', HTTPHandler)
|
||||
reg_http_service('/xmlrpc/', XMLRPCRequestHandler)
|
||||
logging.getLogger("web-services").info("Registered XML-RPC over HTTP")
|
||||
|
||||
if tools.config.get('xmlrpcs', False) \
|
||||
and not tools.config.get('xmlrpc', False):
|
||||
# only register at the secure server
|
||||
reg_http_service('/xmlrpc/', XMLRPCRequestHandler, secure_only=True)
|
||||
logging.getLogger("web-services").info("Registered XML-RPC over HTTPS only")
|
||||
|
||||
class StaticHTTPHandler(HttpLogHandler, FixSendError, HttpOptions, HTTPHandler):
|
||||
_logger = logging.getLogger('httpd')
|
||||
|
|
|
@ -65,21 +65,13 @@ class TinySocketClientThread(threading.Thread):
|
|||
except socket.timeout:
|
||||
#terminate this channel because other endpoint is gone
|
||||
break
|
||||
except netsvc.OpenERPDispatcherException, e:
|
||||
try:
|
||||
new_e = Exception(tools.exception_to_unicode(e.exception)) # avoid problems of pickeling
|
||||
logging.getLogger('web-services').debug("netrpc: rpc-dispatching exception", exc_info=True)
|
||||
ts.mysend(new_e, exception=True, traceback=e.traceback)
|
||||
except Exception:
|
||||
#terminate this channel if we can't properly send back the error
|
||||
logging.getLogger('web-services').exception("netrpc: cannot deliver exception message to client")
|
||||
break
|
||||
except Exception, e:
|
||||
try:
|
||||
new_e = Exception(tools.exception_to_unicode(e)) # avoid problems of pickeling
|
||||
tb = getattr(e, 'traceback', sys.exc_info())
|
||||
tb_s = "".join(traceback.format_exception(*tb))
|
||||
logging.getLogger('web-services').debug("netrpc: communication-level exception", exc_info=True)
|
||||
ts.mysend(e, exception=True, traceback=tb_s)
|
||||
ts.mysend(new_e, exception=True, traceback=tb_s)
|
||||
break
|
||||
except Exception, ex:
|
||||
#terminate this channel if we can't properly send back the error
|
||||
|
|
|
@ -19,18 +19,12 @@
|
|||
#
|
||||
##############################################################################
|
||||
|
||||
import openerp.exceptions
|
||||
import openerp.pooler as pooler
|
||||
import openerp.tools as tools
|
||||
|
||||
#.apidoc title: Authentication helpers
|
||||
|
||||
class ExceptionNoTb(Exception):
|
||||
""" When rejecting a password, hide the traceback
|
||||
"""
|
||||
def __init__(self, msg):
|
||||
super(ExceptionNoTb, self).__init__(msg)
|
||||
self.traceback = ('','','')
|
||||
|
||||
def login(db, login, password):
|
||||
pool = pooler.get_pool(db)
|
||||
user_obj = pool.get('res.users')
|
||||
|
@ -40,7 +34,7 @@ def check_super(passwd):
|
|||
if passwd == tools.config['admin_passwd']:
|
||||
return True
|
||||
else:
|
||||
raise ExceptionNoTb('AccessDenied: Invalid super administrator password.')
|
||||
raise openerp.exceptions.AccessDenied()
|
||||
|
||||
def check(db, uid, passwd):
|
||||
pool = pooler.get_pool(db)
|
||||
|
|
|
@ -38,6 +38,7 @@ import openerp.release as release
|
|||
import openerp.sql_db as sql_db
|
||||
import openerp.tools as tools
|
||||
import openerp.modules
|
||||
import openerp.exceptions
|
||||
|
||||
#.apidoc title: Exported Service methods
|
||||
#.apidoc module-mods: member-order: bysource
|
||||
|
@ -93,7 +94,7 @@ class db(netsvc.ExportService):
|
|||
|
||||
self._pg_psw_env_var_is_set = False # on win32, pg_dump need the PGPASSWORD env var
|
||||
|
||||
def dispatch(self, method, auth, params):
|
||||
def dispatch(self, method, params):
|
||||
if method in [ 'create', 'get_progress', 'drop', 'dump',
|
||||
'restore', 'rename',
|
||||
'change_admin_password', 'migrate_databases',
|
||||
|
@ -161,7 +162,7 @@ class db(netsvc.ExportService):
|
|||
self.actions.pop(id)
|
||||
return (1.0, users)
|
||||
else:
|
||||
e = self.actions[id]['exception']
|
||||
e = self.actions[id]['exception'] # TODO this seems wrong: actions[id]['traceback'] is set, but not 'exception'.
|
||||
self.actions.pop(id)
|
||||
raise Exception, e
|
||||
|
||||
|
@ -302,7 +303,7 @@ class db(netsvc.ExportService):
|
|||
|
||||
def exp_list(self, document=False):
|
||||
if not tools.config['list_db'] and not document:
|
||||
raise Exception('AccessDenied')
|
||||
raise openerp.exceptions.AccessDenied()
|
||||
|
||||
db = sql_db.db_connect('template1')
|
||||
cr = db.cursor()
|
||||
|
@ -356,7 +357,7 @@ class db(netsvc.ExportService):
|
|||
except except_orm, inst:
|
||||
netsvc.abort_response(1, inst.name, 'warning', inst.value)
|
||||
except except_osv, inst:
|
||||
netsvc.abort_response(1, inst.name, inst.exc_type, inst.value)
|
||||
netsvc.abort_response(1, inst.name, 'warning', inst.value)
|
||||
except Exception:
|
||||
import traceback
|
||||
tb_s = reduce(lambda x, y: x+y, traceback.format_exception( sys.exc_type, sys.exc_value, sys.exc_traceback))
|
||||
|
@ -368,20 +369,14 @@ class common(netsvc.ExportService):
|
|||
def __init__(self,name="common"):
|
||||
netsvc.ExportService.__init__(self,name)
|
||||
|
||||
def dispatch(self, method, auth, params):
|
||||
def dispatch(self, method, params):
|
||||
logger = netsvc.Logger()
|
||||
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]) # TODO I didn't see any AuthProxy implementing this method.
|
||||
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','get_stats', 'check_connectivity',
|
||||
'list_http_services']:
|
||||
|
@ -562,7 +557,7 @@ class objects_proxy(netsvc.ExportService):
|
|||
def __init__(self, name="object"):
|
||||
netsvc.ExportService.__init__(self,name)
|
||||
|
||||
def dispatch(self, method, auth, params):
|
||||
def dispatch(self, method, params):
|
||||
(db, uid, passwd ) = params[0:3]
|
||||
params = params[3:]
|
||||
if method == 'obj_list':
|
||||
|
@ -595,7 +590,7 @@ class wizard(netsvc.ExportService):
|
|||
self.wiz_name = {}
|
||||
self.wiz_uid = {}
|
||||
|
||||
def dispatch(self, method, auth, params):
|
||||
def dispatch(self, method, params):
|
||||
(db, uid, passwd ) = params[0:3]
|
||||
params = params[3:]
|
||||
if method not in ['execute','create']:
|
||||
|
@ -628,9 +623,9 @@ class wizard(netsvc.ExportService):
|
|||
if self.wiz_uid[wiz_id] == uid:
|
||||
return self._execute(db, uid, wiz_id, datas, action, context)
|
||||
else:
|
||||
raise Exception, 'AccessDenied'
|
||||
raise openerp.exceptions.AccessDenied()
|
||||
else:
|
||||
raise Exception, 'WizardNotFound'
|
||||
raise openerp.exceptions.Warning('Wizard not found.')
|
||||
|
||||
#
|
||||
# TODO: set a maximum report number per user to avoid DOS attacks
|
||||
|
@ -639,12 +634,6 @@ class wizard(netsvc.ExportService):
|
|||
# False -> True
|
||||
#
|
||||
|
||||
class ExceptionWithTraceback(Exception):
|
||||
def __init__(self, msg, tb):
|
||||
self.message = msg
|
||||
self.traceback = tb
|
||||
self.args = (msg, tb)
|
||||
|
||||
class report_spool(netsvc.ExportService):
|
||||
def __init__(self, name='report'):
|
||||
netsvc.ExportService.__init__(self, name)
|
||||
|
@ -652,7 +641,7 @@ class report_spool(netsvc.ExportService):
|
|||
self.id = 0
|
||||
self.id_protect = threading.Semaphore()
|
||||
|
||||
def dispatch(self, method, auth, params):
|
||||
def dispatch(self, method, params):
|
||||
(db, uid, passwd ) = params[0:3]
|
||||
params = params[3:]
|
||||
if method not in ['report', 'report_get', 'render_report']:
|
||||
|
@ -683,7 +672,7 @@ class report_spool(netsvc.ExportService):
|
|||
(result, format) = obj.create(cr, uid, ids, datas, context)
|
||||
if not result:
|
||||
tb = sys.exc_info()
|
||||
self._reports[id]['exception'] = ExceptionWithTraceback('RML is not available at specified location or not enough data to print!', tb)
|
||||
self._reports[id]['exception'] = openerp.exceptions.DeferredException('RML is not available at specified location or not enough data to print!', tb)
|
||||
self._reports[id]['result'] = result
|
||||
self._reports[id]['format'] = format
|
||||
self._reports[id]['state'] = True
|
||||
|
@ -695,9 +684,9 @@ class report_spool(netsvc.ExportService):
|
|||
logger.notifyChannel('web-services', netsvc.LOG_ERROR,
|
||||
'Exception: %s\n%s' % (str(exception), tb_s))
|
||||
if hasattr(exception, 'name') and hasattr(exception, 'value'):
|
||||
self._reports[id]['exception'] = ExceptionWithTraceback(tools.ustr(exception.name), tools.ustr(exception.value))
|
||||
self._reports[id]['exception'] = openerp.exceptions.DeferredException(tools.ustr(exception.name), tools.ustr(exception.value))
|
||||
else:
|
||||
self._reports[id]['exception'] = ExceptionWithTraceback(tools.exception_to_unicode(exception), tb)
|
||||
self._reports[id]['exception'] = openerp.exceptions.DeferredException(tools.exception_to_unicode(exception), tb)
|
||||
self._reports[id]['state'] = True
|
||||
cr.commit()
|
||||
cr.close()
|
||||
|
@ -726,7 +715,7 @@ class report_spool(netsvc.ExportService):
|
|||
(result, format) = obj.create(cr, uid, ids, datas, context)
|
||||
if not result:
|
||||
tb = sys.exc_info()
|
||||
self._reports[id]['exception'] = ExceptionWithTraceback('RML is not available at specified location or not enough data to print!', tb)
|
||||
self._reports[id]['exception'] = openerp.exceptions.DeferredException('RML is not available at specified location or not enough data to print!', tb)
|
||||
self._reports[id]['result'] = result
|
||||
self._reports[id]['format'] = format
|
||||
self._reports[id]['state'] = True
|
||||
|
@ -738,9 +727,9 @@ class report_spool(netsvc.ExportService):
|
|||
logger.notifyChannel('web-services', netsvc.LOG_ERROR,
|
||||
'Exception: %s\n%s' % (str(exception), tb_s))
|
||||
if hasattr(exception, 'name') and hasattr(exception, 'value'):
|
||||
self._reports[id]['exception'] = ExceptionWithTraceback(tools.ustr(exception.name), tools.ustr(exception.value))
|
||||
self._reports[id]['exception'] = openerp.exceptions.DeferredException(tools.ustr(exception.name), tools.ustr(exception.value))
|
||||
else:
|
||||
self._reports[id]['exception'] = ExceptionWithTraceback(tools.exception_to_unicode(exception), tb)
|
||||
self._reports[id]['exception'] = openerp.exceptions.DeferredException(tools.exception_to_unicode(exception), tb)
|
||||
self._reports[id]['state'] = True
|
||||
cr.commit()
|
||||
cr.close()
|
||||
|
|
|
@ -232,305 +232,3 @@ class HttpOptions:
|
|||
"""
|
||||
return opts
|
||||
|
||||
class MultiHTTPHandler(FixSendError, HttpOptions, 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"
|
||||
default_request_version = "HTTP/0.9" # compatibility with py2.5
|
||||
|
||||
auth_required_msg = """ <html><head><title>Authorization required</title></head>
|
||||
<body>You must authenticate to use this service</body><html>\r\r"""
|
||||
|
||||
def __init__(self, request, client_address, server):
|
||||
self.in_handlers = {}
|
||||
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):
|
||||
""" 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
|
||||
if fore.headers.status:
|
||||
self.log_error("Parse error at headers: %s", fore.headers.status)
|
||||
self.close_connection = 1
|
||||
self.send_error(400,"Parse error at HTTP headers")
|
||||
return
|
||||
|
||||
self.request_version = fore.request_version
|
||||
if hasattr(fore, 'auth_provider'):
|
||||
try:
|
||||
fore.auth_provider.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 self.request_version != 'HTTP/1.1' and ('Darwin/9.' not in fore.headers.get('User-Agent', '')):
|
||||
self.log_error("Cannot require auth at %s", self.request_version)
|
||||
self.send_error(403)
|
||||
return
|
||||
self._get_ignore_body(fore) # consume any body that came, not loose sync with input
|
||||
self.send_response(401,'Authorization required')
|
||||
self.send_header('WWW-Authenticate','%s realm="%s"' % (ae.atype,ae.realm))
|
||||
self.send_header('Connection', 'keep-alive')
|
||||
self.send_header('Content-Type','text/html')
|
||||
self.send_header('Content-Length',len(self.auth_required_msg))
|
||||
self.end_headers()
|
||||
self.wfile.write(self.auth_required_msg)
|
||||
return
|
||||
except AuthRejectedExc,e:
|
||||
self.log_error("Rejected auth: %s" % e.args[0])
|
||||
self.send_error(403,e.args[0])
|
||||
self.close_connection = 1
|
||||
return
|
||||
mname = 'do_' + fore.command
|
||||
if not hasattr(fore, mname):
|
||||
if fore.command == 'OPTIONS':
|
||||
self.do_OPTIONS()
|
||||
return
|
||||
self.send_error(501, "Unsupported method (%r)" % fore.command)
|
||||
return
|
||||
fore.close_connection = 0
|
||||
method = getattr(fore, mname)
|
||||
try:
|
||||
method()
|
||||
except (AuthRejectedExc, AuthRequiredExc):
|
||||
raise
|
||||
except Exception, e:
|
||||
if hasattr(self, 'log_exception'):
|
||||
self.log_exception("Could not run %s", mname)
|
||||
else:
|
||||
self.log_error("Could not run %s: %s", mname, e)
|
||||
self.send_error(500, "Internal error")
|
||||
# may not work if method has already sent data
|
||||
fore.close_connection = 1
|
||||
self.close_connection = 1
|
||||
if hasattr(fore, '_flush'):
|
||||
fore._flush()
|
||||
return
|
||||
|
||||
if fore.close_connection:
|
||||
# print "Closing connection because of handler"
|
||||
self.close_connection = fore.close_connection
|
||||
if hasattr(fore, '_flush'):
|
||||
fore._flush()
|
||||
|
||||
|
||||
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.log_error("Junk http request: %s", self.raw_requestline)
|
||||
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
|
||||
|
||||
if self.path == '*' and self.command == 'OPTIONS':
|
||||
# special handling of path='*', must not use any vdir at all.
|
||||
if not self.parse_request():
|
||||
return
|
||||
self.do_OPTIONS()
|
||||
return
|
||||
vdir = find_http_service(self.path, self.server.proto == 'HTTPS')
|
||||
if vdir:
|
||||
p = vdir.path
|
||||
npath = self.path[len(p):]
|
||||
if not npath.startswith('/'):
|
||||
npath = '/' + npath
|
||||
|
||||
if not self.in_handlers.has_key(p):
|
||||
self.in_handlers[p] = vdir.instanciate_handler(noconnection(self.request),self.client_address,self.server)
|
||||
hnd = self.in_handlers[p]
|
||||
hnd.rfile = self.rfile
|
||||
hnd.wfile = self.wfile
|
||||
self.rlpath = self.raw_requestline
|
||||
try:
|
||||
self._handle_one_foreign(hnd, npath)
|
||||
except IOError, e:
|
||||
if e.errno == errno.EPIPE:
|
||||
self.log_message("Could not complete request %s," \
|
||||
"client closed connection", self.rlpath.rstrip())
|
||||
else:
|
||||
raise
|
||||
else: # no match:
|
||||
self.send_error(404, "Path not found: %s" % self.path)
|
||||
|
||||
def _get_ignore_body(self,fore):
|
||||
if not fore.headers.has_key("content-length"):
|
||||
return
|
||||
max_chunk_size = 10*1024*1024
|
||||
size_remaining = int(fore.headers["content-length"])
|
||||
got = ''
|
||||
while size_remaining:
|
||||
chunk_size = min(size_remaining, max_chunk_size)
|
||||
got = fore.rfile.read(chunk_size)
|
||||
size_remaining -= len(got)
|
||||
|
||||
|
||||
class SecureMultiHTTPHandler(MultiHTTPHandler):
|
||||
def getcert_fnames(self):
|
||||
""" Return a pair with the filenames of ssl cert,key
|
||||
|
||||
Override this to direct to other filenames
|
||||
"""
|
||||
return ('server.cert','server.key')
|
||||
|
||||
def setup(self):
|
||||
import ssl
|
||||
certfile, keyfile = self.getcert_fnames()
|
||||
try:
|
||||
self.connection = ssl.wrap_socket(self.request,
|
||||
server_side=True,
|
||||
certfile=certfile,
|
||||
keyfile=keyfile,
|
||||
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)
|
||||
except Exception:
|
||||
self.request.shutdown(socket.SHUT_RDWR)
|
||||
raise
|
||||
|
||||
def finish(self):
|
||||
# With ssl connections, closing the filehandlers alone may not
|
||||
# work because of ref counting. We explicitly tell the socket
|
||||
# to shutdown.
|
||||
MultiHTTPHandler.finish(self)
|
||||
try:
|
||||
self.connection.shutdown(socket.SHUT_RDWR)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
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 _get_next_name(self):
|
||||
return None
|
||||
|
||||
def _handle_request_noblock(self):
|
||||
"""Start a new thread to process the request."""
|
||||
if not threading: # happens while quitting python
|
||||
return
|
||||
t = threading.Thread(name=self._get_next_name(), target=self._handle_request2)
|
||||
if self.daemon_threads:
|
||||
t.setDaemon (1)
|
||||
t.start()
|
||||
|
||||
def _mark_start(self, thread):
|
||||
""" Mark the start of a request thread """
|
||||
pass
|
||||
|
||||
def _mark_end(self, thread):
|
||||
""" Mark the end of a request thread """
|
||||
pass
|
||||
|
||||
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:
|
||||
self._mark_start(threading.currentThread())
|
||||
request, client_address = self.get_request()
|
||||
if self.verify_request(request, client_address):
|
||||
try:
|
||||
self.process_request(request, client_address)
|
||||
except Exception:
|
||||
self.handle_error(request, client_address)
|
||||
self.close_request(request)
|
||||
except socket.error:
|
||||
return
|
||||
finally:
|
||||
self._mark_end(threading.currentThread())
|
||||
|
||||
#eof
|
||||
|
|
|
@ -36,62 +36,98 @@ import signal
|
|||
import sys
|
||||
import threading
|
||||
import time
|
||||
import traceback
|
||||
|
||||
import openerp
|
||||
import openerp.modules
|
||||
import openerp.tools.config as config
|
||||
import service.websrv_lib as websrv_lib
|
||||
|
||||
# XML-RPC fault codes. Some care must be taken when changing these: the
|
||||
# constants are also defined client-side and must remain in sync.
|
||||
# User code must use the exceptions defined in ``openerp.exceptions`` (not
|
||||
# create directly ``xmlrpclib.Fault`` objects).
|
||||
XML_RPC_FAULT_CODE_APPLICATION_ERROR = 1
|
||||
XML_RPC_FAULT_CODE_DEFERRED_APPLICATION_ERROR = 2
|
||||
XML_RPC_FAULT_CODE_ACCESS_DENIED = 3
|
||||
XML_RPC_FAULT_CODE_ACCESS_ERROR = 4
|
||||
XML_RPC_FAULT_CODE_WARNING = 5
|
||||
|
||||
def xmlrpc_return(start_response, service, method, params):
|
||||
""" Helper to call a service's method with some params, using a
|
||||
wsgi-supplied ``start_response`` callback."""
|
||||
# This mimics SimpleXMLRPCDispatcher._marshaled_dispatch() for exception
|
||||
# handling.
|
||||
"""
|
||||
Helper to call a service's method with some params, using a wsgi-supplied
|
||||
``start_response`` callback.
|
||||
|
||||
This is the place to look at to see the mapping between core exceptions
|
||||
and XML-RPC fault codes.
|
||||
"""
|
||||
# Map OpenERP core exceptions to XML-RPC fault codes. Specific exceptions
|
||||
# defined in ``openerp.exceptions`` are mapped to specific fault codes;
|
||||
# all the other exceptions are mapped to the generic
|
||||
# XML_RPC_FAULT_CODE_APPLICATION_ERROR value.
|
||||
# This also mimics SimpleXMLRPCDispatcher._marshaled_dispatch() for
|
||||
# exception handling.
|
||||
try:
|
||||
result = openerp.netsvc.dispatch_rpc(service, method, params, None) # TODO auth
|
||||
result = openerp.netsvc.dispatch_rpc(service, method, params)
|
||||
response = xmlrpclib.dumps((result,), methodresponse=1, allow_none=False, encoding=None)
|
||||
except openerp.netsvc.OpenERPDispatcherException, e:
|
||||
fault = xmlrpclib.Fault(openerp.tools.exception_to_unicode(e.exception), e.traceback)
|
||||
except openerp.exceptions.Warning, e:
|
||||
fault = xmlrpclib.Fault(XML_RPC_FAULT_CODE_WARNING, str(e))
|
||||
response = xmlrpclib.dumps(fault, allow_none=False, encoding=None)
|
||||
except:
|
||||
exc_type, exc_value, exc_tb = sys.exc_info()
|
||||
fault = xmlrpclib.Fault(1, "%s:%s" % (exc_type, exc_value))
|
||||
except openerp.exceptions.AccessError, e:
|
||||
fault = xmlrpclib.Fault(XML_RPC_FAULT_CODE_ACCESS_ERROR, str(e))
|
||||
response = xmlrpclib.dumps(fault, allow_none=False, encoding=None)
|
||||
except openerp.exceptions.AccessDenied, e:
|
||||
fault = xmlrpclib.Fault(XML_RPC_FAULT_CODE_ACCESS_DENIED, str(e))
|
||||
response = xmlrpclib.dumps(fault, allow_none=False, encoding=None)
|
||||
except openerp.exceptions.DeferredException, e:
|
||||
info = e.traceback
|
||||
# Which one is the best ?
|
||||
formatted_info = "".join(traceback.format_exception(*info))
|
||||
#formatted_info = openerp.tools.exception_to_unicode(e) + '\n' + info
|
||||
fault = xmlrpclib.Fault(XML_RPC_FAULT_CODE_DEFERRED_APPLICATION_ERROR, formatted_info)
|
||||
response = xmlrpclib.dumps(fault, allow_none=False, encoding=None)
|
||||
except Exception, e:
|
||||
info = sys.exc_info()
|
||||
# Which one is the best ?
|
||||
formatted_info = "".join(traceback.format_exception(*info))
|
||||
#formatted_info = openerp.tools.exception_to_unicode(e) + '\n' + info
|
||||
fault = xmlrpclib.Fault(XML_RPC_FAULT_CODE_APPLICATION_ERROR, formatted_info)
|
||||
response = xmlrpclib.dumps(fault, allow_none=None, encoding=None)
|
||||
start_response("200 OK", [('Content-Type','text/xml'), ('Content-Length', str(len(response)))])
|
||||
return [response]
|
||||
|
||||
def wsgi_xmlrpc(environ, start_response):
|
||||
""" The main OpenERP WSGI handler."""
|
||||
if environ['REQUEST_METHOD'] == 'POST' and environ['PATH_INFO'].startswith('/openerp/xmlrpc'):
|
||||
if environ['REQUEST_METHOD'] == 'POST' and environ['PATH_INFO'].startswith('/openerp/6.1/xmlrpc'):
|
||||
length = int(environ['CONTENT_LENGTH'])
|
||||
data = environ['wsgi.input'].read(length)
|
||||
|
||||
params, method = xmlrpclib.loads(data)
|
||||
|
||||
path = environ['PATH_INFO'][len('/openerp/xmlrpc'):]
|
||||
path = environ['PATH_INFO'][len('/openerp/6.1/xmlrpc'):]
|
||||
if path.startswith('/'): path = path[1:]
|
||||
if path.endswith('/'): p = path[:-1]
|
||||
path = path.split('/')
|
||||
|
||||
# All routes are hard-coded. Need a way to register addons-supplied handlers.
|
||||
# All routes are hard-coded.
|
||||
|
||||
# No need for a db segment.
|
||||
if len(path) == 1:
|
||||
service = path[0]
|
||||
|
||||
if service == 'common':
|
||||
if method in ('create_database', 'list', 'server_version'):
|
||||
return xmlrpc_return(start_response, 'db', method, params)
|
||||
else:
|
||||
return xmlrpc_return(start_response, 'common', method, params)
|
||||
if method in ('server_version',):
|
||||
service = 'db'
|
||||
return xmlrpc_return(start_response, service, method, params)
|
||||
|
||||
# A db segment must be given.
|
||||
elif len(path) == 2:
|
||||
service, db_name = path
|
||||
params = (db_name,) + params
|
||||
|
||||
if service == 'model':
|
||||
return xmlrpc_return(start_response, 'object', method, params)
|
||||
elif service == 'report':
|
||||
return xmlrpc_return(start_response, 'report', method, params)
|
||||
service = 'object'
|
||||
return xmlrpc_return(start_response, service, method, params)
|
||||
|
||||
# TODO the body has been read, need to raise an exception (not return None).
|
||||
|
||||
|
|
|
@ -27,6 +27,10 @@ common_proxy_60 = None
|
|||
db_proxy_60 = None
|
||||
object_proxy_60 = None
|
||||
|
||||
common_proxy_61 = None
|
||||
db_proxy_61 = None
|
||||
model_proxy_61 = None
|
||||
|
||||
def setUpModule():
|
||||
"""
|
||||
Start the OpenERP server similary to the openerp-server script and
|
||||
|
@ -40,19 +44,32 @@ def setUpModule():
|
|||
global db_proxy_60
|
||||
global object_proxy_60
|
||||
|
||||
global common_proxy_61
|
||||
global db_proxy_61
|
||||
global model_proxy_61
|
||||
|
||||
# Use the old (pre 6.1) API.
|
||||
url = 'http://%s:%d/xmlrpc/' % (HOST, PORT)
|
||||
common_proxy_60 = xmlrpclib.ServerProxy(url + 'common')
|
||||
db_proxy_60 = xmlrpclib.ServerProxy(url + 'db')
|
||||
object_proxy_60 = xmlrpclib.ServerProxy(url + 'object')
|
||||
|
||||
# Use the new (6.1) API.
|
||||
url = 'http://%s:%d/openerp/6.1/xmlrpc/' % (HOST, PORT)
|
||||
common_proxy_61 = xmlrpclib.ServerProxy(url + 'common')
|
||||
db_proxy_61 = xmlrpclib.ServerProxy(url + 'db')
|
||||
model_proxy_61 = xmlrpclib.ServerProxy(url + 'model/' + DB)
|
||||
|
||||
# Mmm need to make sure the server is listening for XML-RPC requests.
|
||||
time.sleep(10)
|
||||
|
||||
def tearDownModule():
|
||||
""" Shutdown the OpenERP server similarly to a single ctrl-c. """
|
||||
openerp.service.stop_services()
|
||||
|
||||
class test_xmlrpc(unittest2.TestCase):
|
||||
|
||||
def test_xmlrpc_create_database_polling(self):
|
||||
def test_00_xmlrpc_create_database_polling(self):
|
||||
"""
|
||||
Simulate a OpenERP client requesting the creation of a database and
|
||||
polling the server until the creation is complete.
|
||||
|
@ -80,6 +97,13 @@ class test_xmlrpc(unittest2.TestCase):
|
|||
'ir.model', 'search', [], {})
|
||||
assert ids
|
||||
|
||||
def test_xmlrpc_61_ir_model_search(self):
|
||||
""" Try a search on the object service. """
|
||||
ids = model_proxy_61.execute(ADMIN_USER_ID, ADMIN_PASSWORD, 'ir.model', 'search', [])
|
||||
assert ids
|
||||
ids = model_proxy_61.execute(ADMIN_USER_ID, ADMIN_PASSWORD, 'ir.model', 'search', [], {})
|
||||
assert ids
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest2.main()
|
||||
|
||||
|
|
Loading…
Reference in New Issue