Skip to main content

HTTP REST API

Complete reference for the Easy AppServer HTTP REST API.

Overview

The AppServer HTTP API provides REST endpoints for health checks, proxy routing, and future resource management. Built on Go 1.22+ enhanced ServeMux with full HTTP/2 support.

Base URL:

  • Local: http://localhost:8080
  • Production: Configured via APPSERVER_HTTP_PORT

Architecture

The HTTP server includes:

  • HTTP/2 Support: Full HTTP/2 with TLS, h2c (HTTP/2 Cleartext) without TLS
  • Middleware Stack: 9-layer middleware for observability, security, and error handling
  • Dynamic Proxy: Route requests to app backends based on manifest configuration
  • Health Endpoints: Built-in health and readiness checks
  • Graceful Shutdown: Clean connection handling on SIGTERM/SIGINT

Middleware Stack

All requests pass through these middleware layers in order:

  1. Request ID (middleware/request_id.go) - Generate unique X-Request-ID for tracing
  2. Logging (middleware/logging.go) - Structured request/response logging
  3. Metrics (middleware/metrics.go) - Prometheus metrics collection
  4. Tracing (middleware/tracing.go) - OpenTelemetry distributed tracing
  5. CORS (middleware/cors.go) - Cross-Origin Resource Sharing headers
  6. Authentication (middleware/auth.go) - User (Kratos) or app (signature) authentication
  7. Authorization (middleware/authz.go) - OpenFGA permission checks
  8. Error Handling (middleware/errors.go) - Standard error responses
  9. Recovery (middleware/recovery.go) - Panic recovery with error logging

Core Endpoints

Health Check

Returns server health status and uptime.

GET /health

Response: 200 OK

{
"status": "healthy",
"uptime": 3600.5
}

Implementation: pkg/v2/presentation/http/handlers/health.go:27

Use Cases:

  • Monitoring system health checks
  • Load balancer health probes
  • Container orchestration health checks

Readiness Check

Returns readiness status for load balancers.

GET /ready

Response: 200 OK

{
"status": "ready"
}

Implementation: pkg/v2/presentation/http/handlers/health.go:41

Future Enhancement: Will check database, RabbitMQ, OpenFGA connectivity before returning ready.

Use Cases:

  • Kubernetes readiness probes
  • Load balancer backend checks
  • Rolling deployment coordination

Dynamic Proxy Routing

The AppServer dynamically routes HTTP requests to app backends based on manifest configuration.

How It Works

Client Request


┌─────────────────────────────────────┐
│ AppServer HTTP (Port 8080) │
│ │
│ 1. Match route from app manifest │
│ 2. Check permissions (OpenFGA) │
│ 3. Validate rate limits │
│ 4. Forward to upstream │
│ 5. Return response │
└─────────────────────────────────────┘


┌─────────────────────────────────────┐
│ App Backend (e.g., todos-bff) │
│ http://todos-bff:8080 │
└─────────────────────────────────────┘

Route Configuration

Routes are defined in the app manifest:

message WebApi {
string base_path = 1; // e.g., "/api/apps/todos"
string upstream_base_url = 2; // e.g., "http://todos-bff:8080"
bool strip_base_path = 3; // Default: true
int32 timeout_ms = 4; // Request timeout (default: 30000)
repeated string forward_headers = 5; // Headers to forward
repeated RouteSpec routes = 6; // Specific route overrides
repeated string required_permissions = 7; // Permissions required
RateLimit default_rate_limit = 8; // Rate limiting config
HealthCheck health_check = 9; // Upstream health check
string openapi_url = 10; // OpenAPI spec URL
}

message RouteSpec {
string path = 1; // e.g., "/items/{id}"
repeated string methods = 2; // e.g., ["GET", "POST"]
repeated string required_permissions = 3; // Override permissions
RateLimit rate_limit = 4; // Override rate limit
int32 timeout_ms = 5; // Override timeout
}

Example Request Flow

Manifest Configuration:

web_api:
base_path: "/api/apps/todos"
upstream_base_url: "http://todos-bff:8080"
strip_base_path: true
forward_headers:
- "Authorization"
- "Content-Type"
- "X-Request-ID"
required_permissions:
- "todos:read"

Client Request:

GET /api/apps/todos/items/123 HTTP/1.1
Host: localhost:8080
Authorization: Bearer <kratos-session-token>
Content-Type: application/json

Proxied Request (with strip_base_path: true):

GET /items/123 HTTP/1.1
Host: todos-bff:8080
Authorization: Bearer <kratos-session-token>
Content-Type: application/json
X-Request-ID: <generated-uuid>
X-User-ID: <kratos-user-id>

Without stripping (strip_base_path: false):

GET /api/apps/todos/items/123 HTTP/1.1
Host: todos-bff:8080
Authorization: Bearer <kratos-session-token>
Content-Type: application/json
X-Request-ID: <generated-uuid>
X-User-ID: <kratos-user-id>

Path-Based Routing

The proxy matches routes using Go 1.22+ enhanced patterns:

// Exact match
"/api/apps/todos/items"

// Wildcard match
"/api/apps/todos/{path...}"

// Parameter match
"/api/apps/todos/items/{id}"

Header Forwarding

Only headers in the forward_headers allowlist are forwarded to upstreams:

Common Headers:

  • Authorization - Bearer tokens, API keys
  • Content-Type - Request/response content type
  • Accept - Accepted response types
  • X-Request-ID - Request correlation ID
  • X-User-ID - User identifier (added by auth middleware)
  • X-App-Name - App identifier (added by auth middleware)

Security: Sensitive headers like Cookie, X-App-Signature are NOT forwarded unless explicitly listed.

Authentication

The HTTP server supports two authentication methods:

User Authentication (Kratos)

Users authenticate via Ory Kratos sessions:

curl -H "Cookie: ory_kratos_session=<session-token>" \
http://localhost:8080/api/apps/todos/items

Flow:

  1. User logs in via Kratos
  2. Kratos session cookie set
  3. AppServer validates session with Kratos public API
  4. User ID extracted and added to request context

App Authentication (Signature)

Apps authenticate using RSA signature verification:

curl -H "X-App-Name: myapp" \
-H "X-App-Signature: <base64-rsa-signature>" \
-H "X-Request-Timestamp: 2024-11-18T12:00:00Z" \
http://localhost:8080/api/apps/todos/items

Signature Generation:

  1. Concatenate: method + url + timestamp + body
  2. Sign with app's private RSA key
  3. Base64 encode signature
  4. Include in X-App-Signature header

Verification:

  • Signature verified against app's public key (from certificate)
  • Timestamp checked for replay protection (5-minute window)
  • Clock skew tolerance: 30 seconds

Implementation: pkg/v2/infrastructure/auth/signature.go

Authorization

Authorization is enforced via OpenFGA with multi-level permission caching.

Permission Checks

Before proxying requests, the server checks required permissions:

web_api:
base_path: "/api/apps/todos"
required_permissions:
- "todos:read"
routes:
- path: "/items"
methods: ["POST"]
required_permissions:
- "todos:write"

Check Flow:

  1. Extract user/app from auth context
  2. Check local permission cache (in-memory)
  3. On cache miss, check Redis cache
  4. On Redis miss, check OpenFGA
  5. Cache result with TTL (1s local, 5min Redis)

Implementation: pkg/v2/infrastructure/permission/cache/

Permission Format

Permissions follow the pattern: <resource>:<action>

Examples:

  • app:install - Install apps
  • app:uninstall - Uninstall apps
  • settings:read - Read settings
  • settings:write - Update settings
  • todos:read - Read todos
  • todos:write - Create/update todos

Rate Limiting

Rate limiting is enforced per-route using Redis-backed sliding window counters.

Configuration

message RateLimit {
int32 requests = 1; // Max requests
int32 window_seconds = 2; // Time window in seconds
string key_pattern = 3; // Rate limit key pattern
}

Example:

web_api:
default_rate_limit:
requests: 100
window_seconds: 60 # 100 requests per minute
key_pattern: "user:{user_id}"

Rate Limit Keys

Rate limits can be scoped by:

  • User: user:{user_id}
  • App: app:{app_name}
  • IP: ip:{remote_addr}
  • Global: global

Response Headers

Rate limit status is returned in response headers:

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 87
X-RateLimit-Reset: 1700000000

Rate Limit Exceeded

Response: 429 Too Many Requests

{
"error": "rate limit exceeded",
"code": "RATE_LIMIT_EXCEEDED",
"details": {
"limit": 100,
"window": 60,
"retry_after": 15
}
}

Implementation: pkg/v2/infrastructure/ratelimit/

HTTP/2 Support

The server supports HTTP/2 with and without TLS:

With TLS (Full HTTP/2)

server:
http_port: 8080
tls_enabled: true
tls_cert_file: "/path/to/cert.pem"
tls_key_file: "/path/to/key.pem"
http2_enabled: true

Without TLS (h2c)

server:
http_port: 8080
tls_enabled: false
http2_enabled: true # Enables h2c (HTTP/2 Cleartext)

Benefits:

  • Multiplexing: Multiple requests over single connection
  • Header compression: Reduced bandwidth
  • Server push: Proactive resource delivery (future)
  • Binary protocol: Faster parsing

Server Configuration

Complete configuration options:

server:
# Ports
http_port: 8080
address: "0.0.0.0"

# Timeouts
read_timeout: 30s # Read request timeout
write_timeout: 30s # Write response timeout
idle_timeout: 120s # Keep-alive idle timeout
shutdown_timeout: 30s # Graceful shutdown timeout

# TLS
tls_enabled: false
tls_cert_file: ""
tls_key_file: ""

# HTTP/2
http2_enabled: true

# Limits
max_header_bytes: 1048576 # 1MB

Error Responses

All errors follow a standard format:

{
"error": "human-readable error message",
"code": "ERROR_CODE",
"details": {
"field": "additional context"
}
}

Status Codes

CodeStatusDescription
200OKSuccess
400Bad RequestInvalid request syntax or validation error
401UnauthorizedAuthentication required or failed
403ForbiddenInsufficient permissions
404Not FoundResource or route not found
429Too Many RequestsRate limit exceeded
500Internal Server ErrorServer-side error
502Bad GatewayUpstream service error
503Service UnavailableService temporarily down
504Gateway TimeoutUpstream timeout

Error Codes

CodeDescription
INVALID_REQUESTRequest validation failed
AUTHENTICATION_REQUIREDNo authentication provided
AUTHENTICATION_FAILEDInvalid credentials or signature
PERMISSION_DENIEDInsufficient permissions
RATE_LIMIT_EXCEEDEDToo many requests
NOT_FOUNDResource not found
UPSTREAM_ERRORBackend service error
UPSTREAM_TIMEOUTBackend service timeout
INTERNAL_ERRORServer internal error

Testing

Health Checks

# Health check
curl http://localhost:8080/health

# Expected response
{"status":"healthy","uptime":3600.5}

# Readiness check
curl http://localhost:8080/ready

# Expected response
{"status":"ready"}

Authenticated Requests

User Authentication:

curl -H "Cookie: ory_kratos_session=<token>" \
http://localhost:8080/api/apps/todos/items

App Authentication:

# Generate signature (using openssl)
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
MESSAGE="GET/api/apps/todos/items${TIMESTAMP}"
SIGNATURE=$(echo -n "$MESSAGE" | openssl dgst -sha256 -sign app-private-key.pem | base64)

curl -H "X-App-Name: myapp" \
-H "X-App-Signature: $SIGNATURE" \
-H "X-Request-Timestamp: $TIMESTAMP" \
http://localhost:8080/api/apps/todos/items

Rate Limit Testing

# Make multiple requests
for i in {1..110}; do
curl -i http://localhost:8080/api/apps/todos/items
sleep 0.1
done

# After 100 requests, expect:
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1700000060

Observability

The HTTP server integrates with the telemetry stack:

Metrics (Prometheus)

  • http_requests_total{method, path, status} - Total requests
  • http_request_duration_seconds{method, path} - Request duration histogram
  • http_requests_in_flight - Current active requests
  • http_response_size_bytes{method, path} - Response size histogram

Grafana: http://localhost:3000 (pre-configured dashboards)

Tracing (OpenTelemetry)

All requests are traced with:

  • Span name: HTTP {method} {path}
  • Attributes: method, path, status, user_id, app_name, request_id
  • Parent/child relationships for upstream calls

Tempo: http://localhost:3200

Logging

Structured logs for each request:

{
"level": "info",
"time": "2024-11-18T12:00:00Z",
"msg": "HTTP request",
"request_id": "123e4567-e89b-12d3-a456-426614174000",
"method": "GET",
"path": "/api/apps/todos/items",
"status": 200,
"duration_ms": 45.2,
"user_id": "user-123",
"app_name": "todos"
}

Loki: Accessible via Grafana at http://localhost:3000

Future Endpoints

Planned REST API endpoints:

App Management

  • GET /api/v2/apps - List all apps
  • GET /api/v2/apps/{id} - Get app details
  • POST /api/v2/apps/{id}/install - Install app
  • DELETE /api/v2/apps/{id}/uninstall - Uninstall app

Settings Management

  • GET /api/v2/apps/{id}/settings - Get app settings
  • PUT /api/v2/apps/{id}/settings - Update settings
  • POST /api/v2/apps/{id}/settings/validate - Validate settings

Event Queries

  • GET /api/v2/events - Query event store
  • GET /api/v2/events/{id} - Get event details

OpenAPI Documentation

  • GET /api/v2/openapi.json - OpenAPI specification
  • GET /api/v2/docs - Swagger UI

Code References

  • pkg/v2/presentation/http/server.go - HTTP server implementation
  • pkg/v2/presentation/http/router.go - Route registration and middleware
  • pkg/v2/presentation/http/handlers/health.go:27 - Health check handler
  • pkg/v2/presentation/http/proxy_handler.go - Dynamic proxy implementation
  • pkg/v2/presentation/http/middleware/ - Middleware implementations
  • pkg/v2/infrastructure/ratelimit/ - Rate limiting implementation
  • pkg/v2/infrastructure/auth/signature.go - Signature verification