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:
- Using libcurl directly for GET requests (fast)
- Using curl.exe process for POST requests to localhost (reliable)
- Automatically detecting localhost and choosing the right strategy
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 ("<", "<")
Result.replace_substring_all (">", ">")
Result.replace_substring_all ("%"", """)
Result.replace_substring_all ("'", "'")
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?
- Simpler API: Agent-based routing instead of router objects
- Path Parameters: Built-in {id} extraction
- Middleware: Clean middleware pipeline
- JSON Integration: Direct JSON object support
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:
- Stateless clients: Each request is independent
- No shared mutable state: Thread-safe by design
- ECF configured:
concurrency="scoop"
-- Safe to use clients from separate objects
separate
api_client: SIMPLE_WEB_CLIENT
do
-- Each call is isolated
response := api_client.get (url)
end