HTTPS server in Python 3.x

2013 Oct 31

After running into this in a StackOverflow question, I did a quick Google search to see what others had come up with. The search brought up several results for Python 2 (naturally), but few in Python 3. The ones that were there, seemed unnecessarily convoluted or complex.

So, onward to the Python standard library documentation we go. At first notice, the various BaseHTTPRequestHandler and Co. are obviously not the way to go – they are interested with the HTTP layer and not the socket.

We have to dig deeper.

The ssl.wrap_socket documentation seems to be exactly what we are looking for - it takes a plain old socket and wraps it into an SSL context. It also allows you to specify certificate files, ciphers, client- or server-side behaviour and so forth. Brilliant. We only need to find a socket to wrap..

Python 3.3.2 (default, May 21 2013, 11:50:47)
[GCC 4.2.1 Compatible Apple Clang 4.1 ((tags/Apple/clang-421.11.66))] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import http.server
>>> h = http.server.HTTPServer(('localhost', 6593), http.server.SimpleHTTPRequestHandler)
>>> dir(h)
['RequestHandlerClass', '_BaseServer__is_shut_down', '_BaseServer__shutdown_request',
'__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__',
'__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__',
'__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__',
'__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__',
'_handle_request_noblock', 'address_family', 'allow_reuse_address', 'close_request',
'fileno', 'finish_request', 'get_request', 'handle_error', 'handle_request',
'handle_timeout', 'process_request', 'request_queue_size', 'serve_forever',
'server_activate', 'server_address', 'server_bind', 'server_close', 'server_name',
'server_port', 'service_actions', 'shutdown', 'shutdown_request', 'socket', 'socket_type',
'timeout', 'verify_request']

Aha! There is a socket we can use! Let’s see if we can. First, generate a certificate with openssl:

openssl req -new -x509 -keyout server.pem -out server.pem -days 365 -nodes

and create our HTTPS capable server:

import http.server, ssl

server_address = ('localhost', 4443)
httpd = http.server.HTTPServer(server_address, http.server.SimpleHTTPRequestHandler)
httpd.socket = ssl.wrap_socket(httpd.socket,
                               server_side=True,
                               certfile='server.pem',
                               ssl_version=ssl.PROTOCOL_TLSv1)
httpd.serve_forever()

Now let’s try to access it:

$ curl https://localhost:4443 --insecure --ssl-reqd
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Directory listing for /</title>
</head>
<body>
<h1>Directory listing for /</h1>
<hr>
<ul>
<li><a href="https.py">https.py</a></li>
<li><a href="server.pem">server.pem</a></li>
</ul>
<hr>
</body>
</html>

Huzzah.

« Past Future »

Tagged : Python, HTTPS, SSL, server