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