Drop NetRPC and activate JSON-RPC by default
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
* Drop NetRPC and activate JSON-RPC by default
|
||||
* Remove ir.action.wizard_size
|
||||
* Rename expand and fill attributes into yexpand and yfill
|
||||
* Add xalign and yalign as field attributes
|
||||
|
||||
+1
-10
@@ -9,18 +9,13 @@
|
||||
# If empty, the server will listen on all available interfaces
|
||||
#interface =
|
||||
|
||||
# Activate the netrpc protocol
|
||||
#netrpc = True
|
||||
# Configure the port for the netrpc protocol to listen
|
||||
#netport = 8070
|
||||
|
||||
# Activate the xml-rpc protocol
|
||||
#xmlrpc = False
|
||||
# Configure the port for the xml-rpc protocol to listen
|
||||
#xmlport = 8069
|
||||
|
||||
# Activate the json-rpc protocol
|
||||
#jsonrpc = False
|
||||
#jsonrpc = True
|
||||
# Configure the port for the json-rpc protocol to listen
|
||||
#jsonport = 8000
|
||||
# Configure the path of json-rpc data
|
||||
@@ -57,7 +52,6 @@
|
||||
#logfile = False
|
||||
|
||||
# Enable secure connection with SSL
|
||||
#secure_netrpc = False
|
||||
#secure_xmlrpc = False
|
||||
#secure_jsonrpc = False
|
||||
#secure_webdav = False
|
||||
@@ -75,9 +69,6 @@
|
||||
# Configure the path to store attachments and sqlite database
|
||||
#data_path = /var/lib/trytond
|
||||
|
||||
# Limit the number of threads
|
||||
#max_thread = 40
|
||||
|
||||
# Allow to run more than one instance of trytond
|
||||
#multi_server = False
|
||||
|
||||
|
||||
+1
-5
@@ -8,11 +8,9 @@ class ConfigManager(object):
|
||||
self.options = {
|
||||
'hostname': None,
|
||||
'interface': '',
|
||||
'netrpc': True,
|
||||
'netport': 8070,
|
||||
'xmlrpc': False,
|
||||
'xmlport': 8069,
|
||||
'jsonrpc': False,
|
||||
'jsonrpc': True,
|
||||
'jsonport': 8000,
|
||||
'jsondata_path': '/var/www/localhost/tryton',
|
||||
'webdav': False,
|
||||
@@ -31,7 +29,6 @@ class ConfigManager(object):
|
||||
'debug_mode': False,
|
||||
'pidfile': None,
|
||||
'logfile': None,
|
||||
'secure_netrpc': False,
|
||||
'secure_xmlrpc': False,
|
||||
'secure_jsonrpc': False,
|
||||
'secure_webdav': False,
|
||||
@@ -44,7 +41,6 @@ class ConfigManager(object):
|
||||
'smtp_user': False,
|
||||
'smtp_password': False,
|
||||
'data_path': '/var/lib/trytond',
|
||||
'max_thread': 40,
|
||||
'multi_server': False,
|
||||
'session_timeout': 600,
|
||||
'psyco': False,
|
||||
|
||||
@@ -4,6 +4,13 @@ import errno
|
||||
import os
|
||||
import socket
|
||||
import threading
|
||||
import select
|
||||
import sys
|
||||
import gzip
|
||||
try:
|
||||
import cStringIO as StringIO
|
||||
except ImportError:
|
||||
import StringIO
|
||||
|
||||
def endsocket(sock):
|
||||
if os.name != 'nt':
|
||||
@@ -17,7 +24,6 @@ class daemon(threading.Thread):
|
||||
def __init__(self, interface, port, secure, name=None):
|
||||
threading.Thread.__init__(self, name=name)
|
||||
self.secure = secure
|
||||
self.running = False
|
||||
self.ipv6 = False
|
||||
if socket.has_ipv6:
|
||||
try:
|
||||
@@ -27,11 +33,96 @@ class daemon(threading.Thread):
|
||||
pass
|
||||
|
||||
def stop(self):
|
||||
self.running = False
|
||||
self.server.shutdown()
|
||||
return
|
||||
|
||||
def run(self):
|
||||
self.running = True
|
||||
while self.running:
|
||||
self.server.handle_request()
|
||||
endsocket(self.server.socket)
|
||||
self.server.serve_forever()
|
||||
return True
|
||||
|
||||
class GZipRequestHandlerMixin:
|
||||
|
||||
if sys.version_info[:2] <= (2, 6):
|
||||
# Copy from SimpleXMLRPCServer.py with gzip encoding added
|
||||
def do_POST(self):
|
||||
"""Handles the HTTP POST request.
|
||||
|
||||
Attempts to interpret all HTTP POST requests as XML-RPC calls,
|
||||
which are forwarded to the server's _dispatch method for handling.
|
||||
"""
|
||||
|
||||
# Check that the path is legal
|
||||
if not self.is_rpc_path_valid():
|
||||
self.report_404()
|
||||
return
|
||||
|
||||
try:
|
||||
# Get arguments by reading body of request.
|
||||
# We read this in chunks to avoid straining
|
||||
# socket.read(); around the 10 or 15Mb mark, some platforms
|
||||
# begin to have problems (bug #792570).
|
||||
max_chunk_size = 10*1024*1024
|
||||
size_remaining = int(self.headers["content-length"])
|
||||
L = []
|
||||
while size_remaining:
|
||||
chunk_size = min(size_remaining, max_chunk_size)
|
||||
L.append(self.rfile.read(chunk_size))
|
||||
size_remaining -= len(L[-1])
|
||||
data = ''.join(L)
|
||||
|
||||
data = self.decode_request_content(data)
|
||||
if data is None:
|
||||
return #response has been sent
|
||||
|
||||
# In previous versions of SimpleXMLRPCServer, _dispatch
|
||||
# could be overridden in this class, instead of in
|
||||
# SimpleXMLRPCDispatcher. To maintain backwards compatibility,
|
||||
# check to see if a subclass implements _dispatch and dispatch
|
||||
# using that method if present.
|
||||
response = self.server._marshaled_dispatch(
|
||||
data, getattr(self, '_dispatch', None)
|
||||
)
|
||||
except Exception: # This should only happen if the module is buggy
|
||||
# internal error, report as HTTP server error
|
||||
self.send_response(500)
|
||||
self.end_headers()
|
||||
else:
|
||||
# got a valid XML RPC response
|
||||
self.send_response(200)
|
||||
self.send_header("Content-type", "text/xml")
|
||||
|
||||
# Handle gzip encoding
|
||||
if 'gzip' in self.headers.get('Accept-Encoding', '').split(',') \
|
||||
and len(response) > self.encode_threshold:
|
||||
buffer = StringIO.StringIO()
|
||||
output = gzip.GzipFile(mode='wb', fileobj=buffer)
|
||||
output.write(response)
|
||||
output.close()
|
||||
buffer.seek(0)
|
||||
response = buffer.getvalue()
|
||||
self.send_header('Content-Encoding', 'gzip')
|
||||
|
||||
self.send_header("Content-length", str(len(response)))
|
||||
self.end_headers()
|
||||
self.wfile.write(response)
|
||||
|
||||
def decode_request_content(self, data):
|
||||
#support gzip encoding of request
|
||||
encoding = self.headers.get("content-encoding", "identity").lower()
|
||||
if encoding == "identity":
|
||||
return data
|
||||
if encoding == "gzip":
|
||||
f = StringIO.StringIO(data)
|
||||
gzf = gzip.GzipFile(mode="rb", fileobj=f)
|
||||
try:
|
||||
decoded = gzf.read()
|
||||
except IOError:
|
||||
self.send_response(400, "error decoding gzip content")
|
||||
f.close()
|
||||
gzf.close()
|
||||
return decoded
|
||||
else:
|
||||
self.send_response(501, "encoding %r not supported" % encoding)
|
||||
self.send_header("Content-length", "0")
|
||||
self.end_headers()
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ from trytond.protocols.sslsocket import SSLSocket
|
||||
from trytond.protocols.dispatcher import dispatch
|
||||
from trytond.config import CONFIG
|
||||
from trytond.protocols.datatype import Float
|
||||
from trytond.protocols.common import daemon
|
||||
from trytond.protocols.common import daemon, GZipRequestHandlerMixin
|
||||
import SimpleXMLRPCServer
|
||||
import SimpleHTTPServer
|
||||
import SocketServer
|
||||
@@ -12,8 +12,6 @@ import traceback
|
||||
import socket
|
||||
import sys
|
||||
import os
|
||||
import gzip
|
||||
import StringIO
|
||||
try:
|
||||
import fcntl
|
||||
except ImportError:
|
||||
@@ -31,6 +29,8 @@ def object_hook(dct):
|
||||
dct['hour'], dct['minute'], dct['second'])
|
||||
elif dct['__class__'] == 'date':
|
||||
return datetime.date(dct['year'], dct['month'], dct['day'])
|
||||
elif dct['__class__'] == 'Decimal':
|
||||
return Decimal(dct['decimal'])
|
||||
return dct
|
||||
|
||||
class JSONEncoder(json.JSONEncoder):
|
||||
@@ -52,7 +52,9 @@ class JSONEncoder(json.JSONEncoder):
|
||||
'day': obj.day,
|
||||
}
|
||||
elif isinstance(obj, Decimal):
|
||||
return float(obj)
|
||||
return {'__class__': 'Decimal',
|
||||
'decimal': str(obj),
|
||||
}
|
||||
return super(JSONEncoder, self).default(obj)
|
||||
|
||||
|
||||
@@ -64,7 +66,7 @@ class SimpleJSONRPCDispatcher(SimpleXMLRPCServer.SimpleXMLRPCDispatcher):
|
||||
reason to instantiate this class directly.
|
||||
"""
|
||||
|
||||
def _marshaled_dispatch(self, data, dispatch_method=None):
|
||||
def _marshaled_dispatch(self, data, dispatch_method=None, path=None):
|
||||
"""Dispatches an JSON-RPC method from marshalled (JSON) data.
|
||||
|
||||
JSON-RPC methods are dispatched from the marshalled (JSON) data
|
||||
@@ -75,7 +77,7 @@ class SimpleJSONRPCDispatcher(SimpleXMLRPCServer.SimpleXMLRPCDispatcher):
|
||||
existing method through subclassing is the prefered means
|
||||
of changing method dispatch behavior.
|
||||
"""
|
||||
rawreq = json.loads(data, object_hook=object_hook, parse_float=Float)
|
||||
rawreq = json.loads(data, object_hook=object_hook)
|
||||
|
||||
req_id = rawreq.get('id', 0)
|
||||
method = rawreq['method']
|
||||
@@ -104,7 +106,7 @@ class SimpleJSONRPCDispatcher(SimpleXMLRPCServer.SimpleXMLRPCDispatcher):
|
||||
traceb = sys.exc_info()[2]
|
||||
pdb.post_mortem(traceb)
|
||||
# report exception back to server
|
||||
response['error'] = "%s:\n%s" % (sys.exc_value, tb_s)
|
||||
response['error'] = (str(sys.exc_value), tb_s)
|
||||
|
||||
return json.dumps(response, cls=JSONEncoder)
|
||||
|
||||
@@ -126,7 +128,8 @@ class GenericJSONRPCRequestHandler:
|
||||
return res
|
||||
|
||||
|
||||
class SimpleJSONRPCRequestHandler(GenericJSONRPCRequestHandler,
|
||||
class SimpleJSONRPCRequestHandler(GZipRequestHandlerMixin,
|
||||
GenericJSONRPCRequestHandler,
|
||||
SimpleXMLRPCServer.SimpleXMLRPCRequestHandler,
|
||||
SimpleHTTPServer.SimpleHTTPRequestHandler):
|
||||
"""Simple JSON-RPC request handler class.
|
||||
@@ -134,71 +137,15 @@ class SimpleJSONRPCRequestHandler(GenericJSONRPCRequestHandler,
|
||||
Handles all HTTP POST requests and attempts to decode them as
|
||||
JSON-RPC requests.
|
||||
"""
|
||||
protocol_version = "HTTP/1.1"
|
||||
rpc_paths = None
|
||||
encode_threshold = 1400 # common MTU
|
||||
|
||||
# Copy from SimpleXMLRPCServer.py with gzip encoding added
|
||||
def do_POST(self):
|
||||
"""Handles the HTTP POST request.
|
||||
|
||||
Attempts to interpret all HTTP POST requests as JSON-RPC calls,
|
||||
which are forwarded to the server's _dispatch method for handling.
|
||||
"""
|
||||
|
||||
# Check that the path is legal
|
||||
if not self.is_rpc_path_valid():
|
||||
self.report_404()
|
||||
return
|
||||
|
||||
try:
|
||||
# Get arguments by reading body of request.
|
||||
# We read this in chunks to avoid straining
|
||||
# socket.read(); around the 10 or 15Mb mark, some platforms
|
||||
# begin to have problems (bug #792570).
|
||||
max_chunk_size = 10*1024*1024
|
||||
size_remaining = int(self.headers["content-length"])
|
||||
L = []
|
||||
while size_remaining:
|
||||
chunk_size = min(size_remaining, max_chunk_size)
|
||||
L.append(self.rfile.read(chunk_size))
|
||||
size_remaining -= len(L[-1])
|
||||
data = ''.join(L)
|
||||
|
||||
# In previous versions of SimpleXMLRPCServer, _dispatch
|
||||
# could be overridden in this class, instead of in
|
||||
# SimpleXMLRPCDispatcher. To maintain backwards compatibility,
|
||||
# check to see if a subclass implements _dispatch and dispatch
|
||||
# using that method if present.
|
||||
response = self.server._marshaled_dispatch(
|
||||
data, getattr(self, '_dispatch', None)
|
||||
)
|
||||
except Exception: # This should only happen if the module is buggy
|
||||
# internal error, report as HTTP server error
|
||||
self.send_response(500)
|
||||
self.end_headers()
|
||||
else:
|
||||
# got a valid JSON RPC response
|
||||
self.send_response(200)
|
||||
self.send_header("Content-type", "application/json-rpc")
|
||||
|
||||
# Handle gzip encoding
|
||||
if 'gzip' in self.headers.get('Accept-Encoding', '').split(',') \
|
||||
and len(response) > self.encode_threshold:
|
||||
buffer = StringIO.StringIO()
|
||||
output = gzip.GzipFile(mode='wb', fileobj=buffer)
|
||||
output.write(response)
|
||||
output.close()
|
||||
buffer.seek(0)
|
||||
response = buffer.getvalue()
|
||||
self.send_header('Content-Encoding', 'gzip')
|
||||
|
||||
self.send_header("Content-length", str(len(response)))
|
||||
self.end_headers()
|
||||
self.wfile.write(response)
|
||||
|
||||
# shut down the connection
|
||||
self.wfile.flush()
|
||||
self.connection.shutdown(1)
|
||||
def send_header(self, keyword, value):
|
||||
if keyword == 'Content-type' and value == 'text/xml':
|
||||
value = 'application/json-rpc'
|
||||
SimpleXMLRPCServer.SimpleXMLRPCRequestHandler.send_header(self,
|
||||
keyword, value)
|
||||
|
||||
def translate_path(self, path):
|
||||
"""Translate a /-separated PATH to the local filename syntax.
|
||||
@@ -274,6 +221,7 @@ class SimpleJSONRPCServer(SocketServer.TCPServer,
|
||||
class SimpleThreadedJSONRPCServer(SocketServer.ThreadingMixIn,
|
||||
SimpleJSONRPCServer):
|
||||
timeout = 1
|
||||
daemon_threads = True
|
||||
|
||||
def server_bind(self):
|
||||
self.socket.setsockopt(socket.SOL_SOCKET,
|
||||
@@ -304,7 +252,6 @@ class JSONRPCDaemon(daemon):
|
||||
|
||||
def __init__(self, interface, port, secure=False):
|
||||
daemon.__init__(self, interface, port, secure, name='JSONRPCDaemon')
|
||||
|
||||
if self.secure:
|
||||
handler_class = SecureJSONRPCRequestHandler
|
||||
server_class = SecureThreadedJSONRPCServer
|
||||
|
||||
@@ -1,139 +0,0 @@
|
||||
#This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
#this repository contains the full copyright notices and license terms.
|
||||
from trytond.pysocket import PySocket
|
||||
from trytond.protocols.sslsocket import SSLSocket
|
||||
from trytond.config import CONFIG
|
||||
from trytond.protocols.dispatcher import dispatch
|
||||
from trytond.protocols.common import endsocket
|
||||
import threading
|
||||
import select
|
||||
import traceback
|
||||
import socket
|
||||
import sys
|
||||
|
||||
|
||||
class NetRPCClientThread(threading.Thread):
|
||||
def __init__(self, sock, threads, secure):
|
||||
threading.Thread.__init__(self, name='NetRPCClientThread')
|
||||
self.sock = sock
|
||||
self.threads = threads
|
||||
self.running = False
|
||||
self.secure = secure
|
||||
|
||||
def run(self):
|
||||
self.running = True
|
||||
try:
|
||||
pysocket = PySocket(self.sock)
|
||||
except Exception:
|
||||
self.sock.close()
|
||||
self.threads.remove(self)
|
||||
return False
|
||||
timeout = 0
|
||||
while self.running:
|
||||
try:
|
||||
rlist, _, _ = select.select([self.sock], [], [], 1)
|
||||
except select.error:
|
||||
continue
|
||||
if not rlist:
|
||||
timeout += 1
|
||||
if timeout > 600:
|
||||
break
|
||||
continue
|
||||
timeout = 0
|
||||
try:
|
||||
msg = pysocket.receive()
|
||||
except Exception:
|
||||
pysocket.disconnect()
|
||||
self.threads.remove(self)
|
||||
return False
|
||||
host, port = self.sock.getpeername()[:2]
|
||||
try:
|
||||
res = dispatch(host, port, 'NetRPC', *msg)
|
||||
pysocket.send(res)
|
||||
except Exception, exception:
|
||||
tb_s = ''
|
||||
for line in traceback.format_exception(*sys.exc_info()):
|
||||
try:
|
||||
line = line.encode('utf-8', 'ignore')
|
||||
except Exception:
|
||||
continue
|
||||
tb_s += line
|
||||
for path in sys.path:
|
||||
tb_s = tb_s.replace(path, '')
|
||||
if CONFIG['debug_mode']:
|
||||
import pdb
|
||||
tback = sys.exc_info()[2]
|
||||
pdb.post_mortem(tback)
|
||||
try:
|
||||
pysocket.send(exception.args, exception=True,
|
||||
traceback=tb_s)
|
||||
except Exception:
|
||||
pysocket.disconnect()
|
||||
self.threads.remove(self)
|
||||
return False
|
||||
pysocket.disconnect()
|
||||
self.threads.remove(self)
|
||||
return True
|
||||
|
||||
def stop(self):
|
||||
self.running = False
|
||||
|
||||
|
||||
class NetRPCServerThread(threading.Thread):
|
||||
def __init__(self, interface, port, secure=False):
|
||||
threading.Thread.__init__(self, name='NetRPCServerThread')
|
||||
self.socket = None
|
||||
if socket.has_ipv6:
|
||||
try:
|
||||
socket.getaddrinfo(interface or None, port, socket.AF_INET6)
|
||||
self.socket = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
|
||||
except Exception:
|
||||
pass
|
||||
if self.socket is None:
|
||||
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
if secure:
|
||||
self.socket = SSLSocket(self.socket)
|
||||
self.socket.bind((interface, port))
|
||||
self.socket.listen(5)
|
||||
self.threads = []
|
||||
self.secure = secure
|
||||
self.running = False
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
self.running = True
|
||||
while self.running:
|
||||
if not int(CONFIG['max_thread']) \
|
||||
or len(self.threads) < int(CONFIG['max_thread']):
|
||||
try:
|
||||
rlist, _, _ = select.select([self.socket], [], [], 1)
|
||||
except select.error:
|
||||
continue
|
||||
if not rlist:
|
||||
continue
|
||||
(clientsocket, _) = self.socket.accept()
|
||||
c_thread = NetRPCClientThread(clientsocket, self.threads,
|
||||
self.secure)
|
||||
self.threads.append(c_thread)
|
||||
c_thread.start()
|
||||
except Exception:
|
||||
try:
|
||||
self.socket.close()
|
||||
except Exception:
|
||||
pass
|
||||
return False
|
||||
|
||||
def stop(self):
|
||||
self.running = False
|
||||
while len(self.threads):
|
||||
try:
|
||||
thread = self.threads[0]
|
||||
thread.stop()
|
||||
thread.join()
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
endsocket(self.socket)
|
||||
except Exception:
|
||||
return False
|
||||
@@ -78,7 +78,6 @@ if not hasattr(WebDAVServer.DAVRequestHandler, '_config'):
|
||||
class BaseThreadedHTTPServer(SocketServer.ThreadingMixIn,
|
||||
BaseHTTPServer.HTTPServer):
|
||||
timeout = 1
|
||||
max_children = CONFIG['max_thread']
|
||||
|
||||
def server_bind(self):
|
||||
self.socket.setsockopt(socket.SOL_SOCKET,
|
||||
|
||||
@@ -4,7 +4,7 @@ from trytond.protocols.sslsocket import SSLSocket
|
||||
from trytond.protocols.dispatcher import dispatch
|
||||
from trytond.config import CONFIG
|
||||
from trytond.protocols.datatype import Float
|
||||
from trytond.protocols.common import daemon
|
||||
from trytond.protocols.common import daemon, GZipRequestHandlerMixin
|
||||
from trytond import security
|
||||
import SimpleXMLRPCServer
|
||||
import SocketServer
|
||||
@@ -13,8 +13,6 @@ import traceback
|
||||
import socket
|
||||
import sys
|
||||
import os
|
||||
import gzip
|
||||
import StringIO
|
||||
import base64
|
||||
import datetime
|
||||
from types import DictType
|
||||
@@ -102,8 +100,10 @@ class GenericXMLRPCRequestHandler:
|
||||
security.logout(database_name, user, session)
|
||||
|
||||
|
||||
class SimpleXMLRPCRequestHandler(GenericXMLRPCRequestHandler,
|
||||
class SimpleXMLRPCRequestHandler(GZipRequestHandlerMixin,
|
||||
GenericXMLRPCRequestHandler,
|
||||
SimpleXMLRPCServer.SimpleXMLRPCRequestHandler):
|
||||
protocol_version = "HTTP/1.1"
|
||||
rpc_paths = None
|
||||
encode_threshold = 1400 # common MTU
|
||||
|
||||
@@ -127,69 +127,6 @@ class SimpleXMLRPCRequestHandler(GenericXMLRPCRequestHandler,
|
||||
self.send_header("WWW-Authenticate", 'Basic realm="Tryton"')
|
||||
return False
|
||||
|
||||
# Copy from SimpleXMLRPCServer.py with gzip encoding added
|
||||
def do_POST(self):
|
||||
"""Handles the HTTP POST request.
|
||||
|
||||
Attempts to interpret all HTTP POST requests as XML-RPC calls,
|
||||
which are forwarded to the server's _dispatch method for handling.
|
||||
"""
|
||||
|
||||
# Check that the path is legal
|
||||
if not self.is_rpc_path_valid():
|
||||
self.report_404()
|
||||
return
|
||||
|
||||
try:
|
||||
# Get arguments by reading body of request.
|
||||
# We read this in chunks to avoid straining
|
||||
# socket.read(); around the 10 or 15Mb mark, some platforms
|
||||
# begin to have problems (bug #792570).
|
||||
max_chunk_size = 10*1024*1024
|
||||
size_remaining = int(self.headers["content-length"])
|
||||
L = []
|
||||
while size_remaining:
|
||||
chunk_size = min(size_remaining, max_chunk_size)
|
||||
L.append(self.rfile.read(chunk_size))
|
||||
size_remaining -= len(L[-1])
|
||||
data = ''.join(L)
|
||||
|
||||
# In previous versions of SimpleXMLRPCServer, _dispatch
|
||||
# could be overridden in this class, instead of in
|
||||
# SimpleXMLRPCDispatcher. To maintain backwards compatibility,
|
||||
# check to see if a subclass implements _dispatch and dispatch
|
||||
# using that method if present.
|
||||
response = self.server._marshaled_dispatch(
|
||||
data, getattr(self, '_dispatch', None)
|
||||
)
|
||||
except Exception: # This should only happen if the module is buggy
|
||||
# internal error, report as HTTP server error
|
||||
self.send_response(500)
|
||||
self.end_headers()
|
||||
else:
|
||||
# got a valid XML RPC response
|
||||
self.send_response(200)
|
||||
self.send_header("Content-type", "text/xml")
|
||||
|
||||
# Handle gzip encoding
|
||||
if 'gzip' in self.headers.get('Accept-Encoding', '').split(',') \
|
||||
and len(response) > self.encode_threshold:
|
||||
buffer = StringIO.StringIO()
|
||||
output = gzip.GzipFile(mode='wb', fileobj=buffer)
|
||||
output.write(response)
|
||||
output.close()
|
||||
buffer.seek(0)
|
||||
response = buffer.getvalue()
|
||||
self.send_header('Content-Encoding', 'gzip')
|
||||
|
||||
self.send_header("Content-length", str(len(response)))
|
||||
self.end_headers()
|
||||
self.wfile.write(response)
|
||||
|
||||
# shut down the connection
|
||||
self.wfile.flush()
|
||||
self.connection.shutdown(1)
|
||||
|
||||
|
||||
class SecureXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
|
||||
|
||||
@@ -202,6 +139,7 @@ class SecureXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
|
||||
class SimpleThreadedXMLRPCServer(SocketServer.ThreadingMixIn,
|
||||
SimpleXMLRPCServer.SimpleXMLRPCServer):
|
||||
timeout = 1
|
||||
daemon_threads = True
|
||||
|
||||
def server_bind(self):
|
||||
self.socket.setsockopt(socket.SOL_SOCKET,
|
||||
|
||||
@@ -1,253 +0,0 @@
|
||||
#This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
#this repository contains the full copyright notices and license terms.
|
||||
import socket
|
||||
# can't use/fall-back pickle due to different interface :-(
|
||||
import cPickle
|
||||
try:
|
||||
import cStringIO as StringIO
|
||||
except ImportError:
|
||||
import StringIO
|
||||
try:
|
||||
import ssl
|
||||
except ImportError:
|
||||
ssl = None
|
||||
import gzip
|
||||
try:
|
||||
import hashlib
|
||||
except ImportError:
|
||||
hashlib = None
|
||||
import sha
|
||||
import os
|
||||
|
||||
DNS_CACHE = {}
|
||||
MAX_SIZE = 999999999
|
||||
MAX_LENGHT = len(str(MAX_SIZE))
|
||||
CONNECT_TIMEOUT = 5
|
||||
TIMEOUT = 3600
|
||||
GZIP_THRESHOLD = 1400 # common MTU
|
||||
|
||||
_ALLOWED_MODULES = {'datetime': ['datetime', 'date'], 'decimal': ['Decimal']}
|
||||
|
||||
def checkfunction(module, klass):
|
||||
if module in _ALLOWED_MODULES and klass in _ALLOWED_MODULES[module]:
|
||||
mod = __import__(module, {}, {}, ['__all__'])
|
||||
_class = getattr(mod, klass)
|
||||
return _class
|
||||
raise ValueError('Not supported: %s/%s' % (module, klass))
|
||||
|
||||
|
||||
class PySocket:
|
||||
|
||||
def __init__(self, sock=None, fingerprints=None, ca_certs=None):
|
||||
self.sock = sock
|
||||
self.host = None
|
||||
self.hostname = None
|
||||
self.port = None
|
||||
self.ssl = False
|
||||
self.ssl_sock = None
|
||||
self.connected = False
|
||||
self.buffer = ''
|
||||
self.fingerprints = fingerprints
|
||||
if ca_certs and os.path.isfile(ca_certs):
|
||||
self.ca_certs = ca_certs
|
||||
else:
|
||||
self.ca_certs = None
|
||||
|
||||
def connect(self, host, port=False):
|
||||
if not port:
|
||||
buf = host.split('//')[1]
|
||||
host, port = buf.rsplit(':', 1)
|
||||
hostname = host
|
||||
if host in DNS_CACHE:
|
||||
host = DNS_CACHE[host]
|
||||
self.sock = None
|
||||
if socket.has_ipv6:
|
||||
try:
|
||||
socket.getaddrinfo(host, int(port), socket.AF_INET6)
|
||||
self.sock = socket.socket(socket.AF_INET6,
|
||||
socket.SOCK_STREAM)
|
||||
self.sock.settimeout(CONNECT_TIMEOUT)
|
||||
self.sock.connect((host, int(port)))
|
||||
except socket.error:
|
||||
self.sock = None
|
||||
if self.sock is None:
|
||||
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.sock.settimeout(CONNECT_TIMEOUT)
|
||||
self.sock.connect((host, int(port)))
|
||||
DNS_CACHE[hostname], port = self.sock.getpeername()[:2]
|
||||
try:
|
||||
sock = None
|
||||
if socket.has_ipv6:
|
||||
try:
|
||||
socket.getaddrinfo(host, int(port), socket.AF_INET6)
|
||||
sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
|
||||
sock.settimeout(CONNECT_TIMEOUT)
|
||||
sock.connect((host, int(port)))
|
||||
except socket.error:
|
||||
sock = None
|
||||
if sock is None:
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.settimeout(CONNECT_TIMEOUT)
|
||||
sock.connect((host, int(port)))
|
||||
if ssl:
|
||||
ssl_sock = ssl.wrap_socket(sock)
|
||||
self.ssl = True
|
||||
elif hasattr(socket, 'ssl'):
|
||||
ssl_sock = socket.ssl(sock)
|
||||
self.ssl = True
|
||||
except socket.error:
|
||||
pass
|
||||
self.sock.settimeout(TIMEOUT)
|
||||
fingerprint = None
|
||||
if self.ssl:
|
||||
if ssl:
|
||||
self.ssl_sock = ssl.wrap_socket(self.sock,
|
||||
ca_certs=self.ca_certs, cert_reqs=(self.ca_certs and
|
||||
ssl.CERT_REQUIRED or ssl.CERT_NONE))
|
||||
elif hasattr(socket, 'ssl'):
|
||||
self.ssl_sock = socket.ssl(self.sock, certfile=self.ca_certs)
|
||||
try:
|
||||
peercert = self.ssl_sock.getpeercert(True)
|
||||
except socket.error:
|
||||
peercert = None
|
||||
def format_hash(value):
|
||||
return reduce(lambda x, y: x + y[1].upper() +
|
||||
((y[0] % 2 and y[0] + 1 < len(value)) and ':' or ''),
|
||||
enumerate(value), '')
|
||||
if peercert and hashlib:
|
||||
fingerprint = format_hash(hashlib.sha1(peercert).hexdigest())
|
||||
elif peercert:
|
||||
fingerprint = format_hash(sha1.new(peercert).hexdigest())
|
||||
self.host = host
|
||||
self.hostname = hostname
|
||||
self.port = port
|
||||
self.connected = True
|
||||
self.buffer = ''
|
||||
if self.fingerprints is not None:
|
||||
key = (self.hostname, str(self.port))
|
||||
if key in self.fingerprints:
|
||||
if self.fingerprints[key] != fingerprint:
|
||||
self.disconnect()
|
||||
raise Exception('BadFingerprint')
|
||||
elif fingerprint:
|
||||
self.fingerprints[key] = fingerprint
|
||||
|
||||
def disconnect(self):
|
||||
try:
|
||||
sock = self.sock
|
||||
if self.ssl:
|
||||
sock = self.ssl_sock
|
||||
try:
|
||||
shutdown_value = 2
|
||||
if hasattr(socket, 'SHUT_RDWR'):
|
||||
shutdown_value = socket.SHUT_RDWR
|
||||
if hasattr(sock, 'sock_shutdown'):
|
||||
sock.sock_shutdown(shutdown_value)
|
||||
else:
|
||||
sock.shutdown(shutdown_value)
|
||||
except socket.error:
|
||||
pass
|
||||
sock.close()
|
||||
except socket.error:
|
||||
pass
|
||||
self.sock = None
|
||||
self.ssl = False
|
||||
self.ssl_sock = None
|
||||
self.connected = False
|
||||
self.buffer = ''
|
||||
|
||||
def reconnect(self):
|
||||
if self.hostname and self.port:
|
||||
self.disconnect()
|
||||
self.connect(self.hostname, self.port)
|
||||
|
||||
def send(self, msg, exception=False, traceback=None):
|
||||
msg = cPickle.dumps([msg, traceback], protocol=2)
|
||||
gzip_p = False
|
||||
if len(msg) > GZIP_THRESHOLD:
|
||||
buffer = StringIO.StringIO()
|
||||
output = gzip.GzipFile(mode='wb', fileobj=buffer)
|
||||
output.write(msg)
|
||||
output.close()
|
||||
buffer.seek(0)
|
||||
msg = buffer.getvalue()
|
||||
gzip_p = True
|
||||
size = len(msg)
|
||||
msg = str(size) + ' ' + (exception and "1" or "0") \
|
||||
+ (gzip_p and "1" or "0") + msg
|
||||
size = len(msg)
|
||||
|
||||
if self.ssl:
|
||||
totalsent = 0
|
||||
while totalsent < size:
|
||||
sent = self.ssl_sock.write(msg[totalsent:])
|
||||
if sent == 0:
|
||||
raise RuntimeError("socket connection broken")
|
||||
totalsent = totalsent + sent
|
||||
else:
|
||||
self.sock.sendall(msg)
|
||||
|
||||
def receive(self):
|
||||
buf = self.buffer
|
||||
L = []
|
||||
size_remaining = MAX_LENGHT
|
||||
while size_remaining:
|
||||
chunk_size = min(size_remaining, 4096)
|
||||
if self.ssl:
|
||||
chunk = self.ssl_sock.read(chunk_size)
|
||||
else:
|
||||
chunk = self.sock.recv(chunk_size)
|
||||
if chunk == '':
|
||||
raise RuntimeError("socket connection broken")
|
||||
L.append(chunk)
|
||||
size_remaining -= len(chunk)
|
||||
if ' ' in chunk:
|
||||
break
|
||||
if size_remaining < 0:
|
||||
raise RuntimeError("socket connection broken")
|
||||
buf += ''.join(L)
|
||||
size, msg = buf.split(' ', 1)
|
||||
size = int(size)
|
||||
if size > MAX_SIZE:
|
||||
raise RuntimeError("socket connection broken")
|
||||
while len(msg) < 2:
|
||||
chunk_size = min(size + 2, 4096)
|
||||
if self.ssl:
|
||||
msg += self.ssl_sock.read(chunk_size)
|
||||
else:
|
||||
msg += self.sock.recv(chunk_size)
|
||||
if msg == '':
|
||||
raise RuntimeError("socket connection broken")
|
||||
exception = msg[0] != "0"
|
||||
gzip_p = msg[1] != "0"
|
||||
L = [msg[2:]]
|
||||
size_remaining = size - len(L[0])
|
||||
while size_remaining:
|
||||
chunk_size = min(size_remaining, 4096)
|
||||
if self.ssl:
|
||||
chunk = self.ssl_sock.read(chunk_size)
|
||||
else:
|
||||
chunk = self.sock.recv(chunk_size)
|
||||
if chunk == '':
|
||||
raise RuntimeError("socket connection broken")
|
||||
L.append(chunk)
|
||||
size_remaining -= len(chunk)
|
||||
msg = ''.join(L)
|
||||
if len(msg) > size:
|
||||
self.buffer = msg[size:]
|
||||
msg = msg[:size]
|
||||
else:
|
||||
self.buffer = ''
|
||||
msgio = StringIO.StringIO(msg)
|
||||
if gzip_p:
|
||||
output = gzip.GzipFile(mode='r', fileobj=msgio)
|
||||
msgio = StringIO.StringIO(output.read(-1))
|
||||
output.close()
|
||||
unpickler = cPickle.Unpickler(msgio)
|
||||
# cPickle mechanism to import instances (pickle differs here)
|
||||
unpickler.find_global = checkfunction
|
||||
res = unpickler.load()
|
||||
if exception:
|
||||
raise Exception(*(list(res[0]) + [res[1]]))
|
||||
else:
|
||||
return res[0]
|
||||
@@ -170,14 +170,6 @@ class TrytonServer(object):
|
||||
(CONFIG['secure_jsonrpc'] and ' Secure' or '',
|
||||
CONFIG['jsonport']))
|
||||
|
||||
if CONFIG['netrpc']:
|
||||
from trytond.protocols.netrpc import NetRPCServerThread
|
||||
netrpcd = NetRPCServerThread(CONFIG['interface'], CONFIG['netport'],
|
||||
CONFIG['secure_netrpc'])
|
||||
self.logger.info("starting NetRPC%s protocol, port %d" % \
|
||||
(CONFIG['secure_netrpc'] and ' Secure' or '',
|
||||
CONFIG['netport']))
|
||||
|
||||
if CONFIG['webdav']:
|
||||
from trytond.protocols.webdav import WebDAVServerThread
|
||||
webdavd = WebDAVServerThread(CONFIG['interface'],
|
||||
@@ -191,9 +183,6 @@ class TrytonServer(object):
|
||||
if signum == signal.SIGUSR1:
|
||||
Pool.start()
|
||||
return
|
||||
if CONFIG['netrpc']:
|
||||
netrpcd.stop()
|
||||
netrpcd.join()
|
||||
if CONFIG['xmlrpc']:
|
||||
xmlrpcd.stop()
|
||||
xmlrpcd.join()
|
||||
@@ -221,8 +210,6 @@ class TrytonServer(object):
|
||||
signal.signal(signal.SIGUSR1, handler)
|
||||
|
||||
self.logger.info('waiting for connections...')
|
||||
if CONFIG['netrpc']:
|
||||
netrpcd.start()
|
||||
if CONFIG['xmlrpc']:
|
||||
xmlrpcd.start()
|
||||
if CONFIG['jsonrpc']:
|
||||
|
||||
Reference in New Issue
Block a user