Repository: cloudwego/hertz
Pull request #495 (2022-12-27 08:18:53)

feat: add Cookies function to return all the request cookies
#### What type of PR is this?
<!--
Add one of the following kinds:

build: Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)
ci: Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)
docs: Documentation only changes
feat: A new feature
optimize: A new optimization
fix: A bug fix
perf: A code change that improves performance
refactor: A code change that neither fixes a bug nor adds a feature
style: Changes that do not affect the meaning of the code (white space, formatting, missing semi-colons, etc)
test: Adding missing tests or correcting existing tests
chore: Changes to the build process or auxiliary tools and libraries such as documentation generation
-->

feat

#### Check the PR title.
<!--
The description of the title will be attached in Release Notes, 
so please describe it from user-oriented, what this PR does / why we need it.
Please check your PR title with the below requirements:
-->
- [x] This PR title match the format: \<type\>(optional scope): \<description\>
- [x] The description of this PR title is user-oriented and clear enough for others to understand.


#### (Optional) Translate the PR title into Chinese.

添加 `Cookies` 方法

#### (Optional) More detail description for this PR(en: English/zh: Chinese).
<!--
Provide more detailed info for review. If it is a perf type PR, perf data is suggested to give.
-->
en: `Cookies` returns all the request cookies
zh(optional):  `Cookies` 返回所有的请求 Cookie

#### Which issue(s) this PR fixes:
<!--
Automatically closes linked issue when PR is merged.
Eg: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
-->
null

codecov[bot]: # [Codecov](https://codecov.io/gh/cloudwego/hertz/pull/495?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudwego) Report
Base: **69.69**% // Head: **69.65**% // Decreases project coverage by **`-0.03%`** :warning:
> Coverage data is based on head [(`ec4fd7e`)](https://codecov.io/gh/cloudwego/hertz/pull/495?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudwego) compared to base [(`a7a3fc0`)](https://codecov.io/gh/cloudwego/hertz/commit/a7a3fc0c800d3e72813740fdcda49aab78204ac2?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudwego).
> Patch coverage: 86.95% of modified lines in pull request are covered.

<details><summary>Additional details and impacted files</summary>


```diff
@@             Coverage Diff             @@
##           develop     #495      +/-   ##
===========================================
- Coverage    69.69%   69.65%   -0.04%     
===========================================
  Files           93       93              
  Lines         8760     8800      +40     
===========================================
+ Hits          6105     6130      +25     
- Misses        2293     2307      +14     
- Partials       362      363       +1     
```


| [Impacted Files](https://codecov.io/gh/cloudwego/hertz/pull/495?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudwego) | Coverage Δ | |
|---|---|---|
| [pkg/app/client/option.go](https://codecov.io/gh/cloudwego/hertz/pull/495/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudwego#diff-cGtnL2FwcC9jbGllbnQvb3B0aW9uLmdv) | `96.66% <78.57%> (-3.34%)` | :arrow_down: |
| [pkg/app/context.go](https://codecov.io/gh/cloudwego/hertz/pull/495/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudwego#diff-cGtnL2FwcC9jb250ZXh0Lmdv) | `84.59% <100.00%> (+0.29%)` | :arrow_up: |
| [pkg/protocol/http1/client.go](https://codecov.io/gh/cloudwego/hertz/pull/495/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudwego#diff-cGtnL3Byb3RvY29sL2h0dHAxL2NsaWVudC5nbw==) | `49.80% <0.00%> (-1.21%)` | :arrow_down: |
| [pkg/common/errors/errors.go](https://codecov.io/gh/cloudwego/hertz/pull/495/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudwego#diff-cGtnL2NvbW1vbi9lcnJvcnMvZXJyb3JzLmdv) | `87.95% <0.00%> (ø)` | |
| [pkg/common/config/client\_option.go](https://codecov.io/gh/cloudwego/hertz/pull/495/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudwego#diff-cGtnL2NvbW1vbi9jb25maWcvY2xpZW50X29wdGlvbi5nbw==) | `100.00% <0.00%> (ø)` | |
| [pkg/app/client/client.go](https://codecov.io/gh/cloudwego/hertz/pull/495/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudwego#diff-cGtnL2FwcC9jbGllbnQvY2xpZW50Lmdv) | `74.84% <0.00%> (+0.15%)` | :arrow_up: |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudwego). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudwego)

</details>

[:umbrella: View full report at Codecov](https://codecov.io/gh/cloudwego/hertz/pull/495?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudwego).   
:loudspeaker: Do you have feedback about the report comment? [Let us know in this issue](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudwego).

FGYFFFF: Can the title be more detailed?

Patch:
diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml
index 4ba7f71..4e6b54f 100644
--- a/.github/workflows/pr-check.yml
+++ b/.github/workflows/pr-check.yml
@@ -22,7 +22,7 @@ jobs:
       - name: Set up Go
         uses: actions/setup-go@v3
         with:
-          go-version: 1.18
+          go-version: 1.19
 
       - uses: actions/cache@v3
         with:
diff --git a/cmd/hz/app/app.go b/cmd/hz/app/app.go
index 4261bd4..a66f624 100644
--- a/cmd/hz/app/app.go
+++ b/cmd/hz/app/app.go
@@ -141,7 +141,7 @@ func Init() *cli.App {
 	outDirFlag := cli.StringFlag{Name: "out_dir", Usage: "Specify the project path.", Destination: &globalArgs.OutDir}
 	handlerDirFlag := cli.StringFlag{Name: "handler_dir", Usage: "Specify the handler path.", Destination: &globalArgs.HandlerDir}
 	modelDirFlag := cli.StringFlag{Name: "model_dir", Usage: "Specify the model path.", Destination: &globalArgs.ModelDir}
-	clientDirFlag := cli.StringFlag{Name: "client_dir", Usage: "Specify the client path. If not specified, no client code is generated.", Destination: &globalArgs.ClientDir}
+	clientDirFlag := cli.StringFlag{Name: "client_dir", Usage: "Specify the client path. If not specified, IDL generated path is used for 'client' command; no client code is generated for 'new' command", Destination: &globalArgs.ClientDir}
 
 	optPkgFlag := cli.StringSliceFlag{Name: "option_package", Aliases: []string{"P"}, Usage: "Specify the package path. ({include_path}={import_path})"}
 	includesFlag := cli.StringSliceFlag{Name: "proto_path", Aliases: []string{"I"}, Usage: "Add an IDL search path for includes. (Valid only if idl is protobuf)"}
@@ -263,6 +263,7 @@ func Init() *cli.App {
 				&idlFlag,
 				&moduleFlag,
 				&modelDirFlag,
+				&clientDirFlag,
 
 				&includesFlag,
 				&thriftOptionsFlag,
diff --git a/cmd/hz/generator/client.go b/cmd/hz/generator/client.go
index e3320e6..37b2cac 100644
--- a/cmd/hz/generator/client.go
+++ b/cmd/hz/generator/client.go
@@ -43,7 +43,8 @@ type ClientFile struct {
 
 func (pkgGen *HttpPackageGenerator) genClient(pkg *HttpPackage, clientDir string) error {
 	for _, s := range pkg.Services {
-		hertzClientPath := filepath.Join(clientDir, "hertz_client.go")
+		cliDir := util.SubDir(clientDir, util.ToSnakeCase(s.Name))
+		hertzClientPath := filepath.Join(cliDir, hertzClientTplName)
 		isExist, err := util.PathExist(hertzClientPath)
 		if err != nil {
 			return err
@@ -55,8 +56,8 @@ func (pkgGen *HttpPackageGenerator) genClient(pkg *HttpPackage, clientDir string
 			}
 		}
 		client := ClientFile{
-			FilePath:      filepath.Join(clientDir, util.ToSnakeCase(s.Name)+".go"),
-			ServiceName:   util.ToSnakeCase(s.Name),
+			FilePath:      filepath.Join(cliDir, util.ToSnakeCase(s.Name)+".go"),
+			ServiceName:   util.ToCamelCase(s.Name),
 			ClientMethods: s.ClientMethods,
 			BaseDomain:    s.BaseDomain,
 		}
diff --git a/cmd/hz/generator/handler.go b/cmd/hz/generator/handler.go
index 0027a90..6d0fb7b 100644
--- a/cmd/hz/generator/handler.go
+++ b/cmd/hz/generator/handler.go
@@ -19,7 +19,7 @@ package generator
 import (
 	"bytes"
 	"fmt"
-	"io/ioutil"
+	"os"
 	"path/filepath"
 	"strings"
 
@@ -142,7 +142,7 @@ func (pkgGen *HttpPackageGenerator) updateHandler(handler interface{}, handlerTp
 		return pkgGen.TemplateGenerator.Generate(handler, handlerTpl, filePath, noRepeat)
 	}
 
-	file, err := ioutil.ReadFile(filePath)
+	file, err := os.ReadFile(filePath)
 	if err != nil {
 		return err
 	}
diff --git a/cmd/hz/generator/layout.go b/cmd/hz/generator/layout.go
index 82f566c..472b6a2 100644
--- a/cmd/hz/generator/layout.go
+++ b/cmd/hz/generator/layout.go
@@ -21,6 +21,7 @@ import (
 	"errors"
 	"fmt"
 	"io/ioutil"
+	"os"
 	"reflect"
 
 	"github.com/cloudwego/hertz/cmd/hz/util"
@@ -55,7 +56,7 @@ func (lg *LayoutGenerator) Init() error {
 	config := layoutConfig
 	// unmarshal from user-defined config file if it exists
 	if lg.ConfigPath != "" {
-		cdata, err := ioutil.ReadFile(lg.ConfigPath)
+		cdata, err := os.ReadFile(lg.ConfigPath)
 		if err != nil {
 			return fmt.Errorf("read layout config from  %s failed, err: %v", lg.ConfigPath, err.Error())
 		}
diff --git a/cmd/hz/generator/package.go b/cmd/hz/generator/package.go
index 4561267..0476271 100644
--- a/cmd/hz/generator/package.go
+++ b/cmd/hz/generator/package.go
@@ -19,7 +19,7 @@ package generator
 import (
 	"errors"
 	"fmt"
-	"io/ioutil"
+	"os"
 	"path/filepath"
 	"reflect"
 	"text/template"
@@ -70,7 +70,7 @@ func (pkgGen *HttpPackageGenerator) Init() error {
 	config := packageConfig
 	// unmarshal from user-defined config file if it exists
 	if pkgGen.ConfigPath != "" {
-		cdata, err := ioutil.ReadFile(pkgGen.ConfigPath)
+		cdata, err := os.ReadFile(pkgGen.ConfigPath)
 		if err != nil {
 			return fmt.Errorf("read layout config from  %s failed, err: %v", pkgGen.ConfigPath, err.Error())
 		}
@@ -141,8 +141,12 @@ func (pkgGen *HttpPackageGenerator) Generate(pkg *HttpPackage) error {
 	}
 
 	if pkgGen.CmdType == meta.CmdClient {
+		// default client dir
 		clientDir := pkgGen.IdlClientDir
-		clientDir = util.SubDir(clientDir, "hertz")
+		// user specify client dir
+		if len(pkgGen.ClientDir) != 0 {
+			clientDir = pkgGen.ClientDir
+		}
 		if err := pkgGen.genClient(pkg, clientDir); err != nil {
 			return err
 		}
diff --git a/cmd/hz/generator/package_tpl.go b/cmd/hz/generator/package_tpl.go
index ac2197b..3eba61e 100644
--- a/cmd/hz/generator/package_tpl.go
+++ b/cmd/hz/generator/package_tpl.go
@@ -289,9 +289,12 @@ type use interface {
 
 // Definition of global data and types.
 type ResponseResultDecider func(statusCode int, rawResponse *protocol.Response) (isError bool)
-type bindRequestBodyFunc func(c *cli, r *request) (contentType string, body io.Reader, err error)
-type beforeRequestFunc func(*cli, *request) error
-type afterResponseFunc func(*cli, *response) error
+
+type (
+	bindRequestBodyFunc func(c *cli, r *request) (contentType string, body io.Reader, err error)
+	beforeRequestFunc   func(*cli, *request) error
+	afterResponseFunc   func(*cli, *response) error
+)
 
 var (
 	hdrContentTypeKey     = http.CanonicalHeaderKey("Content-Type")
@@ -906,11 +909,13 @@ func (s *{{$.ServiceName}}Client) {{$MethodInfo.Name}}(context context.Context,
 		setRequestOption(reqOpt...).
 		setResult(httpResp).
 		execute("{{$MethodInfo.HTTPMethod}}", "{{$MethodInfo.Path}}")
-	if err == nil {
-		resp = httpResp
+	if err != nil {
+		return nil, nil, err
 	}
+    
+	resp = httpResp
 	rawResponse = ret.rawResponse
-	return resp, rawResponse, err
+	return resp, rawResponse, nil
 }
 {{end}}
 
diff --git a/cmd/hz/meta/const.go b/cmd/hz/meta/const.go
index 8259b58..0bcb21f 100644
--- a/cmd/hz/meta/const.go
+++ b/cmd/hz/meta/const.go
@@ -19,7 +19,7 @@ package meta
 import "runtime"
 
 // Version hz version
-const Version = "v0.4.0"
+const Version = "v0.5.0"
 
 // Mode hz run modes
 type Mode int
diff --git a/cmd/hz/meta/manifest.go b/cmd/hz/meta/manifest.go
index 9f465eb..773d283 100644
--- a/cmd/hz/meta/manifest.go
+++ b/cmd/hz/meta/manifest.go
@@ -18,7 +18,6 @@ package meta
 
 import (
 	"fmt"
-	"io/ioutil"
 	"os"
 	"path/filepath"
 	"regexp"
@@ -43,7 +42,7 @@ func init() {
 var regVersion = regexp.MustCompile(`hz version: v([0-9a-z.]*)`)
 
 func (manifest *Manifest) Validate(dir string) error {
-	rd, err := ioutil.ReadFile(filepath.Join(dir, ManifestFile))
+	rd, err := os.ReadFile(filepath.Join(dir, ManifestFile))
 	if err != nil {
 		return err
 	}
diff --git a/cmd/hz/protobuf/ast.go b/cmd/hz/protobuf/ast.go
index f5895cf..89a51ed 100644
--- a/cmd/hz/protobuf/ast.go
+++ b/cmd/hz/protobuf/ast.go
@@ -229,6 +229,7 @@ func parseAnnotationToClient(clientMethod *generator.ClientMethod, gen *protogen
 	if err != nil {
 		return err
 	}
+	// pb input type must be message
 	inputType := method.Input
 	var (
 		hasBodyAnnotation bool
diff --git a/cmd/hz/thrift/ast.go b/cmd/hz/thrift/ast.go
index 7ef2d17..1a8f607 100644
--- a/cmd/hz/thrift/ast.go
+++ b/cmd/hz/thrift/ast.go
@@ -172,6 +172,10 @@ func parseAnnotationToClient(clientMethod *generator.ClientMethod, p *parser.Typ
 	}
 	thriftgoUtil.SetRootScope(scope)
 	st := scope.StructLike(typeName)
+	if st == nil {
+		logs.Infof("the type '%s' for method '%s' is base type, so skip parse client info\n")
+		return nil
+	}
 	var (
 		hasBodyAnnotation bool
 		hasFormAnnotation bool
@@ -248,6 +252,18 @@ func parseAnnotationToClient(clientMethod *generator.ClientMethod, p *parser.Typ
 
 var BaseThrift = parser.Thrift{}
 
+var baseTypes = map[string]string{
+	"bool":   "bool",
+	"byte":   "int8",
+	"i8":     "int8",
+	"i16":    "int16",
+	"i32":    "int32",
+	"i64":    "int64",
+	"double": "float64",
+	"string": "string",
+	"binary": "[]byte",
+}
+
 func switchBaseType(typ *parser.Type) *model.Type {
 	switch typ.Name {
 	case "bool":
@@ -336,6 +352,13 @@ func (rs ResolvedSymbol) Expression() string {
 		logs.Warnf("%s naming style for %s failed, fall back to %s, please refer to the variable manually!", NameStyle.Name(), rs.Base, rs.Base)
 		base = rs.Base
 	}
+	// base type no need to do name style
+	if model.IsBaseType(rs.Type) {
+		// base type mapping
+		if val, exist := baseTypes[rs.Base]; exist {
+			base = val
+		}
+	}
 	if rs.Src != "" {
 		if !rs.IsValue && model.IsBaseType(rs.Type) {
 			return base
diff --git a/cmd/hz/util/env.go b/cmd/hz/util/env.go
index 51168e2..8385877 100644
--- a/cmd/hz/util/env.go
+++ b/cmd/hz/util/env.go
@@ -20,7 +20,6 @@ import (
 	"bytes"
 	"fmt"
 	"go/build"
-	"io/ioutil"
 	"os"
 	"os/exec"
 	"path/filepath"
@@ -85,7 +84,7 @@ var goModReg = regexp.MustCompile(`^\s*module\s+(\S+)\s*`)
 func SearchGoMod(cwd string, recurse bool) (moduleName, path string, found bool) {
 	for {
 		path = filepath.Join(cwd, "go.mod")
-		data, err := ioutil.ReadFile(path)
+		data, err := os.ReadFile(path)
 		if err == nil {
 			for _, line := range strings.Split(string(data), "\n") {
 				m := goModReg.FindStringSubmatch(line)
diff --git a/pkg/app/client/client.go b/pkg/app/client/client.go
index e128519..1f98cbe 100644
--- a/pkg/app/client/client.go
+++ b/pkg/app/client/client.go
@@ -187,7 +187,6 @@ func DoRedirects(ctx context.Context, req *protocol.Request, resp *protocol.Resp
 // is too small a new slice will be allocated.
 //
 // The function follows redirects. Use Do* for manually handling redirects.
-//
 func Get(ctx context.Context, dst []byte, url string, requestOptions ...config.RequestOption) (statusCode int, body []byte, err error) {
 	return defaultClient.Get(ctx, dst, url, requestOptions...)
 }
diff --git a/pkg/app/context.go b/pkg/app/context.go
index 17b9e4f..157674c 100644
--- a/pkg/app/context.go
+++ b/pkg/app/context.go
@@ -275,8 +275,8 @@ type HijackHandler func(c network.Conn)
 //
 // The server skips calling the handler in the following cases:
 //
-//     * 'Connection: close' header exists in either request or response.
-//     * Unexpected error during response writing to the connection.
+//   - 'Connection: close' header exists in either request or response.
+//   - Unexpected error during response writing to the connection.
 //
 // The server stops processing requests from hijacked connections.
 //
@@ -288,9 +288,8 @@ type HijackHandler func(c network.Conn)
 // Arbitrary 'Connection: Upgrade' protocols may be implemented
 // with HijackHandler. For instance,
 //
-//     * WebSocket ( https://en.wikipedia.org/wiki/WebSocket )
-//     * HTTP/2.0 ( https://en.wikipedia.org/wiki/HTTP/2 )
-//
+//   - WebSocket ( https://en.wikipedia.org/wiki/WebSocket )
+//   - HTTP/2.0 ( https://en.wikipedia.org/wiki/HTTP/2 )
 func (ctx *RequestContext) Hijack(handler HijackHandler) {
 	ctx.hijackHandler = handler
 }
@@ -441,9 +440,10 @@ func (ctx *RequestContext) String(code int, format string, values ...interface{}
 
 // FullPath returns a matched route full path. For not found routes
 // returns an empty string.
-//     router.GET("/user/:id", func(c *hertz.RequestContext) {
-//         c.FullPath() == "/user/:id" // true
-//     })
+//
+//	router.GET("/user/:id", func(c *hertz.RequestContext) {
+//	    c.FullPath() == "/user/:id" // true
+//	})
 func (ctx *RequestContext) FullPath() string {
 	return ctx.fullPath
 }
@@ -507,15 +507,15 @@ func (ctx *RequestContext) FormFile(name string) (*multipart.FileHeader, error)
 //
 // The value is searched in the following places:
 //
-//   * Query string.
-//   * POST or PUT body.
+//   - Query string.
+//   - POST or PUT body.
 //
 // There are more fine-grained methods for obtaining form values:
 //
-//   * QueryArgs for obtaining values from query string.
-//   * PostArgs for obtaining values from POST or PUT body.
-//   * MultipartForm for obtaining values from multipart form.
-//   * FormFile for obtaining uploaded files.
+//   - QueryArgs for obtaining values from query string.
+//   - PostArgs for obtaining values from POST or PUT body.
+//   - MultipartForm for obtaining values from multipart form.
+//   - FormFile for obtaining uploaded files.
 //
 // The returned value is valid until returning from RequestHandler.
 // Use engine.SetCustomFormValueFunc to change action of FormValue.
@@ -678,7 +678,7 @@ func (ctx *RequestContext) SetHandlers(hc HandlersChain) {
 
 // HandlerName returns the main handler's name.
 //
-//For example if the handler is "handleGetUsers()", this function will return "main.handleGetUsers".
+// For example if the handler is "handleGetUsers()", this function will return "main.handleGetUsers".
 func (ctx *RequestContext) HandlerName() string {
 	return utils.NameOfFunction(ctx.handlers.Last())
 }
@@ -889,10 +889,11 @@ func (ctx *RequestContext) GetStringMapStringSlice(key string) (smss map[string]
 
 // Param returns the value of the URL param.
 // It is a shortcut for c.Params.ByName(key)
-//     router.GET("/user/:id", func(c *hertz.RequestContext) {
-//         // a GET request to /user/john
-//         id := c.Param("id") // id == "john"
-//     })
+//
+//	router.GET("/user/:id", func(c *hertz.RequestContext) {
+//	    // a GET request to /user/john
+//	    id := c.Param("id") // id == "john"
+//	})
 func (ctx *RequestContext) Param(key string) string {
 	return ctx.Params.ByName(key)
 }
@@ -1046,22 +1047,23 @@ func (ctx *RequestContext) Cookie(key string) []byte {
 }
 
 // SetCookie adds a Set-Cookie header to the Response's headers.
-//  Parameter introduce:
-//  name and value is used to set cookie's name and value, eg. Set-Cookie: name=value
-//  maxAge is use to set cookie's expiry date, eg. Set-Cookie: name=value; max-age=1
-//  path and domain is used to set the scope of a cookie, eg. Set-Cookie: name=value;domain=localhost; path=/;
-//  secure and httpOnly is used to sent cookies securely; eg. Set-Cookie: name=value;HttpOnly; secure;
-//  sameSite let servers specify whether/when cookies are sent with cross-site requests; eg. Set-Cookie: name=value;HttpOnly; secure; SameSite=Lax;
-//
-//  For example:
-//  1. ctx.SetCookie("user", "hertz", 1, "/", "localhost",protocol.CookieSameSiteLaxMode, true, true)
-//  add response header --->  Set-Cookie: user=hertz; max-age=1; domain=localhost; path=/; HttpOnly; secure; SameSite=Lax;
-//  2. ctx.SetCookie("user", "hertz", 10, "/", "localhost",protocol.CookieSameSiteLaxMode, false, false)
-//  add response header --->  Set-Cookie: user=hertz; max-age=10; domain=localhost; path=/; SameSite=Lax;
-//  3. ctx.SetCookie("", "hertz", 10, "/", "localhost",protocol.CookieSameSiteLaxMode, false, false)
-//  add response header --->  Set-Cookie: hertz; max-age=10; domain=localhost; path=/; SameSite=Lax;
-//  4. ctx.SetCookie("user", "", 10, "/", "localhost",protocol.CookieSameSiteLaxMode, false, false)
-//  add response header --->  Set-Cookie: user=; max-age=10; domain=localhost; path=/; SameSite=Lax;
+//
+//	Parameter introduce:
+//	name and value is used to set cookie's name and value, eg. Set-Cookie: name=value
+//	maxAge is use to set cookie's expiry date, eg. Set-Cookie: name=value; max-age=1
+//	path and domain is used to set the scope of a cookie, eg. Set-Cookie: name=value;domain=localhost; path=/;
+//	secure and httpOnly is used to sent cookies securely; eg. Set-Cookie: name=value;HttpOnly; secure;
+//	sameSite let servers specify whether/when cookies are sent with cross-site requests; eg. Set-Cookie: name=value;HttpOnly; secure; SameSite=Lax;
+//
+//	For example:
+//	1. ctx.SetCookie("user", "hertz", 1, "/", "localhost",protocol.CookieSameSiteLaxMode, true, true)
+//	add response header --->  Set-Cookie: user=hertz; max-age=1; domain=localhost; path=/; HttpOnly; secure; SameSite=Lax;
+//	2. ctx.SetCookie("user", "hertz", 10, "/", "localhost",protocol.CookieSameSiteLaxMode, false, false)
+//	add response header --->  Set-Cookie: user=hertz; max-age=10; domain=localhost; path=/; SameSite=Lax;
+//	3. ctx.SetCookie("", "hertz", 10, "/", "localhost",protocol.CookieSameSiteLaxMode, false, false)
+//	add response header --->  Set-Cookie: hertz; max-age=10; domain=localhost; path=/; SameSite=Lax;
+//	4. ctx.SetCookie("user", "", 10, "/", "localhost",protocol.CookieSameSiteLaxMode, false, false)
+//	add response header --->  Set-Cookie: user=; max-age=10; domain=localhost; path=/; SameSite=Lax;
 func (ctx *RequestContext) SetCookie(name, value string, maxAge int, path, domain string, sameSite protocol.CookieSameSite, secure, httpOnly bool) {
 	if path == "" {
 		path = "/"
@@ -1135,11 +1137,12 @@ func (ctx *RequestContext) PostArgs() *protocol.Args {
 // Query returns the keyed url query value if it exists, otherwise it returns an empty string `("")`.
 //
 // For example:
-//     GET /path?id=1234&name=Manu&value=
-// 	   c.Query("id") == "1234"
-// 	   c.Query("name") == "Manu"
-// 	   c.Query("value") == ""
-// 	   c.Query("wtf") == ""
+//
+//	    GET /path?id=1234&name=Manu&value=
+//		   c.Query("id") == "1234"
+//		   c.Query("name") == "Manu"
+//		   c.Query("value") == ""
+//		   c.Query("wtf") == ""
 func (ctx *RequestContext) Query(key string) string {
 	value, _ := ctx.GetQuery(key)
 	return value
@@ -1159,10 +1162,11 @@ func (ctx *RequestContext) DefaultQuery(key, defaultValue string) string {
 // if it exists `(value, true)` (even when the value is an empty string) will be returned,
 // otherwise it returns `("", false)`.
 // For example:
-//     GET /?name=Manu&lastname=
-//     ("Manu", true) == c.GetQuery("name")
-//     ("", false) == c.GetQuery("id")
-//     ("", true) == c.GetQuery("lastname")
+//
+//	GET /?name=Manu&lastname=
+//	("Manu", true) == c.GetQuery("name")
+//	("", false) == c.GetQuery("id")
+//	("", true) == c.GetQuery("lastname")
 func (ctx *RequestContext) GetQuery(key string) (string, bool) {
 	return ctx.QueryArgs().PeekExists(key)
 }
@@ -1190,9 +1194,10 @@ func (ctx *RequestContext) DefaultPostForm(key, defaultValue string) string {
 // otherwise it returns ("", false).
 //
 // For example, during a PATCH request to update the user's email:
-//     email=mail@example.com  -->  ("mail@example.com", true) := GetPostForm("email") // set email to "mail@example.com"
-// 	   email=                  -->  ("", true) := GetPostForm("email") // set email to ""
-//                             -->  ("", false) := GetPostForm("email") // do nothing with email
+//
+//	    email=mail@example.com  -->  ("mail@example.com", true) := GetPostForm("email") // set email to "mail@example.com"
+//		   email=                  -->  ("", true) := GetPostForm("email") // set email to ""
+//	                            -->  ("", false) := GetPostForm("email") // do nothing with email
 func (ctx *RequestContext) GetPostForm(key string) (string, bool) {
 	if v, exists := ctx.PostArgs().PeekExists(key); exists {
 		return v, exists
diff --git a/pkg/app/fs.go b/pkg/app/fs.go
index 7b468ba..9cd6ba4 100644
--- a/pkg/app/fs.go
+++ b/pkg/app/fs.go
@@ -48,7 +48,6 @@ import (
 	"fmt"
 	"html"
 	"io"
-	"io/ioutil"
 	"mime"
 	"net/http"
 	"os"
@@ -270,8 +269,8 @@ func (r *fsSmallFileReader) WriteTo(w io.Writer) (int64, error) {
 //
 // HTTP response may contain uncompressed file contents in the following cases:
 //
-//   * Missing 'Accept-Encoding: gzip' request header.
-//   * No write access to directory containing the file.
+//   - Missing 'Accept-Encoding: gzip' request header.
+//   - No write access to directory containing the file.
 //
 // Directory contents is returned if path points to directory.
 //
@@ -1077,7 +1076,7 @@ func readFileHeader(f *os.File, compressed bool) ([]byte, error) {
 		R: r,
 		N: 512,
 	}
-	data, err := ioutil.ReadAll(lr)
+	data, err := io.ReadAll(lr)
 	if _, err := f.Seek(0, 0); err != nil {
 		return nil, err
 	}
@@ -1155,12 +1154,11 @@ func ParseByteRange(byteRange []byte, contentLength int) (startPos, endPos int,
 //
 // Examples:
 //
-//   * host=foobar.com, slashesCount=0, original path="/foo/bar".
+//   - host=foobar.com, slashesCount=0, original path="/foo/bar".
 //     Resulting path: "/foobar.com/foo/bar"
 //
-//   * host=img.aaa.com, slashesCount=1, original path="/images/123/456.jpg"
+//   - host=img.aaa.com, slashesCount=1, original path="/images/123/456.jpg"
 //     Resulting path: "/img.aaa.com/123/456.jpg"
-//
 func NewVHostPathRewriter(slashesCount int) PathRewriteFunc {
 	return func(ctx *RequestContext) []byte {
 		path := stripLeadingSlashes(ctx.Path(), slashesCount)
@@ -1217,9 +1215,9 @@ func ServeFileUncompressed(ctx *RequestContext, path string) {
 //
 // Examples:
 //
-//   * slashesCount = 0, original path: "/foo/bar", result: "/foo/bar"
-//   * slashesCount = 1, original path: "/foo/bar", result: "/bar"
-//   * slashesCount = 2, original path: "/foo/bar", result: ""
+//   - slashesCount = 0, original path: "/foo/bar", result: "/foo/bar"
+//   - slashesCount = 1, original path: "/foo/bar", result: "/bar"
+//   - slashesCount = 2, original path: "/foo/bar", result: ""
 //
 // The returned path rewriter may be used as FS.PathRewrite .
 func NewPathSlashesStripper(slashesCount int) PathRewriteFunc {
diff --git a/pkg/app/middlewares/server/recovery/recovery.go b/pkg/app/middlewares/server/recovery/recovery.go
index f05aad2..02eccba 100644
--- a/pkg/app/middlewares/server/recovery/recovery.go
+++ b/pkg/app/middlewares/server/recovery/recovery.go
@@ -20,7 +20,7 @@ import (
 	"bytes"
 	"context"
 	"fmt"
-	"io/ioutil"
+	"os"
 	"runtime"
 
 	"github.com/cloudwego/hertz/pkg/app"
@@ -66,7 +66,7 @@ func stack(skip int) []byte {
 		// Print this much at least.  If we can't find the source, it won't show.
 		fmt.Fprintf(buf, "%s:%d (0x%x)\n", file, line, pc)
 		if file != lastFile {
-			data, err := ioutil.ReadFile(file)
+			data, err := os.ReadFile(file)
 			if err != nil {
 				continue
 			}
diff --git a/pkg/app/server/binding/binding.go b/pkg/app/server/binding/binding.go
index 1d72981..fa4af9d 100644
--- a/pkg/app/server/binding/binding.go
+++ b/pkg/app/server/binding/binding.go
@@ -36,22 +36,25 @@ var defaultBinder = binding.Default()
 
 // BindAndValidate binds data from *protocol.Request to obj and validates them if needed.
 // NOTE:
-//  obj should be a pointer.
+//
+//	obj should be a pointer.
 func BindAndValidate(req *protocol.Request, obj interface{}, pathParams param.Params) error {
 	return defaultBinder.IBindAndValidate(obj, wrapRequest(req), pathParams)
 }
 
 // Bind binds data from *protocol.Request to obj.
 // NOTE:
-//  obj should be a pointer.
+//
+//	obj should be a pointer.
 func Bind(req *protocol.Request, obj interface{}, pathParams param.Params) error {
 	return defaultBinder.IBind(obj, wrapRequest(req), pathParams)
 }
 
 // Validate validates obj with "vd" tag
 // NOTE:
-//  obj should be a pointer.
-//  Validate should be called after Bind.
+//
+//	obj should be a pointer.
+//	Validate should be called after Bind.
 func Validate(obj interface{}) error {
 	return defaultBinder.Validate(obj)
 }
@@ -59,54 +62,61 @@ func Validate(obj interface{}) error {
 // SetLooseZeroMode if set to true,
 // the empty string request parameter is bound to the zero value of parameter.
 // NOTE:
-//  The default is false.
-//  Suitable for these parameter types: query/header/cookie/form .
+//
+//	The default is false.
+//	Suitable for these parameter types: query/header/cookie/form .
 func SetLooseZeroMode(enable bool) {
 	defaultBinder.SetLooseZeroMode(enable)
 }
 
 // SetErrorFactory customizes the factory of validation error.
 // NOTE:
-//  If errFactory==nil, the default is used.
-//  SetErrorFactory will remain in effect once it has been called.
+//
+//	If errFactory==nil, the default is used.
+//	SetErrorFactory will remain in effect once it has been called.
 func SetErrorFactory(bindErrFactory, validatingErrFactory func(failField, msg string) error) {
 	defaultBinder.SetErrorFactory(bindErrFactory, validatingErrFactory)
 }
 
 // MustRegTypeUnmarshal registers unmarshal function of type.
 // NOTE:
-//  It will panic if exist error.
-//  MustRegTypeUnmarshal will remain in effect once it has been called.
+//
+//	It will panic if exist error.
+//	MustRegTypeUnmarshal will remain in effect once it has been called.
 func MustRegTypeUnmarshal(t reflect.Type, fn func(v string, emptyAsZero bool) (reflect.Value, error)) {
 	binding.MustRegTypeUnmarshal(t, fn)
 }
 
 // MustRegValidateFunc registers validator function expression.
 // NOTE:
-//  If force=true, allow to cover the existed same funcName.
-//  MustRegValidateFunc will remain in effect once it has been called.
+//
+//	If force=true, allow to cover the existed same funcName.
+//	MustRegValidateFunc will remain in effect once it has been called.
 func MustRegValidateFunc(funcName string, fn func(args ...interface{}) error, force ...bool) {
 	validator.RegFunc(funcName, fn, force...)
 }
 
 // UseStdJSONUnmarshaler uses encoding/json as json library
 // NOTE:
-//  The current version uses encoding/json by default.
-//  UseStdJSONUnmarshaler will remain in effect once it has been called.
+//
+//	The current version uses encoding/json by default.
+//	UseStdJSONUnmarshaler will remain in effect once it has been called.
 func UseStdJSONUnmarshaler() {
 	binding.ResetJSONUnmarshaler(json.Unmarshal)
 }
 
 // UseGJSONUnmarshaler uses github.com/bytedance/go-tagexpr/v2/binding/gjson as json library
 // NOTE:
-//  UseGJSONUnmarshaler will remain in effect once it has been called.
+//
+//	UseGJSONUnmarshaler will remain in effect once it has been called.
 func UseGJSONUnmarshaler() {
 	gjson.UseJSONUnmarshaler()
 }
 
 // UseThirdPartyJSONUnmarshaler uses third-party json library for binding
 // NOTE:
-//  UseThirdPartyJSONUnmarshaler will remain in effect once it has been called.
+//
+//	UseThirdPartyJSONUnmarshaler will remain in effect once it has been called.
 func UseThirdPartyJSONUnmarshaler(unmarshaler func(data []byte, v interface{}) error) {
 	binding.ResetJSONUnmarshaler(unmarshaler)
 }
diff --git a/pkg/app/server/option.go b/pkg/app/server/option.go
index 7395775..bf44829 100644
--- a/pkg/app/server/option.go
+++ b/pkg/app/server/option.go
@@ -333,7 +333,7 @@ func WithDisablePrintRoute(b bool) config.Option {
 
 // WithOnAccept sets the callback function when a new connection is accepted but cannot
 // receive data in netpoll. In go net, it will be called before converting tls connection
-func WithOnAccept(fn func(conn network.Conn) context.Context) config.Option {
+func WithOnAccept(fn func(conn net.Conn) context.Context) config.Option {
 	return config.Option{F: func(o *config.Options) {
 		o.OnAccept = fn
 	}}
diff --git a/pkg/common/compress/compress.go b/pkg/common/compress/compress.go
index 2586129..bbdd07b 100644
--- a/pkg/common/compress/compress.go
+++ b/pkg/common/compress/compress.go
@@ -155,11 +155,11 @@ func AppendGzipBytes(dst, src []byte) []byte {
 //
 // Supported compression levels are:
 //
-//    * CompressNoCompression
-//    * CompressBestSpeed
-//    * CompressBestCompression
-//    * CompressDefaultCompression
-//    * CompressHuffmanOnly
+//   - CompressNoCompression
+//   - CompressBestSpeed
+//   - CompressBestCompression
+//   - CompressDefaultCompression
+//   - CompressHuffmanOnly
 func AppendGzipBytesLevel(dst, src []byte, level int) []byte {
 	w := &byteSliceWriter{dst}
 	WriteGzipLevel(w, src, level) //nolint:errcheck
@@ -219,11 +219,11 @@ func normalizeCompressLevel(level int) int {
 //
 // Supported compression levels are:
 //
-//    * CompressNoCompression
-//    * CompressBestSpeed
-//    * CompressBestCompression
-//    * CompressDefaultCompression
-//    * CompressHuffmanOnly
+//   - CompressNoCompression
+//   - CompressBestSpeed
+//   - CompressBestCompression
+//   - CompressDefaultCompression
+//   - CompressHuffmanOnly
 func WriteGzipLevel(w io.Writer, p []byte, level int) (int, error) {
 	switch w.(type) {
 	case *byteSliceWriter,
diff --git a/pkg/common/config/option.go b/pkg/common/config/option.go
index 3ba3d38..03c84a0 100644
--- a/pkg/common/config/option.go
+++ b/pkg/common/config/option.go
@@ -83,7 +83,7 @@ type Options struct {
 	// If you'd like to check whether the peer IP is in the blacklist, you can use OnAccept.
 	// In go net, OnAccept is executed after connection accepted but before establishing
 	// tls connection. OnConnect is executed after establishing tls connection.
-	OnAccept  func(conn network.Conn) context.Context
+	OnAccept  func(conn net.Conn) context.Context
 	OnConnect func(ctx context.Context, conn network.Conn) context.Context
 
 	// Registry is used for service registry.
diff --git a/pkg/common/errors/errors.go b/pkg/common/errors/errors.go
index 5d2d7a5..b8d23ff 100644
--- a/pkg/common/errors/errors.go
+++ b/pkg/common/errors/errors.go
@@ -155,10 +155,11 @@ func (msg *Error) JSON() interface{} {
 
 // Errors returns an array will all the error messages.
 // Example:
-// 		c.Error(errors.New("first"))
-// 		c.Error(errors.New("second"))
-// 		c.Error(errors.New("third"))
-// 		c.Errors.Errors() // == []string{"first", "second", "third"}
+//
+//	c.Error(errors.New("first"))
+//	c.Error(errors.New("second"))
+//	c.Error(errors.New("third"))
+//	c.Errors.Errors() // == []string{"first", "second", "third"}
 func (a ErrorChain) Errors() []string {
 	if len(a) == 0 {
 		return nil
diff --git a/pkg/common/stackless/func.go b/pkg/common/stackless/func.go
index 0c86e6a..c12bdea 100644
--- a/pkg/common/stackless/func.go
+++ b/pkg/common/stackless/func.go
@@ -53,9 +53,9 @@ import (
 // The wrapper may save a lot of stack space if the following conditions
 // are met:
 //
-//     - f doesn't contain blocking calls on network, I/O or channels;
-//     - f uses a lot of stack space;
-//     - the wrapper is called from high number of concurrent goroutines.
+//   - f doesn't contain blocking calls on network, I/O or channels;
+//   - f uses a lot of stack space;
+//   - the wrapper is called from high number of concurrent goroutines.
 //
 // The stackless wrapper returns false if the call cannot be processed
 // at the moment due to high load.
diff --git a/pkg/common/ut/request.go b/pkg/common/ut/request.go
index 1f50580..70ca7c3 100644
--- a/pkg/common/ut/request.go
+++ b/pkg/common/ut/request.go
@@ -20,7 +20,6 @@ package ut
 import (
 	"context"
 	"io"
-	"io/ioutil"
 
 	"github.com/cloudwego/hertz/pkg/protocol"
 	"github.com/cloudwego/hertz/pkg/route"
@@ -40,7 +39,7 @@ type Body struct {
 
 // PerformRequest send a constructed request to given engine without network transporting
 //
-// Url can be a standard relative URI or a simple absolute path
+// # Url can be a standard relative URI or a simple absolute path
 //
 // If engine.streamRequestBody is true, it sets body as bodyStream
 // if not, it sets body as bodyBytes
@@ -58,7 +57,7 @@ func PerformRequest(engine *route.Engine, method, url string, body *Body, header
 		if engine.IsStreamRequestBody() || body.Len == -1 {
 			ctx.Request.SetBodyStream(body.Body, body.Len)
 		} else {
-			buf, err := ioutil.ReadAll(&io.LimitedReader{R: body.Body, N: int64(body.Len)})
+			buf, err := io.ReadAll(&io.LimitedReader{R: body.Body, N: int64(body.Len)})
 			ctx.Request.SetBody(buf)
 			if err != nil && err != io.EOF {
 				panic(err)
diff --git a/pkg/common/utils/path.go b/pkg/common/utils/path.go
index fa2b176..a1ebbde 100644
--- a/pkg/common/utils/path.go
+++ b/pkg/common/utils/path.go
@@ -47,12 +47,12 @@ import "strings"
 //
 // The following rules are applied iteratively until no further processing can
 // be done:
-//	1. Replace multiple slashes with a single slash.
-//	2. Eliminate each . path name element (the current directory).
-//	3. Eliminate each inner .. path name element (the parent directory)
-//	   along with the non-.. element that precedes it.
-//	4. Eliminate .. elements that begin a rooted path:
-//	   that is, replace "/.." by "/" at the beginning of a path.
+//  1. Replace multiple slashes with a single slash.
+//  2. Eliminate each . path name element (the current directory).
+//  3. Eliminate each inner .. path name element (the parent directory)
+//     along with the non-.. element that precedes it.
+//  4. Eliminate .. elements that begin a rooted path:
+//     that is, replace "/.." by "/" at the beginning of a path.
 //
 // If the result of this process is an empty string, "/" is returned
 func CleanPath(p string) string {
diff --git a/pkg/network/netpoll/connection.go b/pkg/network/netpoll/connection.go
index 33d2875..683f9c3 100644
--- a/pkg/network/netpoll/connection.go
+++ b/pkg/network/netpoll/connection.go
@@ -49,6 +49,12 @@ func (c *Conn) Peek(n int) (b []byte, err error) {
 	return
 }
 
+func (c *Conn) Read(p []byte) (int, error) {
+	n, err := c.Conn.Read(p)
+	err = normalizeErr(err)
+	return n, err
+}
+
 func (c *Conn) Skip(n int) error {
 	return c.Conn.Skip(n)
 }
diff --git a/pkg/network/netpoll/transport.go b/pkg/network/netpoll/transport.go
index da28bf2..c213060 100644
--- a/pkg/network/netpoll/transport.go
+++ b/pkg/network/netpoll/transport.go
@@ -41,7 +41,7 @@ type transporter struct {
 	listener         net.Listener
 	eventLoop        netpoll.EventLoop
 	listenConfig     *net.ListenConfig
-	OnAccept         func(conn network.Conn) context.Context
+	OnAccept         func(conn net.Conn) context.Context
 	OnConnect        func(ctx context.Context, conn network.Conn) context.Context
 }
 
diff --git a/pkg/network/standard/transport.go b/pkg/network/standard/transport.go
index b6131b2..2605b2e 100644
--- a/pkg/network/standard/transport.go
+++ b/pkg/network/standard/transport.go
@@ -46,7 +46,7 @@ type transport struct {
 	tls              *tls.Config
 	listenConfig     *net.ListenConfig
 	lock             sync.Mutex
-	OnAccept         func(conn network.Conn) context.Context
+	OnAccept         func(conn net.Conn) context.Context
 	OnConnect        func(ctx context.Context, conn network.Conn) context.Context
 }
 
@@ -73,7 +73,7 @@ func (t *transport) serve() (err error) {
 		}
 
 		if t.OnAccept != nil {
-			ctx = t.OnAccept(c)
+			ctx = t.OnAccept(conn)
 		}
 
 		if t.tls != nil {
diff --git a/pkg/protocol/header.go b/pkg/protocol/header.go
index 56f553b..b447cce 100644
--- a/pkg/protocol/header.go
+++ b/pkg/protocol/header.go
@@ -232,7 +232,6 @@ func (h *ResponseHeader) CopyTo(dst *ResponseHeader) {
 	dst.cookies = copyArgs(dst.cookies, h.cookies)
 }
 
-//
 // Multiple headers with the same key may be added with this function.
 // Use Set for setting a single header for the given key.
 //
@@ -610,13 +609,13 @@ func (h *ResponseHeader) PeekLocation() []byte {
 // This doesn't work for a cookie with specific domain or path,
 // you should delete it manually like:
 //
-//      c := AcquireCookie()
-//      c.SetKey(key)
-//      c.SetDomain("example.com")
-//      c.SetPath("/path")
-//      c.SetExpire(CookieExpireDelete)
-//      h.SetCookie(c)
-//      ReleaseCookie(c)
+//	c := AcquireCookie()
+//	c.SetKey(key)
+//	c.SetDomain("example.com")
+//	c.SetPath("/path")
+//	c.SetExpire(CookieExpireDelete)
+//	h.SetCookie(c)
+//	ReleaseCookie(c)
 //
 // Use DelCookie if you want just removing the cookie from response header.
 func (h *ResponseHeader) DelClientCookie(key string) {
@@ -633,13 +632,13 @@ func (h *ResponseHeader) DelClientCookie(key string) {
 // This doesn't work for a cookie with specific domain or path,
 // you should delete it manually like:
 //
-//      c := AcquireCookie()
-//      c.SetKey(key)
-//      c.SetDomain("example.com")
-//      c.SetPath("/path")
-//      c.SetExpire(CookieExpireDelete)
-//      h.SetCookie(c)
-//      ReleaseCookie(c)
+//	c := AcquireCookie()
+//	c.SetKey(key)
+//	c.SetDomain("example.com")
+//	c.SetPath("/path")
+//	c.SetExpire(CookieExpireDelete)
+//	h.SetCookie(c)
+//	ReleaseCookie(c)
 //
 // Use DelCookieBytes if you want just removing the cookie from response header.
 func (h *ResponseHeader) DelClientCookieBytes(key []byte) {
@@ -907,8 +906,8 @@ func (h *RequestHeader) Reset() {
 
 // SetByteRange sets 'Range: bytes=startPos-endPos' header.
 //
-//     * If startPos is negative, then 'bytes=-startPos' value is set.
-//     * If endPos is negative, then 'bytes=startPos-' value is set.
+//   - If startPos is negative, then 'bytes=-startPos' value is set.
+//   - If endPos is negative, then 'bytes=startPos-' value is set.
 func (h *RequestHeader) SetByteRange(startPos, endPos int) {
 	b := h.bufKV.value[:0]
 	b = append(b, bytestr.StrBytes...)
@@ -1166,6 +1165,20 @@ func (h *RequestHeader) Cookie(key string) []byte {
 	return peekArgStr(h.cookies, key)
 }
 
+// Cookies returns all the request cookies.
+//
+// It's a good idea to call protocol.ReleaseCookie to reduce GC load after the cookie used.
+func (h *RequestHeader) Cookies() []*Cookie {
+	var cookies []*Cookie
+	h.VisitAllCookie(func(key, value []byte) {
+		cookie := AcquireCookie()
+		cookie.SetKeyBytes(key)
+		cookie.SetValueBytes(value)
+		cookies = append(cookies, cookie)
+	})
+	return cookies
+}
+
 func (h *RequestHeader) PeekRange() []byte {
 	return h.peek(bytestr.StrRange)
 }
@@ -1336,9 +1349,9 @@ func (h *RequestHeader) UserAgent() []byte {
 // while lowercasing all the other letters.
 // Examples:
 //
-//     * CONNECTION -> Connection
-//     * conteNT-tYPE -> Content-Type
-//     * foo-bar-baz -> Foo-Bar-Baz
+//   - CONNECTION -> Connection
+//   - conteNT-tYPE -> Content-Type
+//   - foo-bar-baz -> Foo-Bar-Baz
 //
 // Disable header names' normalization only if you know what are you doing.
 func (h *RequestHeader) DisableNormalizing() {
@@ -1509,9 +1522,9 @@ func (h *RequestHeader) SetMethodBytes(method []byte) {
 // while lowercasing all the other letters.
 // Examples:
 //
-//     * CONNECTION -> Connection
-//     * conteNT-tYPE -> Content-Type
-//     * foo-bar-baz -> Foo-Bar-Baz
+//   - CONNECTION -> Connection
+//   - conteNT-tYPE -> Content-Type
+//   - foo-bar-baz -> Foo-Bar-Baz
 //
 // Disable header names' normalization only if you know what are you doing.
 func (h *ResponseHeader) DisableNormalizing() {
diff --git a/pkg/protocol/http1/ext/stream.go b/pkg/protocol/http1/ext/stream.go
index 268a137..d72e995 100644
--- a/pkg/protocol/http1/ext/stream.go
+++ b/pkg/protocol/http1/ext/stream.go
@@ -211,7 +211,7 @@ func (rs *bodyStream) Read(p []byte) (int, error) {
 	if err != nil {
 		// the data on stream may be incomplete
 		if err == io.EOF {
-			if rs.offset != rs.contentLength {
+			if rs.offset != rs.contentLength && rs.contentLength != -2 {
 				err = io.ErrUnexpectedEOF
 			}
 			// ensure that skipRest works fine
diff --git a/pkg/protocol/http1/req/request.go b/pkg/protocol/http1/req/request.go
index 895aeaa..13b45ad 100644
--- a/pkg/protocol/http1/req/request.go
+++ b/pkg/protocol/http1/req/request.go
@@ -102,11 +102,11 @@ func GetHTTP1Request(req *protocol.Request) fmt.Stringer {
 //
 // If MayContinue returns true, the caller must:
 //
-//     - Either send StatusExpectationFailed response if request headers don't
-//       satisfy the caller.
-//     - Or send StatusContinue response before reading request body
-//       with ContinueReadBody.
-//     - Or close the connection.
+//   - Either send StatusExpectationFailed response if request headers don't
+//     satisfy the caller.
+//   - Or send StatusContinue response before reading request body
+//     with ContinueReadBody.
+//   - Or close the connection.
 //
 // io.EOF is returned if r is closed before reading the first header byte.
 func ReadHeaderAndLimitBody(req *protocol.Request, r network.Reader, maxBodySize int, preParse ...bool) error {
@@ -133,11 +133,11 @@ func ReadHeaderAndLimitBody(req *protocol.Request, r network.Reader, maxBodySize
 //
 // If MayContinue returns true, the caller must:
 //
-//     - Either send StatusExpectationFailed response if request headers don't
-//       satisfy the caller.
-//     - Or send StatusContinue response before reading request body
-//       with ContinueReadBody.
-//     - Or close the connection.
+//   - Either send StatusExpectationFailed response if request headers don't
+//     satisfy the caller.
+//   - Or send StatusContinue response before reading request body
+//     with ContinueReadBody.
+//   - Or close the connection.
 //
 // io.EOF is returned if r is closed before reading the first header byte.
 func Read(req *protocol.Request, r network.Reader, preParse ...bool) error {
diff --git a/pkg/protocol/request.go b/pkg/protocol/request.go
index 73b3d87..52f28d9 100644
--- a/pkg/protocol/request.go
+++ b/pkg/protocol/request.go
@@ -158,11 +158,11 @@ func (req *Request) BodyBuffer() *bytebufferpool.ByteBuffer {
 //
 // The caller must do one of the following actions if MayContinue returns true:
 //
-//     - Either send StatusExpectationFailed response if request headers don't
-//       satisfy the caller.
-//     - Or send StatusContinue response before reading request body
-//       with ContinueReadBody.
-//     - Or close the connection.
+//   - Either send StatusExpectationFailed response if request headers don't
+//     satisfy the caller.
+//   - Or send StatusContinue response before reading request body
+//     with ContinueReadBody.
+//   - Or close the connection.
 func (req *Request) MayContinue() bool {
 	return bytes.Equal(req.Header.peek(bytestr.StrExpect), bytestr.Str100Continue)
 }
@@ -563,13 +563,15 @@ func parseBasicAuth(auth []byte) (username, password string, ok bool) {
 }
 
 // SetAuthToken sets the auth token header(Default Scheme: Bearer) in the current HTTP request. Header example:
-// 		Authorization: Bearer <auth-token-value-comes-here>
+//
+//	Authorization: Bearer <auth-token-value-comes-here>
 func (req *Request) SetAuthToken(token string) {
 	req.SetHeader(consts.HeaderAuthorization, "Bearer "+token)
 }
 
 // SetAuthSchemeToken sets the auth token scheme type in the HTTP request. For Example:
-//      Authorization: <auth-scheme-value-set-here> <auth-token-value>
+//
+//	Authorization: <auth-scheme-value-set-here> <auth-token-value>
 func (req *Request) SetAuthSchemeToken(scheme, token string) {
 	req.SetHeader(consts.HeaderAuthorization, scheme+" "+token)
 }
@@ -827,7 +829,7 @@ func ReleaseRequest(req *Request) {
 // NewRequest makes a new Request given a method, URL, and
 // optional body.
 //
-// Method's default value is GET
+// # Method's default value is GET
 //
 // Url must contain fully qualified uri, i.e. with scheme and host,
 // and http is assumed if scheme is omitted.
diff --git a/pkg/route/engine.go b/pkg/route/engine.go
index 59a321e..8a9a00b 100644
--- a/pkg/route/engine.go
+++ b/pkg/route/engine.go
@@ -278,12 +278,12 @@ func (engine *Engine) NewContext() *app.RequestContext {
 
 // Shutdown starts the server's graceful exit by next steps:
 //
-// 1. Trigger OnShutdown hooks concurrently and wait them until wait timeout or finish
-// 2. Close the net listener, which means new connection won't be accepted
-// 3. Wait all connections get closed:
-// 	One connection gets closed after reaching out the shorter time of processing
-//	one request (in hand or next incoming), idleTimeout or ExitWaitTime
-// 4. Exit
+//  1. Trigger OnShutdown hooks concurrently and wait them until wait timeout or finish
+//  2. Close the net listener, which means new connection won't be accepted
+//  3. Wait all connections get closed:
+//     One connection gets closed after reaching out the shorter time of processing
+//     one request (in hand or next incoming), idleTimeout or ExitWaitTime
+//  4. Exit
 func (engine *Engine) Shutdown(ctx context.Context) (err error) {
 	if atomic.LoadUint32(&engine.status) != statusRunning {
 		return errStatusNotRunning
diff --git a/pkg/route/routergroup.go b/pkg/route/routergroup.go
index e69c1ca..9abe367 100644
--- a/pkg/route/routergroup.go
+++ b/pkg/route/routergroup.go
@@ -200,7 +200,8 @@ func (group *RouterGroup) StaticFile(relativePath, filepath string) IRoutes {
 // Static serves files from the given file system root.
 // To use the operating system's file system implementation,
 // use :
-//     router.Static("/static", "/var/www")
+//
+//	router.Static("/static", "/var/www")
 func (group *RouterGroup) Static(relativePath, root string) IRoutes {
 	return group.StaticFS(relativePath, &app.FS{Root: root})
 }
diff --git a/version.go b/version.go
index 2f877ce..80702a5 100644
--- a/version.go
+++ b/version.go
@@ -19,5 +19,5 @@ package hertz
 // Name and Version info of this framework, used for statistics and debug
 const (
 	Name    = "Hertz"
-	Version = "v0.4.2"
+	Version = "v0.5.0"
 )


Test patch:
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index ba2af0a..4fae434 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -4,6 +4,9 @@ on: [ push, pull_request ]
 
 jobs:
   lint-and-ut:
+    strategy:
+      matrix:
+        version: [ 1.18, 1.19 ]
     runs-on: [ self-hosted, X64 ]
     steps:
       - uses: actions/checkout@v3
@@ -11,7 +14,7 @@ jobs:
       - name: Set up Go
         uses: actions/setup-go@v3
         with:
-          go-version: 1.18
+          go-version: ${{ matrix.version }}
 
       - uses: actions/cache@v3
         with:
diff --git a/cmd/hz/protobuf/plugin_test.go b/cmd/hz/protobuf/plugin_test.go
index b34d8b8..7d64709 100644
--- a/cmd/hz/protobuf/plugin_test.go
+++ b/cmd/hz/protobuf/plugin_test.go
@@ -17,7 +17,7 @@
 package protobuf
 
 import (
-	"io/ioutil"
+	"os"
 	"strings"
 	"testing"
 
@@ -27,7 +27,7 @@ import (
 )
 
 func TestPlugin_Handle(t *testing.T) {
-	in, err := ioutil.ReadFile("../testdata/request_protoc.out")
+	in, err := os.ReadFile("../testdata/request_protoc.out")
 	if err != nil {
 		t.Fatal(err)
 	}
diff --git a/cmd/hz/protobuf/tag_test.go b/cmd/hz/protobuf/tag_test.go
index 7e6528b..52fd146 100644
--- a/cmd/hz/protobuf/tag_test.go
+++ b/cmd/hz/protobuf/tag_test.go
@@ -17,7 +17,7 @@
 package protobuf
 
 import (
-	"io/ioutil"
+	"os"
 	"strings"
 	"testing"
 
@@ -104,7 +104,7 @@ func TestTagGenerate(t *testing.T) {
 		},
 	}
 
-	in, err := ioutil.ReadFile("./test_data/protobuf_tag_test.out")
+	in, err := os.ReadFile("./test_data/protobuf_tag_test.out")
 	if err != nil {
 		t.Fatal(err)
 	}
diff --git a/cmd/hz/thrift/plugin_test.go b/cmd/hz/thrift/plugin_test.go
index 9d40bcd..a4535f6 100644
--- a/cmd/hz/thrift/plugin_test.go
+++ b/cmd/hz/thrift/plugin_test.go
@@ -17,7 +17,7 @@
 package thrift
 
 import (
-	"io/ioutil"
+	"os"
 	"testing"
 
 	"github.com/cloudwego/hertz/cmd/hz/generator"
@@ -27,7 +27,7 @@ import (
 )
 
 func TestRun(t *testing.T) {
-	data, err := ioutil.ReadFile("../testdata/request_thrift.out")
+	data, err := os.ReadFile("../testdata/request_thrift.out")
 	if err != nil {
 		t.Fatal(err)
 	}
diff --git a/cmd/hz/thrift/tag_test.go b/cmd/hz/thrift/tag_test.go
index 108dea8..143fd8b 100644
--- a/cmd/hz/thrift/tag_test.go
+++ b/cmd/hz/thrift/tag_test.go
@@ -17,7 +17,7 @@
 package thrift
 
 import (
-	"io/ioutil"
+	"os"
 	"strings"
 	"testing"
 
@@ -26,7 +26,7 @@ import (
 )
 
 func TestInsertTag(t *testing.T) {
-	data, err := ioutil.ReadFile("./test_data/thrift_tag_test.out")
+	data, err := os.ReadFile("./test_data/thrift_tag_test.out")
 	if err != nil {
 		t.Fatal(err)
 	}
diff --git a/pkg/app/client/client_test.go b/pkg/app/client/client_test.go
index b769ec8..2798f12 100644
--- a/pkg/app/client/client_test.go
+++ b/pkg/app/client/client_test.go
@@ -48,7 +48,6 @@ import (
 	"errors"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"math/rand"
 	"net"
 	"net/http"
@@ -1504,7 +1503,7 @@ func TestClientReadResponseBodyStream(t *testing.T) {
 	if string(p) != part1 {
 		t.Errorf("read len=%v, read content=%v; want len=%v, want content=%v", r, string(p), len(part1), part1)
 	}
-	left, _ := ioutil.ReadAll(bodyStream)
+	left, _ := io.ReadAll(bodyStream)
 	if string(left) != part2 {
 		t.Errorf("left len=%v, left content=%v; want len=%v, want content=%v", len(left), string(left), len(part2), part2)
 	}
@@ -1871,13 +1870,13 @@ func TestClientReadResponseBodyStreamWithDoubleRequest(t *testing.T) {
 	if bodyStream1 == nil {
 		t.Errorf("bodystream1 is nil")
 	}
-	data, _ := ioutil.ReadAll(bodyStream1)
+	data, _ := io.ReadAll(bodyStream1)
 	if string(data) != part1+part2 {
 		t.Errorf("read len=%v, read content=%v; want len=%v, want content=%v", len(data), data, len(part1+part2), part1+part2)
 	}
 
 	// read left bodystream
-	left, _ := ioutil.ReadAll(bodyStream)
+	left, _ := io.ReadAll(bodyStream)
 	if string(left) != part2 {
 		t.Errorf("left len=%v, left content=%v; want len=%v, want content=%v", len(left), string(left), len(part2), part2)
 	}
diff --git a/pkg/app/context_test.go b/pkg/app/context_test.go
index 4e62a94..fc3c147 100644
--- a/pkg/app/context_test.go
+++ b/pkg/app/context_test.go
@@ -20,7 +20,7 @@ import (
 	"context"
 	"errors"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"reflect"
 	"strings"
 	"testing"
@@ -401,7 +401,7 @@ tailfoobar`
 	if err := req.Read(&ctx.Request, mr); err != nil {
 		t.Fatalf("unexpected error: %s", err)
 	}
-	tail, err := ioutil.ReadAll(mr)
+	tail, err := io.ReadAll(mr)
 	if err != nil {
 		t.Fatalf("unexpected error: %s", err)
 	}
diff --git a/pkg/app/fs_test.go b/pkg/app/fs_test.go
index 389fe9e..36cc0d4 100644
--- a/pkg/app/fs_test.go
+++ b/pkg/app/fs_test.go
@@ -46,7 +46,6 @@ import (
 	"context"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"math/rand"
 	"os"
 	"path"
@@ -185,13 +184,13 @@ func TestServeFileSmallNoReadFrom(t *testing.T) {
 
 	teststr := "hello, world!"
 
-	tempdir, err := ioutil.TempDir("", "httpexpect")
+	tempdir, err := os.MkdirTemp("", "httpexpect")
 	if err != nil {
 		t.Fatal(err)
 	}
 	defer os.RemoveAll(tempdir)
 
-	if err := ioutil.WriteFile(
+	if err := os.WriteFile(
 		path.Join(tempdir, "hello"), []byte(teststr), 0o666); err != nil {
 		t.Fatal(err)
 	}
@@ -399,7 +398,7 @@ func getFileContents(path string) ([]byte, error) {
 		return nil, err
 	}
 	defer f.Close()
-	return ioutil.ReadAll(f)
+	return io.ReadAll(f)
 }
 
 func TestParseByteRangeSuccess(t *testing.T) {
diff --git a/pkg/app/server/hertz_test.go b/pkg/app/server/hertz_test.go
index 0af65ac..de6a593 100644
--- a/pkg/app/server/hertz_test.go
+++ b/pkg/app/server/hertz_test.go
@@ -23,7 +23,6 @@ import (
 	"fmt"
 	"html/template"
 	"io"
-	"io/ioutil"
 	"net"
 	"net/http"
 	"os"
@@ -397,7 +396,7 @@ func TestNotEnoughBodySize(t *testing.T) {
 	resp, err := http.Post("http://127.0.0.1:8889/test", "application/x-www-form-urlencoded", body)
 	assert.Nil(t, err)
 	assert.DeepEqual(t, 413, resp.StatusCode)
-	bodyBytes, _ := ioutil.ReadAll(resp.Body)
+	bodyBytes, _ := io.ReadAll(resp.Body)
 	assert.DeepEqual(t, "Request Entity Too Large", string(bodyBytes))
 }
 
@@ -804,7 +803,7 @@ func TestOnprepare(t *testing.T) {
 	assert.DeepEqual(t, "the server closed connection before returning the first response byte. Make sure the server returns 'Connection: close' response header before closing the connection", err.Error())
 
 	h = New(
-		WithOnAccept(func(conn network.Conn) context.Context {
+		WithOnAccept(func(conn net.Conn) context.Context {
 			conn.Close()
 			return context.Background()
 		}),
@@ -818,4 +817,18 @@ func TestOnprepare(t *testing.T) {
 	if err == nil {
 		t.Fatalf("err should not be nil")
 	}
+
+	h = New(
+		WithOnAccept(func(conn net.Conn) context.Context {
+			assert.DeepEqual(t, conn.LocalAddr().String(), "127.0.0.1:9231")
+			return context.Background()
+		}),
+		WithHostPorts("localhost:9231"),
+		WithTransport(standard.NewTransporter))
+	h.GET("/ping", func(ctx context.Context, c *app.RequestContext) {
+		c.JSON(consts.StatusOK, utils.H{"ping": "pong"})
+	})
+	go h.Spin()
+	time.Sleep(time.Second)
+	c.Get(context.Background(), nil, "http://127.0.0.1:9231/ping")
 }
diff --git a/pkg/common/adaptor/request_test.go b/pkg/common/adaptor/request_test.go
index ba3ec6a..4aff35d 100644
--- a/pkg/common/adaptor/request_test.go
+++ b/pkg/common/adaptor/request_test.go
@@ -18,7 +18,7 @@ package adaptor
 
 import (
 	"context"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"net/url"
 	"strings"
@@ -87,7 +87,7 @@ func makeACall(t *testing.T, method, url string, header http.Header, body string
 		}
 	}
 
-	b, err := ioutil.ReadAll(resp.Body)
+	b, err := io.ReadAll(resp.Body)
 	if err != nil {
 		t.Fatalf("Read body error: %s", err)
 	}
@@ -116,7 +116,7 @@ func handlerAndCheck(t *testing.T, writer http.ResponseWriter, request *http.Req
 		}
 	}
 
-	body, err := ioutil.ReadAll(request.Body)
+	body, err := io.ReadAll(request.Body)
 	if err != nil {
 		t.Fatalf("Read body error: %s", err)
 	}
diff --git a/pkg/common/stackless/writer_test.go b/pkg/common/stackless/writer_test.go
index cae68bf..73347cb 100644
--- a/pkg/common/stackless/writer_test.go
+++ b/pkg/common/stackless/writer_test.go
@@ -47,7 +47,6 @@ import (
 	"compress/gzip"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"testing"
 	"time"
 )
@@ -129,7 +128,7 @@ func testWriterReuse(w Writer, r io.Reader, newReader func(io.Reader) io.Reader)
 	w.Close()
 
 	zr := newReader(r)
-	data, err := ioutil.ReadAll(zr)
+	data, err := io.ReadAll(zr)
 	if err != nil {
 		return fmt.Errorf("unexpected error: %s, data=%q", err, data)
 	}
diff --git a/pkg/common/test/assert/assert.go b/pkg/common/test/assert/assert.go
index e7446c4..44ed32e 100644
--- a/pkg/common/test/assert/assert.go
+++ b/pkg/common/test/assert/assert.go
@@ -19,7 +19,6 @@ package assert
 import (
 	"bytes"
 	"io"
-	"io/ioutil"
 	"reflect"
 	"testing"
 )
@@ -117,7 +116,7 @@ func NotPanic(t testing.TB, fn func()) {
 }
 
 func VerifyTrailer(t *testing.T, r io.Reader, expectedTrailer string) {
-	trailer, err := ioutil.ReadAll(r)
+	trailer, err := io.ReadAll(r)
 	if err != nil {
 		t.Fatalf("Cannot read trailer: %s", err)
 	}
diff --git a/pkg/protocol/header_test.go b/pkg/protocol/header_test.go
index 14446d5..0a7baf2 100644
--- a/pkg/protocol/header_test.go
+++ b/pkg/protocol/header_test.go
@@ -189,6 +189,18 @@ func TestRequestHeaderVisitAll(t *testing.T) {
 		})
 }
 
+func TestRequestHeaderCookies(t *testing.T) {
+	var h RequestHeader
+	h.SetCookie("foo", "bar")
+	h.SetCookie("привет", "мир")
+	cookies := h.Cookies()
+	assert.DeepEqual(t, 2, len(cookies))
+	assert.DeepEqual(t, []byte("foo"), cookies[0].Key())
+	assert.DeepEqual(t, []byte("bar"), cookies[0].Value())
+	assert.DeepEqual(t, []byte("привет"), cookies[1].Key())
+	assert.DeepEqual(t, []byte("мир"), cookies[1].Value())
+}
+
 func TestRequestHeaderDel(t *testing.T) {
 	t.Parallel()
 
diff --git a/pkg/protocol/http1/client_test.go b/pkg/protocol/http1/client_test.go
index f61e491..d8d915c 100644
--- a/pkg/protocol/http1/client_test.go
+++ b/pkg/protocol/http1/client_test.go
@@ -47,7 +47,7 @@ import (
 	"crypto/tls"
 	"errors"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"net"
 	"strings"
 	"sync"
@@ -187,7 +187,7 @@ func testContinueReadResponseBodyStream(t *testing.T, header, body string, maxBo
 		t.Fatalf("should read %d from stream body, but got %d", firstRead, sR)
 	}
 
-	leftB, _ := ioutil.ReadAll(r.BodyStream())
+	leftB, _ := io.ReadAll(r.BodyStream())
 	if len(leftB) != leftBytes {
 		t.Fatalf("should left %d bytes from stream body, but left %d", leftBytes, len(leftB))
 	}
diff --git a/pkg/protocol/http1/req/request_test.go b/pkg/protocol/http1/req/request_test.go
index 8991ab0..01e3f17 100644
--- a/pkg/protocol/http1/req/request_test.go
+++ b/pkg/protocol/http1/req/request_test.go
@@ -47,7 +47,6 @@ import (
 	"errors"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"mime/multipart"
 	"net/url"
 	"strings"
@@ -657,7 +656,7 @@ func TestRequestReadPostNoBody(t *testing.T) {
 		t.Fatalf("unexpected content-length: %d. Expecting 0", r.Header.ContentLength())
 	}
 
-	tail, err := ioutil.ReadAll(zr)
+	tail, err := io.ReadAll(zr)
 	if err != nil {
 		t.Fatalf("unexpected error: %s", err)
 	}
@@ -1049,7 +1048,7 @@ tailfoobar`
 		t.Fatalf("unexpected error: %s", err)
 	}
 
-	tail, err := ioutil.ReadAll(mr)
+	tail, err := io.ReadAll(mr)
 	if err != nil {
 		t.Fatalf("unexpected error: %s", err)
 	}
@@ -1130,7 +1129,7 @@ func testReadIncompleteStream(t *testing.T, header, body string) {
 	if err := ContinueReadBodyStream(&r, mr, 1, true); err != nil {
 		t.Fatalf("error when reading request body stream: %s", err)
 	}
-	readBody, err := ioutil.ReadAll(r.BodyStream())
+	readBody, err := io.ReadAll(r.BodyStream())
 	if !bytes.Equal(readBody, []byte(body)) || len(readBody) != len(body) {
 		t.Fatalf("readBody is not equal to the rawBody: %b(len: %d)", readBody, len(readBody))
 	}
@@ -1160,7 +1159,7 @@ func testReadChunked(t *testing.T, header, body string, firstRead, leftBytes int
 	if fr != firstRead {
 		t.Fatalf("should read %d from stream body, but got %d", streamRead, fr)
 	}
-	leftB, _ := ioutil.ReadAll(r.BodyStream())
+	leftB, _ := io.ReadAll(r.BodyStream())
 	if len(leftB) != leftBytes {
 		t.Fatalf("should left %d bytes from stream body, but left %d", leftBytes, len(leftB))
 	}
@@ -1183,7 +1182,7 @@ func testContinueReadBodyStream(t *testing.T, header, body string, maxBodySize,
 		t.Fatalf("should read %d from stream body, but got %d", firstRead, sR)
 	}
 
-	leftB, _ := ioutil.ReadAll(r.BodyStream())
+	leftB, _ := io.ReadAll(r.BodyStream())
 	if len(leftB) != leftBytes {
 		t.Fatalf("should left %d bytes from stream body, but left %d", leftBytes, len(leftB))
 	}
@@ -1255,7 +1254,7 @@ tailfoobar`
 		t.Fatalf("unexpected error: %s", err)
 	}
 
-	tail, err := ioutil.ReadAll(mr)
+	tail, err := io.ReadAll(mr)
 	if err != nil {
 		t.Fatalf("unexpected error: %s", err)
 	}
diff --git a/pkg/protocol/http1/resp/response_test.go b/pkg/protocol/http1/resp/response_test.go
index 1b4eefb..cf45f65 100644
--- a/pkg/protocol/http1/resp/response_test.go
+++ b/pkg/protocol/http1/resp/response_test.go
@@ -521,3 +521,67 @@ func testResponseReadLimitBodySuccess(t *testing.T, s string, maxBodySize int) {
 		t.Fatalf("unexpected error: %s. s=%q, maxBodySize=%d", err, s, maxBodySize)
 	}
 }
+
+func testResponseReadBodyStreamSuccess(t *testing.T, resp *protocol.Response, response string, expectedStatusCode, expectedContentLength int,
+	expectedContentType, expectedBody, expectedTrailer string,
+) {
+	zr := mock.NewZeroCopyReader(response)
+	err := ReadBodyStream(resp, zr, 0, nil)
+	if err != nil {
+		t.Fatalf("Unexpected error: %s", err)
+	}
+	assert.True(t, resp.IsBodyStream())
+
+	body, err := io.ReadAll(resp.BodyStream())
+	if err != nil && err != io.EOF {
+		t.Fatalf("Unexpected error: %s", err)
+	}
+	verifyResponseHeader(t, &resp.Header, expectedStatusCode, expectedContentLength, expectedContentType, "")
+	if !bytes.Equal(body, []byte(expectedBody)) {
+		t.Fatalf("Unexpected body %q. Expected %q", resp.Body(), []byte(expectedBody))
+	}
+	assert.VerifyTrailer(t, zr, expectedTrailer)
+}
+
+func TestResponseReadBodyStream(t *testing.T) {
+	t.Parallel()
+
+	resp := &protocol.Response{}
+
+	// usual response
+	testResponseReadBodyStreamSuccess(t, resp, "HTTP/1.1 200 OK\r\nContent-Length: 10\r\nContent-Type: foo/bar\r\n\r\n0123456789",
+		consts.StatusOK, 10, "foo/bar", "0123456789", "")
+
+	// zero response
+	testResponseReadBodyStreamSuccess(t, resp, "HTTP/1.1 500 OK\r\nContent-Length: 0\r\nContent-Type: foo/bar\r\n\r\n",
+		consts.StatusInternalServerError, 0, "foo/bar", "", "")
+
+	// response with trailer
+	testResponseReadBodyStreamSuccess(t, resp, "HTTP/1.1 300 OK\r\nContent-Length: 5\r\nContent-Type: bar\r\n\r\n56789aaa",
+		consts.StatusMultipleChoices, 5, "bar", "56789", "aaa")
+
+	// no content-length ('identity' transfer-encoding)
+	testResponseReadBodyStreamSuccess(t, resp, "HTTP/1.1 200 OK\r\nContent-Type: foobar\r\n\r\nzxxc",
+		consts.StatusOK, -2, "foobar", "zxxc", "")
+
+	// explicitly stated 'Transfer-Encoding: identity'
+	testResponseReadBodyStreamSuccess(t, resp, "HTTP/1.1 234 ss\r\nContent-Type: xxx\r\n\r\nxag",
+		234, -2, "xxx", "xag", "")
+
+	// big 'identity' response
+	body := string(mock.CreateFixedBody(100500))
+	testResponseReadBodyStreamSuccess(t, resp, "HTTP/1.1 200 OK\r\nContent-Type: aa\r\n\r\n"+body,
+		consts.StatusOK, -2, "aa", body, "")
+
+	// chunked response
+	testResponseReadBodyStreamSuccess(t, resp, "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nTransfer-Encoding: chunked\r\n\r\n4\r\nqwer\r\n2\r\nty\r\n0\r\n\r\nzzzzz",
+		consts.StatusOK, -1, "text/html", "qwerty", "zzzzz")
+
+	// chunked response with non-chunked Transfer-Encoding.
+	testResponseReadBodyStreamSuccess(t, resp, "HTTP/1.1 230 OK\r\nContent-Type: text\r\nTransfer-Encoding: aaabbb\r\n\r\n2\r\ner\r\n2\r\nty\r\n0\r\n\r\nwe",
+		230, -1, "text", "erty", "we")
+
+	// zero chunked response
+	testResponseReadBodyStreamSuccess(t, resp, "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nTransfer-Encoding: chunked\r\n\r\n0\r\n\r\nzzz",
+		consts.StatusOK, -1, "text/html", "", "zzz")
+}
diff --git a/pkg/protocol/request_test.go b/pkg/protocol/request_test.go
index 745b51d..71fa8fb 100644
--- a/pkg/protocol/request_test.go
+++ b/pkg/protocol/request_test.go
@@ -46,7 +46,6 @@ import (
 	"encoding/base64"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"math"
 	"mime/multipart"
 	"strings"
@@ -148,7 +147,7 @@ tailfoobar`
 	defer r.RemoveMultipartFormFiles()
 
 	// verify tail
-	tail, err := ioutil.ReadAll(mr)
+	tail, err := io.ReadAll(mr)
 	if err != nil {
 		t.Fatalf("unexpected error: %s", err)
 	}
@@ -230,7 +229,7 @@ tailfoobar`
 	defer r.RemoveMultipartFormFiles()
 
 	// all data must be consumed if the content length is unknown
-	tail, err := ioutil.ReadAll(mr)
+	tail, err := io.ReadAll(mr)
 	if err != nil {
 		t.Fatalf("unexpected error: %s", err)
 	}
diff --git a/pkg/route/engine_test.go b/pkg/route/engine_test.go
index e8a3dd5..f9f1528 100644
--- a/pkg/route/engine_test.go
+++ b/pkg/route/engine_test.go
@@ -46,7 +46,7 @@ import (
 	"errors"
 	"fmt"
 	"html/template"
-	"io/ioutil"
+	"io"
 	"net"
 	"net/http"
 	"sync/atomic"
@@ -412,7 +412,7 @@ func TestRenderHtml(t *testing.T) {
 		})
 	})
 	rr := performRequest(e, "GET", "/templateName")
-	b, _ := ioutil.ReadAll(rr.Body)
+	b, _ := io.ReadAll(rr.Body)
 	assert.DeepEqual(t, consts.StatusOK, rr.Code)
 	assert.DeepEqual(t, []byte("<h1>Date: 2017/07/01</h1>"), b)
 	assert.DeepEqual(t, "text/html; charset=utf-8", rr.Header().Get("Content-Type"))
@@ -460,7 +460,7 @@ func TestRenderHtmlOfGlobWithAutoRender(t *testing.T) {
 		})
 	})
 	rr := performRequest(e, "GET", "/templateName")
-	b, _ := ioutil.ReadAll(rr.Body)
+	b, _ := io.ReadAll(rr.Body)
 	assert.DeepEqual(t, consts.StatusOK, rr.Code)
 	assert.DeepEqual(t, []byte("<h1>Date: 2017/07/01</h1>"), b)
 	assert.DeepEqual(t, "text/html; charset=utf-8", rr.Header().Get("Content-Type"))
@@ -498,7 +498,7 @@ func TestRenderHtmlOfFilesWithAutoRender(t *testing.T) {
 		})
 	})
 	rr := performRequest(e, "GET", "/templateName")
-	b, _ := ioutil.ReadAll(rr.Body)
+	b, _ := io.ReadAll(rr.Body)
 	assert.DeepEqual(t, consts.StatusOK, rr.Code)
 	assert.DeepEqual(t, []byte("<h1>Date: 2017/07/01</h1>"), b)
 	assert.DeepEqual(t, "text/html; charset=utf-8", rr.Header().Get("Content-Type"))
diff --git a/pkg/route/routergroup_test.go b/pkg/route/routergroup_test.go
index 3a6a1c0..5f72d21 100644
--- a/pkg/route/routergroup_test.go
+++ b/pkg/route/routergroup_test.go
@@ -42,7 +42,7 @@ package route
 
 import (
 	"context"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"os"
 	"testing"
@@ -137,7 +137,7 @@ func TestRouterGroupStatic(t *testing.T) {
 	}
 	assert.DeepEqual(t, http.StatusOK, w.Code)
 	defer fd.Close()
-	content, err := ioutil.ReadAll(fd)
+	content, err := io.ReadAll(fd)
 	if err != nil {
 		panic(err)
 	}
@@ -154,7 +154,7 @@ func TestRouterGroupStaticFile(t *testing.T) {
 		panic(err)
 	}
 	defer fd.Close()
-	content, err := ioutil.ReadAll(fd)
+	content, err := io.ReadAll(fd)
 	if err != nil {
 		panic(err)
 	}
diff --git a/pkg/route/routes_test.go b/pkg/route/routes_test.go
index 1e76d67..c94baa0 100644
--- a/pkg/route/routes_test.go
+++ b/pkg/route/routes_test.go
@@ -43,7 +43,6 @@ package route
 import (
 	"context"
 	"fmt"
-	"io/ioutil"
 	"net/http"
 	"net/http/httptest"
 	"os"
@@ -398,7 +397,7 @@ func TestRouteParamsByNameWithExtraSlash(t *testing.T) {
 func TestRouteStaticFile(t *testing.T) {
 	// SETUP file
 	testRoot, _ := os.Getwd()
-	f, err := ioutil.TempFile(testRoot, "")
+	f, err := os.CreateTemp(testRoot, "")
 	if err != nil {
 		t.Error(err)
 	}