blob: 86bc95052e709fe3c2136628bc34a17df98716ab [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(),
100 }
101 body = pprint.pformat(stats)
Antoine Pitrouf26f87e2010-10-13 11:27:09 +0000102 body = body.encode('utf-8')
103 self.send_response(200)
104 self.send_header("Content-type", "text/plain; charset=utf-8")
105 self.send_header("Content-Length", str(len(body)))
106 self.end_headers()
107 if send_body:
108 self.wfile.write(body)
109
110 def do_HEAD(self):
111 """Serve a HEAD request."""
112 self.do_GET(send_body=False)
113
114 def log_request(self, format, *args):
115 if support.verbose:
116 BaseHTTPRequestHandler.log_request(self, format, *args)
117
118
Antoine Pitrou803e6d62010-10-13 10:36:15 +0000119class HTTPSServerThread(threading.Thread):
120
121 def __init__(self, context, host=HOST, handler_class=None):
122 self.flag = None
123 self.server = HTTPSServer((host, 0),
124 handler_class or RootedHTTPRequestHandler,
125 context)
126 self.port = self.server.server_port
127 threading.Thread.__init__(self)
128 self.daemon = True
129
130 def __str__(self):
131 return "<%s %s>" % (self.__class__.__name__, self.server)
132
133 def start(self, flag=None):
134 self.flag = flag
135 threading.Thread.start(self)
136
137 def run(self):
138 if self.flag:
139 self.flag.set()
Antoine Pitroud2eca372010-10-29 23:41:37 +0000140 try:
141 self.server.serve_forever(0.05)
142 finally:
143 self.server.server_close()
Antoine Pitrou803e6d62010-10-13 10:36:15 +0000144
145 def stop(self):
146 self.server.shutdown()
147
148
149def make_https_server(case, certfile=CERTFILE, host=HOST, handler_class=None):
150 # we assume the certfile contains both private key and certificate
Antoine Pitrou48e00f32010-10-13 12:06:43 +0000151 context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
Antoine Pitrou803e6d62010-10-13 10:36:15 +0000152 context.load_cert_chain(certfile)
153 server = HTTPSServerThread(context, host, handler_class)
154 flag = threading.Event()
155 server.start(flag)
156 flag.wait()
157 def cleanup():
158 if support.verbose:
159 sys.stdout.write('stopping HTTPS server\n')
160 server.stop()
161 if support.verbose:
162 sys.stdout.write('joining HTTPS thread\n')
163 server.join()
164 case.addCleanup(cleanup)
165 return server
Antoine Pitrouf26f87e2010-10-13 11:27:09 +0000166
167
168if __name__ == "__main__":
169 import argparse
170 parser = argparse.ArgumentParser(
171 description='Run a test HTTPS server. '
172 'By default, the current directory is served.')
173 parser.add_argument('-p', '--port', type=int, default=4433,
174 help='port to listen on (default: %(default)s)')
175 parser.add_argument('-q', '--quiet', dest='verbose', default=True,
176 action='store_false', help='be less verbose')
177 parser.add_argument('-s', '--stats', dest='use_stats_handler', default=False,
178 action='store_true', help='always return stats page')
Antoine Pitrou923df6f2011-12-19 17:16:51 +0100179 parser.add_argument('--curve-name', dest='curve_name', type=str,
180 action='store',
181 help='curve name for EC-based Diffie-Hellman')
Antoine Pitrouf26f87e2010-10-13 11:27:09 +0000182 args = parser.parse_args()
183
184 support.verbose = args.verbose
185 if args.use_stats_handler:
186 handler_class = StatsRequestHandler
187 else:
188 handler_class = RootedHTTPRequestHandler
189 handler_class.root = os.getcwd()
190 context = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
191 context.load_cert_chain(CERTFILE)
Antoine Pitrou923df6f2011-12-19 17:16:51 +0100192 if args.curve_name:
193 context.set_ecdh_curve(args.curve_name)
Antoine Pitrouf26f87e2010-10-13 11:27:09 +0000194
195 server = HTTPSServer(("", args.port), handler_class, context)
Antoine Pitrou4a5f9672010-11-05 20:26:59 +0000196 if args.verbose:
197 print("Listening on https://localhost:{0.port}".format(args))
Antoine Pitrouf26f87e2010-10-13 11:27:09 +0000198 server.serve_forever(0.1)