From 2b6aea2b75067d57e98ac9ea62049e175ecb69eb Mon Sep 17 00:00:00 2001 From: jc666 <201672048@mail.dlut.edu.cn> Date: Wed, 21 Sep 2022 15:56:18 +0800 Subject: [PATCH] feat: add pprof middleware for Hertz framework (#1) * init * add adaptor * add liscense * add license * unify dir name * unify dir name * test case * add gotils * readme_cn * delete license fiber * add group test * license-gotils * Update README_CN.md * Update README_CN.md --- NOTICE | 5 + README.md | 118 ++++++++++++++- README_CN.md | 115 +++++++++++++++ adaptor/handler.go | 92 ++++++++++++ adaptor/handler_test.go | 116 +++++++++++++++ example/custom_prefix/server.go | 40 +++++ example/custom_router_group/server.go | 43 ++++++ example/default/server.go | 39 +++++ go.mod | 21 +++ go.sum | 103 +++++++++++++ licenses/LICENSE-gin-pprof | 21 +++ licenses/LICENSE-gotils | 201 ++++++++++++++++++++++++++ pprof.go | 69 +++++++++ pprof_test.go | 145 +++++++++++++++++++ 14 files changed, 1127 insertions(+), 1 deletion(-) create mode 100644 NOTICE create mode 100644 README_CN.md create mode 100644 adaptor/handler.go create mode 100644 adaptor/handler_test.go create mode 100644 example/custom_prefix/server.go create mode 100644 example/custom_router_group/server.go create mode 100644 example/default/server.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 licenses/LICENSE-gin-pprof create mode 100644 licenses/LICENSE-gotils create mode 100644 pprof.go create mode 100644 pprof_test.go diff --git a/NOTICE b/NOTICE new file mode 100644 index 0000000..bbb12ae --- /dev/null +++ b/NOTICE @@ -0,0 +1,5 @@ +CloudWeGo +Copyright 2022 CloudWeGo authors. + +This product includes software developed at +The Apache Software Foundation (http://www.apache.org/). \ No newline at end of file diff --git a/README.md b/README.md index a46ae92..ee1f609 100644 --- a/README.md +++ b/README.md @@ -1 +1,117 @@ -# .github \ No newline at end of file +# pprof (This is a community driven project) + +English | [中文](README_CN.md) + +pprof middleware for Hertz framework, inspired by [pprof](https://github.com/gin-contrib/pprof). +This project would not have been possible without the support from the CloudWeGo community and previous work done by the gin community. + +- Package pprof serves via its HTTP server runtime profiling data in the format expected by the pprof visualization tool. + + +## Install +```shell +go get github.com/hertz-contrib/pprof +``` + +## Usage +### Example + +```go +func main() { + h := server.Default() + + pprof.Register(h) + + h.GET("/ping", func(c context.Context, ctx *app.RequestContext) { + ctx.JSON(consts.StatusOK, utils.H{"ping": "pong"}) + }) + + h.Spin() +} +``` + +### change default path prefix + +```go +import ( + "context" + "github.com/cloudwego/hertz/pkg/app" + "github.com/cloudwego/hertz/pkg/app/server" + "github.com/cloudwego/hertz/pkg/common/utils" + "github.com/cloudwego/hertz/pkg/protocol/consts" + "hertz-contrib-beiye/pprof" +) + +func main() { + h := server.Default() + + // default is "debug/pprof" + pprof.Register(h, "dev/pprof") + + h.GET("/ping", func(c context.Context, ctx *app.RequestContext) { + ctx.JSON(consts.StatusOK, utils.H{"ping": "pong"}) + }) + + h.Spin() +} + +``` + +### custom router group + +```go +import ( + "context" + "github.com/cloudwego/hertz/pkg/app" + "github.com/cloudwego/hertz/pkg/app/server" + "hertz-contrib-beiye/pprof" + "net/http" +) + +func main() { + h := server.Default() + + pprof.Register(h) + + adminGroup := h.Group("/admin") + adminGroup.GET("/ping", func(c context.Context, ctx *app.RequestContext) { + ctx.JSON(consts.StatusOK, utils.H{"ping": "pong"}) + }) + + pprof.RouteRegister(adminGroup, "pprof") + + h.Spin() +} + +``` + + +### Use the pprof tool + +Then use the pprof tool to look at the heap profile: + +```bash +go tool pprof http://localhost:8888/debug/pprof/heap +``` + +Or to look at a 30-second CPU profile: + +```bash +go tool pprof http://localhost:8888/debug/pprof/profile +``` + +Or to look at the goroutine blocking profile, after calling runtime.SetBlockProfileRate in your program: + +```bash +go tool pprof http://localhost:8888/debug/pprof/block +``` + +Or to collect a 5-second execution trace: + +```bash +wget http://localhost:8888/debug/pprof/trace?seconds=5 +``` + + +## License +This project is under the Apache License 2.0. See the LICENSE file for the full license text. \ No newline at end of file diff --git a/README_CN.md b/README_CN.md new file mode 100644 index 0000000..9bfc4a5 --- /dev/null +++ b/README_CN.md @@ -0,0 +1,115 @@ +# pprof (本项目由CloudWeGo社区贡献者协作开发并维护) + +[English](README.md) | 中文 + +pprof 是为 Hertz 框架开发的中间件,参考了 [Gin](https://github.com/gin-gonic/gin) 中 [pprof](https://github.com/gin-contrib/pprof) 的实现。 +本项目的完成得益于 CloudWeGo 社区的工作以及 Gin 社区所做的相关前置工作。 + + +## 安装 +```shell +go get github.com/hertz-contrib/pprof +``` + +## 使用 +### 代码实例1 + +```go +func main() { + h := server.Default() + + pprof.Register(h) + + h.GET("/ping", func(c context.Context, ctx *app.RequestContext) { + ctx.JSON(consts.StatusOK, utils.H{"ping": "pong"}) + }) + + h.Spin() +} +``` + +### 代码实例2: 自定义前缀 + +```go +import ( + "context" + "github.com/cloudwego/hertz/pkg/app" + "github.com/cloudwego/hertz/pkg/app/server" + "github.com/cloudwego/hertz/pkg/common/utils" + "github.com/cloudwego/hertz/pkg/protocol/consts" + "hertz-contrib-beiye/pprof" +) + +func main() { + h := server.Default() + + // default is "debug/pprof" + pprof.Register(h, "dev/pprof") + + h.GET("/ping", func(c context.Context, ctx *app.RequestContext) { + ctx.JSON(consts.StatusOK, utils.H{"ping": "pong"}) + }) + + h.Spin() +} + +``` + +### 代码实例3: 自定义路由组 + +```go +import ( + "context" + "github.com/cloudwego/hertz/pkg/app" + "github.com/cloudwego/hertz/pkg/app/server" + "hertz-contrib-beiye/pprof" + "net/http" +) + +func main() { + h := server.Default() + + pprof.Register(h) + + adminGroup := h.Group("/admin") + adminGroup.GET("/ping", func(c context.Context, ctx *app.RequestContext) { + ctx.JSON(consts.StatusOK, utils.H{"ping": "pong"}) + }) + + pprof.RouteRegister(adminGroup, "pprof") + + h.Spin() +} + +``` + + +### 如何使用 + +使用 `pprof tool` 工具查看堆栈采样信息: + +```bash +go tool pprof http://localhost:8888/debug/pprof/heap +``` + +使用 `pprof tool` 工具查看 30s 的 CPU 采样信息: + +```bash +go tool pprof http://localhost:8888/debug/pprof/profile +``` + +使用 `pprof tool` 工具查看 go 协程阻塞信息: + +```bash +go tool pprof http://localhost:8888/debug/pprof/block +``` + +使用 `pprof tool` 工具查看 5s 内的执行 trace: + +```bash +wget http://localhost:8888/debug/pprof/trace?seconds=5 +``` + + +## License +This project is under the Apache License 2.0. See the LICENSE file for the full license text. \ No newline at end of file diff --git a/adaptor/handler.go b/adaptor/handler.go new file mode 100644 index 0000000..7abafcd --- /dev/null +++ b/adaptor/handler.go @@ -0,0 +1,92 @@ +/* + * Copyright 2022 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package adaptor + +import ( + "context" + "net/http" + + "github.com/cloudwego/hertz/pkg/app" + "github.com/cloudwego/hertz/pkg/common/adaptor" + "github.com/cloudwego/hertz/pkg/common/hlog" + "github.com/cloudwego/hertz/pkg/protocol/consts" + "github.com/savsgio/gotils/strconv" +) + +// NewHertzHTTPHandlerFunc wraps net/http handler to hertz app.HandlerFunc, +// so it can be passed to hertz server +// +// While this function may be used for easy switching from net/http to hertz, +// it has the following drawbacks comparing to using manually written hertz +// request handler: +// +// - A lot of useful functionality provided by hertz is missing +// from net/http handler. +// +// - net/http -> hertz handler conversion has some overhead, +// so the returned handler will be always slower than manually written +// hertz handler. +// +// So it is advisable using this function only for net/http -> hertz switching. +// Then manually convert net/http handlers to hertz handlers +func NewHertzHTTPHandlerFunc(h http.HandlerFunc) app.HandlerFunc { + return NewHertzHTTPHandler(h) +} + +// NewHertzHTTPHandler wraps net/http handler to hertz app.HandlerFunc, +// so it can be passed to hertz server +// +// While this function may be used for easy switching from net/http to hertz, +// it has the following drawbacks comparing to using manually written hertz +// request handler: +// +// - A lot of useful functionality provided by hertz is missing +// from net/http handler. +// +// - net/http -> hertz handler conversion has some overhead, +// so the returned handler will be always slower than manually written +// hertz handler. +// +// So it is advisable using this function only for net/http -> hertz switching. +// Then manually convert net/http handlers to hertz handlers +func NewHertzHTTPHandler(h http.Handler) app.HandlerFunc { + return func(ctx context.Context, c *app.RequestContext) { + req, err := adaptor.GetCompatRequest(c.GetRequest()) + if err != nil { + hlog.CtxErrorf(ctx, "HERTZ: Get request error: %v", err) + c.String(http.StatusInternalServerError, consts.StatusMessage(http.StatusInternalServerError)) + return + } + req.RequestURI = strconv.B2S(c.Request.RequestURI()) + rw := adaptor.GetCompatResponseWriter(&c.Response) + c.ForEachKey(func(k string, v interface{}) { + ctx = context.WithValue(ctx, k, v) + }) + h.ServeHTTP(rw, req.WithContext(ctx)) + body := c.Response.Body() + // From net/http.ResponseWriter.Write: + // If the Header does not contain a Content-Type line, Write adds a Content-Type set + // to the result of passing the initial 512 bytes of written data to DetectContentType. + if len(c.GetHeader(consts.HeaderContentType)) == 0 { + l := 512 + if len(body) < 512 { + l = len(body) + } + c.Response.Header.Set(consts.HeaderContentType, http.DetectContentType(body[:l])) + } + } +} diff --git a/adaptor/handler_test.go b/adaptor/handler_test.go new file mode 100644 index 0000000..860fa2e --- /dev/null +++ b/adaptor/handler_test.go @@ -0,0 +1,116 @@ +/* + * Copyright 2022 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package adaptor + +import ( + "context" + "io/ioutil" + "net/http" + "net/url" + "reflect" + "testing" + + "github.com/cloudwego/hertz/pkg/app" + "github.com/cloudwego/hertz/pkg/common/test/assert" + "github.com/cloudwego/hertz/pkg/protocol" + "github.com/cloudwego/hertz/pkg/protocol/consts" +) + +func TestNewHertzHandler(t *testing.T) { + t.Parallel() + + expectedMethod := consts.MethodPost + expectedProto := "HTTP/1.1" + expectedProtoMajor := 1 + expectedProtoMinor := 1 + expectedRequestURI := "http://foobar.com/foo/bar?baz=123" + expectedBody := "" + expectedContentLength := len(expectedBody) + expectedHost := "foobar.com" + expectedHeader := map[string]string{ + "Foo-Bar": "baz", + "Abc": "defg", + "XXX-Remote-Addr": "123.43.4543.345", + } + expectedURL, err := url.ParseRequestURI(expectedRequestURI) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + expectedContextKey := "contextKey" + expectedContextValue := "contextValue" + expectedContentType := "text/html; charset=utf-8" + + callsCount := 0 + nethttpH := func(w http.ResponseWriter, r *http.Request) { + callsCount++ + assert.Assertf(t, r.Method == expectedMethod, "unexpected method %q. Expecting %q", r.Method, expectedMethod) + assert.Assertf(t, r.Proto == expectedProto, "unexpected proto %q. Expecting %q", r.Proto, expectedProto) + assert.Assertf(t, r.ProtoMajor == expectedProtoMajor, "unexpected protoMajor %d. Expecting %d", r.ProtoMajor, expectedProtoMajor) + assert.Assertf(t, r.ProtoMinor == expectedProtoMinor, "unexpected protoMinor %d. Expecting %d", r.ProtoMinor, expectedProtoMinor) + assert.Assertf(t, r.RequestURI == expectedRequestURI, "unexpected requestURI %q. Expecting %q", r.RequestURI, expectedRequestURI) + assert.Assertf(t, r.ContentLength == int64(expectedContentLength), "unexpected contentLength %d. Expecting %d", r.ContentLength, expectedContentLength) + assert.Assertf(t, len(r.TransferEncoding) == 0, "unexpected transferEncoding %q. Expecting []", r.TransferEncoding) + assert.Assertf(t, r.Host == expectedHost, "unexpected host %q. Expecting %q", r.Host, expectedHost) + body, err := ioutil.ReadAll(r.Body) + r.Body.Close() + if err != nil { + t.Fatalf("unexpected error when reading request body: %v", err) + } + assert.Assertf(t, string(body) == expectedBody, "unexpected body %q. Expecting %q", body, expectedBody) + assert.Assertf(t, reflect.DeepEqual(r.URL, expectedURL), "unexpected URL: %#v. Expecting %#v", r.URL, expectedURL) + assert.Assertf(t, r.Context().Value(expectedContextKey) == expectedContextValue, + "unexpected context value for key %q. Expecting %q, in fact: %v", expectedContextKey, + expectedContextValue, r.Context().Value(expectedContextKey)) + for k, expectedV := range expectedHeader { + v := r.Header.Get(k) + if v != expectedV { + t.Fatalf("unexpected header value %q for key %q. Expecting %q", v, k, expectedV) + } + } + w.Header().Set("Header1", "value1") + w.Header().Set("Header2", "value2") + w.WriteHeader(http.StatusBadRequest) // nolint:errcheck + w.Write(body) + } + hertzH := NewHertzHTTPHandler(http.HandlerFunc(nethttpH)) + hertzH = setContextValueMiddleware(hertzH, expectedContextKey, expectedContextValue) + var ctx app.RequestContext + var req protocol.Request + req.Header.SetMethod(expectedMethod) + req.SetRequestURI(expectedRequestURI) + req.Header.SetHost(expectedHost) + req.BodyWriter().Write([]byte(expectedBody)) // nolint:errcheck + for k, v := range expectedHeader { + req.Header.Set(k, v) + } + req.CopyTo(&ctx.Request) + hertzH(context.Background(), &ctx) + assert.Assertf(t, callsCount == 1, "unexpected callsCount: %d. Expecting 1", callsCount) + resp := &ctx.Response + assert.Assertf(t, resp.StatusCode() == http.StatusBadRequest, "unexpected statusCode: %d. Expecting %d", resp.StatusCode(), http.StatusBadRequest) + assert.Assertf(t, string(resp.Header.Peek("Header1")) == "value1", "unexpected header value: %q. Expecting %q", resp.Header.Peek("Header1"), "value1") + assert.Assertf(t, string(resp.Header.Peek("Header2")) == "value2", "unexpected header value: %q. Expecting %q", resp.Header.Peek("Header2"), "value2") + assert.Assertf(t, string(resp.Body()) == expectedBody, "unexpected response body %q. Expecting %q", resp.Body(), expectedBody) + assert.Assertf(t, string(resp.Header.Peek("Content-Type")) == expectedContentType, "unexpected content-type %q. Expecting %q", string(resp.Header.Peek("Content-Type")), expectedContentType) +} + +func setContextValueMiddleware(next app.HandlerFunc, key string, value interface{}) app.HandlerFunc { + return func(ctx context.Context, c *app.RequestContext) { + c.Set(key, value) + next(ctx, c) + } +} diff --git a/example/custom_prefix/server.go b/example/custom_prefix/server.go new file mode 100644 index 0000000..75f0b3e --- /dev/null +++ b/example/custom_prefix/server.go @@ -0,0 +1,40 @@ +/* + * Copyright 2022 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package main + +import ( + "context" + + "github.com/cloudwego/hertz/pkg/app" + "github.com/cloudwego/hertz/pkg/app/server" + "github.com/cloudwego/hertz/pkg/common/utils" + "github.com/cloudwego/hertz/pkg/protocol/consts" + "github.com/hertz-contrib/pprof" +) + +func main() { + h := server.Default() + + // default is "debug/pprof" + pprof.Register(h, "dev/pprof") + + h.GET("/ping", func(c context.Context, ctx *app.RequestContext) { + ctx.JSON(consts.StatusOK, utils.H{"ping": "pong"}) + }) + + h.Spin() +} diff --git a/example/custom_router_group/server.go b/example/custom_router_group/server.go new file mode 100644 index 0000000..794ed6e --- /dev/null +++ b/example/custom_router_group/server.go @@ -0,0 +1,43 @@ +/* + * Copyright 2022 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package main + +import ( + "context" + + "github.com/cloudwego/hertz/pkg/app" + "github.com/cloudwego/hertz/pkg/app/server" + "github.com/cloudwego/hertz/pkg/common/utils" + "github.com/cloudwego/hertz/pkg/protocol/consts" + "github.com/hertz-contrib/pprof" +) + +func main() { + h := server.Default() + + pprof.Register(h) + + adminGroup := h.Group("/admin") + + adminGroup.GET("/ping", func(c context.Context, ctx *app.RequestContext) { + ctx.JSON(consts.StatusOK, utils.H{"ping": "pong"}) + }) + + pprof.RouteRegister(adminGroup, "pprof") + + h.Spin() +} diff --git a/example/default/server.go b/example/default/server.go new file mode 100644 index 0000000..ab25b96 --- /dev/null +++ b/example/default/server.go @@ -0,0 +1,39 @@ +/* + * Copyright 2022 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package main + +import ( + "context" + + "github.com/cloudwego/hertz/pkg/app" + "github.com/cloudwego/hertz/pkg/app/server" + "github.com/cloudwego/hertz/pkg/common/utils" + "github.com/cloudwego/hertz/pkg/protocol/consts" + "github.com/hertz-contrib/pprof" +) + +func main() { + h := server.Default() + + pprof.Register(h) + + h.GET("/ping", func(c context.Context, ctx *app.RequestContext) { + ctx.JSON(consts.StatusOK, utils.H{"ping": "pong"}) + }) + + h.Spin() +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..83cb7f8 --- /dev/null +++ b/go.mod @@ -0,0 +1,21 @@ +module github.com/hertz-contrib/pprof + +go 1.16 + +require github.com/cloudwego/hertz v0.3.1 + +require ( + github.com/bytedance/go-tagexpr/v2 v2.9.5 // indirect + github.com/bytedance/gopkg v0.0.0-20220824043955-beb90005fda6 // indirect + github.com/bytedance/sonic v1.4.0 // indirect + github.com/chenzhuoyu/base64x v0.0.0-20220526154910-8bf9453eb81a // indirect + github.com/golang/protobuf v1.5.2 // indirect + github.com/henrylee2cn/ameda v1.5.0 // indirect + github.com/klauspost/cpuid/v2 v2.1.1 // indirect + github.com/nyaruka/phonenumbers v1.1.1 // indirect + github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d + github.com/tidwall/gjson v1.14.3 // indirect + golang.org/x/arch v0.0.0-20220915211755-44deed04936c // indirect + golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8 // indirect + google.golang.org/protobuf v1.28.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..ef68d49 --- /dev/null +++ b/go.sum @@ -0,0 +1,103 @@ +github.com/andeya/goutil v0.0.0-20220704075712-42f2ec55fe8d h1:qZjX5KRJDCA0DaORmzyXuySdlT+MOhx0OOTbUbdPxp0= +github.com/andeya/goutil v0.0.0-20220704075712-42f2ec55fe8d/go.mod h1:jEG5/QnnhG7yGxwFUX6Q+JGMif7sjdHmmNVjn7nhJDo= +github.com/bytedance/go-tagexpr/v2 v2.9.2/go.mod h1:5qsx05dYOiUXOUgnQ7w3Oz8BYs2qtM/bJokdLb79wRM= +github.com/bytedance/go-tagexpr/v2 v2.9.5 h1:OEtlwVLG2Qb8J1P7e1jcJaWx6Pdl2Hf6Xe90vVufZ5E= +github.com/bytedance/go-tagexpr/v2 v2.9.5/go.mod h1:OWf+3Uf1/t7KQgJMMwXEUp1b3uH3rclsma4RfHgue8s= +github.com/bytedance/gopkg v0.0.0-20220413063733-65bf48ffb3a7/go.mod h1:2ZlV9BaUH4+NXIBF0aMdKKAnHTzqH+iMU4KUjAbL23Q= +github.com/bytedance/gopkg v0.0.0-20220824043955-beb90005fda6 h1:/EH/zEUjrW99+lQ8ZKhKoyQy4eW1eskLM43JnQMnQKg= +github.com/bytedance/gopkg v0.0.0-20220824043955-beb90005fda6/go.mod h1:2ZlV9BaUH4+NXIBF0aMdKKAnHTzqH+iMU4KUjAbL23Q= +github.com/bytedance/sonic v1.3.5/go.mod h1:V973WhNhGmvHxW6nQmsHEfHaoU9F3zTF+93rH03hcUQ= +github.com/bytedance/sonic v1.4.0 h1:d6vgPhwgHfpmEiz/9Fzea9fGzWY7RO1TQEySBiRwDLY= +github.com/bytedance/sonic v1.4.0/go.mod h1:V973WhNhGmvHxW6nQmsHEfHaoU9F3zTF+93rH03hcUQ= +github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= +github.com/chenzhuoyu/base64x v0.0.0-20220526154910-8bf9453eb81a h1:lmGPzuocwDxoPAMr9h16zoJY/USZR9jIh99nrmKk1uI= +github.com/chenzhuoyu/base64x v0.0.0-20220526154910-8bf9453eb81a/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= +github.com/cloudwego/hertz v0.3.1 h1:SQc5zyiZDgWipB+HpEeeMt9b6Hsl3ltKkIIjf/pJEXU= +github.com/cloudwego/hertz v0.3.1/go.mod h1:hnv3B7eZ6kMv7CKFHT2OC4LU0mA4s5XPyu/SbixLcrU= +github.com/cloudwego/netpoll v0.2.6 h1:vzN8cyayoa9RdCOG87tqkYO/j2hA4SMLC+vkcNUq6uI= +github.com/cloudwego/netpoll v0.2.6/go.mod h1:1T2WVuQ+MQw6h6DpE45MohSvDTKdy2DlzCx2KsnPI4E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +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/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= +github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= +github.com/goccy/go-json v0.9.4 h1:L8MLKG2mvVXiQu07qB6hmfqeSYQdOnqPot2GhsIwIaI= +github.com/goccy/go-json v0.9.4/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/henrylee2cn/ameda v1.4.8/go.mod h1:liZulR8DgHxdK+MEwvZIylGnmcjzQ6N6f2PlWe7nEO4= +github.com/henrylee2cn/ameda v1.4.10/go.mod h1:liZulR8DgHxdK+MEwvZIylGnmcjzQ6N6f2PlWe7nEO4= +github.com/henrylee2cn/ameda v1.5.0 h1:2hfaHNsRKRyNeObUZSRLNoODWZrCPrY99D8THk7UmDQ= +github.com/henrylee2cn/ameda v1.5.0/go.mod h1:wnTERseg26LtcSrHOPlV3pBGnNwQiz3TNIeMEgNoNlg= +github.com/henrylee2cn/goutil v0.0.0-20210127050712-89660552f6f8/go.mod h1:Nhe/DM3671a5udlv2AdV2ni/MZzgfv2qrPL5nIi3EGQ= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.1.1 h1:t0wUqjowdm8ezddV5k0tLWVklVuvLJpoHeb4WBdydm0= +github.com/klauspost/cpuid/v2 v2.1.1/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/nyaruka/phonenumbers v1.0.55/go.mod h1:sDaTZ/KPX5f8qyV9qN+hIm+4ZBARJrupC6LuhshJq1U= +github.com/nyaruka/phonenumbers v1.1.1 h1:fyoZmpLN2VCmAnc51XcrNOUVP2wT1ZzQl348ggIaXII= +github.com/nyaruka/phonenumbers v1.1.1/go.mod h1:cGaEsOrLjIL0iKGqJR5Rfywy86dSkbApEpXuM9KySNA= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d h1:Q+gqLBOPkFGHyCJxXMRqtUgUbTjI8/Ze8vu8GGyNFwo= +github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d/go.mod h1:Gy+0tqhJvgGlqnTF8CVGP0AaGRjwBtXs/a5PA0Y3+A4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= +github.com/stretchr/testify v1.7.5 h1:s5PTfem8p8EbKQOctVV53k6jCJt3UX4IEJzwh+C324Q= +github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/tidwall/gjson v1.9.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.12.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.13.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.14.3 h1:9jvXn7olKEHU1S9vwoMGliaT8jq1vJ7IH/n9zD9Dnlw= +github.com/tidwall/gjson v1.14.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.4 h1:cuiLzLnaMeBhRmEv00Lpk3tkYrcxpmbU81tAY4Dw0tc= +github.com/tidwall/sjson v1.2.4/go.mod h1:098SZ494YoMWPmMO6ct4dcFnqxwj9r/gF0Etp19pSNM= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.0.0-20220915211755-44deed04936c h1:D79nQrlk0RLnXLgPkvZkxcVJpBTjdlIZpb+aNBfwNWk= +golang.org/x/arch v0.0.0-20220915211755-44deed04936c/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20220110181412-a018aaa089fe/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8 h1:h+EGohizhe9XlX18rfpa8k8RAc5XyaeamM+0VHRd4lc= +golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/licenses/LICENSE-gin-pprof b/licenses/LICENSE-gin-pprof new file mode 100644 index 0000000..4e2cfb0 --- /dev/null +++ b/licenses/LICENSE-gin-pprof @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016 Gin-Gonic + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/licenses/LICENSE-gotils b/licenses/LICENSE-gotils new file mode 100644 index 0000000..8edd0bc --- /dev/null +++ b/licenses/LICENSE-gotils @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2020-present Sergio Andres Virviescas Santana + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/pprof.go b/pprof.go new file mode 100644 index 0000000..640ac6f --- /dev/null +++ b/pprof.go @@ -0,0 +1,69 @@ +/* + * Copyright 2022 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package pprof + +import ( + "net/http/pprof" + + "github.com/cloudwego/hertz/pkg/app/server" + "github.com/cloudwego/hertz/pkg/route" + "github.com/hertz-contrib/pprof/adaptor" +) + +const ( + // DefaultPrefix url prefix of pprof + DefaultPrefix = "/debug/pprof" +) + +func getPrefix(prefixOptions ...string) string { + prefix := DefaultPrefix + if len(prefixOptions) > 0 { + prefix = prefixOptions[0] + } + return prefix +} + +// Register the standard HandlerFuncs from the net/http/pprof package with +// the provided hertz.Hertz. prefixOptions is a optional. If not prefixOptions, +// the default path prefix is used, otherwise first prefixOptions will be path prefix. +func Register(r *server.Hertz, prefixOptions ...string) { + RouteRegister(&(r.RouterGroup), prefixOptions...) +} + +// RouteRegister the standard HandlerFuncs from the net/http/pprof package with +// the provided hertz.RouterGroup. prefixOptions is a optional. If not prefixOptions, +// the default path prefix is used, otherwise first prefixOptions will be path prefix. +func RouteRegister(rg *route.RouterGroup, prefixOptions ...string) { + prefix := getPrefix(prefixOptions...) + + prefixRouter := rg.Group(prefix) + { + prefixRouter.GET("/", adaptor.NewHertzHTTPHandlerFunc(pprof.Index)) + prefixRouter.GET("/cmdline", adaptor.NewHertzHTTPHandlerFunc(pprof.Cmdline)) + + prefixRouter.GET("/profile", adaptor.NewHertzHTTPHandlerFunc(pprof.Profile)) + prefixRouter.POST("/symbol", adaptor.NewHertzHTTPHandlerFunc(pprof.Symbol)) + prefixRouter.GET("/symbol", adaptor.NewHertzHTTPHandlerFunc(pprof.Symbol)) + prefixRouter.GET("/trace", adaptor.NewHertzHTTPHandlerFunc(pprof.Trace)) + prefixRouter.GET("/allocs", adaptor.NewHertzHTTPHandlerFunc(pprof.Handler("allocs").ServeHTTP)) + prefixRouter.GET("/block", adaptor.NewHertzHTTPHandlerFunc(pprof.Handler("block").ServeHTTP)) + prefixRouter.GET("/goroutine", adaptor.NewHertzHTTPHandlerFunc(pprof.Handler("goroutine").ServeHTTP)) + prefixRouter.GET("/heap", adaptor.NewHertzHTTPHandlerFunc(pprof.Handler("heap").ServeHTTP)) + prefixRouter.GET("/mutex", adaptor.NewHertzHTTPHandlerFunc(pprof.Handler("mutex").ServeHTTP)) + prefixRouter.GET("/threadcreate", adaptor.NewHertzHTTPHandlerFunc(pprof.Handler("threadcreate").ServeHTTP)) + } +} diff --git a/pprof_test.go b/pprof_test.go new file mode 100644 index 0000000..3115f06 --- /dev/null +++ b/pprof_test.go @@ -0,0 +1,145 @@ +/* + * Copyright 2022 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package pprof + +import ( + "bytes" + "context" + "io/ioutil" + "net/http" + "testing" + + "github.com/cloudwego/hertz/pkg/app" + "github.com/cloudwego/hertz/pkg/app/server" + "github.com/cloudwego/hertz/pkg/common/test/assert" + "github.com/cloudwego/hertz/pkg/common/ut" +) + +func Test_getPrefix(t *testing.T) { + tests := []struct { + name string + args []string + want string + }{ + {"default value", nil, "/debug/pprof"}, + {"test user input value", []string{"test/pprof"}, "test/pprof"}, + {"test user input value", []string{"test/pprof", "pprof"}, "test/pprof"}, + } + for _, tt := range tests { + if got := getPrefix(tt.args...); got != tt.want { + t.Errorf("%q. getPrefix() = %v, want %v", tt.name, got, tt.want) + } + } +} + +func Test_Non_Pprof_Path(t *testing.T) { + h := server.Default() + + Register(h) + + h.GET("/", func(c context.Context, ctx *app.RequestContext) { + ctx.String(http.StatusOK, "escaped") + }) + + resp := ut.PerformRequest(h.Engine, http.MethodGet, "/", nil) + assert.DeepEqual(t, http.StatusOK, resp.Code) + + b, err := ioutil.ReadAll(resp.Body) + assert.DeepEqual(t, nil, err) + assert.DeepEqual(t, "escaped", string(b)) +} + +func Test_Pprof_Index(t *testing.T) { + h := server.Default() + + Register(h) + + h.GET("/", func(c context.Context, ctx *app.RequestContext) { + ctx.String(http.StatusOK, "escaped") + }) + + resp := ut.PerformRequest(h.Engine, http.MethodGet, "/debug/pprof/", nil) + assert.DeepEqual(t, http.StatusOK, resp.Code) + assert.DeepEqual(t, []byte("text/html; charset=utf-8"), resp.Header().ContentType()) + + b, err := ioutil.ReadAll(resp.Body) + assert.DeepEqual(t, nil, err) + assert.DeepEqual(t, true, bytes.Contains(b, []byte("/debug/pprof/"))) +} + +func Test_Pprof_Router_Group(t *testing.T) { + bearerToken := "Bearer token" + h := server.New() + Register(h) + adminGroup := h.Group("/admin", func(c context.Context, ctx *app.RequestContext) { + if ctx.Request.Header.Get("Authorization") != bearerToken { + ctx.AbortWithStatus(http.StatusForbidden) + return + } + ctx.Next(c) + }) + RouteRegister(adminGroup, "pprof") + + resp := ut.PerformRequest(h.Engine, http.MethodGet, "/admin/pprof/", nil) + assert.DeepEqual(t, http.StatusForbidden, resp.Code) + + header := ut.Header{ + Key: "Authorization", + Value: bearerToken, + } + resp = ut.PerformRequest(h.Engine, http.MethodGet, "/admin/pprof/", nil, header) + assert.DeepEqual(t, http.StatusOK, resp.Code) +} + +func Test_Pprof_Subs(t *testing.T) { + h := server.Default() + + Register(h) + + h.GET("/", func(c context.Context, ctx *app.RequestContext) { + ctx.String(http.StatusOK, "escaped") + }) + + subs := []string{ + "cmdline", "profile", "symbol", "trace", "allocs", "block", + "goroutine", "heap", "mutex", "threadcreate", + } + + for _, sub := range subs { + t.Run(sub, func(t *testing.T) { + target := "/debug/pprof/" + sub + if sub == "profile" { + target += "?seconds=1" + } + resp := ut.PerformRequest(h.Engine, http.MethodGet, target, nil) + assert.DeepEqual(t, http.StatusOK, resp.Code) + }) + } +} + +func Test_Pprof_Other(t *testing.T) { + h := server.Default() + + Register(h) + + h.GET("/", func(c context.Context, ctx *app.RequestContext) { + ctx.String(http.StatusOK, "escaped") + }) + + resp := ut.PerformRequest(h.Engine, http.MethodGet, "/debug/pprof/302", nil) + assert.DeepEqual(t, http.StatusNotFound, resp.Code) +}