blob: 8686153a17f21e99cccfb3a72af3e85abdd12a1a [file] [log] [blame]
Antoine Pitrou803e6d62010-10-13 10:36:15 +00001import os
2import sys
3import ssl
Antoine Pitrouf26f87e2010-10-13 11:27:09 +00004import pprint
Antoine Pitrou84fa4312010-10-13 11:51:05 +00005import socket
Antoine Pitrou803e6d62010-10-13 10:36:15 +00006import urllib.parse
7# Rename HTTPServer to _HTTPServer so as to avoid confusion with HTTPSServer.
Antoine Pitrouf26f87e2010-10-13 11:27:09 +00008from http.server import (HTTPServer as _HTTPServer,
9 SimpleHTTPRequestHandler, BaseHTTPRequestHandler)
Antoine Pitrou803e6d62010-10-13 10:36:15 +000010
11from test import support
Antoine Pitrou66c95c72010-11-05 20:17:55 +000012threading = support.import_module("threading")
Antoine Pitrou803e6d62010-10-13 10:36:15 +000013
14here = os.path.dirname(__file__)
15
16HOST = support.HOST
17CERTFILE = os.path.join(here, 'keycert.pem')
18
19# This one's based on HTTPServer, which is based on SocketServer
20
21class HTTPSServer(_HTTPServer):
22
23 def __init__(self, server_address, handler_class, context):
24 _HTTPServer.__init__(self, server_address, handler_class)
25 self.context = context
26
27 def __str__(self):
28 return ('<%s %s:%s>' %
29 (self.__class__.__name__,
30 self.server_name,
31 self.server_port))
32
33 def get_request(self):
34 # override this to wrap socket with SSL
Antoine Pitrou84fa4312010-10-13 11:51:05 +000035 try:
36 sock, addr = self.socket.accept()
37 sslconn = self.context.wrap_socket(sock, server_side=True)
38 except socket.error as e:
39 # socket errors are silenced by the caller, print them here
40 if support.verbose:
41 sys.stderr.write("Got an error:\n%s\n" % e)
42 raise
Antoine Pitrou803e6d62010-10-13 10:36:15 +000043 return sslconn, addr
44
45class RootedHTTPRequestHandler(SimpleHTTPRequestHandler):
46 # need to override translate_path to get a known root,
47 # instead of using os.curdir, since the test could be
48 # run from anywhere
49
50 server_version = "TestHTTPS/1.0"
51 root = here
52 # Avoid hanging when a request gets interrupted by the client
53 timeout = 5
54
55 def translate_path(self, path):
56 """Translate a /-separated PATH to the local filename syntax.
57
58 Components that mean special things to the local file system
59 (e.g. drive or directory names) are ignored. (XXX They should
60 probably be diagnosed.)
61
62 """
63 # abandon query parameters
64 path = urllib.parse.urlparse(path)[2]
65 path = os.path.normpath(urllib.parse.unquote(path))
66 words = path.split('/')
67 words = filter(None, words)
68 path = self.root
69 for word in words:
70 drive, word = os.path.splitdrive(word)
71 head, word = os.path.split(word)
72 path = os.path.join(path, word)
73 return path
74
75 def log_message(self, format, *args):
76 # we override this to suppress logging unless "verbose"
77 if support.verbose:
78 sys.stdout.write(" server (%s:%d %s):\n [%s] %s\n" %
79 (self.server.server_address,
80 self.server.server_port,
81 self.request.cipher(),
82 self.log_date_time_string(),
83 format%args))
84
Antoine Pitrouf26f87e2010-10-13 11:27:09 +000085
86class StatsRequestHandler(BaseHTTPRequestHandler):
87 """Example HTTP request handler which returns SSL statistics on GET
88 requests.
89 """
90
91 server_version = "StatsHTTPS/1.0"
92
93 def do_GET(self, send_body=True):
94 """Serve a GET request."""
95 sock = self.rfile.raw._sock
96 context = sock.context
Antoine Pitroufb0901c2011-12-18 19:00:02 +010097 stats = {
98 'session_cache': context.session_stats(),
99 'cipher': sock.cipher(),
Antoine Pitrou8abdb8a2011-12-20 10:13:40 +0100100 'compression': sock.compression(),
Antoine Pitroufb0901c2011-12-18 19:00:02 +0100101 }
102 body = pprint.pformat(stats)
Antoine Pitrouf26f87e2010-10-13 11:27:09 +0000103 body = body.encode('utf-8')
104 self.send_response(200)
105 self.send_header("Content-type", "text/plain; charset=utf-8")
106 self.send_header("Content-Length", str(len(body)))
107 self.end_headers()
108 if send_body:
109 self.wfile.write(body)
110
111 def do_HEAD(self):
112 """Serve a HEAD request."""
113 self.do_GET(send_body=False)
114
115 def log_request(self, format, *args):
116 if support.verbose:
117 BaseHTTPRequestHandler.log_request(self, format, *args)
118
119
Antoine Pitrou803e6d62010-10-13 10:36:15 +0000120class HTTPSServerThread(threading.Thread):
121
122 def __init__(self, context, host=HOST, handler_class=None):
123 self.flag = None
124 self.server = HTTPSServer((host, 0),
125 handler_class or RootedHTTPRequestHandler,
126 context)
127 self.port = self.server.server_port
128 threading.Thread.__init__(self)
129 self.daemon = True
130
131 def __str__(self):
132 return "<%s %s>" % (self.__class__.__name__, self.server)
133
134 def start(self, flag=None):
135 self.flag = flag
136 threading.Thread.start(self)
137
138 def run(self):
139 if self.flag:
140 self.flag.set()
Antoine Pitroud2eca372010-10-29 23:41:37 +0000141 try:
142 self.server.serve_forever(0.05)
143 finally:
144 self.server.server_close()
Antoine Pitrou803e6d62010-10-13 10:36:15 +0000145
146 def stop(self):
147 self.server.shutdown()
148
149
150def make_https_server(case, certfile=CERTFILE, host=HOST, handler_class=None):
151 # we assume the certfile contains both private key and certificate
Antoine Pitrou48e00f32010-10-13 12:06:43 +0000152 context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
Antoine Pitrou803e6d62010-10-13 10:36:15 +0000153 context.load_cert_chain(certfile)
154 server = HTTPSServerThread(context, host, handler_class)
155 flag = threading.Event()
156 server.start(flag)
157 flag.wait()
158 def cleanup():
159 if support.verbose:
160 sys.stdout.write('stopping HTTPS server\n')
161 server.stop()
162 if support.verbose:
163 sys.stdout.write('joining HTTPS thread\n')
164 server.join()
165 case.addCleanup(cleanup)
166 return server
Antoine Pitrouf26f87e2010-10-13 11:27:09 +0000167
168
169if __name__ == "__main__":
170 import argparse
171 parser = argparse.ArgumentParser(
172 description='Run a test HTTPS server. '
173 'By default, the current directory is served.')
174 parser.add_argument('-p', '--port', type=int, default=4433,
175 help='port to listen on (default: %(default)s)')
176 parser.add_argument('-q', '--quiet', dest='verbose', default=True,
177 action='store_false', help='be less verbose')
178 parser.add_argument('-s', '--stats', dest='use_stats_handler', default=False,
179 action='store_true', help='always return stats page')
Antoine Pitrou923df6f2011-12-19 17:16:51 +0100180 parser.add_argument('--curve-name', dest='curve_name', type=str,
181 action='store',
182 help='curve name for EC-based Diffie-Hellman')
Antoine Pitrou0e576f12011-12-22 10:03:38 +0100183 parser.add_argument('--dh', dest='dh_file', type=str, action='store',
184 help='PEM file containing DH parameters')
Antoine Pitrouf26f87e2010-10-13 11:27:09 +0000185 args = parser.parse_args()
186
187 support.verbose = args.verbose
188 if args.use_stats_handler:
189 handler_class = StatsRequestHandler
190 else:
191 handler_class = RootedHTTPRequestHandler
192 handler_class.root = os.getcwd()
193 context = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
194 context.load_cert_chain(CERTFILE)
Antoine Pitrou923df6f2011-12-19 17:16:51 +0100195 if args.curve_name:
196 context.set_ecdh_curve(args.curve_name)
Antoine Pitrou0e576f12011-12-22 10:03:38 +0100197 if args.dh_file:
198 context.load_dh_params(args.dh_file)
Antoine Pitrouf26f87e2010-10-13 11:27:09 +0000199
200 server = HTTPSServer(("", args.port), handler_class, context)
Antoine Pitrou4a5f9672010-11-05 20:26:59 +0000201 if args.verbose:
202 print("Listening on https://localhost:{0.port}".format(args))
Antoine Pitrouf26f87e2010-10-13 11:27:09 +0000203 server.serve_forever(0.1)