-
-
Save query/899683 to your computer and use it in GitHub Desktop.
#!/usr/bin/python | |
# rtorrent_xmlrpc | |
# (c) 2011 Roger Que <[email protected]> | |
# | |
# Python module for interacting with rtorrent's XML-RPC interface | |
# directly over SCGI, instead of through an HTTP server intermediary. | |
# Inspired by Glenn Washburn's xmlrpc2scgi.py [1], but subclasses the | |
# built-in xmlrpclib classes so that it is compatible with features | |
# such as MultiCall objects. | |
# | |
# [1] <http://libtorrent.rakshasa.no/wiki/UtilsXmlrpc2scgi> | |
# | |
# Usage: server = SCGIServerProxy('scgi://localhost:7000/') | |
# server = SCGIServerProxy('scgi:///path/to/scgi.sock') | |
# print server.system.listMethods() | |
# mc = xmlrpclib.MultiCall(server) | |
# mc.get_up_rate() | |
# mc.get_down_rate() | |
# print mc() | |
# | |
# | |
# | |
# 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 | |
# | |
# In addition, as a special exception, the copyright holders give | |
# permission to link the code of portions of this program with the | |
# OpenSSL library under certain conditions as described in each | |
# individual source file, and distribute linked combinations | |
# including the two. | |
# | |
# You must obey the GNU General Public License in all respects for | |
# all of the code used other than OpenSSL. If you modify file(s) | |
# with this exception, you may extend this exception to your version | |
# of the file(s), but you are not obligated to do so. If you do not | |
# wish to do so, delete this exception statement from your version. | |
# If you delete this exception statement from all source files in the | |
# program, then also delete it here. | |
# | |
# | |
# | |
# Portions based on Python's xmlrpclib: | |
# | |
# Copyright (c) 1999-2002 by Secret Labs AB | |
# Copyright (c) 1999-2002 by Fredrik Lundh | |
# | |
# By obtaining, using, and/or copying this software and/or its | |
# associated documentation, you agree that you have read, understood, | |
# and will comply with the following terms and conditions: | |
# | |
# Permission to use, copy, modify, and distribute this software and | |
# its associated documentation for any purpose and without fee is | |
# hereby granted, provided that the above copyright notice appears in | |
# all copies, and that both that copyright notice and this permission | |
# notice appear in supporting documentation, and that the name of | |
# Secret Labs AB or the author not be used in advertising or publicity | |
# pertaining to distribution of the software without specific, written | |
# prior permission. | |
# | |
# SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD | |
# TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT- | |
# ABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR | |
# BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY | |
# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, | |
# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS | |
# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |
# OF THIS SOFTWARE. | |
import re | |
import socket | |
import urllib | |
import xmlrpclib | |
class SCGITransport(xmlrpclib.Transport): | |
def single_request(self, host, handler, request_body, verbose=0): | |
# Add SCGI headers to the request. | |
headers = {'CONTENT_LENGTH': str(len(request_body)), 'SCGI': '1'} | |
header = '\x00'.join(('%s\x00%s' % item for item in headers.iteritems())) + '\x00' | |
header = '%d:%s' % (len(header), header) | |
request_body = '%s,%s' % (header, request_body) | |
sock = None | |
try: | |
if host: | |
host, port = urllib.splitport(host) | |
addrinfo = socket.getaddrinfo(host, port, socket.AF_INET, | |
socket.SOCK_STREAM) | |
sock = socket.socket(*addrinfo[0][:3]) | |
sock.connect(addrinfo[0][4]) | |
else: | |
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) | |
sock.connect(handler) | |
self.verbose = verbose | |
sock.send(request_body) | |
return self.parse_response(sock.makefile()) | |
finally: | |
if sock: | |
sock.close() | |
def parse_response(self, response): | |
p, u = self.getparser() | |
response_body = '' | |
while True: | |
data = response.read(1024) | |
if not data: | |
break | |
response_body += data | |
# Remove SCGI headers from the response. | |
response_header, response_body = re.split(r'\n\s*?\n', response_body, | |
maxsplit=1) | |
if self.verbose: | |
print 'body:', repr(response_body) | |
p.feed(response_body) | |
p.close() | |
return u.close() | |
class SCGIServerProxy(xmlrpclib.ServerProxy): | |
def __init__(self, uri, transport=None, encoding=None, verbose=False, | |
allow_none=False, use_datetime=False): | |
type, uri = urllib.splittype(uri) | |
if type not in ('scgi'): | |
raise IOError('unsupported XML-RPC protocol') | |
self.__host, self.__handler = urllib.splithost(uri) | |
if not self.__handler: | |
self.__handler = '/' | |
if transport is None: | |
transport = SCGITransport(use_datetime=use_datetime) | |
self.__transport = transport | |
self.__encoding = encoding | |
self.__verbose = verbose | |
self.__allow_none = allow_none | |
def __close(self): | |
self.__transport.close() | |
def __request(self, methodname, params): | |
# call a method on the remote server | |
request = xmlrpclib.dumps(params, methodname, encoding=self.__encoding, | |
allow_none=self.__allow_none) | |
response = self.__transport.request( | |
self.__host, | |
self.__handler, | |
request, | |
verbose=self.__verbose | |
) | |
if len(response) == 1: | |
response = response[0] | |
return response | |
def __repr__(self): | |
return ( | |
"<SCGIServerProxy for %s%s>" % | |
(self.__host, self.__handler) | |
) | |
__str__ = __repr__ | |
def __getattr__(self, name): | |
# magic method dispatcher | |
return xmlrpclib._Method(self.__request, name) | |
# note: to call a remote object with an non-standard name, use | |
# result getattr(server, "strange-python-name")(args) | |
def __call__(self, attr): | |
"""A workaround to get special attributes on the ServerProxy | |
without interfering with the magic __getattr__ | |
""" | |
if attr == "close": | |
return self.__close | |
elif attr == "transport": | |
return self.__transport | |
raise AttributeError("Attribute %r not found" % (attr,)) |
This is great! Thank you.
Nice! Please note a random problem with headers created using dictionary. Order of keys not guaranteed by single_request( self, host, handler, request, verbose = 0 ). rtorrent checks beginning of request buffer always at current (offset = 0). CONTENT_LENGTH must always be first key in dictionary. Better to use OrderedDictionary.
https://gist.github.com/query/899683#file-rtorrent_xmlrpc-py line 90:
headers = {'CONTENT_LENGTH': str(len(request_body)), 'SCGI': '1'}
https://github.com/rakshasa/rtorrent/blob/master/src/rpc/scgi_task.cc line 143):
if (std::memcmp(current, "CONTENT_LENGTH", 15) != 0)
goto event_read_failed;
Also the response_body length should be verified as non-zero and raise a BrokenPipeError exception so that xmlrpc client script will retry upon failure.
See parse_response( self, response )
Very nice. This is exactly what I have been looking for. Thank you :)
Great!