HTMX & Nginx

Page content

Little Test with HTMX & Nginx

recently, i saw the Keynote - “Full-Stack Python” (Andy “Pandy” Knight) and i read an article about html & websockets. So I thought why not give it a try?

Preview

Requirements

the usual stuff:

  • Virtual Machine (here: OpenBSD VM)
  • FQDN Pointing to your Box
  • SSL Cert

Webroot

on your webserver, create a new webroot wherever you have your pages located.

su - webmaster
mkdir -p /var/www/virtual/your.page.de
cd /var/www/virtual/your.page.de/

main.py

the main part of the python code …

cat << 'EOF' > main.py
from datetime import datetime

from fastapi import FastAPI, Request, WebSocket
from fastapi.templating import Jinja2Templates

templates = Jinja2Templates(directory="templates")

app = FastAPI()

@app.get("/")
async def get(request: Request):
    return templates.TemplateResponse("index.html", {"request": request})

@app.websocket("/ws")
async def time_handler(websocket: WebSocket):
    await websocket.accept()
    # response content
    content = """
        <div hx-swap-oob="beforeend:#content">
        {time}: {message}<br>
        </div>
    """
    while True:
        msg = await websocket.receive_json()
            #content.format(time=time.time(), message=msg["chat_message"])

        now = datetime.now()
        time = now.strftime("%H:%M:%S")
        await websocket.send_text(
            content.format(time=time, message=msg["chat_message"])
        )
EOF

index.html

we need one webpage in the template folder

mkdir -p templates
cat << 'EOF' > templates/index.html
<!DOCTYPE html>
<html>
  <head>
    <!-- include htmx -->
    <script
      src="https://unpkg.com/htmx.org@1.6.0"
      crossorigin="anonymous"
    ></script>
  </head>

  <body>
    <h1>Hello world</h1>

    <!-- websocket connection -->
    <div hx-ws="connect:/ws">
      <!-- input for new messages, send a message to the backend
      via websocket -->
      <form hx-ws="send:submit">
        <input name="chat_message" />
      </form>
    </div>

    <!-- location for new messages from the server -->
    <br>
    <div id="content"></div>

  </body>
</html>
EOF

pyproject.toml

cat << 'EOF' > pyproject.toml                                                                               
[tool.poetry]
name = "your.project.de"
version = "0.1.0"
description = ""
authors = ["<mail@your.domain.de>"]
readme = "README.md"

[tool.poetry.dependencies]
python = "^3.10"
fastapi = "^0.95.1"
uvicorn = "^0.21.1"
jinja2 = "^3.1.2"
websockets = "^11.0.2"
wsproto = "^1.2.0"

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
EOF

create virtualenv

poetry shell
poetry install

nginx.conf

we need a reverse proxy in front of the application. nginx will terminate tls and do the logging

change to nginx vhosts directory

cd /etc/nginx/sites

add config

cat << 'EOF' > your.page.de.conf
upstream websocket {
  server 127.0.0.1:8123;
}

# Redirect to https
server {

    listen        80;
    listen        [::]:80;
    server_name   your.page.de;

    location / {
        return 301    https://$host$request_uri;
    }
}

# WebServer
server {

    listen        443 ssl;
    listen        [::]:443 ssl;
    server_name   your.page.de;

    access_log    /var/log/nginx/your.page.de-access.log;
    error_log     /var/log/nginx/your.page.de-error.log;

    ssl_certificate_key         /etc/ssl/certs/your.domain.de/your.domain.de.key;
    ssl_certificate             /etc/ssl/certs/your.domain.de/fullchain.cer;

    add_header Strict-Transport-Security "max-age=63072000; includeSubdomains;";

    location / {
      proxy_pass http://127.0.0.1:8123;
      proxy_set_header Host $host;
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header X-Forwarded-Proto https;
    }

    location /ws {
      proxy_pass http://websocket;
      proxy_http_version 1.1;
      proxy_set_header Upgrade $http_upgrade;
      proxy_set_header Connection "Upgrade";
      proxy_set_header Host $host;
    }

}
EOF

you need to adapt a few things. Path, Cert, FQDN …

Restart Services

afterwards, start / restart the nginx service …

rcctl restart nginx

run app

… and start the app from the shell

poetry run python3 -m uvicorn main:app --port=8123 --reload

open your browser https://your.page.de


Any Comments ?

sha256: 0bbbad50481c17422697f333762f623ce1363607dcd4b50ec11430eab1e0a109