15. HTTP

🚀 IMPLEMENTATION STATUS: HTTP functions are implemented and working. WebSocket functions are implemented but undergoing testing. Fiber operations are partially implemented.

Table of Contents #

Osprey provides first-class support for HTTP servers and clients, designed with performance, safety, and streaming as core principles. All HTTP functions follow Osprey’s functional programming paradigms and comply with:

15.1 HTTP Core Types #

HTTP Method Union Type #

type HttpMethod = GET | POST | PUT | DELETE | PATCH | HEAD | OPTIONS

HTTP Request Type (Immutable) #

type HttpRequest = {
    method: HttpMethod,
    path: String,
    headers: String,
    body: String,
    queryParams: String
}

HTTP Response Type (Immutable with Streaming - SIMPLIFIED!) #

type HttpResponse = {
    status: Int,
    headers: String,
    contentType: String,
    streamFd: Int,        // File descriptor for streaming
    isComplete: Bool,     // Whether response is fully loaded
    partialBody: String   // Body data (runtime auto-calculates length using strlen)
}

IMPORTANT: The contentLength and partialLength fields have been removed to prevent hardcoded length bugs. The C runtime automatically calculates string lengths using strlen() for security and simplicity.

15.2 HTTP Server Functions #

httpCreateServer(port: Int, address: String) -> Result<ServerID, String> #

Creates an HTTP server bound to the specified port and address.

Parameters:

  • port: Port number (1-65535)
  • address: IP address to bind to (e.g., “127.0.0.1”, “0.0.0.0”)

Returns:

  • Success(serverID): Unique server identifier
  • Err(message): Error description (invalid port, bind failure, etc.)

Implementation Status: ⚠️ INCORRECT - Current C runtime returns raw int64_t instead of Result<ServerID, String>

Example:

let serverResult = httpCreateServer(port: 8080, address: "127.0.0.1")
match serverResult {
    Success serverId => print("Server created with ID: ${serverId}")
    Err message => print("Failed to create server: ${message}")
}

httpListen(serverID: Int, handler: fn(String, String, String, String) -> HttpResponse) -> Result<Success, String> #

Starts the HTTP server listening for requests. Each request is handled in a separate fiber for maximum concurrency.

CRITICAL: The handler function receives structured HTTP request data and must return an HttpResponse object. The C runtime handles HTTP parsing and response formatting.

Parameters:

  • serverID: Server identifier from httpCreateServer
  • handler: Request handler function that takes:
    • method: String - HTTP method (GET, POST, PUT, DELETE, etc.)
    • path: String - Request path (e.g., “/api/users”, “/health”)
    • headers: String - Raw HTTP headers as received
    • body: String - Raw request body data

Returns:

  • Success(): Server started successfully
  • Err(message): Error description

Implementation Status: ⚠️ INCORRECT - Current C runtime expects raw string return from handler, not HttpResponse object. Also returns raw int64_t instead of Result<Success, String>

Example:

fn handleHttpRequest(method: String, path: String, headers: String, body: String) -> HttpResponse = 
    match method {
        "GET" => match path {
            "/health" => HttpResponse {
                status: 200,
                headers: "Content-Type: application/json",
                contentType: "application/json",
                streamFd: -1,
                isComplete: true,
                partialBody: "{\"status\": \"healthy\"}"
            }
            "/api/users" => HttpResponse {
                status: 200,
                headers: "Content-Type: application/json",
                contentType: "application/json",
                streamFd: -1,
                isComplete: true,
                partialBody: "{\"users\": [{\"id\": 1, \"name\": \"Alice\"}, {\"id\": 2, \"name\": \"Bob\"}]}"
            }
            _ => HttpResponse {
                status: 404,
                headers: "Content-Type: application/json",
                contentType: "application/json",
                streamFd: -1,
                isComplete: true,
                partialBody: "{\"error\": \"Not found\"}",
            }
        }
        "POST" => match path {
            "/api/users" => HttpResponse {
                status: 201,
                headers: "Content-Type: application/json",
                contentType: "application/json",
                streamFd: -1,
                isComplete: true,
                partialBody: "{\"id\": 3, \"name\": \"New User\", \"message\": \"User created successfully\"}",
            }
            _ => HttpResponse {
                status: 404,
                headers: "Content-Type: application/json",
                contentType: "application/json",
                streamFd: -1,
                isComplete: true,
                partialBody: "{\"error\": \"Not found\"}",
            }
        }
        _ => HttpResponse {
            status: 405,
            headers: "Content-Type: application/json",
            contentType: "application/json",
            streamFd: -1,
            isComplete: true,
            partialBody: "{\"error\": \"Method not allowed\"}",
        }
    }

let listenResult = httpListen(serverID: serverId, handler: handleHttpRequest)
match listenResult {
    Success _ => print("Server started successfully")
    Err message => print("Failed to start server: ${message}")
}

HTTP Handler Architecture:

The HTTP server uses a structured callback architecture where:

  1. C Runtime handles TCP connections, HTTP parsing, and response formatting
  2. Osprey Handler receives structured request data and returns HttpResponse object
  3. Type-safe abstraction - handler works with HttpResponse types, not raw strings
  4. Maximum performance - direct function pointer callbacks with zero overhead

Handler Function Signature:

fn myHandler(method: String, path: String, headers: String, body: String) -> HttpResponse

Response Handling:

  • Return HttpResponse object with complete response data
  • HTTP status codes are taken from the status field
  • Content-Type headers are taken from the headers or contentType fields
  • Response body is taken from partialBody field

httpStopServer(serverID: Int) -> Result<Success, String> #

Stops the HTTP server and cleans up resources.

Parameters:

  • serverID: Server identifier to stop

Returns:

  • Success(): Server stopped successfully
  • Err(message): Error description

Implementation Status: ⚠️ INCORRECT - Current C runtime returns raw int64_t instead of Result<Success, String>

15.2.1 HTTP Request Handling Bridge #

CRITICAL REQUIREMENT: HTTP servers in Osprey must call back into Osprey code to handle requests. NO ROUTING LOGIC SHALL BE IMPLEMENTED IN C RUNTIME. The C runtime provides only the transport layer; all application logic, routing, and request handling must be implemented in Osprey.

Request Handling Architecture #

When an HTTP server receives a request, the C runtime must:

  1. Parse the HTTP request (method, path, headers, body)
  2. Call the Osprey handler function with structured data
  3. Receive HttpResponse object from Osprey
  4. Send HTTP response back to the client

Function Pointer Callbacks #

When httpListen() is called, the Osprey handler function is passed directly to the C runtime as a function pointer:

C Runtime Function Signature:

int64_t http_listen(int64_t server_id, HttpRequestHandler handler);

Handler Function Pointer Type:

typedef HttpResponse* (*HttpRequestHandler)(char* method, char* path, char* headers, char* body);

Implementation Status: ⚠️ INCORRECT - Current C runtime expects char* return, not HttpResponse*

Callback Architecture Flow #

1. Osprey Code:

fn handleHttpRequest(method: String, path: String, headers: String, body: String) -> HttpResponse = 
    // ... handler implementation

let listenResult = httpListen(serverId, handleHttpRequest)

2. LLVM Code Generation:

  • Generates function pointer for handleHttpRequest
  • Passes function pointer to http_listen() C function

3. C Runtime Implementation (Needs to be fixed):

// In request processing loop:
void handle_client_request(int client_fd, char* method, char* path, char* headers, char* body) {
    if (server->handler) {
        // Call Osprey function directly
        HttpResponse* response = server->handler(method, path, headers, body);
        
        // Format and send HTTP response using response object
        char http_response[8192];
        snprintf(http_response, sizeof(http_response),
            "HTTP/1.1 %d %s\r\n"
            "%s"
            "Connection: close\r\n"
            "\r\n",
            response->status,
            get_status_text(response->status),
            response->headers ? response->headers : "");
        
        // Send headers and body
        send(client_fd, http_response, strlen(http_response), 0);
        send(client_fd, response->partialBody, response->partialLength, 0);
    }
}

Architecture Benefits:

  • Type safety: Handler returns structured HttpResponse object
  • Zero overhead: Direct function calls, no serialization
  • Complete control: Osprey has full control over response format
  • Simple debugging: Direct call stack from C to Osprey
  • Memory efficient: No intermediate data structures

15.3 HTTP Client Functions #

httpCreateClient(baseUrl: String, timeout: Int) -> Result<ClientID, String> #

Creates an HTTP client for making requests.

Parameters:

  • baseUrl: Base URL for requests (e.g., “http://api.example.com”)
  • timeout: Request timeout in milliseconds

Returns:

  • Success(clientID): Unique client identifier
  • Err(message): Error description

Implementation Status: ⚠️ INCORRECT - Current C runtime returns raw int64_t instead of Result<ClientID, String>

Example:

let clientResult = httpCreateClient(baseUrl: "http://jsonplaceholder.typicode.com", timeout: 5000)
match clientResult {
    Success clientId => print("Client created with ID: ${clientId}")
    Err message => print("Failed to create client: ${message}")
}

httpGet(clientID: Int, path: String, headers: String) -> Result<HttpResponse, String> #

Makes an HTTP GET request.

Parameters:

  • clientID: Client identifier from httpCreateClient
  • path: Request path (e.g., “/users/1”)
  • headers: Additional headers (e.g., “Authorization: Bearer token\r\n”)

Returns:

  • Success(response): HttpResponse object with status, headers, and body
  • Err(message): Error description

Implementation Status: ⚠️ INCORRECT - Current C runtime returns raw status code int64_t instead of Result<HttpResponse, String>

Example:

let getResult = httpGet(clientID: clientId, path: "/users", headers: "")
match getResult {
    Success response => print("Request completed with status: ${response.status}")
    Err message => print("Request failed: ${message}")
}

httpPost(clientID: Int, path: String, body: String, headers: String) -> Result<HttpResponse, String> #

Makes an HTTP POST request with a request body.

Parameters:

  • clientID: Client identifier
  • path: Request path
  • body: Request body data
  • headers: Additional headers

Implementation Status: ⚠️ INCORRECT - Current C runtime returns raw status code instead of Result<HttpResponse, String>

Example:

let postData = "{\"name\": \"John\", \"email\": \"john@example.com\"}"
let headers = "Content-Type: application/json\r\n"
let postResult = httpPost(clientID: clientId, path: "/users", body: postData, headers: headers)
match postResult {
    Success response => print("POST completed with status: ${response.status}")
    Err message => print("POST failed: ${message}")
}

httpPut(clientID: Int, path: String, body: String, headers: String) -> Result<HttpResponse, String> #

Makes an HTTP PUT request.

Implementation Status: ⚠️ INCORRECT - Current C runtime returns raw status code instead of Result<HttpResponse, String>

httpDelete(clientID: Int, path: String, headers: String) -> Result<HttpResponse, String> #

Makes an HTTP DELETE request.

Implementation Status: ⚠️ INCORRECT - Current C runtime returns raw status code instead of Result<HttpResponse, String>

httpRequest(clientID: Int, method: HttpMethod, path: String, headers: String, body: String) -> Result<HttpResponse, String> #

Generic HTTP request function for any HTTP method.

Implementation Status: ⚠️ INCORRECT - Current C runtime returns raw status code instead of Result<HttpResponse, String>

httpCloseClient(clientID: Int) -> Result<Success, String> #

Closes the HTTP client and cleans up resources.

Returns:

  • Success(): Client closed successfully
  • Err(message): Error description

Implementation Status: ⚠️ INCORRECT - Current C runtime returns raw int64_t instead of Result<Success, String>

15.4 Complete HTTP Server Example #

A practical HTTP server that demonstrates functional programming with HTTP functions:

fn main() -> Int = {
    let serverResult = httpCreateServer(8080, "127.0.0.1")
    match serverResult {
        Success serverId => {
            print("Server created, starting listener...")
            let listenResult = httpListen(serverId, handleRequest)
            match listenResult {
                Success _ => print("Server stopped")
                Err message => print("Server error: ${message}")
            }
        }
        Err message => print("Failed to create server: ${message}")
    }
    0
}

fn handleRequest(method: String, path: String, headers: String, body: String) -> HttpResponse = 
    match method {
        "GET" => match path {
            "/health" => createHealthResponse()
            "/users" => createUserListResponse()
            "/api/stats" => createStatsResponse()
            _ => createNotFoundResponse()
        }
        "POST" => match path {
            "/users" => createUserFromBody(body)
            "/api/data" => processDataFromBody(body)
            _ => createNotFoundResponse()
        }
        "PUT" => match path {
            "/users/1" => updateUserFromBody(body)
            _ => createNotFoundResponse()
        }
        "DELETE" => match path {
            "/users/1" => createDeleteResponse()
            _ => createNotFoundResponse()
        }
        _ => createMethodNotAllowedResponse()
    }

fn createHealthResponse() -> HttpResponse = HttpResponse {
    status: 200,
    headers: "Content-Type: application/json",
    contentType: "application/json",
    streamFd: -1,
    isComplete: true,
    partialBody: "{\"status\": \"healthy\"}",
}

fn createUserListResponse() -> HttpResponse = HttpResponse {
    status: 200,
    headers: "Content-Type: application/json",
    contentType: "application/json",
    streamFd: -1,
    isComplete: true,
    partialBody: "{\"users\": [{\"id\": 1, \"name\": \"Alice\"}, {\"id\": 2, \"name\": \"Bob\"}]}",
}

fn createNotFoundResponse() -> HttpResponse = HttpResponse {
    status: 404,
    headers: "Content-Type: application/json",
    contentType: "application/json",
    streamFd: -1,
    isComplete: true,
    partialBody: "{\"error\": \"Not found\"}",
}

fn createMethodNotAllowedResponse() -> HttpResponse = HttpResponse {
    status: 405,
    headers: "Content-Type: application/json",
    contentType: "application/json",
    streamFd: -1,
    isComplete: true,
    partialBody: "{\"error\": \"Method not allowed\"}",
}

fn createUserFromBody(body: String) -> HttpResponse = HttpResponse {
    status: 201,
    headers: "Content-Type: application/json",
    contentType: "application/json",
    streamFd: -1,
    isComplete: true,
    partialBody: "{\"id\": 3, \"name\": \"New User\", \"message\": \"User created successfully\"}",
}

fn updateUserFromBody(body: String) -> HttpResponse = HttpResponse {
    status: 200,
    headers: "Content-Type: application/json",
    contentType: "application/json",
    streamFd: -1,
    isComplete: true,
    partialBody: "{\"id\": 1, \"name\": \"Alice Updated\", \"message\": \"User updated successfully\"}",
}

fn createDeleteResponse() -> HttpResponse = HttpResponse {
    status: 200,
    headers: "Content-Type: application/json",
    contentType: "application/json",
    streamFd: -1,
    isComplete: true,
    partialBody: "{\"message\": \"User deleted successfully\"}",
}

fn processDataFromBody(body: String) -> HttpResponse = HttpResponse {
    status: 200,
    headers: "Content-Type: application/json",
    contentType: "application/json",
    streamFd: -1,
    isComplete: true,
    partialBody: "{\"processed\": true, \"length\": ${length(body)}}",
}

fn createStatsResponse() -> HttpResponse = HttpResponse {
    status: 200,
    headers: "Content-Type: application/json",
    contentType: "application/json",
    streamFd: -1,
    isComplete: true,
    partialBody: "{\"server\": \"osprey\", \"version\": \"1.0\", \"uptime\": \"3600\"}",
}

This example demonstrates:

  • HttpResponse object handling with complete response structure
  • Functional programming patterns using pattern matching for routing
  • Type-safe HTTP responses with structured data
  • Modular design with separate functions for different operations
  • Error handling through the Result type system
  • Real-world API patterns (CRUD operations, health checks, stats)