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