diff --git a/modules/caddyhttp/app.go b/modules/caddyhttp/app.go index bc2b896cd..79fd5f414 100644 --- a/modules/caddyhttp/app.go +++ b/modules/caddyhttp/app.go @@ -70,7 +70,8 @@ func init() { // `{http.request.orig_uri.query}` | The request's original query string (without `?`) // `{http.request.orig_uri.prefixed_query}` | The request's original query string with a `?` prefix, if non-empty // `{http.request.port}` | The port part of the request's Host header -// `{http.request.proto}` | The protocol of the request +// `{http.request.proto}` | The raw protocol of the request as returned by Go (e.g., HTTP/2.0 or HTTP/3.0) +// `{http.request.proto_name}` | The spec-defined protocol of the request (e.g., HTTP/2 or HTTP/3) // `{http.request.local.host}` | The host (IP) part of the local address the connection arrived on // `{http.request.local.port}` | The port part of the local address the connection arrived on // `{http.request.local}` | The local address the connection arrived on diff --git a/modules/caddyhttp/replacer.go b/modules/caddyhttp/replacer.go index 623a6ef4b..65f9dd475 100644 --- a/modules/caddyhttp/replacer.go +++ b/modules/caddyhttp/replacer.go @@ -105,6 +105,14 @@ func addHTTPVarsToReplacer(repl *caddy.Replacer, req *http.Request, w http.Respo return "http", true case "http.request.proto": return req.Proto, true + case "http.request.proto_name": + if req.ProtoMajor == 2 { + return "HTTP/2", true + } + if req.ProtoMajor == 3 { + return "HTTP/3", true + } + return req.Proto, true case "http.request.host": host, _, err := net.SplitHostPort(req.Host) if err != nil { diff --git a/modules/caddyhttp/replacer_test.go b/modules/caddyhttp/replacer_test.go index c75fe82ed..4f8d8f0b2 100644 --- a/modules/caddyhttp/replacer_test.go +++ b/modules/caddyhttp/replacer_test.go @@ -266,3 +266,33 @@ eqp31wM9il1n+guTNyxJd+FzVAH+hCZE5K+tCgVDdVFUlDEHHbS/wqb2PSIoouLV } } } + +func TestHTTPProtoNameNormalization(t *testing.T) { + for _, tc := range []struct { + proto string + major int + expectRaw string + expectName string + }{ + {proto: "HTTP/1.0", major: 1, expectRaw: "HTTP/1.0", expectName: "HTTP/1.0"}, + {proto: "HTTP/1.1", major: 1, expectRaw: "HTTP/1.1", expectName: "HTTP/1.1"}, + {proto: "HTTP/2.0", major: 2, expectRaw: "HTTP/2.0", expectName: "HTTP/2"}, + {proto: "HTTP/3.0", major: 3, expectRaw: "HTTP/3.0", expectName: "HTTP/3"}, + } { + req := httptest.NewRequest(http.MethodGet, "/", nil) + req.Proto = tc.proto + req.ProtoMajor = tc.major + repl := caddy.NewReplacer() + addHTTPVarsToReplacer(repl, req, nil) + + gotRaw, okRaw := repl.GetString("http.request.proto") + if !okRaw || gotRaw != tc.expectRaw { + t.Errorf("proto=%s: expected http.request.proto to be %q, got %q (ok=%t)", tc.proto, tc.expectRaw, gotRaw, okRaw) + } + + gotName, okName := repl.GetString("http.request.proto_name") + if !okName || gotName != tc.expectName { + t.Errorf("proto=%s: expected http.request.proto_name to be %q, got %q (ok=%t)", tc.proto, tc.expectName, gotName, okName) + } + } +}