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