HTTP Routing & Policies

HTTP Routing & Policies

Agent Gateway provides powerful HTTP routing capabilities including path matching, header-based routing, rate limiting, retries, and request/response modification.

What you’ll build

In this tutorial, you’ll:

  1. Configure HTTP routing with path, header, and query matching
  2. Create health check endpoints with direct responses
  3. Set up CORS, rate limiting, retries, and timeouts
  4. Implement IP-based authorization
  5. Modify requests and responses with header transformations

Prerequisites

Step 1: Create a working directory

mkdir http-routing-test && cd http-routing-test

Step 2: Create a basic routing configuration

Create a config.yaml file with multiple routing examples:

cat > config.yaml << 'EOF'
binds:
- port: 3000
  listeners:
  - protocol: HTTP
    routes:
    # Health check - direct response without backend
    - name: health-check
      matches:
      - path:
          pathPrefix: /health
      policies:
        directResponse:
          body: '{"status": "healthy"}'
          status: 200

    # API route with path, query, and header matching
    - name: api-v2
      matches:
      - path:
          pathPrefix: /api
        method: GET
        query:
        - name: version
          value:
            exact: v2
        headers:
        - name: x-api-key
          value:
            regex: "key-[a-z0-9]+"
      policies:
        directResponse:
          body: '{"message": "API v2 matched!"}'
          status: 200

    # Route with CORS and rate limiting
    - name: protected-api
      matches:
      - path:
          pathPrefix: /protected
      policies:
        cors:
          allowHeaders: ["content-type", "authorization"]
          allowOrigins: ["https://example.com"]
          allowCredentials: true
          allowMethods: ["GET", "POST"]
          maxAge: 3600s
        localRateLimit:
        - maxTokens: 10
          tokensPerFill: 1
          fillInterval: 1s
        directResponse:
          body: '{"message": "Protected endpoint"}'
          status: 200

    # IP-based authorization
    - name: internal-only
      matches:
      - path:
          pathPrefix: /internal
      policies:
        authorization:
          rules:
          - |
            cidr("127.0.0.0/8").containsIP(source.address)
        directResponse:
          body: '{"message": "Internal access granted"}'
          status: 200

    # Catch-all route
    - name: default
      policies:
        directResponse:
          body: '{"error": "Not found"}'
          status: 404
EOF

Step 3: Start Agent Gateway

agentgateway -f config.yaml

Step 4: Test the routes

Test health check endpoint

curl http://localhost:3000/health

Expected response:

{"status": "healthy"}

Test path + query + header matching

The API v2 route requires all conditions to match:

# Missing query param and header - falls through to 404
curl http://localhost:3000/api
{"error": "Not found"}
# All conditions met - matches API v2
curl "http://localhost:3000/api?version=v2" -H "x-api-key: key-abc123"
{"message": "API v2 matched!"}

Test CORS preflight

curl -X OPTIONS http://localhost:3000/protected \
  -H "Origin: https://example.com" \
  -H "Access-Control-Request-Method: GET" \
  -i

Expected headers:

access-control-allow-origin: https://example.com
access-control-allow-methods: GET,POST
access-control-allow-headers: content-type,authorization
access-control-max-age: 3600

Test rate limiting

# Send 15 rapid requests
for i in $(seq 1 15); do
  code=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:3000/protected)
  echo "Request $i: $code"
done

After exceeding the rate limit (10 tokens), you’ll see 429 Too Many Requests.

Test IP-based authorization

# From localhost (IPv4) - should be allowed
curl http://127.0.0.1:3000/internal
{"message": "Internal access granted"}

Matching rules reference

Path matching

matches:
- path:
    pathPrefix: /api     # Matches /api, /api/users, /api/v1/...
- path:
    exact: /health       # Matches only /health
- path:
    regex: "/users/[0-9]+"  # Regex pattern

Header matching

matches:
- headers:
  - name: x-api-version
    value:
      exact: "2.0"
  - name: authorization
    value:
      regex: "Bearer .+"

Query parameter matching

matches:
- query:
  - name: format
    value:
      exact: json

Method matching

matches:
- method: GET    # Only match GET requests
- method: POST   # Only match POST requests

Traffic policies reference

Rate limiting

policies:
  localRateLimit:
  - maxTokens: 100      # Maximum burst size
    tokensPerFill: 10   # Tokens added per interval
    fillInterval: 1s    # Refill interval

Retries

policies:
  retry:
    attempts: 3
    codes: [502, 503, 504, 429]
    backoff:
      baseInterval: 100ms
      maxInterval: 1s

Timeouts

policies:
  timeout:
    requestTimeout: 30s
    idleTimeout: 60s

CORS

policies:
  cors:
    allowOrigins: ["https://example.com", "https://app.example.com"]
    allowMethods: ["GET", "POST", "PUT", "DELETE"]
    allowHeaders: ["content-type", "authorization"]
    exposeHeaders: ["x-request-id"]
    allowCredentials: true
    maxAge: 3600s

Request/Response header modification

policies:
  requestHeaderModifier:
    add:
      x-request-id: '${uuid()}'
    set:
      x-forwarded-for: '${source.address}'
    remove:
    - x-internal-header
  responseHeaderModifier:
    set:
      x-served-by: agentgateway

URL rewriting

policies:
  urlRewrite:
    path:
      full: "/new-path"
    authority:
      full: "backend.internal"

Request mirroring

policies:
  requestMirror:
    backend:
      host: 127.0.0.1:8081
    percentage: 0.1  # Mirror 10% of traffic

Direct response

policies:
  directResponse:
    body: '{"status": "ok"}'
    status: 200

IP-based authorization

policies:
  authorization:
    rules:
    # Allow specific CIDR ranges
    - |
      cidr("10.0.0.0/8").containsIP(source.address)
    # Or combine with OR logic
    - |
      cidr("192.168.0.0/16").containsIP(source.address) ||
      cidr("172.16.0.0/12").containsIP(source.address)

Routing with backends

When routing to actual backend services:

routes:
- name: api-backend
  matches:
  - path:
      pathPrefix: /api
  policies:
    retry:
      attempts: 3
      codes: [502, 503, 504]
    timeout:
      requestTimeout: 30s
  backends:
  - host: api.internal:8080

- name: mcp-backend
  matches:
  - path:
      pathPrefix: /mcp
  policies:
    cors:
      allowOrigins: ["*"]
      allowHeaders: ["*"]
      exposeHeaders: ["Mcp-Session-Id"]
  backends:
  - mcp:
      targets:
      - name: everything
        stdio:
          cmd: npx
          args: ["@modelcontextprotocol/server-everything"]

Cleanup

Stop the Agent Gateway with Ctrl+C and remove the test directory:

cd .. && rm -rf http-routing-test

Learn more