No, we will not use host matching! Instead, we’ll use our own architecture.
The title makes the content of this article obvious, so let’s jump right into it. This method might be a little tedious for high-traffic websites, but if you have a handful of low-traffic websites and want to host them under a single entity (such as a Heroku dyno), this might save you the cost of registering a distinct entity for each website.
Creating the skeleton project
We start by creating a regular Python folder with VENV and whatnot.
Next, we import Flask and Waitress using pip. You can pick another web server (such as Gunicorn) if you like.
We create the following file / folder structure under the project folder.
- /
- app.py
- domain.json
- static
- domain1
- index.html
- domain2
- index.html
- domain1
- templates
- domain3
- index.html
- about.html
- contact.html
- domain3
app.py is the entry point of the website. You can rename it if you like.
domain.json will contain paths for each domain.
“Static” and “Templates” folders are required by Flask. You can pick other names for those folders, but let’s keep things simple. We are assuming that two domains will be hosted with static files, and one will be hosted with page templates. You can mix and match however you want.
domain1 / 2 / 3 are obviously example folder names.
domain.json
Structure of this file will look like this:
{
"domains": [
{
"hostname": "domain1.com",
"paths":
[{ "path": "",
"static": "domain1/index.html" }]
},
{
"hostname": "domain2.com",
"paths":
[{ "path": "",
"static": "domain2/index.html" }]
},
{
"hostname": "domain3.com",
"paths": [
{ "path": "",
"template": "domain3/index.html" },
{ "path": "about",
"template": "domain3/about.html" },
{ "path": "contact",
"template": "domain3/contact.html" }
]
}
]
}
The content of this file is pretty intuitive. For each domain, we are enlisting the paths and where they should point to. Note that I gave different JSON tags to separate static folder from templates.
In this example;
- domain1.com will point to /static/domain1/index.html
- domain2.com will point to /static/domain2/index.html
- domain3.com will point to /template/domain3/index.html
- domain3.com/about will point to /template/domain3/about.html
- domain3.com/contact will point to /template/domain3/contact.html
app.py
Here is where the magic happens. Check the following example code:
import os
import json
from urllib.parse import urlparse
from flask import Flask, render_template, request
from waitress import serve
##############################
# APPLICATION
##############################
_APP = Flask(__name__)
@_APP.route("/", defaults={'path': ''}, methods=['GET', 'POST'])
@_APP.route("/<path:path>", methods=['GET', 'POST'])
def any_route(path):
# Known paths
hostname = urlparse(request.base_url).hostname
if hostname == "localhost":
pass # hostname = "domain1.com" - for local tests
with open("domain.json") as domain_file:
domains = json.load(domain_file)
for domain in domains["domains"]:
if domain["hostname"] not in hostname:
continue
for domain_path in domain["paths"]:
if path != domain_path["path"]:
continue
if "template" in domain_path:
return render_template(domain_path["template"])
if "static" in domain_path:
return _APP.send_static_file(domain_path["static"])
# Unknown path
return "Page not found"
##############################
# STARTUP
##############################
if __name__ == "__main__":
serve(_APP, port=5000)
You can extend this file to read environment values, do security checks, generate JSON files, etc.; but you get the basic idea. We have a single entry point (any_route), which extracts the domain name and reads domain.json to determine what should be rendered & returned.
Links in html files
In /static/domain1/index.html, links to static files would look like this:
<link rel="stylesheet" href="/static/domain1/style.css">
<img src="/static/domain1/logo.png">
In /template/domain3/index.html, links to static files would look like this (we are assuming to borrow an image from domain1):
<img src="{{url_for('static', filename='domain1/logo01.png')}}">
Pro tip: You can put common libraries from under the static folder and share among domains. For example; you can create the folder /static/bootstrap , put all bootstrap files underneath.
Static HTML files may access bootstrap like:
<script src="/static/bootstrap/js/bootstrap.bundle.min.js" />
Templates may access bootstrap like:
<script src="{{url_for('static', filename='bootstrap/js/bootstrap.bundle.min.js')}}" />
Live usage
As we speak, I am hosting 5 distinct low-traffic websites under a single Heroku dyno using this architecture, and they work just fine. There are many cloud providers where you can host your Python website. I like the simplicity of Heroku and have an article on how to deploy your Python website to Heroku.
You need to be careful about your resources though. Every time you change something and publish to the cloud, you would be publishing all of your websites, entirely!
Leave a Reply