xref: /spdk/scripts/rpc_http_proxy.py (revision 9944c802fb64f8e21bf469d7d37d988e1aaee55e)
15a7f29ddSMaciej Szwed#!/usr/bin/env python3
2*17538bdcSpaul luse#  SPDX-License-Identifier: BSD-3-Clause
3*17538bdcSpaul luse#  Copyright (C) 2018 Intel Corporation
4*17538bdcSpaul luse#  All rights reserved.
5*17538bdcSpaul luse#
65a7f29ddSMaciej Szwed
72bd00162SMaciej Szwedimport argparse
85a7f29ddSMaciej Szwedimport base64
95a7f29ddSMaciej Szwedimport errno
105a7f29ddSMaciej Szwedimport json
117610bc38SKonrad Sztyberimport os
125a7f29ddSMaciej Szwedimport socket
132bd00162SMaciej Szwedimport ssl
145a7f29ddSMaciej Szwedimport sys
155a7f29ddSMaciej Szwedtry:
165a7f29ddSMaciej Szwed    from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
175a7f29ddSMaciej Szwedexcept ImportError:
185a7f29ddSMaciej Szwed    from http.server import HTTPServer
195a7f29ddSMaciej Szwed    from http.server import BaseHTTPRequestHandler
207610bc38SKonrad Sztyber
217610bc38SKonrad Sztybersys.path.append(os.path.dirname(__file__) + '/../python')
227610bc38SKonrad Sztyber
237610bc38SKonrad Sztyberfrom spdk.rpc.client import print_json  # noqa
245a7f29ddSMaciej Szwed
255a7f29ddSMaciej Szwedrpc_sock = None
265a7f29ddSMaciej Szwed
272bd00162SMaciej Szwedparser = argparse.ArgumentParser(description='http(s) proxy for SPDK RPC calls')
282bd00162SMaciej Szwedparser.add_argument('host', help='Host name / IP representing proxy server')
292bd00162SMaciej Szwedparser.add_argument('port', help='Port number', type=int)
302bd00162SMaciej Szwedparser.add_argument('user', help='User name used for authentication')
312bd00162SMaciej Szwedparser.add_argument('password', help='Password used for authentication')
322bd00162SMaciej Szwedparser.add_argument('-s', dest='sock', help='RPC domain socket path', default='/var/tmp/spdk.sock')
332bd00162SMaciej Szwedparser.add_argument('-c', dest='cert', help='SSL certificate')
342bd00162SMaciej Szwed
355a7f29ddSMaciej Szwed
365a7f29ddSMaciej Szweddef rpc_call(req):
375a7f29ddSMaciej Szwed    global rpc_sock
385a7f29ddSMaciej Szwed
395a7f29ddSMaciej Szwed    sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
405a7f29ddSMaciej Szwed    sock.connect(rpc_sock)
415a7f29ddSMaciej Szwed    sock.sendall(req)
425a7f29ddSMaciej Szwed
435a7f29ddSMaciej Szwed    if 'id' not in json.loads(req.decode('ascii')):
445a7f29ddSMaciej Szwed        sock.close()
455a7f29ddSMaciej Szwed        return None
465a7f29ddSMaciej Szwed
475a7f29ddSMaciej Szwed    buf = ''
485a7f29ddSMaciej Szwed    closed = False
495a7f29ddSMaciej Szwed    response = None
505a7f29ddSMaciej Szwed
518770e80bSMichal Berger    print_json(req.decode('ascii'))
528770e80bSMichal Berger
535a7f29ddSMaciej Szwed    while not closed:
545a7f29ddSMaciej Szwed        newdata = sock.recv(1024)
555a7f29ddSMaciej Szwed        if (newdata == b''):
565a7f29ddSMaciej Szwed            closed = True
575a7f29ddSMaciej Szwed        buf += newdata.decode('ascii')
585a7f29ddSMaciej Szwed        try:
595a7f29ddSMaciej Szwed            response = json.loads(buf)
605a7f29ddSMaciej Szwed        except ValueError:
615a7f29ddSMaciej Szwed            continue  # incomplete response; keep buffering
625a7f29ddSMaciej Szwed        break
635a7f29ddSMaciej Szwed
645a7f29ddSMaciej Szwed    sock.close()
655a7f29ddSMaciej Szwed
665a7f29ddSMaciej Szwed    if not response and len(buf) > 0:
675a7f29ddSMaciej Szwed        raise
685a7f29ddSMaciej Szwed
698770e80bSMichal Berger    print_json(buf)
708770e80bSMichal Berger
715a7f29ddSMaciej Szwed    return buf
725a7f29ddSMaciej Szwed
735a7f29ddSMaciej Szwed
745a7f29ddSMaciej Szwedclass ServerHandler(BaseHTTPRequestHandler):
755a7f29ddSMaciej Szwed
765a7f29ddSMaciej Szwed    key = ""
775a7f29ddSMaciej Szwed
785a7f29ddSMaciej Szwed    def do_HEAD(self):
795a7f29ddSMaciej Szwed        self.send_response(200)
805a7f29ddSMaciej Szwed        self.send_header('Content-type', 'text/html')
815a7f29ddSMaciej Szwed        self.end_headers()
825a7f29ddSMaciej Szwed
835a7f29ddSMaciej Szwed    def do_AUTHHEAD(self):
845a7f29ddSMaciej Szwed        self.send_response(401)
855a7f29ddSMaciej Szwed        self.send_header('WWW-Authenticate', 'text/html')
865a7f29ddSMaciej Szwed        self.send_header('Content-type', 'text/html')
875a7f29ddSMaciej Szwed        self.end_headers()
885a7f29ddSMaciej Szwed
895a7f29ddSMaciej Szwed    def do_INTERNALERROR(self):
905a7f29ddSMaciej Szwed        self.send_response(500)
915a7f29ddSMaciej Szwed        self.send_header('Content-type', 'text/html')
925a7f29ddSMaciej Szwed        self.end_headers()
935a7f29ddSMaciej Szwed
945a7f29ddSMaciej Szwed    def do_POST(self):
955a7f29ddSMaciej Szwed        if self.headers['Authorization'] != 'Basic ' + self.key:
965a7f29ddSMaciej Szwed            self.do_AUTHHEAD()
975a7f29ddSMaciej Szwed        else:
98637f1a10SBoris Glimcher            if "Content-Length" in self.headers:
995a7f29ddSMaciej Szwed                data_string = self.rfile.read(int(self.headers['Content-Length']))
100637f1a10SBoris Glimcher            elif "chunked" in self.headers.get("Transfer-Encoding", ""):
101637f1a10SBoris Glimcher                data_string = b''
102637f1a10SBoris Glimcher                while True:
103637f1a10SBoris Glimcher                    line = self.rfile.readline().strip()
104637f1a10SBoris Glimcher                    chunk_length = int(line, 16)
105637f1a10SBoris Glimcher
106637f1a10SBoris Glimcher                    if chunk_length != 0:
107637f1a10SBoris Glimcher                        chunk = self.rfile.read(chunk_length)
108637f1a10SBoris Glimcher                        data_string += chunk
109637f1a10SBoris Glimcher
110637f1a10SBoris Glimcher                    # Each chunk is followed by an additional empty newline
111637f1a10SBoris Glimcher                    # that we have to consume.
112637f1a10SBoris Glimcher                    self.rfile.readline()
113637f1a10SBoris Glimcher
114637f1a10SBoris Glimcher                    # Finally, a chunk size of 0 is an end indication
115637f1a10SBoris Glimcher                    if chunk_length == 0:
116637f1a10SBoris Glimcher                        break
1175a7f29ddSMaciej Szwed
1185a7f29ddSMaciej Szwed            try:
1195a7f29ddSMaciej Szwed                response = rpc_call(data_string)
1205a7f29ddSMaciej Szwed                if response is not None:
1215a7f29ddSMaciej Szwed                    self.do_HEAD()
1225a7f29ddSMaciej Szwed                    self.wfile.write(bytes(response.encode(encoding='ascii')))
1235a7f29ddSMaciej Szwed            except ValueError:
1245a7f29ddSMaciej Szwed                self.do_INTERNALERROR()
1255a7f29ddSMaciej Szwed
1265a7f29ddSMaciej Szwed
1275a7f29ddSMaciej Szweddef main():
1285a7f29ddSMaciej Szwed    global rpc_sock
1295a7f29ddSMaciej Szwed
1302bd00162SMaciej Szwed    args = parser.parse_args()
1312bd00162SMaciej Szwed    rpc_sock = args.sock
1325a7f29ddSMaciej Szwed
1335a7f29ddSMaciej Szwed    # encoding user name and password
1342bd00162SMaciej Szwed    key = base64.b64encode((args.user+':'+args.password).encode(encoding='ascii')).decode('ascii')
1355a7f29ddSMaciej Szwed
1365a7f29ddSMaciej Szwed    try:
1375a7f29ddSMaciej Szwed        ServerHandler.key = key
1382bd00162SMaciej Szwed        httpd = HTTPServer((args.host, args.port), ServerHandler)
1392bd00162SMaciej Szwed        if args.cert is not None:
1402bd00162SMaciej Szwed            httpd.socket = ssl.wrap_socket(httpd.socket, certfile=args.cert, server_side=True)
1415a7f29ddSMaciej Szwed        print('Started RPC http proxy server')
1425a7f29ddSMaciej Szwed        httpd.serve_forever()
1435a7f29ddSMaciej Szwed    except KeyboardInterrupt:
1445a7f29ddSMaciej Szwed        print('Shutting down server')
1455a7f29ddSMaciej Szwed        httpd.socket.close()
1465a7f29ddSMaciej Szwed
1475a7f29ddSMaciej Szwed
1485a7f29ddSMaciej Szwedif __name__ == '__main__':
1495a7f29ddSMaciej Szwed    main()
150