python -m http.server is a very convenient command line tool to start an ad-hoc static web server. This tool is very useful when you want to develop small web applications or serve static files.
But it does not support SSL. A lot of modern web applications as well as browser features require a secured connection. So I want to wrap http.server in SSL and try making it simple to use.
Use http.server with SSL
I modified several functions from the module http.server and write a script ssl_server.py. I'll show you how to use the script and mkcert to serve a static sites with a self-signed SSL certificate.
The first step is to copy the ssl_server.py script to ~/.local/bin and make it executable.
1cp ssl_server.py ~/.local/bin
2chmod +x ~/.local/bin/ssl_server.py
Then install the mkcert tool. You can find the installation instructions on the mkcert GitHub page.
Generate a self-signed certificate and a private key using mkcert(for a domain like example.local).
The command will generate two files example.local.pem and example.local-key.pem. You can use these files to start the SSL server.
1~/.local/bin/ssl_server.py --cert example.local.pem --key example.local-key.pem --port 8443
Now that you have a static web server running on https://example.local:8443.
To visit the sites in your browser, you need to do 2 more things:
- add the domain
example.localto your/etc/hostsfile.
1echo "127.0.0.1 example.local" | sudo tee -a /etc/hosts
- Trust the certificate in your browser.
1# Install using mkcert
2mkcert -install
3
4# Or install manually from mkcert root CA directory
5mkcert -CAROOT
6# copy the root CA file
The ssl_server.py script
The script is as follows:
1#! /usr/bin/env python
2'''
3A wrapper around the standard library's http.server module that adds SSL support.
4'''
5import sys
6import os
7import socket
8import ssl
9from http.server import (
10 SimpleHTTPRequestHandler,
11 CGIHTTPRequestHandler,
12 ThreadingHTTPServer,
13 BaseHTTPRequestHandler,
14 _get_best_family
15)
16
17
18def test(HandlerClass=BaseHTTPRequestHandler,
19 ServerClass=ThreadingHTTPServer,
20 protocol="HTTP/1.0", port=8000, bind=None,
21 cert=None, key=None
22 ):
23 """Test the HTTP request handler class.
24
25 This runs an HTTP server on port 8000 (or the port argument).
26
27 """
28 ServerClass.address_family, addr = _get_best_family(bind, port)
29 HandlerClass.protocol_version = protocol
30 with ServerClass(addr, HandlerClass) as httpd:
31 if cert and key:
32 ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
33 ssl_context.load_cert_chain(cert, key)
34 httpd.socket = ssl_context.wrap_socket(httpd.socket, server_side=True)
35 host, port = httpd.socket.getsockname()[:2]
36 url_host = f'[{host}]' if ':' in host else host
37 print(
38 f"Serving HTTP on {host} port {port} "
39 f"(http://{url_host}:{port}/) ..."
40 )
41 try:
42 httpd.serve_forever()
43 except KeyboardInterrupt:
44 print("\nKeyboard interrupt received, exiting.")
45 sys.exit(0)
46
47if __name__ == '__main__':
48 import argparse
49 import contextlib
50
51 parser = argparse.ArgumentParser()
52 parser.add_argument('--cgi', action='store_true',
53 help='run as CGI server')
54 parser.add_argument('--bind', '-b', metavar='ADDRESS',
55 help='specify alternate bind address '
56 '(default: all interfaces)')
57 parser.add_argument('--directory', '-d', default=os.getcwd(),
58 help='specify alternate directory '
59 '(default: current directory)')
60 parser.add_argument('--port', action='store', default=8000, type=int,
61 nargs='?',
62 help='specify alternate port (default: 8000)')
63 parser.add_argument('--cert', help='specify a certificate file')
64 parser.add_argument('--key', help='specify a private key file')
65 args = parser.parse_args()
66 if args.cgi:
67 handler_class = CGIHTTPRequestHandler
68 else:
69 handler_class = SimpleHTTPRequestHandler
70
71 # ensure dual-stack is not disabled; ref #38907
72 class DualStackServer(ThreadingHTTPServer):
73
74 def server_bind(self):
75 # suppress exception when protocol is IPv4
76 with contextlib.suppress(Exception):
77 self.socket.setsockopt(
78 socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
79 return super().server_bind()
80
81 def finish_request(self, request, client_address):
82 self.RequestHandlerClass(request, client_address, self,
83 directory=args.directory)
84
85 test(
86 HandlerClass=handler_class,
87 ServerClass=DualStackServer,
88 port=args.port,
89 bind=args.bind,
90 cert=args.cert,
91 key=args.key
92 )
The script wraps the httpd.socket with an SSL socket if the cert and key options are provided.
The script keeps the same interface as http.server, but it adds two more options --cert and --key which are used to specify the certificate and the private key files.
Generally, you can use the script like this:
1python ssl_server.py --cert cert.pem --key key.pem
For convenience, you may put cert and key files in a directory and use a bash script to start the server.
1#!/bin/bash
2cert_dir=~/certs # change to your cert directory
3cert=$cert_dir/cert.pem
4key=$cert_dir/key.pem
5python ssl_server.py --cert $cert --key $key $@