separated jsonrpc.Server and the object it's proxying
[p2pool.git] / p2pool / util / jsonrpc.py
1 from __future__ import division
2
3 import json
4
5 from twisted.internet import defer
6 from twisted.python import log
7 from twisted.web import client, error
8
9 import deferred_resource
10
11 class Error(Exception):
12     def __init__(self, code, message, data=None):
13         if not isinstance(code, int):
14             raise TypeError('code must be an int')
15         #if not isinstance(message, unicode):
16         #    raise TypeError('message must be a unicode')
17         self.code, self.message, self.data = code, message, data
18     def __str__(self):
19         return '%i %s' % (self.code, self.message) + (' %r' % (self.data, ) if self.data is not None else '')
20     def _to_obj(self):
21         return {
22             'code': self.code,
23             'message': self.message,
24             'data': self.data,
25         }
26
27 class Proxy(object):
28     def __init__(self, url, headers={}, timeout=5):
29         self._url = url
30         self._headers = headers
31         self._timeout = timeout
32     
33     @defer.inlineCallbacks
34     def callRemote(self, method, *params):
35         id_ = 0
36         
37         try:
38             data = yield client.getPage(
39                 url=self._url,
40                 method='POST',
41                 headers=dict(self._headers, **{'Content-Type': 'application/json'}),
42                 postdata=json.dumps({
43                     'jsonrpc': '2.0',
44                     'method': method,
45                     'params': params,
46                     'id': id_,
47                 }),
48                 timeout=self._timeout,
49             )
50         except error.Error, e:
51             try:
52                 resp = json.loads(e.response)
53             except:
54                 raise e
55         else:
56             resp = json.loads(data)
57         
58         if resp['id'] != id_:
59             raise ValueError('invalid id')
60         if 'error' in resp and resp['error'] is not None:
61             raise Error(**resp['error'])
62         defer.returnValue(resp['result'])
63     
64     def __getattr__(self, attr):
65         if attr.startswith('rpc_'):
66             return lambda *params: self.callRemote(attr[len('rpc_'):], *params)
67         raise AttributeError('%r object has no attribute %r' % (self.__class__.__name__, attr))
68
69 class Server(deferred_resource.DeferredResource):
70     def __init__(self, provider):
71         self._provider = provider
72     
73     @defer.inlineCallbacks
74     def render_POST(self, request):
75         # missing batching, 1.0 notifications
76         
77         id_ = None
78         
79         try:
80             data = request.content.read()
81             
82             try:
83                 req = json.loads(data)
84             except Exception:
85                 raise Error(-32700, u'Parse error')
86             
87             try:
88                 id_ = req.get('id', None)
89                 method = req['method']
90                 if not isinstance(method, basestring):
91                     raise ValueError()
92                 params = req.get('params', [])
93                 if not isinstance(params, list):
94                     raise ValueError()
95             except Exception:
96                 raise Error(-32600, u'Invalid Request')
97             
98             method_meth = getattr(self._provider, 'rpc_' + method, None)
99             if method_meth is None:
100                 raise Error(-32601, u'Method not found')
101             
102             result = yield method_meth(request, *params)
103             
104             if id_ is None:
105                 return
106             
107             error = None
108         except Error, e:
109             result = None
110             error = e._to_obj()
111         except Exception:
112             log.err(None, 'Squelched JSON error:')
113             
114             result = None
115             error = Error(-32099, u'Unknown error')._to_obj()
116         
117         data = json.dumps(dict(
118             jsonrpc='2.0',
119             id=id_,
120             result=result,
121             error=error,
122         ))
123         request.setHeader('Content-Type', 'application/json')
124         request.setHeader('Content-Length', len(data))
125         request.write(data)