HOME BLOG ARCHIVE TAGS

CherryPy-only HTTP and HTTPS app serving

September 02, 2016

CherryPy is a very good Python web framework. It provides its own threaded server, capable of delivering content on top of TLS. A perfect fit for multiple situations, specially when it’s cumbersome to support additional services.

But how do we run CherryPy apps over both HTTP and HTTPS protocols, using only a single engine instance, without extra infrastructure [1]?

TCP/IP servers listening on ports 80/443 are necessary:

  • app.conf
1
2
3
4
5
6
7
8
[global]
server.socket_host = "0.0.0.0"
server.socket_port = 443
server.ssl_module            : "builtin"
server.ssl_private_key       : "/path-to/app_key.pem"
server.ssl_certificate       : "/path-to/app_cert.pem"
server.ssl_certificate_chain : "/path-to/app_cert_chain.pem"
tools.force_tls.on           : True
  • app.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# -*- coding: utf-8 -*-
import os.path
import cherrypy

APP_CONF = os.path.join(os.path.dirname(__file__), "app.conf")

#
# IMPORTANT: an extra server instance also routes app through HTTP port; this extra
# server was used only as a quick way to basically serve /index through port 80;
# following "filter" redirects anything that comes from HTTP port to HTTPS one,
# rendering any clear-text route not reachable externally (including/*specially*
# /index); e.g., authentication can't happen in clear-text;
#
def force_tls():
    if cherrypy.request.scheme == "http":
        # see https://support.google.com/webmasters/answer/6073543?hl=en
        raise cherrypy.HTTPRedirect(cherrypy.url().replace("http:", "https:"),
                                    status=301)

cherrypy.tools.force_tls = cherrypy.Tool("before_handler", force_tls)

def load_http_server():
    # extra server instance to dispatch HTTP
    server = cherrypy._cpserver.Server()
    server.socket_host = "0.0.0.0"
    server.socket_port = 80
    server.subscribe()

class RootController:
    @cherrypy.expose()
    def index(self):
        return "hello world!"

def Root():
    return RootController()

def cherryd_main():
    cherrypy.tree.mount(Root(), "/", APP_CONF)

def dev_main():
    cherrypy.quickstart(Root(), config=APP_CONF)

load_http_server()

cherrypy.config.update(APP_CONF)

if __name__ == "__main__":
    dev_main()
else:
    cherryd_main()

Default setup prepares the app to run over TLS [2], securely; function load_http_server() takes care of the server bound to port 80.

It’s important to notice that a small custom tool is also required, to redirect all HTTP traffic to HTTPS.


Notes:
[1] - web servers like NGINX can work as reverse proxies, conveniently “shielding” services from direct access, and augmenting them with features like load-balancing, caching, and cryptography;
[2] - Root() route function is a factory not strictly needed here, but commonly used when developing with CherryPy;