]>
Commit | Line | Data |
---|---|---|
53e6db90 DC |
1 | """A simple JSON-RPC-like server. |
2 | ||
3 | The server will read and write lines of JSON-encoded method calls and | |
4 | responses. | |
5 | ||
6 | See the documentation of the JSONRPCServer class for further details. | |
7 | ||
8 | """ | |
9 | ||
10 | import json | |
11 | import sys | |
12 | import traceback | |
13 | ||
14 | ||
15 | class JSONRPCServer(object): | |
16 | """Simple JSON-RPC-like server. | |
17 | ||
18 | This class will read single-line JSON expressions from stdin, | |
19 | decode them, and pass them to a handler. Return values from the | |
20 | handler will be JSON-encoded and written to stdout. | |
21 | ||
22 | To implement a handler, you need to subclass this class and add | |
23 | methods starting with "rpc_". Methods then will be found. | |
24 | ||
25 | Method calls should be encoded like this: | |
26 | ||
27 | {"id": 23, "method": "method_name", "params": ["foo", "bar"]} | |
28 | ||
29 | This will call self.rpc_method("foo", "bar"). | |
30 | ||
31 | Responses will be encoded like this: | |
32 | ||
33 | {"id": 23, "result": "foo"} | |
34 | ||
35 | Errors will be encoded like this: | |
36 | ||
37 | {"id": 23, "error": "Simple error message"} | |
38 | ||
39 | See http://www.jsonrpc.org/ for the inspiration of the protocol. | |
40 | ||
41 | """ | |
42 | ||
43 | def __init__(self, stdin=None, stdout=None): | |
44 | """Return a new JSON-RPC server object. | |
45 | ||
46 | It will read lines of JSON data from stdin, and write the | |
47 | responses to stdout. | |
48 | ||
49 | """ | |
50 | if stdin is None: | |
51 | self.stdin = sys.stdin | |
52 | else: | |
53 | self.stdin = stdin | |
54 | if stdout is None: | |
55 | self.stdout = sys.stdout | |
56 | else: | |
57 | self.stdout = stdout | |
58 | ||
59 | def read_json(self): | |
60 | """Read a single line and decode it as JSON. | |
61 | ||
62 | Can raise an EOFError() when the input source was closed. | |
63 | ||
64 | """ | |
65 | line = self.stdin.readline() | |
66 | if line == '': | |
67 | raise EOFError() | |
68 | return json.loads(line) | |
69 | ||
70 | def write_json(self, **kwargs): | |
71 | """Write an JSON object on a single line. | |
72 | ||
73 | The keyword arguments are interpreted as a single JSON object. | |
74 | It's not possible with this method to write non-objects. | |
75 | ||
76 | """ | |
77 | from elpy.json_encoder import JSONEncoder | |
78 | serialized_value = JSONEncoder().encode(kwargs) | |
79 | self.stdout.write(serialized_value + "\n") | |
80 | self.stdout.flush() | |
81 | ||
82 | def handle_request(self): | |
83 | """Handle a single JSON-RPC request. | |
84 | ||
85 | Read a request, call the appropriate handler method, and | |
86 | return the encoded result. Errors in the handler method are | |
87 | caught and encoded as error objects. Errors in the decoding | |
88 | phase are not caught, as we can not respond with an error | |
89 | response to them. | |
90 | ||
91 | """ | |
92 | request = self.read_json() | |
93 | if 'method' not in request: | |
94 | raise ValueError("Received a bad request: {0}" | |
95 | .format(request)) | |
96 | method_name = request['method'] | |
97 | request_id = request.get('id', None) | |
98 | params = request.get('params') or [] | |
99 | try: | |
100 | method = getattr(self, "rpc_" + method_name, None) | |
101 | if method is not None: | |
102 | result = method(*params) | |
103 | else: | |
104 | result = self.handle(method_name, params) | |
105 | if request_id is not None: | |
106 | self.write_json(result=result, | |
107 | id=request_id) | |
108 | except Fault as fault: | |
109 | error = {"message": fault.message, | |
110 | "code": fault.code} | |
111 | if fault.data is not None: | |
112 | error["data"] = fault.data | |
113 | self.write_json(error=error, id=request_id) | |
114 | except Exception as e: | |
115 | error = {"message": str(e), | |
116 | "code": 500, | |
117 | "data": {"traceback": traceback.format_exc()}} | |
118 | self.write_json(error=error, id=request_id) | |
119 | ||
120 | def handle(self, method_name, args): | |
121 | """Handle the call to method_name. | |
122 | ||
123 | You should overwrite this method in a subclass. | |
124 | """ | |
125 | raise Fault("Unknown method {0}".format(method_name)) | |
126 | ||
127 | def serve_forever(self): | |
128 | """Serve requests forever. | |
129 | ||
130 | Errors are not caught, so this is a slight misnomer. | |
131 | ||
132 | """ | |
133 | while True: | |
134 | try: | |
135 | self.handle_request() | |
136 | except (KeyboardInterrupt, EOFError, SystemExit): | |
137 | break | |
138 | ||
139 | ||
140 | class Fault(Exception): | |
141 | """RPC Fault instances. | |
142 | ||
143 | code defines the severity of the warning. | |
144 | ||
145 | 2xx: Normal behavior lead to end of operation, i.e. a warning | |
146 | 4xx: An expected error occurred | |
147 | 5xx: An unexpected error occurred (usually includes a traceback) | |
148 | """ | |
149 | def __init__(self, message, code=500, data=None): | |
150 | super(Fault, self).__init__(message) | |
151 | self.message = message | |
152 | self.code = code | |
153 | self.data = data |