simple_web

User Guide

Getting Started

Installation

  1. Set environment variables:
    export SIMPLE_WEB=/path/to/simple_web
    export SIMPLE_JSON=/path/to/simple_json
    export SIMPLE_PROCESS=/path/to/simple_process
  2. Add to your ECF file:
    <library name="simple_web" location="$SIMPLE_WEB/simple_web.ecf"/>

Dependencies

HTTP Client

Basic Requests

local
    client: SIMPLE_WEB_CLIENT
    response: SIMPLE_WEB_RESPONSE
do
    create client.make

    -- GET request
    response := client.get ("https://api.example.com/users")

    -- POST with body
    response := client.post ("https://api.example.com/users", "name=Alice")

    -- POST with JSON
    response := client.post_json ("https://api.example.com/users",
        '{"name": "Alice", "email": "alice@example.com"}')

    -- PUT request
    response := client.put ("https://api.example.com/users/1", '{"name": "Bob"}')

    -- DELETE request
    response := client.delete ("https://api.example.com/users/1")
end

Handling Responses

local
    response: SIMPLE_WEB_RESPONSE
do
    response := client.get ("https://api.example.com/users")

    -- Check status
    if response.is_success then
        print ("Status: " + response.status_code.out)
        print ("Body: " + response.body)
    elseif response.is_client_error then
        print ("Client error: " + response.status_code.out)
    elseif response.is_server_error then
        print ("Server error: " + response.status_code.out)
    end

    -- Access headers
    if attached response.header ("Content-Type") as ct then
        print ("Content-Type: " + ct)
    end
end

Fluent Request Builder

local
    request: SIMPLE_WEB_REQUEST
    response: SIMPLE_WEB_RESPONSE
do
    -- Build complex request
    create request.make_post ("https://api.example.com/data")
    request
        .with_bearer_token ("your-jwt-token")
        .with_json_body ('{"key": "value"}')
        .with_header ("X-Custom-Header", "custom-value")
        .with_timeout (30000)
        .do_nothing

    response := client.execute (request)
end

Hybrid Client (for Localhost)

Use the hybrid client when making POST requests to localhost services. It works around a known issue in libcurl.

local
    client: SIMPLE_WEB_HYBRID_CLIENT
do
    create client.make

    -- Uses curl.exe process for POST (reliable)
    response := client.post_json ("http://localhost:11434/api/generate",
        '{"model": "llama3", "prompt": "Hello", "stream": false}')

    -- Uses libcurl for GET (fast)
    response := client.get ("http://localhost:11434/api/tags")
end

AI Clients

Ollama Client

local
    ollama: SIMPLE_WEB_OLLAMA_CLIENT
    response: SIMPLE_WEB_RESPONSE
do
    create ollama

    -- Generate completion
    response := ollama.generate ("llama3", "Why is the sky blue?")
    print (response.body)

    -- Chat conversation
    response := ollama.chat ("llama3",
        <<["user", "Hello"],
          ["assistant", "Hi! How can I help?"],
          ["user", "What's the weather?"]>>)

    -- List available models
    response := ollama.list_models

    -- Pull a model
    response := ollama.pull ("mistral")
end

Claude Client

local
    claude: SIMPLE_WEB_CLAUDE_CLIENT
do
    create claude.make ("your-anthropic-api-key")

    response := claude.message ("claude-3-sonnet-20240229",
        "Explain quantum computing in simple terms")

    print (response.body)
end

OpenAI Client

local
    openai: SIMPLE_WEB_OPENAI_CLIENT
do
    create openai.make ("your-openai-api-key")

    response := openai.chat ("gpt-4",
        <<["user", "Hello, GPT!"]>>)
end

Grok Client

local
    grok: SIMPLE_WEB_GROK_CLIENT
do
    create grok.make ("your-xai-api-key")

    response := grok.chat ("grok-1",
        <<["user", "What's trending today?"]>>)
end

HTTP Server

Basic Server Setup

class
    MY_API

create
    make

feature {NONE}

    make
        local
            server: SIMPLE_WEB_SERVER
        do
            create server.make (8080)

            -- Register routes
            server.on_get ("/", agent handle_root)
            server.on_get ("/api/users", agent handle_users)
            server.on_get ("/api/users/{id}", agent handle_user)
            server.on_post ("/api/users", agent create_user)
            server.on_put ("/api/users/{id}", agent update_user)
            server.on_delete ("/api/users/{id}", agent delete_user)

            -- Start (blocking)
            server.start
        end

feature -- Handlers

    handle_root (req: SIMPLE_WEB_SERVER_REQUEST; res: SIMPLE_WEB_SERVER_RESPONSE)
        do
            res.send_text ("Welcome to My API")
        end

    handle_users (req: SIMPLE_WEB_SERVER_REQUEST; res: SIMPLE_WEB_SERVER_RESPONSE)
        do
            res.send_json ('[{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}]')
        end

    handle_user (req: SIMPLE_WEB_SERVER_REQUEST; res: SIMPLE_WEB_SERVER_RESPONSE)
        do
            -- Access path parameter
            if attached req.path_parameter ("id") as id then
                res.send_json ('{"id": ' + id + ', "name": "Alice"}')
            else
                res.set_status (404)
                res.send_json ('{"error": "User not found"}')
            end
        end

    create_user (req: SIMPLE_WEB_SERVER_REQUEST; res: SIMPLE_WEB_SERVER_RESPONSE)
        do
            -- Access request body
            print ("Body: " + req.body)
            res.set_status (201)
            res.send_json ('{"id": 3, "created": true}')
        end

end

Request Object

-- Path parameters: /users/{id} -> req.path_parameter ("id")
if attached req.path_parameter ("id") as id then
    -- Use id
end

-- Query parameters: /search?q=test -> req.query_parameter ("q")
if attached req.query_parameter ("q") as query then
    -- Use query
end

-- Request body
body := req.body

-- Headers
if attached req.header ("Authorization") as auth then
    -- Use auth
end

-- HTTP method
method := req.method  -- "GET", "POST", etc.

Response Object

-- Set status code
res.set_status (200)
res.set_status (201)  -- Created
res.set_status (404)  -- Not Found

-- Send responses
res.send_text ("Plain text response")
res.send_json ('{"key": "value"}')
res.send_json_object (my_json_object)
res.send_html ("<h1>Hello</h1>")

-- Set headers
res.set_header ("X-Custom", "value")

-- Redirect
res.redirect ("/new-location")

Middleware

Using Middleware

local
    server: SIMPLE_WEB_SERVER
do
    create server.make (8080)

    -- Add middleware (order matters)
    server.use (create {SIMPLE_WEB_LOGGING_MIDDLEWARE}.make)
    server.use (create {SIMPLE_WEB_CORS_MIDDLEWARE}.make)
    server.use (create {SIMPLE_WEB_AUTH_MIDDLEWARE}.make_bearer ("secret"))

    -- Register routes
    server.on_get ("/api/data", agent handle_data)

    server.start
end

CORS Middleware

-- Allow all origins
server.use (create {SIMPLE_WEB_CORS_MIDDLEWARE}.make)

-- Allow specific origins
server.use (create {SIMPLE_WEB_CORS_MIDDLEWARE}.make_with_origins (
    <<"http://localhost:3000", "https://myapp.com">>))

Authentication Middleware

-- Bearer token auth
server.use (create {SIMPLE_WEB_AUTH_MIDDLEWARE}.make_bearer ("your-secret"))

-- API key auth (header: X-API-Key)
server.use (create {SIMPLE_WEB_AUTH_MIDDLEWARE}.make_api_key ("your-api-key"))

-- Basic auth
server.use (create {SIMPLE_WEB_AUTH_MIDDLEWARE}.make_basic ("user", "pass"))

Custom Middleware

class
    MY_MIDDLEWARE

inherit
    SIMPLE_WEB_MIDDLEWARE

feature

    process (req: SIMPLE_WEB_SERVER_REQUEST; res: SIMPLE_WEB_SERVER_RESPONSE): BOOLEAN
            -- Return True to continue, False to stop
        do
            -- Pre-processing
            print ("Request: " + req.method + " " + req.path)

            -- Continue to next middleware/handler
            Result := True

            -- Or stop the chain:
            -- res.set_status (403)
            -- res.send_json ('{"error": "Forbidden"}')
            -- Result := False
        end

end

Security

Input Sanitization

local
    sanitizer: SIMPLE_WEB_INPUT_SANITIZER
do
    create sanitizer

    -- XSS prevention
    safe_html := sanitizer.escape_html (user_input)

    -- Path traversal prevention
    if sanitizer.is_safe_path (file_path) then
        -- Safe to use
    end

    -- Header injection prevention
    safe_header := sanitizer.sanitize_header (header_value)

    -- SQL injection prevention (basic)
    safe_value := sanitizer.escape_sql (user_value)
end

JSON Handling

local
    json: SIMPLE_JSON
do
    create json

    -- Parse request body as JSON
    if attached json.parse (req.body) as v then
        if v.is_object then
            if attached v.as_object.string_item ("name") as name then
                -- Use name
            end
        end
    end

    -- Build JSON response
    response_obj := json.new_object
        .put_string ("status", "success")
        .put_integer ("id", new_id)

    res.send_json_object (response_obj)
end