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