Skip to content

Instantly share code, notes, and snippets.

@FND
Last active March 17, 2024 18:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save FND/82e9fe2fe220595a561f4bfd2a035122 to your computer and use it in GitHub Desktop.
Save FND/82e9fe2fe220595a561f4bfd2a035122 to your computer and use it in GitHub Desktop.
Enhance SSR with Python (WSGI)
# http://editorconfig.org
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
max_line_length = 80
indent_style = tab
indent_size = 4
[*.{md,py}]
indent_style = space
[COMMIT_EDITMSG]
trim_trailing_whitespace = false
max_line_length = 72
indent_style = space

Enhance SSR with Python

minimal(ish) WSGI application using Enhance SSR

note that the application itself is neither pretty nor fully functional; that's not the point here

rendering logic resides within render functions in app as well as in *.js

Getting Started

  1. create a virtual environment and install Extism:

    $ python -m venv venv
    $ . venv/bin/activate
    $ pip install extism
    

    (use deactivate to exit the virtual environment again)

  2. download the wasm module:

    $ curl -L https://github.com/enhance-dev/enhance-ssr-wasm/releases/download/v0.0.4/enhance-ssr.wasm.gz | gunzip > ./enhance_ssr.wasm
    
  3. launch the application with ./app; it will be available at http://localhost:8080/

#!/usr/bin/env python3
import extism
import json
import os
from uuid import uuid4
from random import sample, shuffle, randint
ITEMS_URL = "/items"
ELEMENTS = {
"form-field": "./form_field.js"
}
SAMPLE = """
lorem ipsum dolor sit amet consectetur adipisicing elit sed do eiusmod tempor incididunt ut labore et dolore magna aliqua ut enim ad minim veniam quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur excepteur sint occaecat cupidatat non proident sunt in culpa qui officia deserunt mollit anim id est laborum
""".strip().split(" ")
ROOT_DIR = os.path.dirname(__file__)
MANIFEST = {
"wasm": [{ "path": "%s/enhance_ssr.wasm" % ROOT_DIR }]
}
STORE = {}
# populate element definitions
for name, filepath in ELEMENTS.items():
with open(os.path.join(ROOT_DIR, filepath), "r") as fh:
ELEMENTS[name] = fh.read()
# populate store
for item in range(5):
_id, *_ = str(uuid4()).split("-")
desc = sample(SAMPLE, randint(3, 7))
shuffle(desc)
STORE[_id] = {
"done": False,
"desc": " ".join(desc)
}
def handle_request(environ, start_response):
method = environ["REQUEST_METHOD"]
uri = environ.get("PATH_INFO", "")
if method != "GET":
start_response("405 Method Not Allowed", [("Content-Type", "text/plain")])
msg = "unsupported HTTP method\n\n".encode("utf-8")
if method == "POST":
return [msg, request_body(environ)]
else:
return [msg.strip()]
if uri == "/":
start_response("302 Found", [("Location", ITEMS_URL)])
return []
if uri == ITEMS_URL:
return render_collection(environ, start_response)
if uri.startswith("%s/" % ITEMS_URL):
_id = uri[len(ITEMS_URL) + 1:]
return render_entity(_id, environ, start_response)
start_response("404 Not Found", [("Content-Type", "text/plain")])
return ["resource not found".encode("utf-8")]
def render_collection(environ, start_response):
params = { "prefix": ITEMS_URL }
items = ("""
<li>
<form-field type="checkbox" name="done" caption="Done"></form-field>
<a href="%(prefix)s/%(id)s">%(desc)s</a>
</li>
""".strip() % { **params, **item, "id": _id } for _id, item in STORE.items())
return render("200 OK", """
<h1>All Items</h1>
<ul>%s</ul>
""".strip() % "\n".join(items), environ, start_response)
def render_entity(_id, environ, start_response):
return render("200 OK", """
<h1>Item %s</h1>
<p>%s</p>
<form method="post" action="%s">
<form-field type="hidden" name="id"></form-field>
<form-field type="checkbox" name="done" caption="Done"></form-field>
<form-field name="desc" caption="Description"></form-field>
</form>
""".strip() % (_id, STORE[_id]["desc"], ITEMS_URL), environ, start_response)
def render(status_line, template, environ, start_response):
start_response(status_line, [("Content-Type", "text/html")])
data = {
"markup": template,
"elements": ELEMENTS,
"initialState": {}
}
with extism.Plugin(MANIFEST, wasi=True) as plugin: #XXX: inefficient; repeated for each request
res = plugin.call("ssr", json.dumps(data), ingest)
return [res["document"].encode("utf-8")]
def ingest(output):
return json.loads(bytes(output).decode("utf-8"))
def request_body(environ):
try:
payload_size = int(environ.get("CONTENT_LENGTH", 0))
except ValueError:
payload_size = 0
return environ["wsgi.input"].read(payload_size)
if __name__ == "__main__":
from wsgiref.simple_server import make_server
host = "localhost"
port = 8080
srv = make_server(host, port, handle_request)
print("→ http://%s:%s" % (host, port))
srv.serve_forever()
function FormField({ html, state }) {
let { attrs } = state;
let { type = "text" } = attrs;
let checkbox = type === "checkbox";
let content = [
html`<b>${attrs.caption}</b>`,
html`<input type=${type} name=${attrs.name}>`
];
if(checkbox) {
content.reverse();
}
let css = checkbox ? null : html`
<style scope="global">
label:not(:has(input[type=checkbox])) {
&,
& b {
display: block;
}
}
</style>
`;
return html`
${css}
<label>
${content[0]}
${content[1]}
</label>
`;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment