-
-
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!