@marcel91200 / caddy Mirror · read-only / commits / 915793f6
Fork 0

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS

Mirrored from https://github.com/Slazee/caddy.git

Synced Nov 11, 09:45 UTC

caddyhttp: add {http.request.proto_name} placeholder for spec-compliant protocol names (#7782)

* caddyhttp: add {http.request.proto_name} placeholder for spec-compliant protocol names {http.request.proto} exposes Go's raw http.Request.Proto field which returns HTTP/2.0 and HTTP/3.0 for HTTP/2 and HTTP/3 respectively. These strings are non-standard since the specs define them as HTTP/2 and HTTP/3. To preserve backward compat (especially CGI/FastCGI expectations), {http.request.proto} is kept as-is. A new {http.request.proto_name} placeholder is introduced that normalises the version string to the spec-defined form: HTTP/2.0 -> HTTP/2 HTTP/3.0 -> HTTP/3 all others returned unchanged Closes #7734 * caddyhttp: Use ProtoMajor for proto_name normalization and update docs --------- Co-authored-by: jalikajalika5 <105954036+jalikajalika5@users.noreply.github.com>

Muhammad Syafri, S.Kom committed Jun 4, 2026 at 22:03 UTC 915793f6e009669c4c750bc9a8bcf3c96784e646
3 files changed +40 -1
modules/caddyhttp/app.go
+2 -1
@@ -70,7 +70,8 @@ func init() {
70 // `{http.request.orig_uri.query}` | The request's original query string (without `?`)
71 // `{http.request.orig_uri.prefixed_query}` | The request's original query string with a `?` prefix, if non-empty
72 // `{http.request.port}` | The port part of the request's Host header
73 -// `{http.request.proto}` | The protocol of the request
73 +// `{http.request.proto}` | The raw protocol of the request as returned by Go (e.g., HTTP/2.0 or HTTP/3.0)
74 +// `{http.request.proto_name}` | The spec-defined protocol of the request (e.g., HTTP/2 or HTTP/3)
75 // `{http.request.local.host}` | The host (IP) part of the local address the connection arrived on
76 // `{http.request.local.port}` | The port part of the local address the connection arrived on
77 // `{http.request.local}` | The local address the connection arrived on
modules/caddyhttp/replacer.go
+8
@@ -105,6 +105,14 @@ func addHTTPVarsToReplacer(repl *caddy.Replacer, req *http.Request, w http.Respo
105 return "http", true
106 case "http.request.proto":
107 return req.Proto, true
108 + case "http.request.proto_name":
109 + if req.ProtoMajor == 2 {
110 + return "HTTP/2", true
111 + }
112 + if req.ProtoMajor == 3 {
113 + return "HTTP/3", true
114 + }
115 + return req.Proto, true
116 case "http.request.host":
117 host, _, err := net.SplitHostPort(req.Host)
118 if err != nil {
modules/caddyhttp/replacer_test.go
+30
@@ -266,3 +266,33 @@ eqp31wM9il1n+guTNyxJd+FzVAH+hCZE5K+tCgVDdVFUlDEHHbS/wqb2PSIoouLV
266 }
267 }
268 }
269 +
270 +func TestHTTPProtoNameNormalization(t *testing.T) {
271 + for _, tc := range []struct {
272 + proto string
273 + major int
274 + expectRaw string
275 + expectName string
276 + }{
277 + {proto: "HTTP/1.0", major: 1, expectRaw: "HTTP/1.0", expectName: "HTTP/1.0"},
278 + {proto: "HTTP/1.1", major: 1, expectRaw: "HTTP/1.1", expectName: "HTTP/1.1"},
279 + {proto: "HTTP/2.0", major: 2, expectRaw: "HTTP/2.0", expectName: "HTTP/2"},
280 + {proto: "HTTP/3.0", major: 3, expectRaw: "HTTP/3.0", expectName: "HTTP/3"},
281 + } {
282 + req := httptest.NewRequest(http.MethodGet, "/", nil)
283 + req.Proto = tc.proto
284 + req.ProtoMajor = tc.major
285 + repl := caddy.NewReplacer()
286 + addHTTPVarsToReplacer(repl, req, nil)
287 +
288 + gotRaw, okRaw := repl.GetString("http.request.proto")
289 + if !okRaw || gotRaw != tc.expectRaw {
290 + t.Errorf("proto=%s: expected http.request.proto to be %q, got %q (ok=%t)", tc.proto, tc.expectRaw, gotRaw, okRaw)
291 + }
292 +
293 + gotName, okName := repl.GetString("http.request.proto_name")
294 + if !okName || gotName != tc.expectName {
295 + t.Errorf("proto=%s: expected http.request.proto_name to be %q, got %q (ok=%t)", tc.proto, tc.expectName, gotName, okName)
296 + }
297 + }
298 +}