scripts: region gate takes the status from curl and the version from the final response only (review fix)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 files changed,
+50
−4
region_service_gate_test.go+42 −0scripts/region-service-gate.sh+8 −4
diff --git a/region_service_gate_test.go b/region_service_gate_test.go| index d31d970e1..1a33241b8 100644 |
| --- a/region_service_gate_test.go |
| +++ b/region_service_gate_test.go |
| @@ -48,3 +48,45 @@ func TestRegionServiceGate(t *testing.T) { |
| t.Fatalf("the gate went through a proxy: %v %s", err, out) |
| } |
| } |
| + |
| +// Review fix round 1: the gate judges the final response only. The status is |
| +// the final status line, not any header that starts with "HTTP", and |
| +// X-Carlos-Version is read from the final response's header block, not from |
| +// an informational response ahead of it. |
| +func TestRegionServiceGateJudgesTheFinalResponse(t *testing.T) { |
| + const version = "abc1234" |
| + gate := func(h http.HandlerFunc) (string, error) { |
| + srv := httptest.NewTLSServer(h) |
| + defer srv.Close() |
| + host, port, _ := net.SplitHostPort(srv.Listener.Addr().String()) |
| + cmd := exec.Command("./scripts/region-service-gate.sh", "probe.example", version, host) |
| + cmd.Env = append(cmd.Environ(), "GATE_PORT="+port, "GATE_INSECURE=1", "GATE_SKIP_PUBLIC=1") |
| + out, err := cmd.CombinedOutput() |
| + return string(out), err |
| + } |
| + // A 500 that carries a header looking like a status line and the right version. |
| + if out, err := gate(func(w http.ResponseWriter, r *http.Request) { |
| + w.Header().Set("X-Carlos-Version", version) |
| + w.Header().Set("HTTP-Debug", "200") |
| + w.WriteHeader(http.StatusInternalServerError) |
| + }); err == nil || !strings.Contains(out, "FAIL") { |
| + t.Errorf("a 500 with an HTTP-Debug header passed: %s", out) |
| + } |
| + // 103 Early Hints carries the version; the final 200 does not. |
| + if out, err := gate(func(w http.ResponseWriter, r *http.Request) { |
| + w.Header().Set("X-Carlos-Version", version) |
| + w.WriteHeader(http.StatusEarlyHints) |
| + w.Header().Del("X-Carlos-Version") |
| + w.WriteHeader(http.StatusOK) |
| + }); err == nil || !strings.Contains(out, "FAIL") { |
| + t.Errorf("a version only on the 103 passed: %s", out) |
| + } |
| + // Both blocks carry the version: the final one is right, so it passes. |
| + if out, err := gate(func(w http.ResponseWriter, r *http.Request) { |
| + w.Header().Set("X-Carlos-Version", version) |
| + w.WriteHeader(http.StatusEarlyHints) |
| + w.WriteHeader(http.StatusOK) |
| + }); err != nil { |
| + t.Errorf("a 103 and a 200 that both carry the version failed: %v %s", err, out) |
| + } |
| +} |
diff --git a/scripts/region-service-gate.sh b/scripts/region-service-gate.sh| index ea6311f4a..a339f723f 100755 |
| --- a/scripts/region-service-gate.sh |
| +++ b/scripts/region-service-gate.sh |
| @@ -22,16 +22,20 @@ trap 'rm -f "$hdr"' EXIT |
| fail=0 |
| check() { # <label> <expected-ip or ""> [curl args...] |
| local label="$1" eip="$2"; shift 2 |
| - local ip status version |
| + local out ip status version |
| # -q must be first: it stops curl reading ~/.curlrc. $insecure is |
| # unquoted on purpose so that empty expands to nothing (bash 3.2 safe). |
| - ip="$(curl -q -s $insecure --noproxy '*' -D "$hdr" -o /dev/null -w '%{remote_ip}' --max-time 20 "$@" "https://$host:$port/")" || { |
| + # The status is curl's own final %{http_code}, never a header that happens |
| + # to start with "HTTP". The version is read only from the final response's |
| + # header block: -D also dumps 1xx blocks (103 Early Hints), so the block |
| + # starts again at every real status line ("HTTP/x.y NNN"). |
| + out="$(curl -q -s $insecure --noproxy '*' -D "$hdr" -o /dev/null -w '%{remote_ip} %{http_code}' --max-time 20 "$@" "https://$host:$port/")" || { |
| echo "FAIL $label: request failed"; fail=1; return; } |
| + read -r ip status <<<"$out" |
| if [ -n "$eip" ] && [ "$ip" != "$eip" ]; then |
| echo "FAIL $label: connected to $ip, not $eip"; fail=1; return |
| fi |
| - status="$(awk 'toupper($1) ~ /^HTTP/ {s=$2} END {print s}' "$hdr")" |
| - version="$(awk -F': *' 'tolower($1)=="x-carlos-version" {gsub(/\r/,"",$2); print $2}' "$hdr")" |
| + version="$(awk -F': *' '/^HTTP\/[0-9.]+ [0-9][0-9][0-9]/ {v=""; next} tolower($1)=="x-carlos-version" {gsub(/\r/,"",$2); v = (v=="" ? $2 : v "," $2)} END {print v}' "$hdr")" |
| if [[ "$status" != 2* ]]; then echo "FAIL $label: status $status"; fail=1; return; fi |
| if [ "$version" != "$want" ]; then echo "FAIL $label: X-Carlos-Version '$version', want '$want'"; fail=1; return; fi |
| echo "ok $label: $status $version" |