HTTP Framework Handler
The HTTP Framework Handler enables actors to serve HTTP requests, turning them into fully-functional web services. It provides a bridge between incoming HTTP requests and actor functions, allowing actors to respond to web traffic while maintaining Theater's state verification model.
Overview
The HTTP Framework Handler implements the ntwk:theater/http-framework
interface, providing:
- A way for actors to receive and respond to HTTP requests
- Conversion between HTTP requests and actor-friendly formats
- Automatic state chain recording of all HTTP interactions
- Comprehensive error handling
Configuration
To use the HTTP Framework Handler, add it to your actor's manifest:
[[handlers]]
type = "http-framework"
config = {}
The HTTP Framework Handler works in conjunction with the built-in HTTP server capability in Theater, which routes requests to the appropriate actors based on path configurations.
Interface
The HTTP Framework Handler is defined using the following WIT interface:
interface http-framework {
use types.{state};
use http-types.{http-request, http-response};
handle-request: func(state: state, req: http-request) -> result<tuple<state, http-response>, string>;
}
HTTP Request Structure
The HttpRequest
type has the following structure:
#![allow(unused)] fn main() { struct HttpRequest { method: String, uri: String, path: String, query: Option<String>, headers: Vec<(String, String)>, body: Option<Vec<u8>>, } }
method
: The HTTP method (GET, POST, PUT, DELETE, etc.)uri
: The full request URIpath
: The path component of the URIquery
: Optional query stringheaders
: A list of HTTP headers as key-value pairsbody
: Optional request body as bytes
HTTP Response Structure
The HttpResponse
type has the following structure:
#![allow(unused)] fn main() { struct HttpResponse { status: u16, headers: Vec<(String, String)>, body: Option<Vec<u8>>, } }
status
: The HTTP status codeheaders
: A list of response headers as key-value pairsbody
: Optional response body as bytes
Handling HTTP Requests
To handle HTTP requests, actors implement the handle-request
function:
#![allow(unused)] fn main() { fn handle_request(state: Option<Vec<u8>>, req: HttpRequest) -> Result<(Option<Vec<u8>>, HttpResponse), String> { // Process the request and update state let new_state = process_request(&state, &req)?; // Generate a response let response = HttpResponse { status: 200, headers: vec![ ("Content-Type".to_string(), "application/json".to_string()), ], body: Some(b"Hello, world!".to_vec()), }; Ok((new_state, response)) } }
Routing
The HTTP Framework Handler maps incoming HTTP requests to actor functions based on the request path. This is configured through the Theater system's HTTP server configuration.
For example, to route all requests to /api/users
to a specific actor:
# System configuration
[[http_routes]]
path = "/api/users"
actor_id = "user-service-actor"
# In the actor's manifest
[[handlers]]
type = "http-framework"
config = {}
State Chain Integration
Every HTTP request and response is recorded in the actor's state chain, creating a verifiable history of all web interactions. The chain events include:
-
HttpFrameworkRequestCall: Records when a request is received, including:
- HTTP method
- Path
- Headers count
- Body size
-
HttpFrameworkRequestResult: Records the result of processing a request, including:
- Status code
- Headers count
- Body size
- Processing time
-
Error: Records any errors that occur during request processing, including:
- Operation type
- Path
- Error message
Error Handling
The HTTP Framework Handler provides two layers of error handling:
-
Framework-Level Errors: Handled by the framework itself, such as:
- Routing errors
- Method not allowed
- Actor not found
- Malformed requests
-
Actor-Level Errors: Returned by the actor's
handle-request
function, which can:- Return a custom error response
- Provide detailed error information
- Choose appropriate HTTP status codes
If an actor returns an error, the framework generates a 500 Internal Server Error response with the error message in the body (in development mode only).
Security Considerations
When using the HTTP Framework Handler, consider the following security aspects:
- Input Validation: Always validate and sanitize all HTTP request data
- Authentication: Implement proper authentication for protected endpoints
- Rate Limiting: Consider rate limiting to prevent abuse
- Error Information: Be careful about exposing error details in production
- CORS Policies: Implement appropriate CORS headers for browser security
- Content Security: Set proper content security policies
Implementation Details
Under the hood, the HTTP Framework Handler:
- Receives HTTP requests from the Theater HTTP server
- Converts them to the
HttpRequest
format - Retrieves the current actor state
- Calls the actor's
handle-request
function - Updates the actor's state with the new state
- Converts the
HttpResponse
back to an HTTP response - Records all operations in the state chain
- Returns the response to the client
Best Practices
- RESTful Design: Follow RESTful principles for API design
- Stateless Design: Keep HTTP handlers as stateless as possible
- Error Handling: Implement proper error handling with appropriate status codes
- Content Types: Set appropriate Content-Type headers
- Validation: Validate all incoming data
- Testing: Test all endpoints with various input scenarios
- Documentation: Document your API endpoints clearly
Examples
Example 1: Simple JSON API
#![allow(unused)] fn main() { fn handle_request(state: Option<Vec<u8>>, req: HttpRequest) -> Result<(Option<Vec<u8>>, HttpResponse), String> { // Parse the current state or initialize it let current_state: AppState = match state { Some(data) => serde_json::from_slice(&data).map_err(|e| e.to_string())?, None => AppState::default(), }; match (req.method.as_str(), req.path.as_str()) { ("GET", "/api/items") => { // Return all items let items_json = serde_json::to_vec(¤t_state.items).map_err(|e| e.to_string())?; Ok(( state, HttpResponse { status: 200, headers: vec![ ("Content-Type".to_string(), "application/json".to_string()), ], body: Some(items_json), } )) }, ("POST", "/api/items") => { // Add a new item if let Some(body) = req.body { let new_item: Item = serde_json::from_slice(&body).map_err(|e| e.to_string())?; // Update state let mut new_state = current_state.clone(); new_state.items.push(new_item); // Serialize new state let new_state_bytes = serde_json::to_vec(&new_state).map_err(|e| e.to_string())?; // Return success response Ok(( Some(new_state_bytes), HttpResponse { status: 201, headers: vec![ ("Content-Type".to_string(), "application/json".to_string()), ], body: Some(b"{\"status\":\"created\"}".to_vec()), } )) } else { // Return error for missing body Ok(( state, HttpResponse { status: 400, headers: vec![ ("Content-Type".to_string(), "application/json".to_string()), ], body: Some(b"{\"error\":\"Missing request body\"}".to_vec()), } )) } }, _ => { // Return 404 for unmatched routes Ok(( state, HttpResponse { status: 404, headers: vec![ ("Content-Type".to_string(), "application/json".to_string()), ], body: Some(b"{\"error\":\"Not found\"}".to_vec()), } )) } } } }
Example 2: File Serving
#![allow(unused)] fn main() { fn handle_request(state: Option<Vec<u8>>, req: HttpRequest) -> Result<(Option<Vec<u8>>, HttpResponse), String> { // Only handle GET requests if req.method != "GET" { return Ok(( state, HttpResponse { status: 405, headers: vec![ ("Content-Type".to_string(), "text/plain".to_string()), ("Allow".to_string(), "GET".to_string()), ], body: Some(b"Method Not Allowed".to_vec()), } )); } // Extract the requested file path let path = req.path.trim_start_matches('/'); // Use the filesystem handler to read the file match filesystem::read_file(path) { Ok(file_content) => { // Determine content type based on file extension let content_type = match path.split('.').last() { Some("html") => "text/html", Some("css") => "text/css", Some("js") => "application/javascript", Some("json") => "application/json", Some("png") => "image/png", Some("jpg") | Some("jpeg") => "image/jpeg", Some("svg") => "image/svg+xml", _ => "application/octet-stream", }; // Return the file content Ok(( state, HttpResponse { status: 200, headers: vec![ ("Content-Type".to_string(), content_type.to_string()), ], body: Some(file_content), } )) }, Err(_) => { // File not found Ok(( state, HttpResponse { status: 404, headers: vec![ ("Content-Type".to_string(), "text/plain".to_string()), ], body: Some(b"File Not Found".to_vec()), } )) } } } }
Related Topics
- HTTP Client Handler - For making HTTP requests from actors
- Message Server Handler - For actor-to-actor communication
- File System Handler - For accessing the file system