This document describes the migration from the custom asyncio-based HTTP server to aiohttp.
The NLWeb application has been migrated to use aiohttp, a modern asynchronous HTTP framework for Python. This migration provides:
- Better HTTP/1.1 compliance
- Built-in WebSocket support (for future features)
- More robust request/response handling
- Better performance and security
- Simplified codebase
- Built-in features for streaming, keep-alive, and connection management
# Using the startup script
./startup_aiohttp.sh
# Or directly with Python
cd code/python
python -m webserver.aiohttp_server
# Or using the app file
python app-aiohttp.py# Use aiohttp server (default)
export USE_AIOHTTP=true
./startup_aiohttp.sh
# Use legacy server
export USE_AIOHTTP=false
./startup_aiohttp.shThe new server uses aiohttp's middleware system located in /code/python/webserver/middleware/:
- cors.py - CORS handling with proper preflight support
- error_handler.py - Centralized error handling and formatting
- logging_middleware.py - Request/response logging
- auth.py - Authentication handling for protected endpoints
- streaming.py - SSE streaming support detection and headers
Routes are now organized by functionality in /code/python/webserver/routes/:
- static.py - Static file serving (/, /static/, /html/)
- api.py - Core API endpoints (/ask, /who, /sites)
- health.py - Health check endpoints (/health, /ready)
- oauth.py - OAuth endpoints (to be implemented)
- mcp.py - Model Context Protocol endpoints (to be implemented)
- conversation.py - Conversation management (to be implemented)
The streaming implementation now uses aiohttp's built-in features:
- StreamResponse for SSE (Server-Sent Events)
- Built-in chunked encoding
- Automatic keep-alive handling
- Connection state management via
transport.is_closing()
# Configured in AppRunner
runner = web.AppRunner(app, keepalive_timeout=75)response = web.StreamResponse(
headers={
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'X-Accel-Buffering': 'no'
}
)
await response.prepare(request)
await response.write(b'data: {"message": "hello"}\n\n')app.on_shutdown.append(shutdown_handler)
app.on_cleanup.append(cleanup_handler)The migration maintains full backward compatibility:
- All existing endpoints work the same way
- Request/response formats are unchanged
- SSE streaming format is preserved
- Query parameters and headers are handled identically
A compatibility layer (AioHttpStreamingWrapper) ensures existing handlers work without modification:
NLWebHandlerworks unchangedGenerateAnswerworks unchangedWhoHandlerworks unchanged
- Performance: Better connection handling and resource utilization
- Reliability: Battle-tested framework with proper HTTP compliance
- Features: Access to aiohttp ecosystem (sessions, WebSockets, etc.)
- Maintainability: Less custom code to maintain
- Security: Regular security updates from aiohttp team
- ✅ Core server infrastructure
- ✅ Middleware system (CORS, error handling, logging, auth, streaming)
- ✅ Static file serving
- ✅ Basic API endpoints (/ask, /who, /sites)
- ✅ SSE streaming support
- ✅ Health check endpoints
- ✅ Compatibility layer for existing handlers
- ⏳ OAuth endpoints migration
- ⏳ MCP endpoints migration
- ⏳ Conversation management endpoints
- ⏳ Performance testing and optimization
- ⏳ Full integration testing
- ⏳ Update deployment scripts for production
To test the new server:
-
Start the server:
cd code/python python -m webserver.aiohttp_server -
Test endpoints:
# Health check curl http://localhost:8000/health # Static files curl http://localhost:8000/ # API endpoints curl http://localhost:8000/who curl http://localhost:8000/sites # SSE streaming curl -H "Accept: text/event-stream" http://localhost:8000/ask?q=test
The server uses the same configuration file (config/config_webserver.yaml) as the legacy server. All configuration options are preserved and work identically.