From 6378d14fe0f2e8ff59d5c33a88b5b27340bb4d22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=97=E5=AD=90?= Date: Mon, 16 Dec 2024 23:57:49 +0800 Subject: [PATCH 1/5] feat: optimize performance --- context.go | 22 ++++++++++++++++++---- context_request.go | 18 +++++++++++++++--- context_response.go | 11 +++++++++-- utils.go | 26 ++++++++++++++++++++++---- 4 files changed, 64 insertions(+), 13 deletions(-) diff --git a/context.go b/context.go index 2487ff5..02d5577 100644 --- a/context.go +++ b/context.go @@ -2,6 +2,7 @@ package fiber import ( "context" + "sync" "time" "github.com/gofiber/fiber/v2" @@ -31,25 +32,38 @@ func Background() http.Context { return NewContext(httpCtx) } +var contextPool = sync.Pool{New: func() any { + return &Context{} +}} + type Context struct { instance *fiber.Ctx request http.ContextRequest + response http.ContextResponse } -func NewContext(ctx *fiber.Ctx) http.Context { - return &Context{instance: ctx} +func NewContext(c *fiber.Ctx) *Context { + ctx := contextPool.Get().(*Context) + ctx.instance = c + return ctx } func (c *Context) Request() http.ContextRequest { if c.request == nil { - c.request = NewContextRequest(c, LogFacade, ValidationFacade) + request := NewContextRequest(c, LogFacade, ValidationFacade) + c.request = request } return c.request } func (c *Context) Response() http.ContextResponse { - return NewContextResponse(c.instance, &ResponseOrigin{Ctx: c.instance}) + if c.response == nil { + response := NewContextResponse(c.instance, &ResponseOrigin{Ctx: c.instance}) + c.response = response + } + + return c.response } func (c *Context) WithValue(key any, value any) { diff --git a/context_request.go b/context_request.go index c0c3b73..7ee9f72 100644 --- a/context_request.go +++ b/context_request.go @@ -8,6 +8,7 @@ import ( "regexp" "strconv" "strings" + "sync" "github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2/utils" @@ -25,6 +26,13 @@ import ( "github.com/valyala/fasthttp/fasthttpadaptor" ) +var contextRequestPool = sync.Pool{New: func() any { + return &ContextRequest{ + log: LogFacade, + validation: ValidationFacade, + } +}} + type ContextRequest struct { ctx *Context instance *fiber.Ctx @@ -34,12 +42,16 @@ type ContextRequest struct { } func NewContextRequest(ctx *Context, log log.Log, validation contractsvalidate.Validation) contractshttp.ContextRequest { + request := contextRequestPool.Get().(*ContextRequest) httpBody, err := getHttpBody(ctx) if err != nil { - LogFacade.Error(fmt.Sprintf("%+v", err)) + log.Error(fmt.Sprintf("%+v", errors.Unwrap(err))) } - - return &ContextRequest{ctx: ctx, instance: ctx.instance, httpBody: httpBody, log: log, validation: validation} + request.ctx = ctx + request.instance = ctx.instance + request.httpBody = httpBody + request.validation = validation + return request } func (r *ContextRequest) AbortWithStatus(code int) { diff --git a/context_response.go b/context_response.go index 1c4a24b..e3f8b92 100644 --- a/context_response.go +++ b/context_response.go @@ -14,13 +14,20 @@ import ( "github.com/valyala/fasthttp" ) +var contextResponsePool = sync.Pool{New: func() any { + return &ContextResponse{} +}} + type ContextResponse struct { instance *fiber.Ctx origin contractshttp.ResponseOrigin } -func NewContextResponse(instance *fiber.Ctx, origin contractshttp.ResponseOrigin) *ContextResponse { - return &ContextResponse{instance, origin} +func NewContextResponse(instance *fiber.Ctx, origin contractshttp.ResponseOrigin) contractshttp.ContextResponse { + response := contextResponsePool.Get().(*ContextResponse) + response.instance = instance + response.origin = origin + return response } func (r *ContextResponse) Cookie(cookie contractshttp.Cookie) contractshttp.ContextResponse { diff --git a/utils.go b/utils.go index c4668be..0bb9644 100644 --- a/utils.go +++ b/utils.go @@ -22,8 +22,17 @@ func middlewaresToFiberHandlers(middlewares []httpcontract.Middleware) []fiber.H } func handlerToFiberHandler(handler httpcontract.HandlerFunc) fiber.Handler { - return func(ctx *fiber.Ctx) error { - if response := handler(NewContext(ctx)); response != nil { + return func(c *fiber.Ctx) error { + context := NewContext(c) + defer func() { + contextRequestPool.Put(context.request) + contextResponsePool.Put(context.response) + context.request = nil + context.response = nil + contextPool.Put(context) + }() + + if response := handler(context); response != nil { return response.Render() } return nil @@ -31,8 +40,17 @@ func handlerToFiberHandler(handler httpcontract.HandlerFunc) fiber.Handler { } func middlewareToFiberHandler(middleware httpcontract.Middleware) fiber.Handler { - return func(ctx *fiber.Ctx) error { - middleware(NewContext(ctx)) + return func(c *fiber.Ctx) error { + context := NewContext(c) + defer func() { + contextRequestPool.Put(context.request) + contextResponsePool.Put(context.response) + context.request = nil + context.response = nil + contextPool.Put(context) + }() + + middleware(context) return nil } } From e726dbe7c624b9c7884d615aaeed3a7970f5f985 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=97=E5=AD=90?= Date: Tue, 17 Dec 2024 00:12:08 +0800 Subject: [PATCH 2/5] feat: add Listen method --- route.go | 29 ++++++++++++ route_test.go | 124 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 153 insertions(+) diff --git a/route.go b/route.go index 703663a..13a2fc4 100644 --- a/route.go +++ b/route.go @@ -2,8 +2,10 @@ package fiber import ( "context" + "crypto/tls" "errors" "fmt" + "net" "net/http" "reflect" "runtime" @@ -128,6 +130,33 @@ func (r *Route) GlobalMiddleware(middlewares ...httpcontract.Middleware) { r.setMiddlewares(fiberHandlers) } +// Listen listen server +// Listen 监听服务器 +func (r *Route) Listen(l net.Listener) error { + return r.instance.Listener(l) +} + +// ListenTLS listen TLS server +// ListenTLS 监听 TLS 服务器 +func (r *Route) ListenTLS(l net.Listener, certFile, keyFile string) error { + cert, err := tls.LoadX509KeyPair(certFile, keyFile) + if err != nil { + return err + } + + tlsHandler := &fiber.TLSHandler{} + tlsConfig := &tls.Config{ + MinVersion: tls.VersionTLS12, + Certificates: []tls.Certificate{ + cert, + }, + GetCertificate: tlsHandler.GetClientInfo, + } + + r.instance.SetTLSHandler(tlsHandler) + return r.Listen(tls.NewListener(l, tlsConfig)) +} + // Run run server // Run 运行服务器 func (r *Route) Run(host ...string) error { diff --git a/route_test.go b/route_test.go index 964d5ef..4f26313 100644 --- a/route_test.go +++ b/route_test.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "mime/multipart" + "net" "net/http" "sync" "sync/atomic" @@ -45,6 +46,129 @@ func TestFallback(t *testing.T) { assert.Nil(t, err) assert.Equal(t, "not found", string(body)) } +func TestListen(t *testing.T) { + var ( + err error + mockConfig *configmocks.Config + route *Route + ) + + tests := []struct { + name string + setup func(host string, port string) error + host string + port string + expectError error + }{ + { + name: "success listen", + setup: func(host string, port string) error { + go func() { + l, err := net.Listen("tcp", host) + assert.Nil(t, err) + assert.Nil(t, route.Listen(l)) + }() + time.Sleep(1 * time.Second) + + return errors.New("error") + }, + host: "127.0.0.1:3100", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + mockConfig = configmocks.NewConfig(t) + mockConfig.EXPECT().GetBool("http.drivers.fiber.prefork", false).Return(false).Once() + mockConfig.EXPECT().GetInt("http.drivers.fiber.body_limit", 4096).Return(4096).Once() + mockConfig.EXPECT().GetInt("http.drivers.fiber.header_limit", 4096).Return(4096).Once() + + route, err = NewRoute(mockConfig, nil) + assert.Nil(t, err) + route.Get("/", func(ctx contractshttp.Context) contractshttp.Response { + return ctx.Response().Json(200, contractshttp.Json{ + "Hello": "Goravel", + }) + }) + if err := test.setup(test.host, test.port); err == nil { + time.Sleep(1 * time.Second) + hostUrl := "http://" + test.host + if test.port != "" { + hostUrl = hostUrl + ":" + test.port + } + resp, err := http.Get(hostUrl) + assert.Nil(t, err) + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + assert.Nil(t, err) + assert.Equal(t, "{\"Hello\":\"Goravel\"}", string(body)) + } + mockConfig.AssertExpectations(t) + }) + } +} + +func TestListenTLS(t *testing.T) { + var ( + err error + mockConfig *configmocks.Config + route *Route + ) + + tests := []struct { + name string + setup func(host string) error + host string + expectError error + }{ + { + name: "success listen", + setup: func(host string) error { + go func() { + l, err := net.Listen("tcp", host) + assert.Nil(t, err) + assert.Nil(t, route.ListenTLS(l, "test_ca.crt", "test_ca.key")) + }() + + return nil + }, + host: "127.0.0.1:3101", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + mockConfig = configmocks.NewConfig(t) + mockConfig.EXPECT().GetBool("http.drivers.fiber.prefork", false).Return(false).Once() + mockConfig.EXPECT().GetInt("http.drivers.fiber.body_limit", 4096).Return(4096).Once() + mockConfig.EXPECT().GetInt("http.drivers.fiber.header_limit", 4096).Return(4096).Once() + ConfigFacade = mockConfig + + route, err = NewRoute(mockConfig, nil) + assert.Nil(t, err) + route.Get("/", func(ctx contractshttp.Context) contractshttp.Response { + return ctx.Response().Json(200, contractshttp.Json{ + "Hello": "Goravel", + }) + }) + if err := test.setup(test.host); err == nil { + time.Sleep(1 * time.Second) + tr := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + client := &http.Client{Transport: tr} + resp, err := client.Get("https://" + test.host) + assert.Nil(t, err) + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + assert.Nil(t, err) + assert.Equal(t, "{\"Hello\":\"Goravel\"}", string(body)) + } + }) + } +} func TestRun(t *testing.T) { var ( From 5c2b9d535745117971446ebfb7c6e587bd0e8826 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=97=E5=AD=90?= Date: Tue, 17 Dec 2024 10:57:53 +0800 Subject: [PATCH 3/5] feat: optimize --- context_request.go | 3 ++- route.go | 8 +++++- route_test.go | 66 ++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 73 insertions(+), 4 deletions(-) diff --git a/context_request.go b/context_request.go index 7ee9f72..d5e89ee 100644 --- a/context_request.go +++ b/context_request.go @@ -45,11 +45,12 @@ func NewContextRequest(ctx *Context, log log.Log, validation contractsvalidate.V request := contextRequestPool.Get().(*ContextRequest) httpBody, err := getHttpBody(ctx) if err != nil { - log.Error(fmt.Sprintf("%+v", errors.Unwrap(err))) + log.Error(fmt.Sprintf("%+v", err)) } request.ctx = ctx request.instance = ctx.instance request.httpBody = httpBody + request.log = log request.validation = validation return request } diff --git a/route.go b/route.go index 13a2fc4..6b08651 100644 --- a/route.go +++ b/route.go @@ -138,7 +138,13 @@ func (r *Route) Listen(l net.Listener) error { // ListenTLS listen TLS server // ListenTLS 监听 TLS 服务器 -func (r *Route) ListenTLS(l net.Listener, certFile, keyFile string) error { +func (r *Route) ListenTLS(l net.Listener) error { + return r.ListenTLSWithCert(l, r.config.GetString("http.tls.ssl.cert"), r.config.GetString("http.tls.ssl.key")) +} + +// ListenTLSWithCert listen TLS server with cert file and key file +// ListenTLSWithCert 使用证书文件和密钥文件监听 TLS 服务器 +func (r *Route) ListenTLSWithCert(l net.Listener, certFile, keyFile string) error { cert, err := tls.LoadX509KeyPair(certFile, keyFile) if err != nil { return err diff --git a/route_test.go b/route_test.go index 4f26313..ce231bc 100644 --- a/route_test.go +++ b/route_test.go @@ -104,7 +104,6 @@ func TestListen(t *testing.T) { assert.Nil(t, err) assert.Equal(t, "{\"Hello\":\"Goravel\"}", string(body)) } - mockConfig.AssertExpectations(t) }) } } @@ -128,7 +127,70 @@ func TestListenTLS(t *testing.T) { go func() { l, err := net.Listen("tcp", host) assert.Nil(t, err) - assert.Nil(t, route.ListenTLS(l, "test_ca.crt", "test_ca.key")) + assert.Nil(t, route.ListenTLS(l)) + }() + + return nil + }, + host: "127.0.0.1:3101", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + mockConfig = configmocks.NewConfig(t) + mockConfig.EXPECT().GetBool("http.drivers.fiber.prefork", false).Return(false).Once() + mockConfig.EXPECT().GetInt("http.drivers.fiber.body_limit", 4096).Return(4096).Once() + mockConfig.EXPECT().GetInt("http.drivers.fiber.header_limit", 4096).Return(4096).Once() + mockConfig.EXPECT().GetString("http.tls.ssl.cert").Return("test_ca.crt").Once() + mockConfig.EXPECT().GetString("http.tls.ssl.key").Return("test_ca.key").Once() + ConfigFacade = mockConfig + + route, err = NewRoute(mockConfig, nil) + assert.Nil(t, err) + route.Get("/", func(ctx contractshttp.Context) contractshttp.Response { + return ctx.Response().Json(200, contractshttp.Json{ + "Hello": "Goravel", + }) + }) + if err := test.setup(test.host); err == nil { + time.Sleep(1 * time.Second) + tr := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + client := &http.Client{Transport: tr} + resp, err := client.Get("https://" + test.host) + assert.Nil(t, err) + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + assert.Nil(t, err) + assert.Equal(t, "{\"Hello\":\"Goravel\"}", string(body)) + } + }) + } +} + +func TestListenTLSWithCert(t *testing.T) { + var ( + err error + mockConfig *configmocks.Config + route *Route + ) + + tests := []struct { + name string + setup func(host string) error + host string + expectError error + }{ + { + name: "success listen", + setup: func(host string) error { + go func() { + l, err := net.Listen("tcp", host) + assert.Nil(t, err) + assert.Nil(t, route.ListenTLSWithCert(l, "test_ca.crt", "test_ca.key")) }() return nil From e545066dd6b0d88af7caf6bdee59a32a29ad5c69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=97=E5=AD=90?= Date: Tue, 17 Dec 2024 10:59:30 +0800 Subject: [PATCH 4/5] fix: test --- route_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/route_test.go b/route_test.go index ce231bc..7530002 100644 --- a/route_test.go +++ b/route_test.go @@ -195,7 +195,7 @@ func TestListenTLSWithCert(t *testing.T) { return nil }, - host: "127.0.0.1:3101", + host: "127.0.0.1:3102", }, } From 5caf9b3b53364317418664ab94cb27dce5b3b310 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=97=E5=AD=90?= Date: Tue, 17 Dec 2024 12:14:52 +0800 Subject: [PATCH 5/5] feat: go mod tidy --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index dba361d..52d8191 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/gofiber/fiber/v2 v2.52.5 github.com/gofiber/template/html/v2 v2.1.2 github.com/gookit/validate v1.5.3 - github.com/goravel/framework v1.14.1-0.20241214083012-9527d29ad584 + github.com/goravel/framework v1.14.1-0.20241217035357-003a28740efe github.com/savioxavier/termlink v1.4.1 github.com/spf13/cast v1.7.0 github.com/stretchr/testify v1.10.0 @@ -33,9 +33,9 @@ require ( github.com/catppuccin/go v0.2.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/charmbracelet/bubbles v0.20.0 // indirect - github.com/charmbracelet/bubbletea v1.2.3 // indirect + github.com/charmbracelet/bubbletea v1.2.5-0.20241205214244-9306010a31ee // indirect github.com/charmbracelet/huh v0.6.0 // indirect - github.com/charmbracelet/huh/spinner v0.0.0-20241211235322-ceae3bbcfbb4 // indirect + github.com/charmbracelet/huh/spinner v0.0.0-20241216182847-438e4f741435 // indirect github.com/charmbracelet/lipgloss v1.0.0 // indirect github.com/charmbracelet/x/ansi v0.4.5 // indirect github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 // indirect @@ -143,7 +143,7 @@ require ( go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.9.0 // indirect golang.org/x/crypto v0.31.0 // indirect - golang.org/x/exp v0.0.0-20241210194714-1829a127f884 // indirect + golang.org/x/exp v0.0.0-20241215155358-4a5509556b9e // indirect golang.org/x/net v0.31.0 // indirect golang.org/x/oauth2 v0.23.0 // indirect golang.org/x/sync v0.10.0 // indirect diff --git a/go.sum b/go.sum index 38a9071..1b04280 100644 --- a/go.sum +++ b/go.sum @@ -130,12 +130,12 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/charmbracelet/bubbles v0.20.0 h1:jSZu6qD8cRQ6k9OMfR1WlM+ruM8fkPWkHvQWD9LIutE= github.com/charmbracelet/bubbles v0.20.0/go.mod h1:39slydyswPy+uVOHZ5x/GjwVAFkCsV8IIVy+4MhzwwU= -github.com/charmbracelet/bubbletea v1.2.3 h1:d9MdMsANIYZB5pE1KkRqaUV6GfsiWm+/9z4fTuGVm9I= -github.com/charmbracelet/bubbletea v1.2.3/go.mod h1:Qr6fVQw+wX7JkWWkVyXYk/ZUQ92a6XNekLXa3rR18MM= +github.com/charmbracelet/bubbletea v1.2.5-0.20241205214244-9306010a31ee h1:xNijbIIsd6zADvvqrQj3kfKmLqJshZpCspKAfspXkFU= +github.com/charmbracelet/bubbletea v1.2.5-0.20241205214244-9306010a31ee/go.mod h1:Hbk5+oE4a7cDyjfdPi4sHZ42aGTMYcmHnVDhsRswn7A= github.com/charmbracelet/huh v0.6.0 h1:mZM8VvZGuE0hoDXq6XLxRtgfWyTI3b2jZNKh0xWmax8= github.com/charmbracelet/huh v0.6.0/go.mod h1:GGNKeWCeNzKpEOh/OJD8WBwTQjV3prFAtQPpLv+AVwU= -github.com/charmbracelet/huh/spinner v0.0.0-20241211235322-ceae3bbcfbb4 h1:FTJ/03WaUpEiZ5oK4/n22eqyAtj8Pi0Uu64oo9ZzBU8= -github.com/charmbracelet/huh/spinner v0.0.0-20241211235322-ceae3bbcfbb4/go.mod h1:3/xTBdgqRzAb+eUKRAGi9ix/K6QxsS0nGtd4zp+/tJs= +github.com/charmbracelet/huh/spinner v0.0.0-20241216182847-438e4f741435 h1:GnQvPBetPFyWaq4xVP4iia8UZAaLMVUk4UZ1O3Gdx44= +github.com/charmbracelet/huh/spinner v0.0.0-20241216182847-438e4f741435/go.mod h1:YqGqPo+vKnyTc0xppm1sv3Ir8FwG9bSW2H33LT++Xdg= github.com/charmbracelet/lipgloss v1.0.0 h1:O7VkGDvqEdGi93X+DeqsQ7PKHDgtQfF8j8/O2qFMQNg= github.com/charmbracelet/lipgloss v1.0.0/go.mod h1:U5fy9Z+C38obMs+T+tJqst9VGzlOYGj4ri9reL3qUlo= github.com/charmbracelet/x/ansi v0.4.5 h1:LqK4vwBNaXw2AyGIICa5/29Sbdq58GbGdFngSexTdRM= @@ -357,8 +357,8 @@ github.com/gookit/validate v1.5.3 h1:czD1H+fcOJX+dfY0vzjHSMb+jrKTMeI8Fhu59ZMIB9o github.com/gookit/validate v1.5.3/go.mod h1:1OfvI1eGJqSODHERKdxvPGcconT4eTvVLmYt1A3QZlI= github.com/goravel/file-rotatelogs/v2 v2.4.2 h1:g68AzbePXcm0V2CpUMc9j4qVzcDn7+7aoWSjZ51C0m4= github.com/goravel/file-rotatelogs/v2 v2.4.2/go.mod h1:23VuSW8cBS4ax5cmbV+5AaiLpq25b8UJ96IhbAkdo8I= -github.com/goravel/framework v1.14.1-0.20241214083012-9527d29ad584 h1:fQAK2exHkhWX0EfBiqvmDiLAyCfHEKUXClF/AeR6sUU= -github.com/goravel/framework v1.14.1-0.20241214083012-9527d29ad584/go.mod h1:CNeStutJe+vR9tmCCuV09XicIPusG5lQCC1TVFuH+jA= +github.com/goravel/framework v1.14.1-0.20241217035357-003a28740efe h1:i8rVlwV4VeR5492mI8lRsxNLfIoLhRAtF8zfvPf3KZA= +github.com/goravel/framework v1.14.1-0.20241217035357-003a28740efe/go.mod h1:y4I8sLmuqf3jRKMvALwF3HB4ttKBFsZBLMBLkkfGoXg= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -671,8 +671,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20241210194714-1829a127f884 h1:Y/Mj/94zIQQGHVSv1tTtQBDaQaJe62U9bkDZKKyhPCU= -golang.org/x/exp v0.0.0-20241210194714-1829a127f884/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= +golang.org/x/exp v0.0.0-20241215155358-4a5509556b9e h1:4qufH0hlUYs6AO6XmZC3GqfDPGSXHVXUFR6OND+iJX4= +golang.org/x/exp v0.0.0-20241215155358-4a5509556b9e/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=