simple_web

Architecture Guide

Architectural Overview

simple_web provides a high-level HTTP library with both client and server capabilities, built on top of EiffelStudio's proven HTTP infrastructure.

Design Philosophy

  • Facade Pattern: Simple APIs hiding complex internals
  • Builder Pattern: Fluent request construction
  • Strategy Pattern: Multiple client implementations
  • Chain of Responsibility: Middleware pipeline

Module Structure

simple_web/
├── src/
│   ├── client/           # HTTP client classes
│   │   ├── simple_web_client.e
│   │   ├── simple_web_hybrid_client.e
│   │   ├── simple_web_request.e
│   │   └── simple_web_response.e
│   │
│   ├── server/           # HTTP server classes
│   │   ├── simple_web_server.e
│   │   ├── simple_web_server_request.e
│   │   ├── simple_web_server_response.e
│   │   └── simple_web_server_execution.e
│   │
│   ├── ai/               # AI service clients
│   │   ├── simple_web_ollama_client.e
│   │   ├── simple_web_claude_client.e
│   │   ├── simple_web_openai_client.e
│   │   └── simple_web_grok_client.e
│   │
│   ├── auth/             # Authentication middleware
│   │   ├── simple_web_auth_middleware.e
│   │   └── simple_web_cors_middleware.e
│   │
│   ├── core/             # Middleware base
│   │   ├── simple_web_middleware.e
│   │   └── simple_web_logging_middleware.e
│   │
│   └── constants/        # Shared constants
│       └── simple_web_constants.e
│
├── examples/             # Example applications
│   ├── todo_api/
│   └── wms_api/
│
└── testing/              # Test suite

HTTP Client Architecture

                    +-------------------+
                    | SIMPLE_WEB_CLIENT |  (Primary Client)
                    +-------------------+
                            |
                            | uses
                            v
                    +-------------------+
                    | curl_http_client  |  (EiffelStudio Library)
                    +-------------------+

                    +------------------------+
                    | SIMPLE_WEB_HYBRID_CLIENT|  (Localhost Workaround)
                    +------------------------+
                            |
            +---------------+---------------+
            |                               |
    +---------------+               +---------------+
    |   libcurl     |               | curl.exe      |
    |   (GET)       |               | (POST)        |
    +---------------+               +---------------+

Why Two Clients?

The standard curl_http_client library has a known issue with POST request bodies to localhost. The hybrid client works around this by:

Request Builder Pattern

class
    SIMPLE_WEB_REQUEST

feature -- Builder

    with_header (a_name, a_value: STRING): like Current
        do
            headers.put (a_value, a_name)
            Result := Current
        ensure
            chained: Result = Current
            header_added: headers.has (a_name)
        end

    with_bearer_token (a_token: STRING): like Current
        do
            Result := with_header ("Authorization", "Bearer " + a_token)
        end

end

HTTP Server Architecture

+-------------------+
| SIMPLE_WEB_SERVER |
+-------------------+
        |
        | inherits
        v
+-------------------+
| WSF_DEFAULT_SERVICE |  (EWF Framework)
+-------------------+
        |
        | creates
        v
+-----------------------------+
| SIMPLE_WEB_SERVER_EXECUTION |
+-----------------------------+
        |
        | processes
        v
+-------------------+     +--------------------+
|  Route Matching   | --> | Handler Invocation |
+-------------------+     +--------------------+
        |
        | wraps
        v
+------------------------+     +-------------------------+
| SIMPLE_WEB_SERVER_     |     | SIMPLE_WEB_SERVER_      |
|      REQUEST           |     |      RESPONSE           |
+------------------------+     +-------------------------+
        |                               |
        | wraps                         | wraps
        v                               v
+------------------------+     +-------------------------+
|   WSF_REQUEST          |     |   WSF_RESPONSE          |
+------------------------+     +-------------------------+

Route Registration

feature -- Route Registration

    on_get (a_pattern: STRING; a_handler: PROCEDURE [...])
        do
            add_route ("GET", a_pattern, a_handler)
        end

feature {NONE} -- Implementation

    routes: HASH_TABLE [TUPLE [method: STRING; handler: PROCEDURE [...]]; STRING]
        -- Pattern -> (method, handler) mapping

    add_route (a_method, a_pattern: STRING; a_handler: PROCEDURE [...])
        do
            routes.put ([a_method, a_handler], a_pattern)
        end

Path Parameter Extraction

-- Pattern: /api/users/{id}/posts/{post_id}
-- Request: /api/users/42/posts/7
-- Extracted: {id -> "42", post_id -> "7"}

extract_path_params (a_pattern, a_path: STRING): HASH_TABLE [STRING, STRING]
    do
        -- Split both into segments
        -- Match {name} patterns to actual values
        -- Build parameter table
    end

Middleware Architecture

Middleware follows the Chain of Responsibility pattern:

Request
    |
    v
+-------------------+
| Logging Middleware | --> Log request
+-------------------+
    | continue
    v
+-------------------+
| CORS Middleware   | --> Add CORS headers
+-------------------+
    | continue
    v
+-------------------+
| Auth Middleware   | --> Check authorization
+-------------------+
    | continue (or stop with 401)
    v
+-------------------+
| Route Handler     | --> Business logic
+-------------------+
    |
    v
Response

Middleware Interface

deferred class
    SIMPLE_WEB_MIDDLEWARE

feature

    process (req: SIMPLE_WEB_SERVER_REQUEST; res: SIMPLE_WEB_SERVER_RESPONSE): BOOLEAN
            -- Process request. Return True to continue chain, False to stop.
        deferred
        end

end

Middleware Execution

execute_middleware (req, res: ...)
    do
        across middleware_chain as mw loop
            if not mw.process (req, res) then
                -- Middleware stopped the chain
                return
            end
        end

        -- All middleware passed, invoke route handler
        invoke_handler (req, res)
    end

AI Client Architecture

AI clients share a common pattern built on the HTTP client:

+-------------------+
| SIMPLE_WEB_CLIENT |
+-------------------+
        ^
        | uses
        |
+-------------------+     +-------------------+
| OLLAMA_CLIENT     |     | CLAUDE_CLIENT     |
+-------------------+     +-------------------+
        |                         |
        | API-specific            | API-specific
        | request building        | request building
        v                         v
    localhost:11434         api.anthropic.com

API Abstraction

class
    SIMPLE_WEB_OLLAMA_CLIENT

feature

    generate (a_model, a_prompt: STRING): SIMPLE_WEB_RESPONSE
        local
            json: SIMPLE_JSON
            body: SIMPLE_JSON_OBJECT
        do
            create json
            body := json.new_object
                .put_string ("model", a_model)
                .put_string ("prompt", a_prompt)
                .put_boolean ("stream", False)

            Result := client.post_json (
                base_url + "/api/generate",
                body.to_json)
        end

feature {NONE}

    client: SIMPLE_WEB_HYBRID_CLIENT
        -- Use hybrid for localhost reliability

    base_url: STRING = "http://localhost:11434"

end

Security Architecture

Input Sanitization

class
    SIMPLE_WEB_INPUT_SANITIZER

feature -- XSS Prevention

    escape_html (a_input: STRING): STRING
        do
            Result := a_input.twin
            Result.replace_substring_all ("&", "&")
            Result.replace_substring_all ("<", "&lt;")
            Result.replace_substring_all (">", "&gt;")
            Result.replace_substring_all ("%"", "&quot;")
            Result.replace_substring_all ("'", "&#x27;")
        end

feature -- Path Traversal Prevention

    is_safe_path (a_path: STRING): BOOLEAN
        do
            Result := not a_path.has_substring ("..")
                  and not a_path.has_substring ("~")
        end

feature -- Header Injection Prevention

    sanitize_header (a_value: STRING): STRING
        do
            Result := a_value.twin
            -- Remove newlines that could inject headers
            Result.replace_substring_all ("%N", "")
            Result.replace_substring_all ("%R", "")
        end

end

Authentication Flow

Request with Authorization header
    |
    v
+-------------------+
| Auth Middleware   |
+-------------------+
    |
    +-- No header --> 401 Unauthorized
    |
    +-- Invalid --> 401 Unauthorized
    |
    +-- Valid --> Continue to handler

EWF Integration

simple_web builds on the Eiffel Web Framework (EWF):

EWF Component simple_web Wrapper
WSF_REQUEST SIMPLE_WEB_SERVER_REQUEST
WSF_RESPONSE SIMPLE_WEB_SERVER_RESPONSE
WSF_DEFAULT_SERVICE SIMPLE_WEB_SERVER
WSF_EXECUTION SIMPLE_WEB_SERVER_EXECUTION

Why Wrap EWF?

Design by Contract

feature -- Route Registration

    on_get (a_pattern: STRING; a_handler: PROCEDURE [...])
        require
            pattern_attached: a_pattern /= Void
            handler_attached: a_handler /= Void
        do
            add_route ("GET", a_pattern, a_handler)
        ensure
            route_registered: has_route ("GET", a_pattern)
        end

feature -- Response

    set_status (a_code: INTEGER)
        require
            valid_status: a_code >= 100 and a_code < 600
        ensure
            status_set: status_code = a_code
        end

SCOOP Compatibility

The library is designed for SCOOP concurrency:

-- Safe to use clients from separate objects
separate
    api_client: SIMPLE_WEB_CLIENT
do
    -- Each call is isolated
    response := api_client.get (url)
end