aa334a69ea3579195bd8119724a16d389aa9a748
[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 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 class Proxy(object):
38     def __init__(self, url, headers={}, timeout=5):
39         self._url = url
40         self._headers = headers
41         self._timeout = timeout
42     
43     @defer.inlineCallbacks
44     def callRemote(self, method, *params):
45         id_ = 0
46         
47         try:
48             data = yield client.getPage(
49                 url=self._url,
50                 method='POST',
51                 headers=dict(self._headers, **{'Content-Type': 'application/json'}),
52                 postdata=json.dumps({
53                     'jsonrpc': '2.0',
54                     'method': method,
55                     'params': params,
56                     'id': id_,
57                 }),
58                 timeout=self._timeout,
59             )
60         except error.Error, e:
61             try:
62                 resp = json.loads(e.response)
63             except:
64                 raise e
65         else:
66             resp = json.loads(data)
67         
68         if resp['id'] != id_:
69             raise ValueError('invalid id')
70         if 'error' in resp and resp['error'] is not None:
71             raise Error_for_code(resp['error']['code'])(resp['error']['message'], resp['error'].get('data', None))
72         defer.returnValue(resp['result'])
73     
74     def __getattr__(self, attr):
75         if attr.startswith('rpc_'):
76             return lambda *params: self.callRemote(attr[len('rpc_'):], *params)
77         raise AttributeError('%r object has no attribute %r' % (self.__class__.__name__, attr))
78
79 class Server(deferred_resource.DeferredResource):
80     def __init__(self, provider):
81         deferred_resource.DeferredResource.__init__(self)
82         self._provider = provider
83     
84     @defer.inlineCallbacks
85     def render_POST(self, request):
86         id_ = None
87         
88         try:
89             try:
90                 data = request.content.read()
91                 
92                 try:
93                     req = json.loads(data)
94                 except Exception:
95                     raise Error_for_code(-32700)(u'Parse error')
96                 
97                 id_ = req.get('id', None)
98                 method = req.get('method', None)
99                 if not isinstance(method, basestring):
100                     raise Error_for_code(-32600)(u'Invalid Request')
101                 params = req.get('params', [])
102                 if not isinstance(params, list):
103                     raise Error_for_code(-32600)(u'Invalid Request')
104                 
105                 method_meth = getattr(self._provider, 'rpc_' + method, None)
106                 if method_meth is None:
107                     raise Error_for_code(-32601)(u'Method not found')
108                 
109                 result = yield method_meth(request, *params)
110                 error = None
111             except Error:
112                 raise
113             except Exception:
114                 log.err(None, 'Squelched JSON error:')
115                 raise Error_for_code(-32099)(u'Unknown error')
116         except Error, e:
117             result = None
118             error = e._to_obj()
119         
120         data = json.dumps(dict(
121             jsonrpc='2.0',
122             id=id_,
123             result=result,
124             error=error,
125         ))
126         request.setHeader('Content-Type', 'application/json')
127         request.setHeader('Content-Length', len(data))
128         request.write(data)