• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3.4
2#
3#   Copyright 2016- Google, Inc.
4#
5#   Licensed under the Apache License, Version 2.0 (the "License");
6#   you may not use this file except in compliance with the License.
7#   You may obtain a copy of the License at
8#
9#       http://www.apache.org/licenses/LICENSE-2.0
10#
11#   Unless required by applicable law or agreed to in writing, software
12#   distributed under the License is distributed on an "AS IS" BASIS,
13#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14#   See the License for the specific language governing permissions and
15#   limitations under the License.
16
17"""
18A simple JSON RPC client.
19"""
20import json
21import time
22from urllib import request
23
24class HTTPError(Exception):
25    pass
26
27class RemoteError(Exception):
28    pass
29
30def JSONCounter():
31    """A counter that generates JSON RPC call IDs.
32
33    Follows the increasing integer sequence. Every time this function is
34    called, the next number in the sequence is returned.
35    """
36    i = 0
37    while True:
38        yield i
39        i += 1
40
41class JSONRPCClient:
42    COUNTER = JSONCounter()
43    headers = {'content-type': 'application/json'}
44    def __init__(self, baseurl):
45        self._baseurl = baseurl
46
47    def call(self, path, methodname=None, *args):
48        """Wrapper for the internal _call method.
49
50        A retry is performed if the initial call fails to compensate for
51        unstable networks.
52
53        Params:
54            path: Path of the rpc service to be appended to the base url.
55            methodname: Method name of the RPC call.
56            args: A tuple of arguments for the RPC call.
57
58        Returns:
59            The returned message of the JSON RPC call from the server.
60        """
61        try:
62            return self._call(path, methodname, *args)
63        except:
64            # Take five and try again
65            time.sleep(5)
66            return self._call(path, methodname, *args)
67
68    def _post_json(self, url, payload):
69        """Performs an HTTP POST request with a JSON payload.
70
71        Params:
72            url: The full URL to post the payload to.
73            payload: A JSON string to be posted to server.
74
75        Returns:
76            The HTTP response code and text.
77        """
78        req = request.Request(url)
79        req.add_header('Content-Type', 'application/json')
80        resp = request.urlopen(req, data=payload.encode("utf-8"))
81        txt = resp.read()
82        return resp.code, txt.decode('utf-8')
83
84    def _call(self, path, methodname=None, *args):
85        """Performs a JSON RPC call and return the response.
86
87        Params:
88            path: Path of the rpc service to be appended to the base url.
89            methodname: Method name of the RPC call.
90            args: A tuple of arguments for the RPC call.
91
92        Returns:
93            The returned message of the JSON RPC call from the server.
94
95        Raises:
96            HTTPError: Raised if the http post return code is not 200.
97            RemoteError: Raised if server returned an error.
98        """
99        jsonid = next(JSONRPCClient.COUNTER)
100        payload = json.dumps({"method": methodname,
101                              "params": args,
102                              "id": jsonid})
103        url = self._baseurl + path
104        status_code, text = self._post_json(url, payload)
105        if status_code != 200:
106            raise HTTPError(text)
107        r = json.loads(text)
108        if r['error']:
109            raise RemoteError(r['error'])
110        return r['result']
111
112    def sys(self, *args):
113        return self.call("sys", *args)
114
115    def __getattr__(self, name):
116        def rpc_call(*args):
117            return self.call('uci', name, *args)
118        return rpc_call
119