c124d038634f4228ff0bd7263848e46f5e13617c
[p2pool.git] / p2pool / util / jsonrpc.py
1 from __future__ import division
2
3 import json
4 import weakref
5
6 from twisted.internet import defer
7 from twisted.python import failure, log
8 from twisted.web import client, error
9
10 from p2pool.util import deferred_resource, memoize
11
12 class Error(Exception):
13     def __init__(self, code, message, data=None):
14         if type(self) is Error:
15             raise TypeError("can't directly instantiate Error class; use Error_for_code")
16         if not isinstance(code, int):
17             raise TypeError('code must be an int')
18         #if not isinstance(message, unicode):
19         #    raise TypeError('message must be a unicode')
20         self.code, self.message, self.data = code, message, data
21     def __str__(self):
22         return '%i %s' % (self.code, self.message) + (' %r' % (self.data, ) if self.data is not None else '')
23     def _to_obj(self):
24         return {
25             'code': self.code,
26             'message': self.message,
27             'data': self.data,
28         }
29
30 @memoize.memoize_with_backing(weakref.WeakValueDictionary())
31 def Error_for_code(code):
32     class NarrowError(Error):
33         def __init__(self, *args, **kwargs):
34             Error.__init__(self, code, *args, **kwargs)
35     return NarrowError
36
37
38 class Proxy(object):
39     def __init__(self, func, services=[]):
40         self._func = func
41         self._services = services
42     
43     def __getattr__(self, attr):
44         if attr.startswith('rpc_'):
45             return lambda *params: self._func('.'.join(self._services + [attr[len('rpc_'):]]), params)
46         elif attr.startswith('svc_'):
47             return Proxy(self._func, self._services + [attr[len('svc_'):]])
48         else:
49             raise AttributeError('%r object has no attribute %r' % (self.__class__.__name__, attr))
50
51 @defer.inlineCallbacks
52 def _handle(data, provider, preargs=(), response_handler=None):
53         id_ = None
54         
55         try:
56             try:
57                 try:
58                     req = json.loads(data)
59                 except Exception:
60                     raise Error_for_code(-32700)(u'Parse error')
61                 
62                 if 'result' in req or 'error' in req:
63                     response_handler(req['id'], req['result'] if 'error' not in req or req['error'] is None else
64                         failure.Failure(Error_for_code(req['error']['code'])(req['error']['message'], req['error'].get('data', None))))
65                     defer.returnValue(None)
66                 
67                 id_ = req.get('id', None)
68                 method = req.get('method', None)
69                 if not isinstance(method, basestring):
70                     raise Error_for_code(-32600)(u'Invalid Request')
71                 params = req.get('params', [])
72                 if not isinstance(params, list):
73                     raise Error_for_code(-32600)(u'Invalid Request')
74                 
75                 for service_name in method.split('.')[:-1]:
76                     provider = getattr(provider, 'svc_' + service_name, None)
77                     if provider is None:
78                         raise Error_for_code(-32601)(u'Service not found')
79                 
80                 method_meth = getattr(provider, 'rpc_' + method.split('.')[-1], None)
81                 if method_meth is None:
82                     raise Error_for_code(-32601)(u'Method not found')
83                 
84                 result = yield method_meth(*list(preargs) + list(params))
85                 error = None
86             except Error:
87                 raise
88             except Exception:
89                 log.err(None, 'Squelched JSON error:')
90                 raise Error_for_code(-32099)(u'Unknown error')
91         except Error, e:
92             result = None
93             error = e._to_obj()
94         
95         defer.returnValue(json.dumps(dict(
96             jsonrpc='2.0',
97             id=id_,
98             result=result,
99             error=error,
100         )))
101
102 # HTTP
103
104 @defer.inlineCallbacks
105 def _http_do(url, headers, timeout, method, params):
106     id_ = 0
107     
108     try:
109         data = yield client.getPage(
110             url=url,
111             method='POST',
112             headers=dict(headers, **{'Content-Type': 'application/json'}),
113             postdata=json.dumps({
114                 'jsonrpc': '2.0',
115                 'method': method,
116                 'params': params,
117                 'id': id_,
118             }),
119             timeout=timeout,
120         )
121     except error.Error, e:
122         try:
123             resp = json.loads(e.response)
124         except:
125             raise e
126     else:
127         resp = json.loads(data)
128     
129     if resp['id'] != id_:
130         raise ValueError('invalid id')
131     if 'error' in resp and resp['error'] is not None:
132         raise Error_for_code(resp['error']['code'])(resp['error']['message'], resp['error'].get('data', None))
133     defer.returnValue(resp['result'])
134 HTTPProxy = lambda url, headers={}, timeout=5: Proxy(lambda method, params: _http_do(url, headers, timeout, method, params))
135
136 class HTTPServer(deferred_resource.DeferredResource):
137     def __init__(self, provider):
138         deferred_resource.DeferredResource.__init__(self)
139         self._provider = provider
140     
141     @defer.inlineCallbacks
142     def render_POST(self, request):
143         data = yield _handle(request.content.read(), self._provider, preargs=[request])
144         assert data is not None
145         request.setHeader('Content-Type', 'application/json')
146         request.setHeader('Content-Length', len(data))
147         request.write(data)