json error handling
[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):
30         self._url = url
31         self._auth = auth
32     
33     @defer.inlineCallbacks
34     def callRemote(self, method, *params):
35         id_ = 0
36         
37         headers = {
38             'Content-Type': 'text/json',
39         }
40         if self._auth is not None:
41             headers['Authorization'] = 'Basic ' + base64.b64encode(':'.join(self._auth))
42         try:
43             data = yield client.getPage(
44                 url=self._url,
45                 method='POST',
46                 headers=headers,
47                 postdata=json.dumps({
48                     'jsonrpc': '2.0',
49                     'method': method,
50                     'params': params,
51                     'id': id_,
52                 }),
53             )
54         except error.Error, e:
55             resp = json.loads(e.response)
56         else:
57             resp = json.loads(data)
58         
59         if resp['id'] != id_:
60             raise ValueError('invalid id')
61         if 'error' in resp and resp['error'] is not None:
62             raise Error(**resp['error'])
63         defer.returnValue(resp['result'])
64     
65     def __getattr__(self, attr):
66         if attr.startswith('rpc_'):
67             return lambda *params: self.callRemote(attr[len('rpc_'):], *params)
68         raise AttributeError('%r object has no attribute %r' % (self.__class__.__name__, attr))
69
70 class Server(deferred_resource.DeferredResource):
71     extra_headers = None
72     
73     @defer.inlineCallbacks
74     def render_POST(self, request):
75         # missing batching, 1.0 notifications
76         data = request.content.read()
77         
78         if self.extra_headers is not None:
79             for name, value in self.extra_headers.iteritems():
80                 request.setHeader(name, value)
81         
82         try:
83             try:
84                 req = json.loads(data)
85             except Exception:
86                 raise RemoteError(-32700, u'Parse error')
87         except Error, e:
88             # id unknown
89             request.write(json.dumps({
90                 'jsonrpc': '2.0',
91                 'id': None,
92                 'result': None,
93                 'error': e._to_obj(),
94             }))
95         
96         id_ = req.get('id', None)
97         
98         try:
99             try:
100                 method = req['method']
101                 if not isinstance(method, unicode):
102                     raise ValueError()
103                 params = req.get('params', [])
104                 if not isinstance(params, list):
105                     raise ValueError()
106             except Exception:
107                 raise Error(-32600, u'Invalid Request')
108             
109             method_name = 'rpc_' + method
110             if not hasattr(self, method_name):
111                 raise Error(-32601, u'Method not found')
112             method_meth = getattr(self, method_name)
113             
114             if hasattr(method_meth, "takes_headers"):
115                 params = [request.received_headers] + list(params)
116             
117             try:
118                 result = yield method_meth(*params)
119             except Exception:
120                 print 'Squelched JSON method error:'
121                 log.err()
122                 raise Error(-32099, u'Unknown error')
123             
124             if id_ is None:
125                 return
126             
127             request.write(json.dumps({
128                 'jsonrpc': '2.0',
129                 'id': id_,
130                 'result': result,
131                 'error': None,
132             }))
133         except Error, e:
134             request.write(json.dumps({
135                 'jsonrpc': '2.0',
136                 'id': id_,
137                 'result': None,
138                 'error': e._to_obj(),
139             }))