Skip to content

How-To: In-Process Integration with FastAPI & Web Applications

NSE v2.0.0 uses a single-process root architecture. This guide explains how to integrate NSE into a FastAPI or Svelte full-stack web application.


FastAPI Lifespan & State Integration

Store the NetnsController instance in FastAPI app.state and clean up namespaces during application shutdown:

import asyncio
from contextlib import asynccontextmanager
from fastapi import FastAPI, HTTPException, Request
from nse.core.netns_controller import NetnsController, TestRun
from nse.core.pipeline import run_test_pipeline
from nse.models.test_request import TestRequest

@asynccontextmanager
async def lifespan(app: FastAPI):
    # Initialize controller and run startup sweep for orphan netns/veth links
    app.state.controller = NetnsController()
    app.state.runs = {}
    app.state.tasks = {}
    yield
    # Teardown pending tasks & clean remaining namespaces
    for task in app.state.tasks.values():
        if not task.done():
            task.cancel()
    app.state.controller.cleanup_all()

app = FastAPI(title="NSE Web Service", lifespan=lifespan)

Non-Blocking Execution in Route Handlers

Enqueue test runs using asyncio.create_task():

@app.post("/api/test")
async def submit_test(request: TestRequest, req: Request):
    test_id = uuid.uuid4().hex[:12]
    controller: NetnsController = req.app.state.controller

    run = TestRun(test_id=test_id, netns_name=f"nse_{test_id}", request=request)
    req.app.state.runs[test_id] = run

    # Launch pipeline task in-process asynchronously
    task = asyncio.create_task(
        run_test_pipeline(request=request, controller=controller, run=run)
    )
    req.app.state.tasks[test_id] = task

    return {"test_id": test_id}

WebSocket Event Streaming

Stream live TraceEvent objects to the browser:

from fastapi import WebSocket, APIRouter

@app.websocket("/ws/{test_id}")
async def trace_stream(websocket: WebSocket, test_id: str):
    await websocket.accept()
    run: TestRun = websocket.app.state.runs.get(test_id)

    try:
        while True:
            event = await run.event_queue.get()
            if event is None:
                await websocket.send_json({"type": "done"})
                break
            await websocket.send_text(event.model_dump_json())
    finally:
        with contextlib.suppress(RuntimeError):
            await websocket.close()