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