UtilsXmlrpc2scgi: xmlrpc2scgi.py

File xmlrpc2scgi.py, 7.9 KB (added by glenn, 4 years ago)
Line 
1#!/usr/bin/env python
2
3# Copyright (C) 2005-2007, Glenn Washburn
4#
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation; either version 2 of the License, or
8# (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program; if not, write to the Free Software
17# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18#
19# In addition, as a special exception, the copyright holders give
20# permission to link the code of portions of this program with the
21# OpenSSL library under certain conditions as described in each
22# individual source file, and distribute linked combinations
23# including the two.
24#
25# You must obey the GNU General Public License in all respects for
26# all of the code used other than OpenSSL.  If you modify file(s)
27# with this exception, you may extend this exception to your version
28# of the file(s), but you are not obligated to do so.  If you do not
29# wish to do so, delete this exception statement from your version.
30# If you delete this exception statement from all source files in the
31# program, then also delete it here.
32#
33# Contact:  Glenn Washburn <crass@berlios.de>
34
35import sys, cStringIO as StringIO
36import xmlrpclib, urllib, urlparse, socket
37
38# this allows us to parse scgi urls just like http ones
39from urlparse import uses_netloc
40uses_netloc.append('scgi')
41
42def do_scgi_xmlrpc_request(host, methodname, params=()):
43    """
44        Send an xmlrpc request over scgi to host.
45        host:       scgi://host:port/path
46        methodname: xmlrpc method name
47        params:     tuple of simple python objects
48        returns:    xmlrpc response
49    """
50    xmlreq = xmlrpclib.dumps(params, methodname)
51    xmlresp = SCGIRequest(host).send(xmlreq)
52    #~ print xmlresp
53   
54    return xmlresp
55
56def do_scgi_xmlrpc_request_py(host, methodname, params=()):
57    """
58        Send an xmlrpc request over scgi to host.
59        host:       scgi://host:port/path
60        methodname: xmlrpc method name
61        params:     tuple of simple python objects
62        returns:    xmlrpc response converted to python
63    """
64    xmlresp = do_scgi_xmlrpc_request(host, methodname, params)
65    return xmlrpclib.loads(xmlresp)[0][0]
66
67class SCGIRequest(object):
68    """ See spec at: http://python.ca/scgi/protocol.txt
69        Send an SCGI request.
70       
71        Use tcp socket
72        SCGIRequest('scgi://host:port').send(data)
73       
74        Or use the named unix domain socket
75        SCGIRequest('scgi:///tmp/rtorrent.sock').send(data)
76    """
77   
78    def __init__(self, url):
79        self.url=url
80        self.resp_headers=[]
81   
82    def __send(self, scgireq):
83        scheme, netloc, path, query, frag = urlparse.urlsplit(self.url)
84        host, port = urllib.splitport(netloc)
85        #~ print '>>>', (netloc, host, port)
86       
87        if netloc:
88            addrinfo = socket.getaddrinfo(host, port, socket.AF_INET, socket.SOCK_STREAM)
89           
90            assert len(addrinfo) == 1, "There's more than one? %r"%addrinfo
91            #~ print addrinfo
92           
93            sock = socket.socket(*addrinfo[0][:3])
94            sock.connect(addrinfo[0][4])
95        else:
96            # if no host then assume unix domain socket
97            sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
98            sock.connect(path)
99       
100        sock.send(scgireq)
101        recvdata = resp = sock.recv(1024)
102        while recvdata != '':
103            recvdata = sock.recv(1024)
104            #~ print 'Trying to receive more: %r'%recvdata
105            resp += recvdata
106        sock.close()
107        return resp
108   
109    def send(self, data):
110        "Send data over scgi to url and get response"
111        scgiresp = self.__send(self.add_required_scgi_headers(data))
112        resp, self.resp_headers = self.get_scgi_resp(scgiresp)
113        return resp
114   
115    @staticmethod
116    def encode_netstring(string):
117        "Encode string as netstring"
118        return '%d:%s,'%(len(string), string)
119   
120    @staticmethod
121    def make_headers(headers):
122        "Make scgi header list"
123        #~ return '\x00'.join(headers)+'\x00'
124        return '\x00'.join(['%s\x00%s'%t for t in headers])+'\x00'
125   
126    @staticmethod
127    def add_required_scgi_headers(data, headers=[]):
128        "Wrap data in an scgi request,\nsee spec at: http://python.ca/scgi/protocol.txt"
129        # See spec at: http://python.ca/scgi/protocol.txt
130        headers = SCGIRequest.make_headers([
131            ('CONTENT_LENGTH', str(len(data))),
132            ('SCGI', '1'),
133        ] + headers)
134       
135        enc_headers = SCGIRequest.encode_netstring(headers)
136       
137        return enc_headers+data
138   
139    @staticmethod
140    def gen_headers(file):
141        "Get header lines from scgi response"
142        line = file.readline().rstrip()
143        while line.strip():
144            yield line
145            line = file.readline().rstrip()
146   
147    @staticmethod
148    def get_scgi_resp(resp):
149        "Get xmlrpc response from scgi response"
150        fresp = StringIO.StringIO(resp)
151        headers = []
152        for line in SCGIRequest.gen_headers(fresp):
153            #~ print "Header: %r"%line
154            headers.append(line.split(': ', 1))
155       
156        xmlresp = fresp.read()
157        return (xmlresp, headers)
158
159class RTorrentXMLRPCClient(object):
160    """
161    The following is an exmple of how to use this class.
162    rtorrent_host='http://localhost:33000'
163    rtc = RTorrentXMLRPCClient(rtorrent_host)
164    for infohash in rtc.download_list('complete'):
165        if rtc.d.get_ratio(infohash) > 500:
166            print "%s has a ratio of over 0.5"%(rtc.d.get_name(infohash))
167    """
168   
169    def __init__(self, url, methodname=''):
170        self.url = url
171        self.methodname = methodname
172   
173    def __call__(self, *args):
174        #~ print "%s%r"%(self.methodname, args)
175        scheme, netloc, path, query, frag = urlparse.urlsplit(self.url)
176        xmlreq = xmlrpclib.dumps(args, self.methodname)
177        if scheme == 'scgi':
178            xmlresp = SCGIRequest(self.url).send(xmlreq)
179            return xmlrpclib.loads(xmlresp)[0][0]
180            #~ return do_scgi_xmlrpc_request_py(self.url, self.methodname, args)
181        elif scheme == 'http':
182            raise Exception('Unsupported protocol')
183        elif scheme == '':
184            raise Exception('Unsupported protocol')
185        else:
186            raise Exception('Unsupported protocol')
187   
188    def __getattr__(self, attr):
189        methodname = self.methodname and '.'.join([self.methodname,attr]) or attr
190        return RTorrentXMLRPCClient(self.url, methodname)
191
192def convert_params_to_native(params):
193    "Parse xmlrpc-c command line arg syntax"
194    #~ print 'convert_params_to_native', params
195    cparams = []
196    # parse parameters
197    for param in params:
198        if len(param) < 2 or param[1] != '/':
199            cparams.append(param)
200            continue
201       
202        if param[0] == 'i':
203            ptype = int
204        elif param[0] == 'b':
205            ptype = bool
206        elif param[0] == 's':
207            ptype = str
208        else:
209            cparams.append(param)
210            continue
211       
212        cparams.append(ptype(param[2:]))
213   
214    return tuple(cparams)
215
216def main(argv):
217    output_python=False
218   
219    if argv[0] == '-p':
220        output_python=True
221        argv.pop(0)
222   
223    host, methodname = argv[:2]
224   
225    respxml = do_scgi_xmlrpc_request(host, methodname,
226                                    convert_params_to_native(argv[2:]))
227    #~ respxml = RTorrentXMLRPCClient(host, methodname)(convert_params_to_native(argv[2:]))
228   
229    if not output_python:
230        print respxml
231    else:
232        print xmlrpclib.loads(respxml)[0][0]
233
234if __name__ == "__main__":
235    main(sys.argv[1:])