xref: /spdk/scripts/rpc_http_proxy.py (revision 9944c802fb64f8e21bf469d7d37d988e1aaee55e)
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 rpc_call(req):
37    global rpc_sock
38
39    sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
40    sock.connect(rpc_sock)
41    sock.sendall(req)
42
43    if 'id' not in json.loads(req.decode('ascii')):
44        sock.close()
45        return None
46
47    buf = ''
48    closed = False
49    response = None
50
51    print_json(req.decode('ascii'))
52
53    while not closed:
54        newdata = sock.recv(1024)
55        if (newdata == b''):
56            closed = True
57        buf += newdata.decode('ascii')
58        try:
59            response = json.loads(buf)
60        except ValueError:
61            continue  # incomplete response; keep buffering
62        break
63
64    sock.close()
65
66    if not response and len(buf) > 0:
67        raise
68
69    print_json(buf)
70
71    return buf
72
73
74class ServerHandler(BaseHTTPRequestHandler):
75
76    key = ""
77
78    def do_HEAD(self):
79        self.send_response(200)
80        self.send_header('Content-type', 'text/html')
81        self.end_headers()
82
83    def do_AUTHHEAD(self):
84        self.send_response(401)
85        self.send_header('WWW-Authenticate', 'text/html')
86        self.send_header('Content-type', 'text/html')
87        self.end_headers()
88
89    def do_INTERNALERROR(self):
90        self.send_response(500)
91        self.send_header('Content-type', 'text/html')
92        self.end_headers()
93
94    def do_POST(self):
95        if self.headers['Authorization'] != 'Basic ' + self.key:
96            self.do_AUTHHEAD()
97        else:
98            if "Content-Length" in self.headers:
99                data_string = self.rfile.read(int(self.headers['Content-Length']))
100            elif "chunked" in self.headers.get("Transfer-Encoding", ""):
101                data_string = b''
102                while True:
103                    line = self.rfile.readline().strip()
104                    chunk_length = int(line, 16)
105
106                    if chunk_length != 0:
107                        chunk = self.rfile.read(chunk_length)
108                        data_string += chunk
109
110                    # Each chunk is followed by an additional empty newline
111                    # that we have to consume.
112                    self.rfile.readline()
113
114                    # Finally, a chunk size of 0 is an end indication
115                    if chunk_length == 0:
116                        break
117
118            try:
119                response = rpc_call(data_string)
120                if response is not None:
121                    self.do_HEAD()
122                    self.wfile.write(bytes(response.encode(encoding='ascii')))
123            except ValueError:
124                self.do_INTERNALERROR()
125
126
127def main():
128    global rpc_sock
129
130    args = parser.parse_args()
131    rpc_sock = args.sock
132
133    # encoding user name and password
134    key = base64.b64encode((args.user+':'+args.password).encode(encoding='ascii')).decode('ascii')
135
136    try:
137        ServerHandler.key = key
138        httpd = HTTPServer((args.host, args.port), ServerHandler)
139        if args.cert is not None:
140            httpd.socket = ssl.wrap_socket(httpd.socket, certfile=args.cert, server_side=True)
141        print('Started RPC http proxy server')
142        httpd.serve_forever()
143    except KeyboardInterrupt:
144        print('Shutting down server')
145        httpd.socket.close()
146
147
148if __name__ == '__main__':
149    main()
150