Created
April 2, 2011 17:25
-
-
Save query/899683 to your computer and use it in GitHub Desktop.
Python module for interacting with rtorrent's XML-RPC interface directly over SCGI.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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,)) |
Very nice. This is exactly what I have been looking for. Thank you :)
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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 )