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