Skip to content

Framework adapters for popular web frameworks (Flask, FastAPI, Express, etc.) #38

@richardkiene

Description

@richardkiene

Summary

Enable users to run code written with popular web frameworks (Flask, FastAPI, Express, etc.) in fabricks without requiring WASI-specific interfaces. Instead of exposing WASI HTTP implementation details to users, fabricks would provide compile-time adapters that transform standard framework code into WASI HTTP handlers.

Problem

Currently, to run an HTTP service in fabricks, users must implement language-specific interfaces:

  • Rust: Direct wasi:http/incoming-handler implementation
  • Python: Custom handler(request) -> dict function
  • Node.js: Custom handler pattern

This leaks WASI implementation details into user code and prevents running existing applications without modification.

Why Not Runtime Bridging?

We investigated running traditional HTTP servers inside WASM with an internal bridge (see exploration below), but this is not technically feasible with current WASI:

Blocker Details
No socket binding WASI forbids WASM modules from calling bind()/listen(). Only the host can create listening sockets.
Threading withdrawn wasi-threads was officially withdrawn (Aug 2023). No concurrent connection handling.
Blocking deadlock A blocking accept() loop freezes the entire WASM instance.
Architectural mismatch wasi:http/incoming-handler was designed to move the accept loop to the host.

Every production WASM platform (Fermyon Spin, Fastly, wasmCloud, Cloudflare Workers) uses the handler model for this reason.

Proposed Solution: Compile-Time Framework Adapters

Instead of runtime bridging, build compile-time adapters that transform framework code into WASI HTTP handlers.

User Experience

Flask example:

from flask import Flask, request, jsonify
app = Flask(__name__)

@app.route('/')
def hello():
    return "Hello World!"

@app.route('/users/<int:id>')
def get_user(id):
    return jsonify({"id": id, "name": "Alice"})

# No app.run() needed - fabricks handles serving

Fabrickfile:

[from]
source = "python"

[python]
framework = "flask"
app = "main:app"  # Like gunicorn/uvicorn syntax

User runs:

fabricks service run ./my-flask-app --network default

The user writes standard Flask code. Fabricks handles the WASI HTTP transformation automatically.

How It Works

The adapter generates a WASI HTTP handler wrapper at build time:

# Generated by fabricks (user never sees this)
from user_app import app  # User's Flask app

class WasiHttpHandler:
    def handle(self, wasi_request, response_out):
        # 1. Convert WASI request → WSGI environ
        environ = self._to_wsgi_environ(wasi_request)
        
        # 2. Call Flask app via WSGI interface
        response_body = []
        def start_response(status, headers):
            self.status = status
            self.headers = headers
        
        result = app.wsgi_app(environ, start_response)
        
        # 3. Convert WSGI response → WASI HTTP response
        self._send_wasi_response(response_out, self.status, self.headers, result)

The key insight: WSGI/ASGI are already abstraction layers. We adapt those interfaces, not the frameworks directly.

Implementation Plan

Phase 1: Python WSGI Adapter (Flask, Django, Bottle)

  1. Create WSGI → WASI HTTP adapter

    • Convert IncomingRequest → WSGI environ dict
    • Handle start_response() callback
    • Convert response iterator → ResponseOutparam
  2. Update PythonBuilder

    • Add framework and app config options
    • Generate wrapper that imports user's WSGI app
    • Bundle adapter + user code into component
  3. Supported frameworks:

    • Flask
    • Django
    • Bottle
    • Any WSGI-compliant app

Phase 2: Python ASGI Adapter (FastAPI, Starlette)

  1. Create ASGI → WASI HTTP adapter

    • Handle async request/response lifecycle
    • Support streaming responses
  2. Supported frameworks:

    • FastAPI
    • Starlette
    • Quart
    • Any ASGI-compliant app

Phase 3: Node.js Adapters

  1. Express adapter

    • Transform Express middleware chain → handler
    • Support req/res objects
  2. Hono/Fastify adapters

    • Similar approach for other popular frameworks

Phase 4: Rust Adapters (Optional)

  1. Axum/Actix-web adapters
    • Tower service → handler transformation
    • May be less needed since Rust devs often work closer to the metal

Fabrickfile Configuration

[from]
source = "python"  # or "nodejs", "rust"

# Python-specific
[python]
framework = "flask"      # or "fastapi", "django", "wsgi", "asgi"
app = "main:app"         # Module path to app object
# Optional: requirements handling
requirements = "requirements.txt"

# Node.js-specific  
[nodejs]
framework = "express"    # or "hono", "fastify"
app = "app.js"           # Entry point
handler = "app"          # Exported app object

Benefits

  1. Zero WASI knowledge required - Users write standard framework code
  2. Run existing apps - Migrate Flask/FastAPI/Express apps with minimal changes
  3. Familiar patterns - Use decorators, middleware, routing they already know
  4. Ecosystem compatibility - Works with framework extensions and middleware

Technical Considerations

Request/Response Mapping

WASI HTTP WSGI ASGI
method() REQUEST_METHOD scope["method"]
path_with_query() PATH_INFO + QUERY_STRING scope["path"] + scope["query_string"]
headers() HTTP_* environ vars scope["headers"]
consume() body wsgi.input receive()
ResponseOutparam start_response() + iterator send()

Limitations to Document

  • No WebSockets (yet) - WASI HTTP is request/response only
  • No background tasks - Handler must complete synchronously
  • No file uploads to disk - Must use memory or granted filesystem paths
  • No subprocess spawning - WASI sandbox restriction

Success Criteria

  • User can deploy unmodified Flask app with only Fabrickfile changes
  • User can deploy unmodified FastAPI app with only Fabrickfile changes
  • User can deploy unmodified Express app with only Fabrickfile changes
  • Documentation clearly shows migration path from traditional deployment
  • Error messages guide users when using unsupported framework features

References

Related

  • Supersedes investigation into runtime HTTP bridging (not feasible with current WASI)
  • Builds on existing language builders (RustBuilder, GoBuilder, PythonBuilder)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions