xref: /spdk/scripts/rpc_http_proxy.py (revision 0ed85362c8132a2d1927757fbcade66b6660d26a)
1#!/usr/bin/env python3
2
3import argparse
4import base64
5import errno
6import json
7import socket
8import ssl
9import sys
10try:
11    from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
12except ImportError:
13    from http.server import HTTPServer
14    from http.server import BaseHTTPRequestHandler
15
16rpc_sock = None
17
18parser = argparse.ArgumentParser(description='http(s) proxy for SPDK RPC calls')
19parser.add_argument('host', help='Host name / IP representing proxy server')
20parser.add_argument('port', help='Port number', type=int)
21parser.add_argument('user', help='User name used for authentication')
22parser.add_argument('password', help='Password used for authentication')
23parser.add_argument('-s', dest='sock', help='RPC domain socket path', default='/var/tmp/spdk.sock')
24parser.add_argument('-c', dest='cert', help='SSL certificate')
25
26
27def print_usage_and_exit(status):
28    print('Usage: rpc_http_proxy.py <server IP> <server port> <user name>' +
29          ' <password> <SPDK RPC socket (optional, default: /var/tmp/spdk.sock)>')
30    sys.exit(status)
31
32
33def rpc_call(req):
34    global rpc_sock
35
36    sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
37    sock.connect(rpc_sock)
38    sock.sendall(req)
39
40    if 'id' not in json.loads(req.decode('ascii')):
41        sock.close()
42        return None
43
44    buf = ''
45    closed = False
46    response = None
47
48    while not closed:
49        newdata = sock.recv(1024)
50        if (newdata == b''):
51            closed = True
52        buf += newdata.decode('ascii')
53        try:
54            response = json.loads(buf)
55        except ValueError:
56            continue  # incomplete response; keep buffering
57        break
58
59    sock.close()
60
61    if not response and len(buf) > 0:
62        raise
63
64    return buf
65
66
67class ServerHandler(BaseHTTPRequestHandler):
68
69    key = ""
70
71    def do_HEAD(self):
72        self.send_response(200)
73        self.send_header('Content-type', 'text/html')
74        self.end_headers()
75
76    def do_AUTHHEAD(self):
77        self.send_response(401)
78        self.send_header('WWW-Authenticate', 'text/html')
79        self.send_header('Content-type', 'text/html')
80        self.end_headers()
81
82    def do_INTERNALERROR(self):
83        self.send_response(500)
84        self.send_header('Content-type', 'text/html')
85        self.end_headers()
86
87    def do_POST(self):
88        if self.headers['Authorization'] != 'Basic ' + self.key:
89            self.do_AUTHHEAD()
90        else:
91            data_string = self.rfile.read(int(self.headers['Content-Length']))
92
93            try:
94                response = rpc_call(data_string)
95                if response is not None:
96                    self.do_HEAD()
97                    self.wfile.write(bytes(response.encode(encoding='ascii')))
98            except ValueError:
99                self.do_INTERNALERROR()
100
101
102def main():
103    global rpc_sock
104
105    args = parser.parse_args()
106    rpc_sock = args.sock
107
108    # encoding user name and password
109    key = base64.b64encode((args.user+':'+args.password).encode(encoding='ascii')).decode('ascii')
110
111    try:
112        ServerHandler.key = key
113        httpd = HTTPServer((args.host, args.port), ServerHandler)
114        if args.cert is not None:
115            httpd.socket = ssl.wrap_socket(httpd.socket, certfile=args.cert, server_side=True)
116        print('Started RPC http proxy server')
117        httpd.serve_forever()
118    except KeyboardInterrupt:
119        print('Shutting down server')
120        httpd.socket.close()
121
122
123if __name__ == '__main__':
124    main()
125