Browse Source

perf: reduce allocs when creating $_SERVER (#540)

* perf: reduce allocs when creating $_SERVER

* improve

* refactor: prevent C allocs when populating $_SERVER

* cs

* remove append()

* simplify

* wip

* cleanup

* add cache

* cleanup otter init

* some fixes

* cleanup

* test with a leak

* remove const?

* add const

* wip

* wip

* allocate dynamic variables in Go memory

* cleanup

* typo

* bump otter

* chore: bump deps
Kévin Dunglas 1 year ago
parent
commit
07a74e5c5a
10 changed files with 251 additions and 147 deletions
  1. 5 5
      caddy/caddy.go
  2. 8 4
      caddy/go.mod
  3. 16 8
      caddy/go.sum
  4. 58 38
      cgi.go
  5. 67 58
      frankenphp.c
  6. 57 19
      frankenphp.go
  7. 10 4
      frankenphp.h
  8. 15 8
      frankenphp_test.go
  9. 5 1
      go.mod
  10. 10 2
      go.sum

+ 5 - 5
caddy/caddy.go

@@ -205,7 +205,7 @@ type FrankenPHPModule struct {
 	// ResolveRootSymlink enables resolving the `root` directory to its actual value by evaluating a symbolic link, if one exists.
 	ResolveRootSymlink *bool `json:"resolve_root_symlink,omitempty"`
 	// Env sets an extra environment variable to the given value. Can be specified more than once for multiple environment variables.
-	Env    map[string]string `json:"env,omitempty"`
+	Env    frankenphp.PreparedEnv `json:"env,omitempty"`
 	logger *zap.Logger
 }
 
@@ -256,7 +256,7 @@ func (f FrankenPHPModule) ServeHTTP(w http.ResponseWriter, r *http.Request, _ ca
 	documentRoot := repl.ReplaceKnown(f.Root, "")
 
 	env := make(map[string]string, len(f.Env)+1)
-	env["REQUEST_URI"] = origReq.URL.RequestURI()
+	env["REQUEST_URI\x00"] = origReq.URL.RequestURI()
 	for k, v := range f.Env {
 		env[k] = repl.ReplaceKnown(v, "")
 	}
@@ -265,7 +265,7 @@ func (f FrankenPHPModule) ServeHTTP(w http.ResponseWriter, r *http.Request, _ ca
 		r,
 		frankenphp.WithRequestDocumentRoot(documentRoot, *f.ResolveRootSymlink),
 		frankenphp.WithRequestSplitPath(f.SplitPath),
-		frankenphp.WithRequestEnv(env),
+		frankenphp.WithRequestPreparedEnv(env),
 	)
 
 	if err != nil {
@@ -298,9 +298,9 @@ func (f *FrankenPHPModule) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
 					return d.ArgErr()
 				}
 				if f.Env == nil {
-					f.Env = make(map[string]string)
+					f.Env = make(frankenphp.PreparedEnv)
 				}
-				f.Env[args[0]] = args[1]
+				f.Env[args[0]+"\x00"] = args[1]
 
 			case "resolve_root_symlink":
 				if d.NextArg() {

+ 8 - 4
caddy/go.mod

@@ -47,6 +47,8 @@ require (
 	github.com/dgraph-io/ristretto v0.1.1 // indirect
 	github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect
 	github.com/dlclark/regexp2 v1.11.0 // indirect
+	github.com/dolthub/maphash v0.1.0 // indirect
+	github.com/dolthub/swiss v0.2.1 // indirect
 	github.com/dunglas/httpsfv v1.0.2 // indirect
 	github.com/dunglas/mercure v0.15.9 // indirect
 	github.com/dunglas/vulcain v1.0.1 // indirect
@@ -54,6 +56,7 @@ require (
 	github.com/felixge/httpsnoop v1.0.4 // indirect
 	github.com/fsnotify/fsnotify v1.7.0 // indirect
 	github.com/fxamacker/cbor/v2 v2.5.0 // indirect
+	github.com/gammazero/deque v0.2.1 // indirect
 	github.com/getkin/kin-openapi v0.122.0 // indirect
 	github.com/go-chi/chi/v5 v5.0.10 // indirect
 	github.com/go-kit/kit v0.13.0 // indirect
@@ -105,6 +108,7 @@ require (
 	github.com/mastercactapus/proxyprotocol v0.0.4 // indirect
 	github.com/mattn/go-colorable v0.1.13 // indirect
 	github.com/mattn/go-isatty v0.0.20 // indirect
+	github.com/maypok86/otter v1.1.1 // indirect
 	github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
 	github.com/mholt/acmez v1.2.0 // indirect
 	github.com/micromdm/scep/v2 v2.1.0 // indirect
@@ -175,13 +179,13 @@ require (
 	go.step.sm/linkedca v0.20.1 // indirect
 	go.uber.org/mock v0.4.0 // indirect
 	go.uber.org/multierr v1.11.0 // indirect
-	golang.org/x/crypto v0.20.0 // indirect
+	golang.org/x/crypto v0.21.0 // indirect
 	golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect
 	golang.org/x/mod v0.15.0 // indirect
-	golang.org/x/net v0.21.0 // indirect
+	golang.org/x/net v0.22.0 // indirect
 	golang.org/x/sync v0.6.0 // indirect
-	golang.org/x/sys v0.17.0 // indirect
-	golang.org/x/term v0.17.0 // indirect
+	golang.org/x/sys v0.18.0 // indirect
+	golang.org/x/term v0.18.0 // indirect
 	golang.org/x/text v0.14.0 // indirect
 	golang.org/x/tools v0.18.0 // indirect
 	google.golang.org/genproto/googleapis/api v0.0.0-20240228224816-df926f6c8641 // indirect

+ 16 - 8
caddy/go.sum

@@ -106,6 +106,10 @@ github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55k
 github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
 github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
 github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
+github.com/dolthub/maphash v0.1.0 h1:bsQ7JsF4FkkWyrP3oCnFJgrCUAFbFf3kOl4L/QxPDyQ=
+github.com/dolthub/maphash v0.1.0/go.mod h1:gkg4Ch4CdCDu5h6PMriVLawB7koZ+5ijb9puGMV50a4=
+github.com/dolthub/swiss v0.2.1 h1:gs2osYs5SJkAaH5/ggVJqXQxRXtWshF6uE0lgR/Y3Gw=
+github.com/dolthub/swiss v0.2.1/go.mod h1:8AhKZZ1HK7g18j7v7k6c5cYIGEZJcPn0ARsai8cUrh0=
 github.com/dunglas/caddy-cbrotli v1.0.0 h1:+WNqXBkWyMcIpXB2rVZ3nwcElUbuAzf0kPxNXU4D+u0=
 github.com/dunglas/caddy-cbrotli v1.0.0/go.mod h1:KZsUu3fnQBgO0o3YDoQuO3Z61dFgUncr1F8rg8acwQw=
 github.com/dunglas/httpsfv v1.0.2 h1:iERDp/YAfnojSDJ7PW3dj1AReJz4MrwbECSSE59JWL0=
@@ -130,6 +134,8 @@ github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nos
 github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
 github.com/fxamacker/cbor/v2 v2.5.0 h1:oHsG0V/Q6E/wqTS2O1Cozzsy69nqCiguo5Q1a1ADivE=
 github.com/fxamacker/cbor/v2 v2.5.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo=
+github.com/gammazero/deque v0.2.1 h1:qSdsbG6pgp6nL7A0+K/B7s12mcCY/5l5SIUpMOl+dC0=
+github.com/gammazero/deque v0.2.1/go.mod h1:LFroj8x4cMYCukHJDbxFCkT+r9AndaJnFMuZDV34tuU=
 github.com/getkin/kin-openapi v0.122.0 h1:WB9Jbl0Hp/T79/JF9xlSW5Kl9uYdk/AWD0yAd9HOM10=
 github.com/getkin/kin-openapi v0.122.0/go.mod h1:PCWw/lfBrJY4HcdqE3jj+QFkaFK8ABoqo7PvqVhXXqw=
 github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk=
@@ -339,6 +345,8 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky
 github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
 github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
 github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+github.com/maypok86/otter v1.1.1 h1:1WyOfBz2m8bjc4dAmJeUSuyUshoTP8ViiYmvRWO+53w=
+github.com/maypok86/otter v1.1.1/go.mod h1:IuSnpxeUyjKPPjqGzhGKOO26tedMNl45vwGcdXsEi8U=
 github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
 github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
 github.com/mholt/acmez v1.2.0 h1:1hhLxSgY5FvH5HCnGUuwbKY2VQVo8IU7rxXKSnZ7F30=
@@ -589,8 +597,8 @@ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5y
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
 golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
-golang.org/x/crypto v0.20.0 h1:jmAMJJZXr5KiCw05dfYK9QnqaqKLYXijU23lsEdcQqg=
-golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ=
+golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
+golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
 golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ=
 golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=
 golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
@@ -608,8 +616,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
 golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
-golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
-golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
+golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
+golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
 golang.org/x/oauth2 v0.17.0 h1:6m3ZPmLEFdVxKKWnKq4VqZ60gutO35zm+zrAHVmHyDQ=
 golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -641,15 +649,15 @@ golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
-golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
+golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
 golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
-golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U=
-golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
+golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
+golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=

+ 58 - 38
cgi.go

@@ -1,12 +1,15 @@
 package frankenphp
 
+// #include "frankenphp.h"
 import "C"
 import (
 	"crypto/tls"
 	"net"
 	"net/http"
 	"path/filepath"
+	"runtime"
 	"strings"
+	"unsafe"
 )
 
 type serverKey int
@@ -33,22 +36,44 @@ const (
 	sslProtocol
 )
 
-func allocServerVariable(cArr *[27]*C.char, env map[string]string, serverKey serverKey, envKey string, val string) {
-	if val, ok := env[envKey]; ok {
-		cArr[serverKey] = C.CString(val)
-		delete(env, envKey)
+var knownServerKeys = map[string]struct{}{
+	"CONTENT_LENGTH\x00":    {},
+	"DOCUMENT_ROOT\x00":     {},
+	"DOCUMENT_URI\x00":      {},
+	"GATEWAY_INTERFACE\x00": {},
+	"HTTP_HOST\x00":         {},
+	"HTTPS\x00":             {},
+	"PATH_INFO\x00":         {},
+	"PHP_SELF\x00":          {},
+	"REMOTE_ADDR\x00":       {},
+	"REMOTE_HOST\x00":       {},
+	"REMOTE_PORT\x00":       {},
+	"REQUEST_SCHEME\x00":    {},
+	"SCRIPT_FILENAME\x00":   {},
+	"SCRIPT_NAME\x00":       {},
+	"SERVER_NAME\x00":       {},
+	"SERVER_PORT\x00":       {},
+	"SERVER_PROTOCOL\x00":   {},
+	"SERVER_SOFTWARE\x00":   {},
+	"SSL_PROTOCOL\x00":      {},
+}
 
+func setKnownServerVariable(p *runtime.Pinner, cArr *[27]C.go_string, serverKey serverKey, val string) {
+	if val == "" {
 		return
 	}
 
-	cArr[serverKey] = C.CString(val)
+	valData := unsafe.StringData(val)
+	p.Pin(valData)
+	cArr[serverKey].len = C.size_t(len(val))
+	cArr[serverKey].data = (*C.char)(unsafe.Pointer(valData))
 }
 
 // computeKnownVariables returns a set of CGI environment variables for the request.
 //
 // TODO: handle this case https://github.com/caddyserver/caddy/issues/3718
 // Inspired by https://github.com/caddyserver/caddy/blob/master/modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go
-func computeKnownVariables(request *http.Request) (cArr [27]*C.char) {
+func computeKnownVariables(request *http.Request, p *runtime.Pinner) (cArr [27]C.go_string) {
 	fc, fcOK := FromContext(request.Context())
 	if !fcOK {
 		panic("not a FrankenPHP request")
@@ -67,32 +92,30 @@ func computeKnownVariables(request *http.Request) (cArr [27]*C.char) {
 	ip = strings.Replace(ip, "[", "", 1)
 	ip = strings.Replace(ip, "]", "", 1)
 
-	ra, raOK := fc.env["REMOTE_ADDR"]
+	ra, raOK := fc.env["REMOTE_ADDR\x00"]
 	if raOK {
-		cArr[remoteAddr] = C.CString(ra)
-		delete(fc.env, "REMOTE_ADDR")
+		setKnownServerVariable(p, &cArr, remoteAddr, ra)
 	} else {
-		cArr[remoteAddr] = C.CString(ip)
+		setKnownServerVariable(p, &cArr, remoteAddr, ip)
 	}
 
-	if rh, ok := fc.env["REMOTE_HOST"]; ok {
-		cArr[remoteHost] = C.CString(rh) // For speed, remote host lookups disabled
-		delete(fc.env, "REMOTE_HOST")
+	if rh, ok := fc.env["REMOTE_HOST\x00"]; ok {
+		setKnownServerVariable(p, &cArr, remoteHost, rh) // For speed, remote host lookups disabled
 	} else {
 		if raOK {
-			cArr[remoteHost] = C.CString(ip)
+			setKnownServerVariable(p, &cArr, remoteHost, ip)
 		} else {
 			cArr[remoteHost] = cArr[remoteAddr]
 		}
 	}
 
-	allocServerVariable(&cArr, fc.env, remotePort, "REMOTE_PORT", port)
-	allocServerVariable(&cArr, fc.env, documentRoot, "DOCUMENT_ROOT", fc.documentRoot)
-	allocServerVariable(&cArr, fc.env, pathInfo, "PATH_INFO", fc.pathInfo)
-	allocServerVariable(&cArr, fc.env, phpSelf, "PHP_SELF", request.URL.Path)
-	allocServerVariable(&cArr, fc.env, documentUri, "DOCUMENT_URI", fc.docURI)
-	allocServerVariable(&cArr, fc.env, scriptFilename, "SCRIPT_FILENAME", fc.scriptFilename)
-	allocServerVariable(&cArr, fc.env, scriptName, "SCRIPT_NAME", fc.scriptName)
+	setKnownServerVariable(p, &cArr, remotePort, port)
+	setKnownServerVariable(p, &cArr, documentRoot, fc.documentRoot)
+	setKnownServerVariable(p, &cArr, pathInfo, fc.pathInfo)
+	setKnownServerVariable(p, &cArr, phpSelf, request.URL.Path)
+	setKnownServerVariable(p, &cArr, documentUri, fc.docURI)
+	setKnownServerVariable(p, &cArr, scriptFilename, fc.scriptFilename)
+	setKnownServerVariable(p, &cArr, scriptName, fc.scriptName)
 
 	var rs string
 	if request.TLS == nil {
@@ -100,26 +123,24 @@ func computeKnownVariables(request *http.Request) (cArr [27]*C.char) {
 	} else {
 		rs = "https"
 
-		if h, ok := fc.env["HTTPS"]; ok {
-			cArr[https] = C.CString(h)
-			delete(fc.env, "HTTPS")
+		if h, ok := fc.env["HTTPS\x00"]; ok {
+			setKnownServerVariable(p, &cArr, https, h)
 		} else {
-			cArr[https] = C.CString("on")
+			setKnownServerVariable(p, &cArr, https, "on")
 		}
 
 		// and pass the protocol details in a manner compatible with apache's mod_ssl
 		// (which is why these have a SSL_ prefix and not TLS_).
-		if p, ok := fc.env["SSL_PROTOCOL"]; ok {
-			cArr[sslProtocol] = C.CString(p)
-			delete(fc.env, "SSL_PROTOCOL")
+		if pr, ok := fc.env["SSL_PROTOCOL\x00"]; ok {
+			setKnownServerVariable(p, &cArr, sslProtocol, pr)
 		} else {
 			if v, ok := tlsProtocolStrings[request.TLS.Version]; ok {
-				cArr[sslProtocol] = C.CString(v)
+				setKnownServerVariable(p, &cArr, sslProtocol, v)
 			}
 		}
 	}
-	allocServerVariable(&cArr, fc.env, requestScheme, "REQUEST_SCHEME", rs)
 
+	setKnownServerVariable(p, &cArr, requestScheme, rs)
 	reqHost, reqPort, _ := net.SplitHostPort(request.Host)
 
 	if reqHost == "" {
@@ -140,9 +161,9 @@ func computeKnownVariables(request *http.Request) (cArr [27]*C.char) {
 		}
 	}
 
-	allocServerVariable(&cArr, fc.env, serverName, "SERVER_NAME", reqHost)
+	setKnownServerVariable(p, &cArr, serverName, reqHost)
 	if reqPort != "" {
-		allocServerVariable(&cArr, fc.env, serverPort, "SERVER_PORT", reqPort)
+		setKnownServerVariable(p, &cArr, serverPort, reqPort)
 	}
 
 	// Variables defined in CGI 1.1 spec
@@ -150,12 +171,11 @@ func computeKnownVariables(request *http.Request) (cArr [27]*C.char) {
 	// the parent environment from interfering.
 
 	// These values can not be override
-	cArr[contentLength] = C.CString(request.Header.Get("Content-Length"))
-
-	allocServerVariable(&cArr, fc.env, gatewayInterface, "GATEWAY_INTERFACE", "CGI/1.1")
-	allocServerVariable(&cArr, fc.env, serverProtocol, "SERVER_PROTOCOL", request.Proto)
-	allocServerVariable(&cArr, fc.env, serverSoftware, "SERVER_SOFTWARE", "FrankenPHP")
-	allocServerVariable(&cArr, fc.env, httpHost, "HTTP_HOST", request.Host) // added here, since not always part of headers
+	setKnownServerVariable(p, &cArr, contentLength, request.Header.Get("Content-Length"))
+	setKnownServerVariable(p, &cArr, gatewayInterface, "CGI/1.1")
+	setKnownServerVariable(p, &cArr, serverProtocol, request.Proto)
+	setKnownServerVariable(p, &cArr, serverSoftware, "FrankenPHP")
+	setKnownServerVariable(p, &cArr, httpHost, request.Host) // added here, since not always part of headers
 
 	return
 }

+ 67 - 58
frankenphp.c

@@ -433,6 +433,7 @@ static uintptr_t frankenphp_request_shutdown() {
 
   free(ctx);
   SG(server_context) = NULL;
+  ctx = NULL;
 
 #if defined(ZTS)
   ts_free_thread();
@@ -572,8 +573,23 @@ static char *frankenphp_read_cookies(void) {
   return ctx->cookie_data;
 }
 
-static void frankenphp_register_known_variable(const char *key, char *value,
-                                               zval *track_vars_array, bool f) {
+static void frankenphp_register_known_variable(const char *key, go_string value,
+                                               zval *track_vars_array) {
+  if (value.data == NULL) {
+    php_register_variable_safe(key, "", 0, track_vars_array);
+    return;
+  }
+
+  size_t new_val_len;
+  if (sapi_module.input_filter(PARSE_SERVER, key, &value.data, value.len,
+                               &new_val_len)) {
+    php_register_variable_safe(key, value.data, new_val_len, track_vars_array);
+  }
+}
+
+static void
+frankenphp_register_variable_from_request_info(const char *key, char *value,
+                                               zval *track_vars_array) {
   if (value == NULL) {
     return;
   }
@@ -583,91 +599,80 @@ static void frankenphp_register_known_variable(const char *key, char *value,
                                &new_val_len)) {
     php_register_variable_safe(key, value, new_val_len, track_vars_array);
   }
-
-  if (f) {
-    free(value);
-    value = NULL;
-  }
 }
 
-void frankenphp_register_bulk_variables(char *known_variables[27],
-                                        char **dynamic_variables, size_t size,
-                                        zval *track_vars_array) {
+void frankenphp_register_bulk_variables(go_string known_variables[27],
+                                        php_variable *dynamic_variables,
+                                        size_t size, zval *track_vars_array) {
   /* Not used, but must be present */
-  frankenphp_register_known_variable("AUTH_TYPE", "", track_vars_array, false);
-  frankenphp_register_known_variable("REMOTE_IDENT", "", track_vars_array,
-                                     false);
+  php_register_variable_safe("AUTH_TYPE", "", 0, track_vars_array);
+  php_register_variable_safe("REMOTE_IDENT", "", 0, track_vars_array);
 
   /* Allocated in frankenphp_update_server_context() */
-  frankenphp_register_known_variable("CONTENT_TYPE",
-                                     (char *)SG(request_info).content_type,
-                                     track_vars_array, false);
-  frankenphp_register_known_variable("PATH_TRANSLATED",
-                                     (char *)SG(request_info).path_translated,
-                                     track_vars_array, false);
-  frankenphp_register_known_variable(
-      "QUERY_STRING", SG(request_info).query_string, track_vars_array, false);
-  frankenphp_register_known_variable("REMOTE_USER",
-                                     (char *)SG(request_info).auth_user,
-                                     track_vars_array, false);
-  frankenphp_register_known_variable("REQUEST_METHOD",
-                                     (char *)SG(request_info).request_method,
-                                     track_vars_array, false);
-  frankenphp_register_known_variable(
-      "REQUEST_URI", SG(request_info).request_uri, track_vars_array, false);
+  frankenphp_register_variable_from_request_info(
+      "CONTENT_TYPE", (char *)SG(request_info).content_type, track_vars_array);
+  frankenphp_register_variable_from_request_info(
+      "PATH_TRANSLATED", (char *)SG(request_info).path_translated,
+      track_vars_array);
+  frankenphp_register_variable_from_request_info(
+      "QUERY_STRING", SG(request_info).query_string, track_vars_array);
+  frankenphp_register_variable_from_request_info(
+      "REMOTE_USER", (char *)SG(request_info).auth_user, track_vars_array);
+  frankenphp_register_variable_from_request_info(
+      "REQUEST_METHOD", (char *)SG(request_info).request_method,
+      track_vars_array);
+  frankenphp_register_variable_from_request_info(
+      "REQUEST_URI", SG(request_info).request_uri, track_vars_array);
 
   /* Known variables */
   frankenphp_register_known_variable("CONTENT_LENGTH", known_variables[0],
-                                     track_vars_array, true);
+                                     track_vars_array);
   frankenphp_register_known_variable("DOCUMENT_ROOT", known_variables[1],
-                                     track_vars_array, true);
+                                     track_vars_array);
   frankenphp_register_known_variable("DOCUMENT_URI", known_variables[2],
-                                     track_vars_array, true);
+                                     track_vars_array);
   frankenphp_register_known_variable("GATEWAY_INTERFACE", known_variables[3],
-                                     track_vars_array, true);
+                                     track_vars_array);
   frankenphp_register_known_variable("HTTP_HOST", known_variables[4],
-                                     track_vars_array, true);
+                                     track_vars_array);
   frankenphp_register_known_variable("HTTPS", known_variables[5],
-                                     track_vars_array, true);
+                                     track_vars_array);
   frankenphp_register_known_variable("PATH_INFO", known_variables[6],
-                                     track_vars_array, true);
+                                     track_vars_array);
   frankenphp_register_known_variable("PHP_SELF", known_variables[7],
-                                     track_vars_array, true);
+                                     track_vars_array);
   frankenphp_register_known_variable("REMOTE_ADDR", known_variables[8],
-                                     track_vars_array,
-                                     known_variables[8] != known_variables[9]);
+                                     track_vars_array);
   frankenphp_register_known_variable("REMOTE_HOST", known_variables[9],
-                                     track_vars_array, true);
+                                     track_vars_array);
   frankenphp_register_known_variable("REMOTE_PORT", known_variables[10],
-                                     track_vars_array, true);
+                                     track_vars_array);
   frankenphp_register_known_variable("REQUEST_SCHEME", known_variables[11],
-                                     track_vars_array, true);
+                                     track_vars_array);
   frankenphp_register_known_variable("SCRIPT_FILENAME", known_variables[12],
-                                     track_vars_array, true);
+                                     track_vars_array);
   frankenphp_register_known_variable("SCRIPT_NAME", known_variables[13],
-                                     track_vars_array, true);
+                                     track_vars_array);
   frankenphp_register_known_variable("SERVER_NAME", known_variables[14],
-                                     track_vars_array, true);
+                                     track_vars_array);
   frankenphp_register_known_variable("SERVER_PORT", known_variables[15],
-                                     track_vars_array, true);
+                                     track_vars_array);
   frankenphp_register_known_variable("SERVER_PROTOCOL", known_variables[16],
-                                     track_vars_array, true);
+                                     track_vars_array);
   frankenphp_register_known_variable("SERVER_SOFTWARE", known_variables[17],
-                                     track_vars_array, true);
+                                     track_vars_array);
   frankenphp_register_known_variable("SSL_PROTOCOL", known_variables[18],
-                                     track_vars_array, true);
+                                     track_vars_array);
 
   size_t new_val_len;
-  for (size_t i = 0; i < size; i = i + 2) {
-    if (sapi_module.input_filter(
-            PARSE_SERVER, dynamic_variables[i], &dynamic_variables[i + 1],
-            strlen(dynamic_variables[i + 1]), &new_val_len)) {
-      php_register_variable_safe(dynamic_variables[i], dynamic_variables[i + 1],
-                                 new_val_len, track_vars_array);
+  for (size_t i = 0; i < size; i++) {
+    if (sapi_module.input_filter(PARSE_SERVER, dynamic_variables[i].var,
+                                 &dynamic_variables[i].data,
+                                 dynamic_variables[i].data_len, &new_val_len)) {
+      php_register_variable_safe(dynamic_variables[i].var,
+                                 dynamic_variables[i].data, new_val_len,
+                                 track_vars_array);
     }
-
-    free(dynamic_variables[i]);
-    free(dynamic_variables[i + 1]);
   }
 }
 
@@ -746,6 +751,7 @@ static void *manager_thread(void *arg) {
 
   threadpool thpool = thpool_init(*((int *)arg));
   free(arg);
+  arg = NULL;
 
   uintptr_t rh;
   while ((rh = go_fetch_request())) {
@@ -799,6 +805,7 @@ int frankenphp_request_startup() {
   frankenphp_server_context *ctx = SG(server_context);
   SG(server_context) = NULL;
   free(ctx);
+  ctx = NULL;
 
   php_request_shutdown((void *)0);
 
@@ -808,6 +815,7 @@ int frankenphp_request_startup() {
 int frankenphp_execute_script(char *file_name) {
   if (frankenphp_request_startup() == FAILURE) {
     free(file_name);
+    file_name = NULL;
 
     return FAILURE;
   }
@@ -817,6 +825,7 @@ int frankenphp_execute_script(char *file_name) {
   zend_file_handle file_handle;
   zend_stream_init_filename(&file_handle, file_name);
   free(file_name);
+  file_name = NULL;
 
   file_handle.primary_script = 1;
 

+ 57 - 19
frankenphp.go

@@ -47,6 +47,7 @@ import (
 	"sync"
 	"unsafe"
 
+	"github.com/maypok86/otter"
 	"go.uber.org/zap"
 	// debug on Linux
 	//_ "github.com/ianlancetaylor/cgosymbolizer"
@@ -115,7 +116,7 @@ func (l syslogLevel) String() string {
 type FrankenPHPContext struct {
 	documentRoot string
 	splitPath    []string
-	env          map[string]string
+	env          PreparedEnv
 	logger       *zap.Logger
 
 	docURI         string
@@ -539,44 +540,81 @@ func go_ub_write(rh C.uintptr_t, cBuf *C.char, length C.int) (C.size_t, C.bool)
 	return C.size_t(i), C.bool(clientHasClosed(r))
 }
 
+func createHeaderKeyCache() otter.Cache[string, string] {
+	c, err := otter.MustBuilder[string, string](256).Build()
+	if err != nil {
+		panic(err)
+	}
+
+	return c
+}
+
+var headerKeyCache = createHeaderKeyCache()
+
+// There are around 60 common request headers according to https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Request_fields
+// Give some space for custom headers
+
 //export go_register_variables
 func go_register_variables(rh C.uintptr_t, trackVarsArray *C.zval) {
 	r := cgo.Handle(rh).Value().(*http.Request)
 	fc := r.Context().Value(contextKey).(*FrankenPHPContext)
 
-	le := (len(fc.env) + len(r.Header)) * 2
-	dynamicVariables := make([]*C.char, le)
+	p := &runtime.Pinner{}
+
+	dynamicVariables := make([]C.php_variable, len(fc.env)+len(r.Header))
+	p.Pin(unsafe.SliceData(dynamicVariables))
+
+	var l int
 
-	var i int
 	// Add all HTTP headers to env variables
 	for field, val := range r.Header {
-		k := "HTTP_" + headerNameReplacer.Replace(strings.ToUpper(field))
+		k, ok := headerKeyCache.Get(field)
+		if !ok {
+			k = "HTTP_" + headerNameReplacer.Replace(strings.ToUpper(field)) + "\x00"
+			headerKeyCache.SetIfAbsent(field, k)
+		}
+
 		if _, ok := fc.env[k]; ok {
 			continue
 		}
 
-		dynamicVariables[i] = C.CString(k)
-		i++
+		v := strings.Join(val, ", ")
 
-		dynamicVariables[i] = C.CString(strings.Join(val, ", "))
-		i++
+		kData := unsafe.StringData(k)
+		vData := unsafe.StringData(v)
+
+		p.Pin(kData)
+		p.Pin(vData)
+
+		dynamicVariables[l]._var = (*C.char)(unsafe.Pointer(kData))
+		dynamicVariables[l].data_len = C.size_t(len(v))
+		dynamicVariables[l].data = (*C.char)(unsafe.Pointer(vData))
+
+		l++
 	}
 
 	for k, v := range fc.env {
-		dynamicVariables[i] = C.CString(k)
-		i++
+		if _, ok := knownServerKeys[k]; ok {
+			continue
+		}
 
-		dynamicVariables[i] = C.CString(v)
-		i++
-	}
+		kData := unsafe.StringData(k)
+		vData := unsafe.Pointer(unsafe.StringData(v))
+
+		p.Pin(kData)
+		p.Pin(vData)
+
+		dynamicVariables[l]._var = (*C.char)(unsafe.Pointer(kData))
+		dynamicVariables[l].data_len = C.size_t(len(v))
+		dynamicVariables[l].data = (*C.char)(unsafe.Pointer(vData))
 
-	var dynamicVariablesPtr **C.char = nil
-	if le > 0 {
-		dynamicVariablesPtr = &dynamicVariables[0]
+		l++
 	}
 
-	knownVariables := computeKnownVariables(r)
-	C.frankenphp_register_bulk_variables(&knownVariables[0], dynamicVariablesPtr, C.size_t(le), trackVarsArray)
+	knownVariables := computeKnownVariables(r, p)
+	C.frankenphp_register_bulk_variables(&knownVariables[0], unsafe.SliceData(dynamicVariables), C.size_t(l), trackVarsArray)
+
+	p.Unpin()
 
 	fc.env = nil
 }

+ 10 - 4
frankenphp.h

@@ -13,9 +13,15 @@
 
 typedef struct go_string {
   size_t len;
-  const char *data;
+  char *data;
 } go_string;
 
+typedef struct php_variable {
+  const char *var;
+  size_t data_len;
+  char *data;
+} php_variable;
+
 typedef struct frankenphp_version {
   unsigned char major_version;
   unsigned char minor_version;
@@ -44,9 +50,9 @@ int frankenphp_update_server_context(
     char *auth_user, char *auth_password, int proto_num);
 int frankenphp_request_startup();
 int frankenphp_execute_script(char *file_name);
-void frankenphp_register_bulk_variables(char *known_variables[27],
-                                        char **dynamic_variables, size_t size,
-                                        zval *track_vars_array);
+void frankenphp_register_bulk_variables(go_string known_variables[27],
+                                        php_variable *dynamic_variables,
+                                        size_t size, zval *track_vars_array);
 
 int frankenphp_execute_script_cli(char *script, int argc, char **argv);
 

+ 15 - 8
frankenphp_test.go

@@ -1,3 +1,7 @@
+// In all tests, headers added to requests are copied on the heap using strings.Clone.
+// This was originally a workaround for https://github.com/golang/go/issues/65286#issuecomment-1920087884 (fixed in Go 1.22),
+// but this allows to catch panics occuring in real life but not when the string is in the internal binary memory.
+
 package frankenphp_test
 
 import (
@@ -128,8 +132,8 @@ func TestServerVariable_worker(t *testing.T) {
 func testServerVariable(t *testing.T, opts *testOptions) {
 	runTest(t, func(handler func(http.ResponseWriter, *http.Request), _ *httptest.Server, i int) {
 		req := httptest.NewRequest("POST", fmt.Sprintf("http://example.com/server-variable.php/baz/bat?foo=a&bar=b&i=%d#hash", i), strings.NewReader("foo"))
-		req.SetBasicAuth("kevin", "password")
-		req.Header.Add("Content-Type", "text/plain")
+		req.SetBasicAuth(strings.Clone("kevin"), strings.Clone("password"))
+		req.Header.Add(strings.Clone("Content-Type"), strings.Clone("text/plain"))
 		w := httptest.NewRecorder()
 		handler(w, req)
 
@@ -171,13 +175,14 @@ func TestPathInfo_worker(t *testing.T) {
 	testPathInfo(t, &testOptions{workerScript: "server-variable.php"})
 }
 func testPathInfo(t *testing.T, opts *testOptions) {
+	cwd, _ := os.Getwd()
+	testDataDir := cwd + strings.Clone("/testdata/")
+	path := strings.Clone("/server-variable.php/pathinfo")
+
 	runTest(t, func(_ func(http.ResponseWriter, *http.Request), _ *httptest.Server, i int) {
 		handler := func(w http.ResponseWriter, r *http.Request) {
-			cwd, _ := os.Getwd()
-			testDataDir := cwd + "/testdata/"
-
 			requestURI := r.URL.RequestURI()
-			r.URL.Path = "/server-variable.php/pathinfo"
+			r.URL.Path = path
 
 			rewriteRequest, err := frankenphp.NewRequestWithContext(r,
 				frankenphp.WithRequestDocumentRoot(testDataDir, false),
@@ -271,7 +276,7 @@ func testPostSuperGlobals(t *testing.T, opts *testOptions) {
 	runTest(t, func(handler func(http.ResponseWriter, *http.Request), _ *httptest.Server, i int) {
 		formData := url.Values{"baz": {"bat"}, "i": {fmt.Sprintf("%d", i)}}
 		req := httptest.NewRequest("POST", fmt.Sprintf("http://example.com/super-globals.php?foo=bar&iG=%d", i), strings.NewReader(formData.Encode()))
-		req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
+		req.Header.Set("Content-Type", strings.Clone("application/x-www-form-urlencoded"))
 		w := httptest.NewRecorder()
 		handler(w, req)
 
@@ -785,8 +790,10 @@ func BenchmarkServerSuperGlobal(b *testing.B) {
 		"PHP_SHA256":      "4ffa3e44afc9c590e28dc0d2d31fc61f0139f8b335f11880a121b9f9b9f0634e",
 	}
 
+	preparedEnv := frankenphp.PrepareEnv(env)
+
 	handler := func(w http.ResponseWriter, r *http.Request) {
-		req, err := frankenphp.NewRequestWithContext(r, frankenphp.WithRequestDocumentRoot(testDataDir, false), frankenphp.WithRequestEnv(env))
+		req, err := frankenphp.NewRequestWithContext(r, frankenphp.WithRequestDocumentRoot(testDataDir, false), frankenphp.WithRequestPreparedEnv(preparedEnv))
 		if err != nil {
 			panic(err)
 		}

+ 5 - 1
go.mod

@@ -7,13 +7,17 @@ toolchain go1.22.0
 retract v1.0.0-rc.1 // Human error
 
 require (
+	github.com/maypok86/otter v1.1.1
 	github.com/stretchr/testify v1.9.0
 	go.uber.org/zap v1.27.0
-	golang.org/x/net v0.21.0
+	golang.org/x/net v0.22.0
 )
 
 require (
 	github.com/davecgh/go-spew v1.1.1 // indirect
+	github.com/dolthub/maphash v0.1.0 // indirect
+	github.com/dolthub/swiss v0.2.1 // indirect
+	github.com/gammazero/deque v0.2.1 // indirect
 	github.com/kr/pretty v0.3.1 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
 	github.com/rogpeppe/go-internal v1.12.0 // indirect

+ 10 - 2
go.sum

@@ -1,6 +1,12 @@
 github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/dolthub/maphash v0.1.0 h1:bsQ7JsF4FkkWyrP3oCnFJgrCUAFbFf3kOl4L/QxPDyQ=
+github.com/dolthub/maphash v0.1.0/go.mod h1:gkg4Ch4CdCDu5h6PMriVLawB7koZ+5ijb9puGMV50a4=
+github.com/dolthub/swiss v0.2.1 h1:gs2osYs5SJkAaH5/ggVJqXQxRXtWshF6uE0lgR/Y3Gw=
+github.com/dolthub/swiss v0.2.1/go.mod h1:8AhKZZ1HK7g18j7v7k6c5cYIGEZJcPn0ARsai8cUrh0=
+github.com/gammazero/deque v0.2.1 h1:qSdsbG6pgp6nL7A0+K/B7s12mcCY/5l5SIUpMOl+dC0=
+github.com/gammazero/deque v0.2.1/go.mod h1:LFroj8x4cMYCukHJDbxFCkT+r9AndaJnFMuZDV34tuU=
 github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
 github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
 github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
@@ -8,6 +14,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/maypok86/otter v1.1.1 h1:1WyOfBz2m8bjc4dAmJeUSuyUshoTP8ViiYmvRWO+53w=
+github.com/maypok86/otter v1.1.1/go.mod h1:IuSnpxeUyjKPPjqGzhGKOO26tedMNl45vwGcdXsEi8U=
 github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -22,8 +30,8 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
 go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
 go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
 go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
-golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
-golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
+golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
+golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
 golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
 golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

Some files were not shown because too many files changed in this diff