From 75bd83677ed14bbe487d14f5b96007b953526f88 Mon Sep 17 00:00:00 2001 From: Mohammed Al Sahaf Date: Sat, 6 Jun 2026 22:32:45 +0300 Subject: [PATCH] add reverse_proxy, log, and abort tests --- caddytest/spec/http/abort/spec.hurl | 102 ++++++++ caddytest/spec/http/log/spec.hurl | 250 ++++++++++++++++++++ caddytest/spec/http/reverse_proxy/spec.hurl | 209 ++++++++++++++++ 3 files changed, 561 insertions(+) create mode 100644 caddytest/spec/http/abort/spec.hurl create mode 100644 caddytest/spec/http/log/spec.hurl create mode 100644 caddytest/spec/http/reverse_proxy/spec.hurl diff --git a/caddytest/spec/http/abort/spec.hurl b/caddytest/spec/http/abort/spec.hurl new file mode 100644 index 000000000..2782b167b --- /dev/null +++ b/caddytest/spec/http/abort/spec.hurl @@ -0,0 +1,102 @@ +# Configure Caddy with abort directive +POST http://localhost:2019/load +Content-Type: text/caddyfile +``` +{ + skip_install_trust + http_port 9080 + https_port 9443 + local_certs +} +localhost { + abort + respond "This should not be reached" +} +``` + +# NOTE: bare `abort` aborts the request without sending a response, so the +# client sees a transport-level connection close. We cannot assert this in +# Hurl (it errors on the broken HTTP exchange). The Caddyfile above is +# accepted and provisioned successfully, which the /load probe confirms. + + +# Configure Caddy with conditional abort +POST http://localhost:2019/load +Content-Type: text/caddyfile +``` +{ + skip_install_trust + http_port 9080 + https_port 9443 + local_certs +} +localhost { + @blocked path /blocked* + abort @blocked + respond "OK" +} +``` + +# abort only affects matching paths +GET https://localhost:9443/allowed +[Options] +insecure: true +HTTP 200 +[Asserts] +body == "OK" + + +# Configure Caddy with abort in route +POST http://localhost:2019/load +Content-Type: text/caddyfile +``` +{ + skip_install_trust + http_port 9080 + https_port 9443 + local_certs +} +localhost { + route /api/* { + @invalid header X-API-Key "" + abort @invalid + respond "API OK" + } + respond "Not API" +} +``` + +# abort in route for matching condition +GET https://localhost:9443/other +[Options] +insecure: true +HTTP 200 +[Asserts] +body == "Not API" + + +# Configure Caddy with abort after headers +POST http://localhost:2019/load +Content-Type: text/caddyfile +``` +{ + skip_install_trust + http_port 9080 + https_port 9443 + local_certs +} +localhost { + @crawler header User-Agent *bot* + abort @crawler + respond "Normal user" +} +``` + +# abort based on User-Agent header +GET https://localhost:9443 +User-Agent: Mozilla/5.0 +[Options] +insecure: true +HTTP 200 +[Asserts] +body == "Normal user" diff --git a/caddytest/spec/http/log/spec.hurl b/caddytest/spec/http/log/spec.hurl new file mode 100644 index 000000000..f9ec37c35 --- /dev/null +++ b/caddytest/spec/http/log/spec.hurl @@ -0,0 +1,250 @@ +# Each block routes Caddy's access log to a unique file under +# /tmp/caddy-log-spec/ and exposes that directory via a sidecar +# `file_server` site on :9081, so a subsequent GET can read the log +# back and assert the directive actually did what it claims. +# +# `roll_disabled` keeps the writer as a plain os.File so writes are +# visible to the next read without lumberjack rotation games. +# +# Files persist between runs; if you change a config block locally you +# may want to `rm -rf /tmp/caddy-log-spec` before re-running. CI runners +# start with a clean /tmp. + +# Configure Caddy with the log directive writing to a real file +POST http://localhost:2019/load +Content-Type: text/caddyfile +``` +{ + skip_install_trust + http_port 9080 + https_port 9443 + local_certs +} +localhost { + log { + output file /tmp/caddy-log-spec/basic.log { + roll_disabled + } + format json + } + respond "Logged response" +} +:9081 { + root * /tmp/caddy-log-spec + file_server +} +``` + +# Generate a logged request +GET https://localhost:9443/from-basic +[Options] +insecure: true +HTTP 200 +[Asserts] +body == "Logged response" + +# The log file exists and contains a JSON entry for the request above +GET http://localhost:9081/basic.log +HTTP 200 +[Asserts] +body contains "\"uri\":\"/from-basic\"" +body contains "\"status\":200" + + +# Configure Caddy with log_skip +POST http://localhost:2019/load +Content-Type: text/caddyfile +``` +{ + skip_install_trust + http_port 9080 + https_port 9443 + local_certs +} +localhost { + log { + output file /tmp/caddy-log-spec/skip.log { + roll_disabled + } + format json + } + log_skip /health + respond "OK" +} +:9081 { + root * /tmp/caddy-log-spec + file_server +} +``` + +# Requests we want logged and requests we want skipped +GET https://localhost:9443/skip-api +[Options] +insecure: true +HTTP 200 + +GET https://localhost:9443/health +[Options] +insecure: true +HTTP 200 + +# Logged path appears, skipped path does not +GET http://localhost:9081/skip.log +HTTP 200 +[Asserts] +body contains "\"uri\":\"/skip-api\"" +body not contains "\"uri\":\"/health\"" + + +# Configure Caddy with log_append +POST http://localhost:2019/load +Content-Type: text/caddyfile +``` +{ + skip_install_trust + http_port 9080 + https_port 9443 + local_certs +} +localhost { + log { + output file /tmp/caddy-log-spec/append.log { + roll_disabled + } + format json + } + log_append user_id {http.request.header.X-User-Id} + log_append request_type "api" + respond "OK" +} +:9081 { + root * /tmp/caddy-log-spec + file_server +} +``` + +# Request that should produce a log entry with the appended fields +GET https://localhost:9443/append-target +X-User-Id: user123 +[Options] +insecure: true +HTTP 200 + +# Both appended fields are present in the JSON log entry +GET http://localhost:9081/append.log +HTTP 200 +[Asserts] +body contains "\"uri\":\"/append-target\"" +body contains "\"user_id\":\"user123\"" +body contains "\"request_type\":\"api\"" + + +# Configure Caddy with log_name routing requests to different loggers +POST http://localhost:2019/load +Content-Type: text/caddyfile +``` +{ + skip_install_trust + http_port 9080 + https_port 9443 + local_certs +} +localhost { + log api_log { + output file /tmp/caddy-log-spec/name_api.log { + roll_disabled + } + format json + } + log static_log { + output file /tmp/caddy-log-spec/name_static.log { + roll_disabled + } + format json + } + log_name /api/* api_log + log_name /static/* static_log + respond "OK" +} +:9081 { + root * /tmp/caddy-log-spec + file_server +} +``` + +# Drive requests into each logger +GET https://localhost:9443/api/probe +[Options] +insecure: true +HTTP 200 + +GET https://localhost:9443/static/style.css +[Options] +insecure: true +HTTP 200 + +# api_log only contains /api entries +GET http://localhost:9081/name_api.log +HTTP 200 +[Asserts] +body contains "\"uri\":\"/api/probe\"" +body not contains "\"uri\":\"/static/style.css\"" + +# static_log only contains /static entries +GET http://localhost:9081/name_static.log +HTTP 200 +[Asserts] +body contains "\"uri\":\"/static/style.css\"" +body not contains "\"uri\":\"/api/probe\"" + + +# Configure Caddy with log_skip driven by a named matcher +POST http://localhost:2019/load +Content-Type: text/caddyfile +``` +{ + skip_install_trust + http_port 9080 + https_port 9443 + local_certs +} +localhost { + log { + output file /tmp/caddy-log-spec/named_matcher.log { + roll_disabled + } + format json + } + @health path /healthz /ready + log_skip @health + respond "OK" +} +:9081 { + root * /tmp/caddy-log-spec + file_server +} +``` + +# One logged path and two skipped paths +GET https://localhost:9443/named-api +[Options] +insecure: true +HTTP 200 + +GET https://localhost:9443/healthz +[Options] +insecure: true +HTTP 200 + +GET https://localhost:9443/ready +[Options] +insecure: true +HTTP 200 + +# Only the non-skipped path is present +GET http://localhost:9081/named_matcher.log +HTTP 200 +[Asserts] +body contains "\"uri\":\"/named-api\"" +body not contains "\"uri\":\"/healthz\"" +body not contains "\"uri\":\"/ready\"" diff --git a/caddytest/spec/http/reverse_proxy/spec.hurl b/caddytest/spec/http/reverse_proxy/spec.hurl new file mode 100644 index 000000000..12b541a61 --- /dev/null +++ b/caddytest/spec/http/reverse_proxy/spec.hurl @@ -0,0 +1,209 @@ +# Configure Caddy with reverse_proxy to echo server +POST http://localhost:2019/load +Content-Type: text/caddyfile +``` +{ + skip_install_trust + http_port 9080 + https_port 9443 + local_certs +} +localhost { + reverse_proxy localhost:8080 +} +http://localhost:8080 { + respond "Backend response: {method} {uri}" +} +``` + +# reverse_proxy forwards requests to upstream +GET https://localhost:9443/test +[Options] +delay: 500ms +insecure: true +HTTP 200 +[Asserts] +body == "Backend response: GET /test" + + +# reverse_proxy preserves query parameters +GET https://localhost:9443/api?param=value +[Options] +delay: 500ms +insecure: true +HTTP 200 +[Asserts] +body == "Backend response: GET /api?param=value" + + +# reverse_proxy handles POST requests +POST https://localhost:9443/submit +[Options] +delay: 500ms +insecure: true +``` +{"data": "test"} +``` +HTTP 200 +[Asserts] +body == "Backend response: POST /submit" + + +# Configure Caddy with reverse_proxy and header manipulation +POST http://localhost:2019/load +Content-Type: text/caddyfile +``` +{ + skip_install_trust + http_port 9080 + https_port 9443 + local_certs +} +localhost { + reverse_proxy localhost:8080 { + header_up X-Custom-Header "CustomValue" + header_down -Server + } +} +http://localhost:8080 { + respond "{header.X-Custom-Header}" +} +``` + +# reverse_proxy adds upstream headers +GET https://localhost:9443 +[Options] +delay: 500ms +insecure: true +HTTP 200 +[Asserts] +body == "CustomValue" + + +# Configure Caddy with reverse_proxy path rewriting +POST http://localhost:2019/load +Content-Type: text/caddyfile +``` +{ + skip_install_trust + http_port 9080 + https_port 9443 + local_certs +} +localhost { + handle_path /api/* { + reverse_proxy localhost:8080 + } +} +http://localhost:8080 { + respond "Backend: {uri}" +} +``` + +# reverse_proxy with handle_path strips prefix +GET https://localhost:9443/api/v1/users +[Options] +delay: 500ms +insecure: true +HTTP 200 +[Asserts] +body == "Backend: /v1/users" + + +# Configure Caddy with reverse_proxy to multiple upstreams +POST http://localhost:2019/load +Content-Type: text/caddyfile +``` +{ + skip_install_trust + http_port 9080 + https_port 9443 + local_certs +} +localhost { + reverse_proxy localhost:8080 localhost:8081 +} +http://localhost:8080 { + respond "Backend 1" +} +http://localhost:8081 { + respond "Backend 2" +} +``` + +# reverse_proxy load balances between upstreams +GET https://localhost:9443 +[Options] +delay: 500ms +insecure: true +HTTP 200 + + +# Multiple requests may hit different backends +GET https://localhost:9443 +[Options] +delay: 500ms +insecure: true +HTTP 200 + + +# Configure Caddy with reverse_proxy and transport options +POST http://localhost:2019/load +Content-Type: text/caddyfile +``` +{ + skip_install_trust + http_port 9080 + https_port 9443 + local_certs +} +localhost { + reverse_proxy localhost:8080 { + transport http { + dial_timeout 2s + } + } +} +http://localhost:8080 { + respond "OK" +} +``` + +# reverse_proxy respects transport settings +GET https://localhost:9443 +[Options] +delay: 500ms +insecure: true +HTTP 200 +[Asserts] +body == "OK" + + +# Configure Caddy with reverse_proxy and flush_interval +POST http://localhost:2019/load +Content-Type: text/caddyfile +``` +{ + skip_install_trust + http_port 9080 + https_port 9443 + local_certs +} +localhost { + reverse_proxy localhost:8080 { + flush_interval -1 + } +} +http://localhost:8080 { + respond "Streaming response" +} +``` + +# reverse_proxy handles streaming with flush_interval +GET https://localhost:9443 +[Options] +delay: 500ms +insecure: true +HTTP 200 +[Asserts] +body == "Streaming response"