UtilsXmlrpc2scgi: xmlrpc2scgi.2.py

File xmlrpc2scgi.2.py, 6.1 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 encode_netstring(string):
43    "Encode string as netstring"
44    return '%d:%s,'%(len(string), string)
45
46def make_headers(headers):
47    "Make scgi header list"
48    return '\x00'.join(headers)+'\x00'
49
50def convert_xmlrpc2sgi_req(xmlreq):
51    "Wrap xmlrpc request in an scgi request,\nsee spec at: http://python.ca/scgi/protocol.txt"
52    # See spec at: http://python.ca/scgi/protocol.txt
53    headers = make_headers([
54        'CONTENT_LENGTH', str(len(xmlreq)),
55        'SCGI', '1',
56    ])
57   
58    enc_headers = encode_netstring(headers)
59   
60    return enc_headers+xmlreq
61
62def send_scgi(url, scgireq):
63    "Send request and get response from url"
64    scheme, netloc, path, query, frag = urlparse.urlsplit(url)
65    host, port = urllib.splitport(netloc)
66    #~ print '>>>', (netloc, host, port)
67   
68    addrinfo = socket.getaddrinfo(host, port, socket.AF_INET, socket.SOCK_STREAM)
69   
70    assert len(addrinfo) == 1, "There's more than one? %r"%addrinfo
71    #~ print addrinfo
72   
73    sock = socket.socket(*addrinfo[0][:3])
74   
75    sock.connect(addrinfo[0][4])
76    sock.send(scgireq)
77    recvdata = resp = sock.recv(1024)
78    while recvdata != '':
79        recvdata = sock.recv(1024)
80        #~ print 'Trying to receive more: %r'%recvdata
81        resp += recvdata
82    sock.close()
83    return resp
84
85def gen_headers(file):
86    "Get header lines from scgi response"
87    line = file.readline().rstrip()
88    while line.strip():
89        yield line
90        line = file.readline().rstrip()
91
92def get_scgi_resp(resp):
93    "Get xmlrpc response from scgi response"
94    fresp = StringIO.StringIO(resp)
95    for line in gen_headers(fresp):
96        #~ print "Header: %s"%line
97        pass
98   
99    xmlresp = fresp.read()
100    return xmlresp
101
102def convert_params_to_native(params):
103    "Parse xmlrpc-c command line arg syntax"
104    #~ print 'convert_params_to_native', params
105    cparams = []
106    # parse parameters
107    for param in params:
108        if param[1] != '/':
109            cparams.append(param)
110            continue
111       
112        if param[0] == 'i':
113            ptype = int
114        elif param[0] == 'b':
115            ptype = bool
116        elif param[0] == 's':
117            ptype = str
118       
119       
120        cparams.append(ptype(param[2:]))
121   
122    return tuple(cparams)
123
124def do_scgi_xmlrpc_request(host, methodname, params=()):
125    """
126        Send an xmlrpc request over scgi to host.
127        host: scgi://host:port/path
128        methodname: xmlrpc method name
129        params: tuple of simple python objects
130        returns xmlrpc response
131    """
132    xmlreq = xmlrpclib.dumps(params, methodname)
133   
134    scgireq = convert_xmlrpc2sgi_req(xmlreq)
135   
136    #~ print xmlreq, params
137    #~ print repr(scgireq)
138    #~ sys.stdout.write(scgireq)
139   
140    resp = send_scgi(host, scgireq)
141    #~ print resp
142   
143    respxml = get_scgi_resp(resp)
144    #~ print respxml
145   
146    return respxml
147
148def do_scgi_xmlrpc_request_py(host, methodname, params=()):
149    """
150        Send an xmlrpc request over scgi to host.
151        host: scgi://host:port/path
152        methodname: xmlrpc method name
153        params: tuple of simple python objects
154        returns xmlrpc response converted to python
155    """
156    xmlresp = do_scgi_xmlrpc_request(host, methodname, params)
157    return xmlrpclib.loads(xmlresp)[0][0]
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        return do_scgi_xmlrpc_request_py(self.url, self.methodname, args)
176   
177    def __getattr__(self, attr):
178        methodname = self.methodname and '.'.join([self.methodname,attr]) or attr
179        return RTorrentXMLRPCClient(self.url, methodname)
180
181
182def main(argv):
183    output_python=False
184   
185    if argv[0] == '-p':
186        output_python=True
187        argv.pop(0)
188   
189    host, methodname = argv[:2]
190   
191    respxml = do_scgi_xmlrpc_request(host, methodname,
192                                    convert_params_to_native(argv[2:]))
193   
194    if not output_python:
195        print respxml
196    else:
197        print xmlrpclib.loads(respxml)[0][0]
198
199if __name__ == "__main__":
200    main(sys.argv[1:])