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