Repository: cloudwego/hertz
Pull request #889 (2023-08-04 09:40:07)

feat: add a public method to update engine status
#### 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.
- [x] Attach the PR updating the user documentation if the current PR requires user awareness at the usage level. [User docs repo](https://github.com/cloudwego/cloudwego.github.io).

#### (Optional) Translate the PR title into Chinese.
设置 hertz engine 为 running 状态

#### (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: set hertz engine to be 'running'
zh(optional): 设置 hertz engine 为 running 状态
https://bytedance.feishu.cn/docx/PgWvdDR7VojwI7xjySPcshXDnqc
related pr: https://github.com/cloudwego/cwgo/pull/79

#### (Optional) Which issue(s) this PR fixes:
<!--
Automatically closes linked issue when PR is merged.
Eg: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
-->

#### (Optional) The PR that updates user documentation:
<!--
If the current PR requires user awareness at the usage level, please submit a PR to update user docs. [User docs repo](https://github.com/cloudwego/cloudwego.github.io)
-->


codecov[bot]: ## [Codecov](https://app.codecov.io/gh/cloudwego/hertz/pull/889?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudwego) Report
Patch coverage: **`33.33%`** and project coverage change: **`-0.04%`** :warning:
> Comparison is base [(`f4cde92`)](https://app.codecov.io/gh/cloudwego/hertz/commit/f4cde92c4781a355113b92e7a9e444b69e640fbb?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudwego) 79.86% compared to head [(`492f408`)](https://app.codecov.io/gh/cloudwego/hertz/pull/889?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudwego) 79.83%.
> Report is 6 commits behind head on develop.

> :exclamation: Current head 492f408 differs from pull request most recent head e15823b. Consider uploading reports for the commit e15823b to get more accurate results

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


```diff
@@             Coverage Diff             @@
##           develop     #889      +/-   ##
===========================================
- Coverage    79.86%   79.83%   -0.04%     
===========================================
  Files           97       97              
  Lines         9587     9602      +15     
===========================================
+ Hits          7657     7666       +9     
- Misses        1430     1435       +5     
- Partials       500      501       +1     
```


| [Files Changed](https://app.codecov.io/gh/cloudwego/hertz/pull/889?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudwego) | Coverage Δ | |
|---|---|---|
| [pkg/network/netpoll/connection.go](https://app.codecov.io/gh/cloudwego/hertz/pull/889?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudwego#diff-cGtnL25ldHdvcmsvbmV0cG9sbC9jb25uZWN0aW9uLmdv) | `70.21% <0.00%> (-3.13%)` | :arrow_down: |
| [pkg/network/standard/connection.go](https://app.codecov.io/gh/cloudwego/hertz/pull/889?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudwego#diff-cGtnL25ldHdvcmsvc3RhbmRhcmQvY29ubmVjdGlvbi5nbw==) | `81.13% <0.00%> (-0.49%)` | :arrow_down: |
| [pkg/common/test/mock/network.go](https://app.codecov.io/gh/cloudwego/hertz/pull/889?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudwego#diff-cGtnL2NvbW1vbi90ZXN0L21vY2svbmV0d29yay5nbw==) | `91.97% <33.33%> (-1.15%)` | :arrow_down: |
| [pkg/protocol/http1/client.go](https://app.codecov.io/gh/cloudwego/hertz/pull/889?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudwego#diff-cGtnL3Byb3RvY29sL2h0dHAxL2NsaWVudC5nbw==) | `73.42% <33.33%> (+0.14%)` | :arrow_up: |
| [pkg/route/engine.go](https://app.codecov.io/gh/cloudwego/hertz/pull/889?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudwego#diff-cGtnL3JvdXRlL2VuZ2luZS5nbw==) | `60.66% <33.33%> (+0.12%)` | :arrow_up: |
| [pkg/protocol/http1/ext/common.go](https://app.codecov.io/gh/cloudwego/hertz/pull/889?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudwego#diff-cGtnL3Byb3RvY29sL2h0dHAxL2V4dC9jb21tb24uZ28=) | `70.12% <100.00%> (+0.19%)` | :arrow_up: |

... and [2 files with indirect coverage changes](https://app.codecov.io/gh/cloudwego/hertz/pull/889/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudwego)


</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/cloudwego/hertz/pull/889?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudwego).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudwego).


Patch:
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 1099f2a..9f38755 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -21,16 +21,17 @@ 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:
 -->
-- [ ] This PR title match the format: `<type>(optional scope): <description>`.
+- [ ] This PR title match the format: \<type\>(optional scope): \<description\>
 - [ ] The description of this PR title is user-oriented and clear enough for others to understand.
-- [ ] Attach the PR updating the user documentation if the current PR requires user awareness at the usage level. [User docs repo](https://github.com/cloudwego/cloudwego.github.io).
+- [ ] Attach the PR updating the user documentation if the current PR requires user awareness at the usage level. [User docs repo](https://github.com/cloudwego/cloudwego.github.io)
+
 
 #### (Optional) Translate the PR title into Chinese.
 
 
-#### (Optional) More detail description for this PR(en: English/zh: Chinese).
+#### (Optional) More detailed 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.
+Provide more detailed info for review(e.g., it's recommended to provide perf data if this is a perf type PR).
 -->
 en:
 zh(optional):
@@ -44,4 +45,4 @@ Eg: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
 #### (Optional) The PR that updates user documentation:
 <!--
 If the current PR requires user awareness at the usage level, please submit a PR to update user docs. [User docs repo](https://github.com/cloudwego/cloudwego.github.io)
--->
+-->
\ No newline at end of file
diff --git a/cmd/hz/app/app.go b/cmd/hz/app/app.go
index 269e5ba..a6c44c0 100644
--- a/cmd/hz/app/app.go
+++ b/cmd/hz/app/app.go
@@ -321,8 +321,6 @@ func Init() *cli.App {
 				&snakeNameFlag,
 				&rmTagFlag,
 				&excludeFilesFlag,
-				&customLayout,
-				&customLayoutData,
 				&customPackage,
 				&protoPluginsFlag,
 				&thriftPluginsFlag,
diff --git a/cmd/hz/generator/handler.go b/cmd/hz/generator/handler.go
index 4d797d2..2a949c5 100644
--- a/cmd/hz/generator/handler.go
+++ b/cmd/hz/generator/handler.go
@@ -44,6 +44,7 @@ type HttpMethod struct {
 	RefPackage         string // handler import dir
 	RefPackageAlias    string // handler import alias
 	ModelPackage       map[string]string
+	GenHandler         bool // Whether to generate one handler, when an idl interface corresponds to multiple http method
 	// Annotations     map[string]string
 	Models map[string]*model.Model
 }
@@ -78,8 +79,10 @@ func (pkgGen *HttpPackageGenerator) genHandler(pkg *HttpPackage, handlerDir, han
 					return fmt.Errorf("generate handler %s failed, err: %v", handler.FilePath, err.Error())
 				}
 
-				if err := pkgGen.updateHandler(handler, handlerTplName, handler.FilePath, false); err != nil {
-					return fmt.Errorf("generate handler %s failed, err: %v", handler.FilePath, err.Error())
+				if m.GenHandler {
+					if err := pkgGen.updateHandler(handler, handlerTplName, handler.FilePath, false); err != nil {
+						return fmt.Errorf("generate handler %s failed, err: %v", handler.FilePath, err.Error())
+					}
 				}
 			}
 		} else { // generate handler service
@@ -105,6 +108,15 @@ func (pkgGen *HttpPackageGenerator) genHandler(pkg *HttpPackage, handlerDir, han
 				return fmt.Errorf("generate handler %s failed, err: %v", handler.FilePath, err.Error())
 			}
 
+			// Avoid generating duplicate handlers when IDL interface corresponds to multiple http methods
+			methods := handler.Methods
+			handler.Methods = []*HttpMethod{}
+			for _, m := range methods {
+				if m.GenHandler {
+					handler.Methods = append(handler.Methods, m)
+				}
+			}
+
 			if err := pkgGen.updateHandler(handler, handlerTplName, handler.FilePath, false); err != nil {
 				return fmt.Errorf("generate handler %s failed, err: %v", handler.FilePath, err.Error())
 			}
diff --git a/cmd/hz/generator/layout.go b/cmd/hz/generator/layout.go
index d007fa8..ea189f5 100644
--- a/cmd/hz/generator/layout.go
+++ b/cmd/hz/generator/layout.go
@@ -124,11 +124,22 @@ func (lg *LayoutGenerator) GenerateByService(service Layout) error {
 
 	if !service.NeedGoMod {
 		gomodFile := "go.mod"
-		if _, exist := lg.tpls["go.mod"]; exist {
+		if _, exist := lg.tpls[gomodFile]; exist {
 			delete(lg.tpls, gomodFile)
 		}
 	}
 
+	if util.IsWindows() {
+		buildSh := "build.sh"
+		bootstrapSh := defaultScriptDir + sp + "bootstrap.sh"
+		if _, exist := lg.tpls[buildSh]; exist {
+			delete(lg.tpls, buildSh)
+		}
+		if _, exist := lg.tpls[bootstrapSh]; exist {
+			delete(lg.tpls, bootstrapSh)
+		}
+	}
+
 	sd, err := serviceToLayoutData(service)
 	if err != nil {
 		return err
diff --git a/cmd/hz/generator/layout_tpl.go b/cmd/hz/generator/layout_tpl.go
index 6f2b4c3..68a6d63 100644
--- a/cmd/hz/generator/layout_tpl.go
+++ b/cmd/hz/generator/layout_tpl.go
@@ -201,24 +201,20 @@ func GeneratedRegister(r *server.Hertz){
 		},
 		{
 			Path: "build.sh",
-			Body: `
-#!/bin/bash
+			Body: `#!/bin/bash
 RUN_NAME={{.ServiceName}}
 mkdir -p output/bin
 cp script/* output 2>/dev/null
 chmod +x output/bootstrap.sh
-go build -o output/bin/${RUN_NAME}
-`,
+go build -o output/bin/${RUN_NAME}`,
 		},
 		{
 			Path: defaultScriptDir + sp + "bootstrap.sh",
-			Body: `
-#!/bin/bash
+			Body: `#!/bin/bash
 CURDIR=$(cd $(dirname $0); pwd)
 BinaryName={{.ServiceName}}
 echo "$CURDIR/bin/${BinaryName}"
-exec $CURDIR/bin/${BinaryName}
-`,
+exec $CURDIR/bin/${BinaryName}`,
 		},
 	},
 }
diff --git a/cmd/hz/generator/package.go b/cmd/hz/generator/package.go
index 9eb00ad..01a6884 100644
--- a/cmd/hz/generator/package.go
+++ b/cmd/hz/generator/package.go
@@ -167,6 +167,9 @@ func (pkgGen *HttpPackageGenerator) Generate(pkg *HttpPackage) error {
 		if err := pkgGen.genClient(pkg, clientDir); err != nil {
 			return err
 		}
+		if err := pkgGen.genCustomizedFile(pkg); err != nil {
+			return err
+		}
 		return nil
 	}
 
diff --git a/cmd/hz/generator/template_funcs.go b/cmd/hz/generator/template_funcs.go
index 556ffb1..2d33d72 100644
--- a/cmd/hz/generator/template_funcs.go
+++ b/cmd/hz/generator/template_funcs.go
@@ -20,15 +20,22 @@ import (
 	"strings"
 	"text/template"
 
+	"github.com/Masterminds/sprig/v3"
 	"github.com/cloudwego/hertz/cmd/hz/util"
 )
 
-var funcMap = template.FuncMap{
-	"GetUniqueHandlerOutDir": getUniqueHandlerOutDir,
-	"ToSnakeCase":            util.ToSnakeCase,
-	"Split":                  strings.Split,
-	"Trim":                   strings.Trim,
-}
+var funcMap = func() template.FuncMap {
+	m := template.FuncMap{
+		"GetUniqueHandlerOutDir": getUniqueHandlerOutDir,
+		"ToSnakeCase":            util.ToSnakeCase,
+		"Split":                  strings.Split,
+		"Trim":                   strings.Trim,
+	}
+	for key, f := range sprig.TxtFuncMap() {
+		m[key] = f
+	}
+	return m
+}()
 
 // getUniqueHandlerOutDir uses to get unique "api.handler_path"
 func getUniqueHandlerOutDir(methods []*HttpMethod) (ret []string) {
diff --git a/cmd/hz/go.mod b/cmd/hz/go.mod
index b65eeeb..712c5a2 100644
--- a/cmd/hz/go.mod
+++ b/cmd/hz/go.mod
@@ -3,6 +3,7 @@ module github.com/cloudwego/hertz/cmd/hz
 go 1.16
 
 require (
+	github.com/Masterminds/sprig/v3 v3.2.3
 	github.com/cloudwego/thriftgo v0.1.7
 	github.com/hashicorp/go-version v1.5.0
 	github.com/jhump/protoreflect v1.12.0
diff --git a/cmd/hz/go.sum b/cmd/hz/go.sum
index 23aa603..de5c00d 100644
--- a/cmd/hz/go.sum
+++ b/cmd/hz/go.sum
@@ -1,6 +1,12 @@
 cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
+github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
+github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
+github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g=
+github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
+github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA=
+github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM=
 github.com/apache/thrift v0.13.0 h1:5hryIiq9gtn+MiLVn0wP37kb/uTeRZgN08WoCsAhIhI=
 github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
@@ -11,6 +17,8 @@ github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnht
 github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
 github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
 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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
@@ -35,9 +43,15 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
 github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 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/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
 github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/hashicorp/go-version v1.5.0 h1:O293SZ2Eg+AAYijkVK3jR786Am1bhDEh2GHT0tIVE5E=
 github.com/hashicorp/go-version v1.5.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
+github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4=
+github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
+github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA=
+github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
 github.com/jhump/gopoet v0.0.0-20190322174617-17282ff210b3/go.mod h1:me9yfT6IJSlOL3FCfrg+L6yzUEZ+5jW6WHt4Sk+UPUI=
 github.com/jhump/gopoet v0.1.0/go.mod h1:me9yfT6IJSlOL3FCfrg+L6yzUEZ+5jW6WHt4Sk+UPUI=
 github.com/jhump/goprotoc v0.5.0/go.mod h1:VrbvcYrQOrTi3i0Vf+m+oqQWk9l72mjkJCYo7UvLHRQ=
@@ -49,11 +63,22 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ=
+github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
+github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY=
+github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
+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/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
+github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
+github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
+github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 github.com/urfave/cli/v2 v2.23.0 h1:pkly7gKIeYv3olPAeNajNpLjeJrmTPYCoZWaV+2VfvE=
 github.com/urfave/cli/v2 v2.23.0/go.mod h1:1CNUng3PtjQMtRzJO4FMXBQvkGtuYRxxiR9xMa7jMwI=
@@ -63,6 +88,8 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5t
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A=
+golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
@@ -78,6 +105,7 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
 golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
 golang.org/x/net v0.3.0 h1:VWL6FNY2bEEmsGVKabSlHu5Irp34xmMRoqb/9lF9lxk=
 golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -95,15 +123,18 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
 golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
 golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
 golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -148,6 +179,7 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogR
 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/cmd/hz/meta/const.go b/cmd/hz/meta/const.go
index 8e7490d..abda7c7 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.6.6"
+const Version = "v0.6.7"
 
 const DefaultServiceName = "hertz_service"
 
diff --git a/cmd/hz/protobuf/api/api.pb.go b/cmd/hz/protobuf/api/api.pb.go
index 9707626..4c1d8c6 100644
--- a/cmd/hz/protobuf/api/api.pb.go
+++ b/cmd/hz/protobuf/api/api.pb.go
@@ -1,16 +1,17 @@
 // Code generated by protoc-gen-go. DO NOT EDIT.
 // versions:
-// 	protoc-gen-go v1.28.1
+// 	protoc-gen-go v1.30.0
 // 	protoc        v3.21.12
 // source: api.proto
 
 package api
 
 import (
+	reflect "reflect"
+
 	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
 	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
 	descriptorpb "google.golang.org/protobuf/types/descriptorpb"
-	reflect "reflect"
 )
 
 const (
@@ -317,6 +318,22 @@ var file_api_proto_extTypes = []protoimpl.ExtensionInfo{
 		Tag:           "bytes,50731,opt,name=base_domain_compatible",
 		Filename:      "api.proto",
 	},
+	{
+		ExtendedType:  (*descriptorpb.ServiceOptions)(nil),
+		ExtensionType: (*string)(nil),
+		Field:         50732,
+		Name:          "api.service_path",
+		Tag:           "bytes,50732,opt,name=service_path",
+		Filename:      "api.proto",
+	},
+	{
+		ExtendedType:  (*descriptorpb.MessageOptions)(nil),
+		ExtensionType: (*string)(nil),
+		Field:         50830,
+		Name:          "api.reserve",
+		Tag:           "bytes,50830,opt,name=reserve",
+		Filename:      "api.proto",
+	},
 }
 
 // Extension fields to descriptorpb.FieldOptions.
@@ -413,6 +430,14 @@ var (
 	//
 	// optional string base_domain_compatible = 50731;
 	E_BaseDomainCompatible = &file_api_proto_extTypes[36]
+	// optional string service_path = 50732;
+	E_ServicePath = &file_api_proto_extTypes[37]
+)
+
+// Extension fields to descriptorpb.MessageOptions.
+var (
+	// optional string reserve = 50830;
+	E_Reserve = &file_api_proto_extTypes[38]
 )
 
 var File_api_proto protoreflect.FileDescriptor
@@ -563,7 +588,15 @@ var file_api_proto_rawDesc = []byte{
 	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,
 	0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xab, 0x8c, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52,
 	0x14, 0x62, 0x61, 0x73, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x43, 0x6f, 0x6d, 0x70, 0x61,
-	0x74, 0x69, 0x62, 0x6c, 0x65, 0x42, 0x06, 0x5a, 0x04, 0x2f, 0x61, 0x70, 0x69,
+	0x74, 0x69, 0x62, 0x6c, 0x65, 0x3a, 0x44, 0x0a, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,
+	0x5f, 0x70, 0x61, 0x74, 0x68, 0x12, 0x1f, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,
+	0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x4f,
+	0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xac, 0x8c, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b,
+	0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x50, 0x61, 0x74, 0x68, 0x3a, 0x3b, 0x0a, 0x07, 0x72,
+	0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x12, 0x1f, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
+	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
+	0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x8e, 0x8d, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52,
+	0x07, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x42, 0x06, 0x5a, 0x04, 0x2f, 0x61, 0x70, 0x69,
 }
 
 var file_api_proto_goTypes = []interface{}{
@@ -571,6 +604,7 @@ var file_api_proto_goTypes = []interface{}{
 	(*descriptorpb.MethodOptions)(nil),    // 1: google.protobuf.MethodOptions
 	(*descriptorpb.EnumValueOptions)(nil), // 2: google.protobuf.EnumValueOptions
 	(*descriptorpb.ServiceOptions)(nil),   // 3: google.protobuf.ServiceOptions
+	(*descriptorpb.MessageOptions)(nil),   // 4: google.protobuf.MessageOptions
 }
 var file_api_proto_depIdxs = []int32{
 	0,  // 0: api.raw_body:extendee -> google.protobuf.FieldOptions
@@ -610,10 +644,12 @@ var file_api_proto_depIdxs = []int32{
 	2,  // 34: api.http_code:extendee -> google.protobuf.EnumValueOptions
 	3,  // 35: api.base_domain:extendee -> google.protobuf.ServiceOptions
 	3,  // 36: api.base_domain_compatible:extendee -> google.protobuf.ServiceOptions
-	37, // [37:37] is the sub-list for method output_type
-	37, // [37:37] is the sub-list for method input_type
-	37, // [37:37] is the sub-list for extension type_name
-	0,  // [0:37] is the sub-list for extension extendee
+	3,  // 37: api.service_path:extendee -> google.protobuf.ServiceOptions
+	4,  // 38: api.reserve:extendee -> google.protobuf.MessageOptions
+	39, // [39:39] is the sub-list for method output_type
+	39, // [39:39] is the sub-list for method input_type
+	39, // [39:39] is the sub-list for extension type_name
+	0,  // [0:39] is the sub-list for extension extendee
 	0,  // [0:0] is the sub-list for field type_name
 }
 
@@ -629,7 +665,7 @@ func file_api_proto_init() {
 			RawDescriptor: file_api_proto_rawDesc,
 			NumEnums:      0,
 			NumMessages:   0,
-			NumExtensions: 37,
+			NumExtensions: 39,
 			NumServices:   0,
 		},
 		GoTypes:           file_api_proto_goTypes,
diff --git a/cmd/hz/protobuf/api/api.proto b/cmd/hz/protobuf/api/api.proto
index 72edf41..71a3cd3 100644
--- a/cmd/hz/protobuf/api/api.proto
+++ b/cmd/hz/protobuf/api/api.proto
@@ -64,6 +64,7 @@ extend google.protobuf.ServiceOptions {
 
   // 50731~50760 used to extend service option by hz
   optional string base_domain_compatible = 50731;
+  optional string service_path = 50732;
 }
 
 extend google.protobuf.MessageOptions {
diff --git a/cmd/hz/protobuf/ast.go b/cmd/hz/protobuf/ast.go
index fcb8197..d607a83 100644
--- a/cmd/hz/protobuf/ast.go
+++ b/cmd/hz/protobuf/ast.go
@@ -19,6 +19,7 @@ package protobuf
 import (
 	"fmt"
 	"path/filepath"
+	"sort"
 	"strings"
 
 	"github.com/cloudwego/hertz/cmd/hz/generator"
@@ -129,12 +130,25 @@ func astToService(ast *descriptorpb.FileDescriptorProto, resolver *Resolver, cmd
 		ms := s.GetMethod()
 		methods := make([]*generator.HttpMethod, 0, len(ms))
 		clientMethods := make([]*generator.ClientMethod, 0, len(ms))
+		servicePathAnno := checkFirstOption(api.E_ServicePath, s.GetOptions())
+		servicePath := ""
+		if val, ok := servicePathAnno.(string); ok {
+			servicePath = val
+		}
 		for _, m := range ms {
-			hmethod, vpath := checkFirstOptions(HttpMethodOptions, m.GetOptions())
-			if hmethod == "" {
+			rs := getAllOptions(HttpMethodOptions, m.GetOptions())
+			if len(rs) == 0 {
 				continue
 			}
-			path := vpath.(string)
+			httpOpts := httpOptions{}
+			for k, v := range rs {
+				httpOpts = append(httpOpts, httpOption{
+					method: k,
+					path:   v.(string),
+				})
+			}
+			// turn the map into a slice and sort it to make sure getting the results in the same order every time
+			sort.Sort(httpOpts)
 
 			var handlerOutDir string
 			genPath := getCompatibleAnnotation(m.GetOptions(), api.E_HandlerPath, api.E_HandlerPathCompatible)
@@ -142,6 +156,9 @@ func astToService(ast *descriptorpb.FileDescriptorProto, resolver *Resolver, cmd
 			if !ok || len(handlerOutDir) == 0 {
 				handlerOutDir = ""
 			}
+			if len(handlerOutDir) == 0 {
+				handlerOutDir = servicePath
+			}
 
 			// protoGoInfo can get generated "Go Info" for proto file.
 			// the type name may be different between "***.proto" and "***.pb.go"
@@ -181,10 +198,11 @@ func astToService(ast *descriptorpb.FileDescriptorProto, resolver *Resolver, cmd
 
 			method := &generator.HttpMethod{
 				Name:       util.CamelString(m.GetName()),
-				HTTPMethod: hmethod,
-				Path:       path,
+				HTTPMethod: httpOpts[0].method,
+				Path:       httpOpts[0].path,
 				Serializer: serializer,
 				OutputDir:  handlerOutDir,
+				GenHandler: true,
 			}
 
 			goOptMapAlias := make(map[string]string, 1)
@@ -223,6 +241,16 @@ func astToService(ast *descriptorpb.FileDescriptorProto, resolver *Resolver, cmd
 			method.ReturnTypePackage = respPackage
 
 			methods = append(methods, method)
+			for idx, anno := range httpOpts {
+				if idx == 0 {
+					continue
+				}
+				tmp := *method
+				tmp.HTTPMethod = anno.method
+				tmp.Path = anno.path
+				tmp.GenHandler = false
+				methods = append(methods, &tmp)
+			}
 
 			if cmdType == meta.CmdClient {
 				clientMethod := &generator.ClientMethod{}
@@ -326,7 +354,7 @@ func parseAnnotationToClient(clientMethod *generator.ClientMethod, gen *protogen
 			clientMethod.FormFileCode += fmt.Sprintf("%q: req.Get%s(),\n", val, f.GoName)
 		}
 		if !hasAnnotation && strings.EqualFold(clientMethod.HTTPMethod, "get") {
-			clientMethod.QueryParamsCode += fmt.Sprintf("%q: req.Get%s(),\n", checkSnakeName(f.GoName), f.GoName)
+			clientMethod.QueryParamsCode += fmt.Sprintf("%q: req.Get%s(),\n", checkSnakeName(string(f.Desc.Name())), f.GoName)
 		}
 	}
 	clientMethod.BodyParamsCode = meta.SetBodyParam
diff --git a/cmd/hz/protobuf/tags.go b/cmd/hz/protobuf/tags.go
index 23c0464..1abf231 100644
--- a/cmd/hz/protobuf/tags.go
+++ b/cmd/hz/protobuf/tags.go
@@ -94,6 +94,38 @@ var (
 	SerializerOptions = map[*protoimpl.ExtensionInfo]string{api.E_Serializer: "serializer"}
 )
 
+type httpOption struct {
+	method string
+	path   string
+}
+
+type httpOptions []httpOption
+
+func (s httpOptions) Len() int {
+	return len(s)
+}
+
+func (s httpOptions) Swap(i, j int) {
+	s[i], s[j] = s[j], s[i]
+}
+
+func (s httpOptions) Less(i, j int) bool {
+	return s[i].method < s[j].method
+}
+
+func getAllOptions(extensions map[*protoimpl.ExtensionInfo]string, opts ...protoreflect.ProtoMessage) map[string]interface{} {
+	out := map[string]interface{}{}
+	for _, opt := range opts {
+		for e, t := range extensions {
+			if proto.HasExtension(opt, e) {
+				v := proto.GetExtension(opt, e)
+				out[t] = v
+			}
+		}
+	}
+	return out
+}
+
 func checkFirstOptions(extensions map[*protoimpl.ExtensionInfo]string, opts ...protoreflect.ProtoMessage) (string, interface{}) {
 	for _, opt := range opts {
 		for e, t := range extensions {
diff --git a/cmd/hz/thrift/ast.go b/cmd/hz/thrift/ast.go
index 557b50a..8372235 100644
--- a/cmd/hz/thrift/ast.go
+++ b/cmd/hz/thrift/ast.go
@@ -18,6 +18,7 @@ package thrift
 
 import (
 	"fmt"
+	"sort"
 	"strings"
 
 	"github.com/cloudwego/hertz/cmd/hz/config"
@@ -98,26 +99,34 @@ func astToService(ast *parser.Thrift, resolver *Resolver, args *config.Argument)
 		}
 		methods := make([]*generator.HttpMethod, 0, len(ms))
 		clientMethods := make([]*generator.ClientMethod, 0, len(ms))
+		servicePathAnno := getAnnotation(s.Annotations, ApiServicePath)
+		servicePath := ""
+		if len(servicePathAnno) > 0 {
+			servicePath = servicePathAnno[0]
+		}
 		for _, m := range ms {
 			rs := getAnnotations(m.Annotations, HttpMethodAnnotations)
-			if len(rs) > 1 {
-				return nil, fmt.Errorf("too many 'api.XXX' annotations: %s", rs)
-			}
 			if len(rs) == 0 {
 				continue
 			}
-
-			var handlerOutDir string
+			httpAnnos := httpAnnotations{}
+			for k, v := range rs {
+				httpAnnos = append(httpAnnos, httpAnnotation{
+					method: k,
+					path:   v,
+				})
+			}
+			// turn the map into a slice and sort it to make sure getting the results in the same order every time
+			sort.Sort(httpAnnos)
+			handlerOutDir := servicePath
 			genPaths := getAnnotation(m.Annotations, ApiGenPath)
-			if len(genPaths) == 0 {
-				handlerOutDir = ""
-			} else if len(genPaths) > 1 {
-				return nil, fmt.Errorf("too many 'api.handler_path' for %s", m.Name)
-			} else {
+			if len(genPaths) == 1 {
 				handlerOutDir = genPaths[0]
+			} else if len(genPaths) > 0 {
+				return nil, fmt.Errorf("too many 'api.handler_path' for %s", m.Name)
 			}
 
-			hmethod, path := util.GetFirstKV(rs)
+			hmethod, path := httpAnnos[0].method, httpAnnos[0].path
 			if len(path) != 1 || path[0] == "" {
 				return nil, fmt.Errorf("invalid api.%s  for %s.%s: %s", hmethod, s.Name, m.Name, path)
 			}
@@ -175,6 +184,7 @@ func astToService(ast *parser.Thrift, resolver *Resolver, args *config.Argument)
 				Path:               path[0],
 				Serializer:         sr,
 				OutputDir:          handlerOutDir,
+				GenHandler:         true,
 				// Annotations:     m.Annotations,
 			}
 			refs := resolver.ExportReferred(false, true)
@@ -187,6 +197,20 @@ func astToService(ast *parser.Thrift, resolver *Resolver, args *config.Argument)
 			}
 			models.MergeMap(method.Models)
 			methods = append(methods, method)
+			for idx, anno := range httpAnnos {
+				if idx == 0 {
+					continue
+				}
+				tmp := *method
+				hmethod, path := anno.method, anno.path
+				if len(path) != 1 || path[0] == "" {
+					return nil, fmt.Errorf("invalid api.%s  for %s.%s: %s", hmethod, s.Name, m.Name, path)
+				}
+				tmp.HTTPMethod = hmethod
+				tmp.Path = path[0]
+				tmp.GenHandler = false
+				methods = append(methods, &tmp)
+			}
 			if args.CmdType == meta.CmdClient {
 				clientMethod := &generator.ClientMethod{}
 				clientMethod.HttpMethod = method
@@ -288,7 +312,7 @@ func parseAnnotationToClient(clientMethod *generator.ClientMethod, p *parser.Typ
 			clientMethod.FormFileCode += fmt.Sprintf("%q: req.Get%s(),\n", fileName, field.GoName().String())
 		}
 		if !hasAnnotation && strings.EqualFold(clientMethod.HTTPMethod, "get") {
-			clientMethod.QueryParamsCode += fmt.Sprintf("%q: req.Get%s(),\n", checkSnakeName(field.GoName().String()), field.GoName().String())
+			clientMethod.QueryParamsCode += fmt.Sprintf("%q: req.Get%s(),\n", checkSnakeName(field.GetName()), field.GoName().String())
 		}
 	}
 	clientMethod.BodyParamsCode = meta.SetBodyParam
@@ -353,18 +377,7 @@ func getAllExtendFunction(svc *parser.Service, ast *parser.Thrift, resolver *Res
 				}
 				funcs := extendSvc.GetFunctions()
 				for _, f := range funcs {
-					// the method of other file is extended, and the package of req/resp needs to be changed
-					// ex. base.thrift -> Resp Method(Req){}
-					//					  base.Resp Method(base.Req){}
-					// todo: support container for Struct
-					if len(f.Arguments) > 0 {
-						if !strings.Contains(f.Arguments[0].Type.Name, ".") && f.Arguments[0].Type.Category.IsStruct() {
-							f.Arguments[0].Type.Name = base + "." + f.Arguments[0].Type.Name
-						}
-					}
-					if !strings.Contains(f.FunctionType.Name, ".") && f.FunctionType.Category.IsStruct() {
-						f.FunctionType.Name = base + "." + f.FunctionType.Name
-					}
+					processExtendsType(f, base)
 				}
 				extendFuncs, err := getAllExtendFunction(extendSvc, ast, resolver, args)
 				if err != nil {
@@ -392,18 +405,7 @@ func getAllExtendFunction(svc *parser.Service, ast *parser.Thrift, resolver *Res
 			if found {
 				funcs := extendSvc.GetFunctions()
 				for _, f := range funcs {
-					// the method of other file is extended, and the package of req/resp needs to be changed
-					// ex. base.thrift -> Resp Method(Req){}
-					//					  base.Resp Method(base.Req){}
-					// todo: support container for Struct
-					if len(f.Arguments) > 0 {
-						if !strings.Contains(f.Arguments[0].Type.Name, ".") && f.Arguments[0].Type.Category.IsStruct() {
-							f.Arguments[0].Type.Name = base + "." + f.Arguments[0].Type.Name
-						}
-					}
-					if !strings.Contains(f.FunctionType.Name, ".") && f.FunctionType.Category.IsStruct() {
-						f.FunctionType.Name = base + "." + f.FunctionType.Name
-					}
+					processExtendsType(f, base)
 				}
 				extendFuncs, err := getAllExtendFunction(extendSvc, refAst, resolver, args)
 				if err != nil {
@@ -418,6 +420,53 @@ func getAllExtendFunction(svc *parser.Service, ast *parser.Thrift, resolver *Res
 	return res, nil
 }
 
+func processExtendsType(f *parser.Function, base string) {
+	// the method of other file is extended, and the package of req/resp needs to be changed
+	// ex. base.thrift -> Resp Method(Req){}
+	//					  base.Resp Method(base.Req){}
+	if len(f.Arguments) > 0 {
+		if f.Arguments[0].Type.Category.IsContainerType() {
+			switch f.Arguments[0].Type.Category {
+			case parser.Category_Set, parser.Category_List:
+				if !strings.Contains(f.Arguments[0].Type.ValueType.Name, ".") && f.Arguments[0].Type.ValueType.Category.IsStruct() {
+					f.Arguments[0].Type.ValueType.Name = base + "." + f.Arguments[0].Type.ValueType.Name
+				}
+			case parser.Category_Map:
+				if !strings.Contains(f.Arguments[0].Type.ValueType.Name, ".") && f.Arguments[0].Type.ValueType.Category.IsStruct() {
+					f.Arguments[0].Type.ValueType.Name = base + "." + f.Arguments[0].Type.ValueType.Name
+				}
+				if !strings.Contains(f.Arguments[0].Type.KeyType.Name, ".") && f.Arguments[0].Type.KeyType.Category.IsStruct() {
+					f.Arguments[0].Type.KeyType.Name = base + "." + f.Arguments[0].Type.KeyType.Name
+				}
+			}
+		} else {
+			if !strings.Contains(f.Arguments[0].Type.Name, ".") && f.Arguments[0].Type.Category.IsStruct() {
+				f.Arguments[0].Type.Name = base + "." + f.Arguments[0].Type.Name
+			}
+		}
+	}
+
+	if f.FunctionType.Category.IsContainerType() {
+		switch f.FunctionType.Category {
+		case parser.Category_Set, parser.Category_List:
+			if !strings.Contains(f.FunctionType.ValueType.Name, ".") && f.FunctionType.ValueType.Category.IsStruct() {
+				f.FunctionType.ValueType.Name = base + "." + f.FunctionType.ValueType.Name
+			}
+		case parser.Category_Map:
+			if !strings.Contains(f.FunctionType.ValueType.Name, ".") && f.FunctionType.ValueType.Category.IsStruct() {
+				f.FunctionType.ValueType.Name = base + "." + f.FunctionType.ValueType.Name
+			}
+			if !strings.Contains(f.FunctionType.KeyType.Name, ".") && f.FunctionType.KeyType.Category.IsStruct() {
+				f.FunctionType.KeyType.Name = base + "." + f.FunctionType.KeyType.Name
+			}
+		}
+	} else {
+		if !strings.Contains(f.FunctionType.Name, ".") && f.FunctionType.Category.IsStruct() {
+			f.FunctionType.Name = base + "." + f.FunctionType.Name
+		}
+	}
+}
+
 func getUniqueResolveDependentName(name string, resolver *Resolver) string {
 	rawName := name
 	for i := 0; i < 10000; i++ {
diff --git a/cmd/hz/thrift/plugin.go b/cmd/hz/thrift/plugin.go
index ff4fdf6..8b840e7 100644
--- a/cmd/hz/thrift/plugin.go
+++ b/cmd/hz/thrift/plugin.go
@@ -77,6 +77,8 @@ func (plugin *Plugin) Run() int {
 	}
 	plugin.rmTags = args.RmTags
 	if args.CmdType == meta.CmdModel {
+		// check tag options for model mode
+		CheckTagOption(plugin.args)
 		res, err := plugin.GetResponse(nil, args.OutDir)
 		if err != nil {
 			logs.Errorf("get response failed: %s", err.Error())
diff --git a/cmd/hz/thrift/tags.go b/cmd/hz/thrift/tags.go
index 5b2e8b6..96c414d 100644
--- a/cmd/hz/thrift/tags.go
+++ b/cmd/hz/thrift/tags.go
@@ -63,7 +63,8 @@ const (
 const (
 	ApiBaseDomain    = "api.base_domain"
 	ApiServiceGroup  = "api.service_group"
-	ApiServiceGenDir = "api.service_gen_dir"
+	ApiServiceGenDir = "api.service_gen_dir" // handler_dir for handler_by_service
+	ApiServicePath   = "api.service_path"    // declare the path to the service's handler according to this annotation for handler_by_method
 )
 
 var (
@@ -141,6 +142,25 @@ func getAnnotation(input parser.Annotations, target string) []string {
 	return []string{}
 }
 
+type httpAnnotation struct {
+	method string
+	path   []string
+}
+
+type httpAnnotations []httpAnnotation
+
+func (s httpAnnotations) Len() int {
+	return len(s)
+}
+
+func (s httpAnnotations) Swap(i, j int) {
+	s[i], s[j] = s[j], s[i]
+}
+
+func (s httpAnnotations) Less(i, j int) bool {
+	return s[i].method < s[j].method
+}
+
 func getAnnotations(input parser.Annotations, targets map[string]string) map[string][]string {
 	if len(input) == 0 || len(targets) == 0 {
 		return nil
diff --git a/go.mod b/go.mod
index 78039a7..b5eeb52 100644
--- a/go.mod
+++ b/go.mod
@@ -7,7 +7,7 @@ require (
 	github.com/bytedance/gopkg v0.0.0-20220413063733-65bf48ffb3a7
 	github.com/bytedance/mockey v1.2.1
 	github.com/bytedance/sonic v1.8.1
-	github.com/cloudwego/netpoll v0.3.2
+	github.com/cloudwego/netpoll v0.4.2-0.20230807055039-52fd5fb7b00f
 	github.com/fsnotify/fsnotify v1.5.4
 	github.com/tidwall/gjson v1.13.0 // indirect
 	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
diff --git a/go.sum b/go.sum
index fe32d2b..59e21cf 100644
--- a/go.sum
+++ b/go.sum
@@ -10,8 +10,8 @@ github.com/bytedance/sonic v1.8.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZX
 github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
 github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
 github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
-github.com/cloudwego/netpoll v0.3.2 h1:/998ICrNMVBo4mlul4j7qcIeY7QnEfuCCPPwck9S3X4=
-github.com/cloudwego/netpoll v0.3.2/go.mod h1:xVefXptcyheopwNDZjDPcfU6kIjZXZ4nY550k1yH9eQ=
+github.com/cloudwego/netpoll v0.4.2-0.20230807055039-52fd5fb7b00f h1:8iWPKjHdXl4tjcSxUJTavnhRL5JPupYvxbtsAlm2Igw=
+github.com/cloudwego/netpoll v0.4.2-0.20230807055039-52fd5fb7b00f/go.mod h1:xVefXptcyheopwNDZjDPcfU6kIjZXZ4nY550k1yH9eQ=
 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=
diff --git a/licenses/LICENSE-sprig.txt b/licenses/LICENSE-sprig.txt
new file mode 100644
index 0000000..9e6cf57
--- /dev/null
+++ b/licenses/LICENSE-sprig.txt
@@ -0,0 +1,19 @@
+Copyright (C) 2013-2020 Masterminds
+
+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.
\ No newline at end of file
diff --git a/pkg/app/context.go b/pkg/app/context.go
index 559b84d..9a6e454 100644
--- a/pkg/app/context.go
+++ b/pkg/app/context.go
@@ -80,48 +80,72 @@ type ClientIP func(ctx *RequestContext) string
 
 type ClientIPOptions struct {
 	RemoteIPHeaders []string
-	TrustedProxies  map[string]bool
+	TrustedCIDRs    []*net.IPNet
 }
 
-var defaultClientIPOptions = ClientIPOptions{
-	RemoteIPHeaders: []string{"X-Real-IP", "X-Forwarded-For"},
-	TrustedProxies: map[string]bool{
-		"0.0.0.0": true,
+var defaultTrustedCIDRs = []*net.IPNet{
+	{ // 0.0.0.0/0 (IPv4)
+		IP:   net.IP{0x0, 0x0, 0x0, 0x0},
+		Mask: net.IPMask{0x0, 0x0, 0x0, 0x0},
+	},
+	{ // ::/0 (IPv6)
+		IP:   net.IP{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
+		Mask: net.IPMask{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
 	},
 }
 
+var defaultClientIPOptions = ClientIPOptions{
+	RemoteIPHeaders: []string{"X-Forwarded-For", "X-Real-IP"},
+	TrustedCIDRs:    defaultTrustedCIDRs,
+}
+
 // ClientIPWithOption used to generate custom ClientIP function and set by engine.SetClientIPFunc
 func ClientIPWithOption(opts ClientIPOptions) ClientIP {
 	return func(ctx *RequestContext) string {
 		RemoteIPHeaders := opts.RemoteIPHeaders
-		TrustedProxies := opts.TrustedProxies
+		TrustedCIDRs := opts.TrustedCIDRs
 
-		remoteIP, _, err := net.SplitHostPort(strings.TrimSpace(ctx.RemoteAddr().String()))
+		remoteIPStr, _, err := net.SplitHostPort(strings.TrimSpace(ctx.RemoteAddr().String()))
 		if err != nil {
 			return ""
 		}
-		trusted := isTrustedProxy(TrustedProxies, remoteIP)
+
+		remoteIP := net.ParseIP(remoteIPStr)
+		if remoteIP == nil {
+			return ""
+		}
+
+		trusted := isTrustedProxy(TrustedCIDRs, remoteIP)
 
 		if trusted {
 			for _, headerName := range RemoteIPHeaders {
-				ip, valid := validateHeader(TrustedProxies, ctx.Request.Header.Get(headerName))
+				ip, valid := validateHeader(TrustedCIDRs, ctx.Request.Header.Get(headerName))
 				if valid {
 					return ip
 				}
 			}
 		}
 
-		return remoteIP
+		return remoteIPStr
 	}
 }
 
-// isTrustedProxy will check whether the IP address is included in the trusted list according to TrustedProxies
-func isTrustedProxy(trustedProxies map[string]bool, remoteIP string) bool {
-	return trustedProxies[remoteIP]
+// isTrustedProxy will check whether the IP address is included in the trusted list according to trustedCIDRs
+func isTrustedProxy(trustedCIDRs []*net.IPNet, remoteIP net.IP) bool {
+	if trustedCIDRs == nil {
+		return false
+	}
+
+	for _, cidr := range trustedCIDRs {
+		if cidr.Contains(remoteIP) {
+			return true
+		}
+	}
+	return false
 }
 
 // validateHeader will parse X-Real-IP and X-Forwarded-For header and return the Initial client IP address or an untrusted IP address
-func validateHeader(trustedProxies map[string]bool, header string) (clientIP string, valid bool) {
+func validateHeader(trustedCIDRs []*net.IPNet, header string) (clientIP string, valid bool) {
 	if header == "" {
 		return "", false
 	}
@@ -135,7 +159,7 @@ func validateHeader(trustedProxies map[string]bool, header string) (clientIP str
 
 		// X-Forwarded-For is appended by proxy
 		// Check IPs in reverse order and stop when find untrusted proxy
-		if (i == 0) || (!isTrustedProxy(trustedProxies, ipStr)) {
+		if (i == 0) || (!isTrustedProxy(trustedCIDRs, ip)) {
 			return ipStr, true
 		}
 	}
diff --git a/pkg/common/hlog/default.go b/pkg/common/hlog/default.go
index 6bc624a..bdb2a80 100644
--- a/pkg/common/hlog/default.go
+++ b/pkg/common/hlog/default.go
@@ -149,10 +149,15 @@ func (ll *defaultLogger) logf(lv Level, format *string, v ...interface{}) {
 	}
 	msg := lv.toString()
 	if format != nil {
-		msg += fmt.Sprintf(*format, v...)
+		if len(v) > 0 {
+			msg += fmt.Sprintf(*format, v...)
+		} else {
+			msg += *format
+		}
 	} else {
 		msg += fmt.Sprint(v...)
 	}
+
 	ll.stdlog.Output(ll.depth, msg)
 	if lv == LevelFatal {
 		os.Exit(1)
diff --git a/pkg/common/utils/chunk.go b/pkg/common/utils/chunk.go
index 6fa11d0..92c8194 100644
--- a/pkg/common/utils/chunk.go
+++ b/pkg/common/utils/chunk.go
@@ -65,9 +65,9 @@ func ParseChunkSize(r network.Reader) (int, error) {
 	return n, nil
 }
 
+// SkipCRLF will only skip the next CRLF("\r\n"), otherwise, error will be returned.
 func SkipCRLF(reader network.Reader) error {
 	p, err := reader.Peek(len(bytestr.StrCRLF))
-	reader.Skip(len(p)) // nolint: errcheck
 	if err != nil {
 		return err
 	}
@@ -75,5 +75,6 @@ func SkipCRLF(reader network.Reader) error {
 		return errBrokenChunk
 	}
 
+	reader.Skip(len(p)) // nolint: errcheck
 	return nil
 }
diff --git a/pkg/protocol/header.go b/pkg/protocol/header.go
index 3ac8801..f7533ef 100644
--- a/pkg/protocol/header.go
+++ b/pkg/protocol/header.go
@@ -1064,6 +1064,12 @@ func (h *RequestHeader) DelBytes(key []byte) {
 	h.del(h.bufKV.key)
 }
 
+// Del deletes header with the given key.
+func (h *RequestHeader) Del(key string) {
+	k := getHeaderKeyBytes(&h.bufKV, key, h.disableNormalizing)
+	h.del(k)
+}
+
 func (h *RequestHeader) SetArgBytes(key, value []byte, noValue bool) {
 	h.h = setArgBytes(h.h, key, value, noValue)
 }
diff --git a/pkg/protocol/http1/client.go b/pkg/protocol/http1/client.go
index c2b7cf5..09fbc10 100644
--- a/pkg/protocol/http1/client.go
+++ b/pkg/protocol/http1/client.go
@@ -72,9 +72,12 @@ import (
 	respI "github.com/cloudwego/hertz/pkg/protocol/http1/resp"
 )
 
-var errConnectionClosed = errs.NewPublic("the server closed connection before returning the first response byte. " +
-	"Make sure the server returns 'Connection: close' response header before closing the connection")
-var errTimeout = errs.New(errs.ErrTimeout, errs.ErrorTypePublic, "host client")
+var (
+	errConnectionClosed = errs.NewPublic("the server closed connection before returning the first response byte. " +
+		"Make sure the server returns 'Connection: close' response header before closing the connection")
+
+	errTimeout = errs.New(errs.ErrTimeout, errs.ErrorTypePublic, "host client")
+)
 
 // HostClient balances http requests among hosts listed in Addr.
 //
@@ -699,7 +702,8 @@ func (c *HostClient) doNonNilReqResp(req *protocol.Request, resp *protocol.Respo
 
 	shouldCloseConn = resetConnection || req.ConnectionClose() || resp.ConnectionClose()
 
-	if c.ResponseBodyStream {
+	// In stream mode, we still can close/release the connection immediately if there is no content on the wire.
+	if c.ResponseBodyStream && resp.BodyStream() != protocol.NoResponseBody {
 		return false, err
 	}
 
diff --git a/pkg/protocol/http1/ext/common.go b/pkg/protocol/http1/ext/common.go
index 8649883..2f2280a 100644
--- a/pkg/protocol/http1/ext/common.go
+++ b/pkg/protocol/http1/ext/common.go
@@ -51,6 +51,7 @@ import (
 	"github.com/cloudwego/hertz/internal/bytesconv"
 	"github.com/cloudwego/hertz/internal/bytestr"
 	errs "github.com/cloudwego/hertz/pkg/common/errors"
+	"github.com/cloudwego/hertz/pkg/common/hlog"
 	"github.com/cloudwego/hertz/pkg/common/utils"
 	"github.com/cloudwego/hertz/pkg/network"
 	"github.com/cloudwego/hertz/pkg/protocol"
@@ -117,12 +118,17 @@ func WriteBodyChunked(w network.Writer, r io.Reader) error {
 			if err == nil {
 				panic("BUG: io.Reader returned 0, nil")
 			}
-			if err == io.EOF {
-				if err = WriteChunk(w, buf[:0], true); err != nil {
-					break
-				}
-				err = nil
+
+			if !errors.Is(err, io.EOF) {
+				hlog.SystemLogger().Warnf("writing chunked response body encountered an error from the reader, "+
+					"this may cause the short of the content in response body, error: %s", err.Error())
+			}
+
+			if err = WriteChunk(w, buf[:0], true); err != nil {
+				break
 			}
+
+			err = nil
 			break
 		}
 		if err = WriteChunk(w, buf[:n], true); err != nil {
diff --git a/pkg/protocol/http1/resp/writer.go b/pkg/protocol/http1/resp/writer.go
index 745ab4c..7b50cd4 100644
--- a/pkg/protocol/http1/resp/writer.go
+++ b/pkg/protocol/http1/resp/writer.go
@@ -17,6 +17,7 @@
 package resp
 
 import (
+	"runtime"
 	"sync"
 
 	"github.com/cloudwego/hertz/pkg/network"
@@ -24,6 +25,16 @@ import (
 	"github.com/cloudwego/hertz/pkg/protocol/http1/ext"
 )
 
+var chunkReaderPool sync.Pool
+
+func init() {
+	chunkReaderPool = sync.Pool{
+		New: func() interface{} {
+			return &chunkedBodyWriter{}
+		},
+	}
+}
+
 type chunkedBodyWriter struct {
 	sync.Once
 	finalizeErr error
@@ -59,6 +70,14 @@ func (c *chunkedBodyWriter) Flush() error {
 // Warning: do not call this method by yourself, unless you know what you are doing.
 func (c *chunkedBodyWriter) Finalize() error {
 	c.Do(func() {
+		// in case no actual data from user
+		if !c.wroteHeader {
+			c.r.Header.SetContentLength(-1)
+			if c.finalizeErr = WriteHeader(&c.r.Header, c.w); c.finalizeErr != nil {
+				return
+			}
+			c.wroteHeader = true
+		}
 		c.finalizeErr = ext.WriteChunk(c.w, nil, true)
 		if c.finalizeErr != nil {
 			return
@@ -68,9 +87,19 @@ func (c *chunkedBodyWriter) Finalize() error {
 	return c.finalizeErr
 }
 
+func (c *chunkedBodyWriter) release() {
+	c.r = nil
+	c.w = nil
+	c.finalizeErr = nil
+	c.wroteHeader = false
+	chunkReaderPool.Put(c)
+}
+
 func NewChunkedBodyWriter(r *protocol.Response, w network.Writer) network.ExtWriter {
-	return &chunkedBodyWriter{
-		r: r,
-		w: w,
-	}
+	extWriter := chunkReaderPool.Get().(*chunkedBodyWriter)
+	extWriter.r = r
+	extWriter.w = w
+	extWriter.Once = sync.Once{}
+	runtime.SetFinalizer(extWriter, (*chunkedBodyWriter).release)
+	return extWriter
 }
diff --git a/pkg/protocol/request.go b/pkg/protocol/request.go
index ef70f92..d047318 100644
--- a/pkg/protocol/request.go
+++ b/pkg/protocol/request.go
@@ -76,7 +76,6 @@ var (
 // NoBody is an io.ReadCloser with no bytes. Read always returns EOF
 // and Close always returns nil. It can be used in an outgoing client
 // request to explicitly signal that a request has zero bytes.
-// An alternative, however, is to simply set Request.Body to nil.
 var NoBody = noBody{}
 
 type noBody struct{}
diff --git a/pkg/protocol/response.go b/pkg/protocol/response.go
index 016af92..8beb385 100644
--- a/pkg/protocol/response.go
+++ b/pkg/protocol/response.go
@@ -54,7 +54,13 @@ import (
 	"github.com/cloudwego/hertz/pkg/network"
 )
 
-var responsePool sync.Pool
+var (
+	responsePool sync.Pool
+	// NoResponseBody is an io.ReadCloser with no bytes. Read always returns EOF
+	// and Close always returns nil. It can be used in an ingoing client
+	// response to explicitly signal that a response has zero bytes.
+	NoResponseBody = noBody{}
+)
 
 // Response represents HTTP response.
 //
@@ -334,6 +340,9 @@ func (resp *Response) SetBody(body []byte) {
 }
 
 func (resp *Response) BodyStream() io.Reader {
+	if resp.bodyStream == nil {
+		resp.bodyStream = NoResponseBody
+	}
 	return resp.bodyStream
 }
 
diff --git a/pkg/route/engine.go b/pkg/route/engine.go
index 10cffac..480e092 100644
--- a/pkg/route/engine.go
+++ b/pkg/route/engine.go
@@ -341,8 +341,8 @@ func (engine *Engine) Run() (err error) {
 		return err
 	}
 
-	if !atomic.CompareAndSwapUint32(&engine.status, statusInitialized, statusRunning) {
-		return errAlreadyRunning
+	if err = engine.MarkAsRunning(); err != nil {
+		return err
 	}
 	defer atomic.StoreUint32(&engine.status, statusClosed)
 
@@ -1022,3 +1022,12 @@ func versionToALNP(v uint32) string {
 	}
 	return ""
 }
+
+// MarkAsRunning will mark the status of the hertz engine as "running".
+// Warning: do not call this method by yourself, unless you know what you are doing.
+func (engine *Engine) MarkAsRunning() (err error) {
+	if !atomic.CompareAndSwapUint32(&engine.status, statusInitialized, statusRunning) {
+		return errAlreadyRunning
+	}
+	return nil
+}
diff --git a/version.go b/version.go
index 2f7a82f..2793606 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.6.7"
+	Version = "v0.6.8"
 )


Test patch:
diff --git a/pkg/app/client/client_test.go b/pkg/app/client/client_test.go
index 8449cd4..e7dcbf5 100644
--- a/pkg/app/client/client_test.go
+++ b/pkg/app/client/client_test.go
@@ -2269,7 +2269,7 @@ func TestClientDoWithDialFunc(t *testing.T) {
 
 func TestClientState(t *testing.T) {
 	opt := config.NewOptions([]config.Option{})
-	opt.Addr = "127.0.0.1:11000"
+	opt.Addr = ":10037"
 	engine := route.NewEngine(opt)
 	go engine.Run()
 
@@ -2282,12 +2282,12 @@ func TestClientState(t *testing.T) {
 			case int32(0):
 				assert.DeepEqual(t, 1, hcs.ConnPoolState().TotalConnNum)
 				assert.DeepEqual(t, 1, hcs.ConnPoolState().PoolConnNum)
-				assert.DeepEqual(t, "127.0.0.1:11000", hcs.ConnPoolState().Addr)
+				assert.DeepEqual(t, "127.0.0.1:10037", hcs.ConnPoolState().Addr)
 				atomic.StoreInt32(&state, int32(1))
 			case int32(1):
 				assert.DeepEqual(t, 0, hcs.ConnPoolState().TotalConnNum)
 				assert.DeepEqual(t, 0, hcs.ConnPoolState().PoolConnNum)
-				assert.DeepEqual(t, "127.0.0.1:11000", hcs.ConnPoolState().Addr)
+				assert.DeepEqual(t, "127.0.0.1:10037", hcs.ConnPoolState().Addr)
 				atomic.StoreInt32(&state, int32(2))
 				return
 			case int32(2):
@@ -2295,7 +2295,7 @@ func TestClientState(t *testing.T) {
 			}
 		}, time.Second*9))
 
-	client.Get(context.Background(), nil, "http://127.0.0.1:11000")
+	client.Get(context.Background(), nil, "http://127.0.0.1:10037")
 	time.Sleep(time.Second * 22)
 }
 
diff --git a/pkg/app/context_test.go b/pkg/app/context_test.go
index 8bf52f4..88cd263 100644
--- a/pkg/app/context_test.go
+++ b/pkg/app/context_test.go
@@ -17,10 +17,15 @@
 package app
 
 import (
+	"bytes"
 	"context"
+	"encoding/xml"
 	"errors"
 	"fmt"
+	"html/template"
 	"io/ioutil"
+	"net"
+	"os"
 	"reflect"
 	"strings"
 	"testing"
@@ -82,6 +87,17 @@ func TestContext(t *testing.T) {
 	}
 }
 
+func TestValue(t *testing.T) {
+	ctx := NewContext(0)
+
+	v := ctx.Value("testContextKey")
+	assert.Nil(t, v)
+
+	ctx.Set("testContextKey", "testValue")
+	v = ctx.Value("testContextKey")
+	assert.DeepEqual(t, "testValue", v)
+}
+
 func TestContextNotModified(t *testing.T) {
 	reqContext := NewContext(0)
 	reqContext.Response.SetStatusCode(consts.StatusOK)
@@ -273,6 +289,10 @@ func TestQuery(t *testing.T) {
 		t.Fatalf("unexpected query: %#v, expected menu", ctx.Query("name"))
 	}
 
+	if ctx.DefaultQuery("name", "default value") != "menu" {
+		t.Fatalf("unexpected query: %#v, expected menu", ctx.Query("name"))
+	}
+
 	if ctx.DefaultQuery("defaultQuery", "default value") != "default value" {
 		t.Fatalf("unexpected query: %#v, expected `default value`", ctx.Query("defaultQuery"))
 	}
@@ -436,6 +456,15 @@ tailfoobar`
 		}
 	}
 
+	err = ctx.SaveUploadedFile(f.File["fileaaa"][0], "TODO")
+	assert.Nil(t, err)
+	fileInfo, err := os.Stat("TODO")
+	assert.Nil(t, err)
+	assert.DeepEqual(t, "TODO", fileInfo.Name())
+	assert.DeepEqual(t, f.File["fileaaa"][0].Size, fileInfo.Size())
+	err = os.Remove("TODO")
+	assert.Nil(t, err)
+
 	ff, err := ctx.FormFile("fileaaa")
 	if err != nil || ff == nil {
 		t.Fatalf("unexpected error happened when ctx.FormFile()")
@@ -525,6 +554,13 @@ func TestRequestContext_Header(t *testing.T) {
 	if val != "" {
 		t.Fatalf("unexpected %q. Expecting %q", val, "")
 	}
+
+	c.Header("header_key1", "header_val1")
+	c.Header("header_key1", "")
+	val = string(c.Response.Header.Peek("header_key1"))
+	if val != "" {
+		t.Fatalf("unexpected %q. Expecting %q", val, "")
+	}
 }
 
 func TestRequestContext_Keys(t *testing.T) {
@@ -554,6 +590,10 @@ func TestRequestContext_Handler(t *testing.T) {
 	if val != "123" {
 		t.Fatalf("unexpected %v. Expecting %v", val, "123")
 	}
+
+	c.handlers = nil
+	handler := c.Handler()
+	assert.Nil(t, handler)
 }
 
 func TestRequestContext_Handlers(t *testing.T) {
@@ -576,6 +616,24 @@ func TestRequestContext_HandlerName(t *testing.T) {
 	}
 }
 
+func TestNext(t *testing.T) {
+	c := NewContext(0)
+	a := 0
+
+	testFunc1 := func(c context.Context, ctx *RequestContext) {
+		a = 1
+	}
+	testFunc3 := func(c context.Context, ctx *RequestContext) {
+		a = 3
+	}
+	c.handlers = HandlersChain{testFunc1, testFunc3}
+
+	c.Next(context.Background())
+
+	assert.True(t, c.index == 2)
+	assert.DeepEqual(t, 3, a)
+}
+
 func TestContextError(t *testing.T) {
 	c := NewContext(0)
 	assert.Nil(t, c.Errors)
@@ -631,6 +689,77 @@ func TestRender(t *testing.T) {
 
 	assert.DeepEqual(t, consts.StatusOK, c.Response.StatusCode())
 	assert.True(t, strings.Contains(string(c.Response.Body()), "test"))
+
+	c.Reset()
+	c.Render(110, &render.Data{
+		ContentType: "application/json; charset=utf-8",
+		Data:        []byte("{\"test\":1}"),
+	})
+	assert.DeepEqual(t, "application/json; charset=utf-8", string(c.Response.Header.ContentType()))
+	assert.DeepEqual(t, "", string(c.Response.Body()))
+
+	c.Reset()
+	c.Render(consts.StatusNoContent, &render.Data{
+		ContentType: "application/json; charset=utf-8",
+		Data:        []byte("{\"test\":1}"),
+	})
+	assert.DeepEqual(t, "application/json; charset=utf-8", string(c.Response.Header.ContentType()))
+	assert.DeepEqual(t, "", string(c.Response.Body()))
+
+	c.Reset()
+	c.Render(consts.StatusNotModified, &render.Data{
+		ContentType: "application/json; charset=utf-8",
+		Data:        []byte("{\"test\":1}"),
+	})
+	assert.DeepEqual(t, "application/json; charset=utf-8", string(c.Response.Header.ContentType()))
+	assert.DeepEqual(t, "", string(c.Response.Body()))
+}
+
+func TestHTML(t *testing.T) {
+	c := NewContext(0)
+
+	tmpl := template.Must(template.New("").
+		Delims("{[{", "}]}").
+		Funcs(template.FuncMap{}).
+		ParseFiles("../common/testdata/template/index.tmpl"))
+
+	r := &render.HTMLProduction{Template: tmpl}
+	c.HTMLRender = r
+	c.HTML(consts.StatusOK, "index.tmpl", utils.H{"title": "Main website"})
+
+	assert.DeepEqual(t, []byte("text/html; charset=utf-8"), c.Response.Header.Peek("Content-Type"))
+	assert.DeepEqual(t, []byte("<html><h1>Main website</h1></html>"), c.Response.Body())
+}
+
+type xmlmap map[string]interface{}
+
+// Allows type H to be used with xml.Marshal
+func (h xmlmap) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
+	start.Name = xml.Name{
+		Space: "",
+		Local: "map",
+	}
+	if err := e.EncodeToken(start); err != nil {
+		return err
+	}
+	for key, value := range h {
+		elem := xml.StartElement{
+			Name: xml.Name{Space: "", Local: key},
+			Attr: []xml.Attr{},
+		}
+		if err := e.EncodeElement(value, elem); err != nil {
+			return err
+		}
+	}
+
+	return e.EncodeToken(xml.EndElement{Name: start.Name})
+}
+
+func TestXML(t *testing.T) {
+	c := NewContext(0)
+	c.XML(consts.StatusOK, xmlmap{"foo": "bar"})
+	assert.DeepEqual(t, []byte("<map><foo>bar</foo></map>"), c.Response.Body())
+	assert.DeepEqual(t, []byte("application/xml; charset=utf-8"), c.Response.Header.Peek("Content-Type"))
 }
 
 func TestJSON(t *testing.T) {
@@ -654,6 +783,7 @@ func TestContextReset(t *testing.T) {
 	c.Params = param.Params{param.Param{}}
 	c.Error(errors.New("test")) // nolint: errcheck
 	c.Set("foo", "bar")
+	c.Finished()
 	c.Request.SetIsTLS(true)
 	c.ResetWithoutConn()
 	c.Request.URI()
@@ -664,6 +794,7 @@ func TestContextReset(t *testing.T) {
 	assert.Nil(t, c.Errors.ByType(errs.ErrorTypeAny))
 	assert.DeepEqual(t, 0, len(c.Params))
 	assert.DeepEqual(t, int8(-1), c.index)
+	assert.Nil(t, c.finished)
 }
 
 func TestContextContentType(t *testing.T) {
@@ -672,51 +803,66 @@ func TestContextContentType(t *testing.T) {
 	assert.DeepEqual(t, consts.MIMEApplicationJSONUTF8, bytesconv.B2s(c.ContentType()))
 }
 
-func TestClientIp(t *testing.T) {
+type MockIpConn struct {
+	*mock.Conn
+	RemoteIp string
+	Port     int
+}
+
+func (c *MockIpConn) RemoteAddr() net.Addr {
+	return &net.UDPAddr{
+		IP:   net.ParseIP(c.RemoteIp),
+		Port: c.Port,
+	}
+}
+
+func newContextClientIPTest() *RequestContext {
 	c := NewContext(0)
-	c.conn = mock.NewConn("")
-	// 0.0.0.0 simulates a trusted proxy server
-	c.Request.Header.Set("X-Forwarded-For", "  126.0.0.2, 0.0.0.0 ")
-	val := c.ClientIP()
-	if val != "126.0.0.2" {
-		t.Fatalf("unexpected %v. Expecting %v", val, "126.0.0.2")
-	}
-	// no proxy server
-	c = NewContext(0)
-	c.conn = mock.NewConn("")
-	c.Request.Header.Set("X-Real-Ip", "126.0.0.1")
-	val = c.ClientIP()
-	if val != "126.0.0.1" {
-		t.Fatalf("unexpected %v. Expecting %v", val, "126.0.0.1")
-	}
-	// custom RemoteIPHeaders and TrustedProxies
+	c.conn = &MockIpConn{
+		Conn:     mock.NewConn(""),
+		RemoteIp: "127.0.0.1",
+		Port:     8080,
+	}
+	c.Request.Header.Set("X-Real-IP", " 10.10.10.10  ")
+	c.Request.Header.Set("X-Forwarded-For", "  20.20.20.20, 30.30.30.30")
+	return c
+}
+
+func TestClientIp(t *testing.T) {
+	c := newContextClientIPTest()
+	// default X-Forwarded-For and X-Real-IP behaviour
+	assert.DeepEqual(t, "20.20.20.20", c.ClientIP())
+
+	c.Request.Header.DelBytes([]byte("X-Forwarded-For"))
+	assert.DeepEqual(t, "10.10.10.10", c.ClientIP())
+
+	c.Request.Header.Set("X-Forwarded-For", "30.30.30.30  ")
+	assert.DeepEqual(t, "30.30.30.30", c.ClientIP())
+
+	// No trusted CIDRS
+	c = newContextClientIPTest()
 	opts := ClientIPOptions{
 		RemoteIPHeaders: []string{"X-Forwarded-For", "X-Real-IP"},
-		TrustedProxies: map[string]bool{
-			"0.0.0.0": true,
-		},
+		TrustedCIDRs:    nil,
 	}
-	c = NewContext(0)
 	c.SetClientIPFunc(ClientIPWithOption(opts))
-	c.conn = mock.NewConn("")
-	c.Request.Header.Set("X-Forwarded-For", "  126.0.0.2, 0.0.0.0 ")
-	val = c.ClientIP()
-	if val != "126.0.0.2" {
-		t.Fatalf("unexpected %v. Expecting %v", val, "126.0.0.2")
-	}
-	// no trusted proxy server
+	assert.DeepEqual(t, "127.0.0.1", c.ClientIP())
+
+	_, cidr, _ := net.ParseCIDR("30.30.30.30/32")
 	opts = ClientIPOptions{
 		RemoteIPHeaders: []string{"X-Forwarded-For", "X-Real-IP"},
-		TrustedProxies:  nil,
+		TrustedCIDRs:    []*net.IPNet{cidr},
 	}
-	c = NewContext(0)
 	c.SetClientIPFunc(ClientIPWithOption(opts))
-	c.conn = mock.NewConn("")
-	c.Request.Header.Set("X-Forwarded-For", "  126.0.0.2, 0.0.0.0 ")
-	val = c.ClientIP()
-	if val != "0.0.0.0" {
-		t.Fatalf("unexpected %v. Expecting %v", val, "0.0.0.0")
+	assert.DeepEqual(t, "127.0.0.1", c.ClientIP())
+
+	_, cidr, _ = net.ParseCIDR("127.0.0.1/32")
+	opts = ClientIPOptions{
+		RemoteIPHeaders: []string{"X-Forwarded-For", "X-Real-IP"},
+		TrustedCIDRs:    []*net.IPNet{cidr},
 	}
+	c.SetClientIPFunc(ClientIPWithOption(opts))
+	assert.DeepEqual(t, "30.30.30.30", c.ClientIP())
 }
 
 func TestSetClientIPFunc(t *testing.T) {
@@ -751,6 +897,16 @@ func TestRemoteAddr(t *testing.T) {
 	assert.DeepEqual(t, "0.0.0.0:0", addr)
 }
 
+func TestRequestBodyStream(t *testing.T) {
+	c := NewContext(0)
+	s := "testRequestBodyStream"
+	mr := bytes.NewBufferString(s)
+	c.Request.SetBodyStream(mr, -1)
+	data, err := ioutil.ReadAll(c.RequestBodyStream())
+	assert.Nil(t, err)
+	assert.DeepEqual(t, "testRequestBodyStream", string(data))
+}
+
 func TestContextIsAborted(t *testing.T) {
 	ctx := NewContext(0)
 	assert.False(t, ctx.IsAborted())
@@ -828,6 +984,23 @@ func TestRequestCtxFormValue(t *testing.T) {
 	if string(v) != "1" {
 		t.Fatalf("unexpected value %q. Expecting %q", v, "1")
 	}
+
+	ctx.Request.Reset()
+	s := `------WebKitFormBoundaryJwfATyF8tmxSJnLg
+Content-Disposition: form-data; name="f"
+
+fff
+------WebKitFormBoundaryJwfATyF8tmxSJnLg
+`
+	mr := bytes.NewBufferString(s)
+	ctx.Request.SetBodyStream(mr, -1)
+	ctx.Request.Header.SetContentLength(len(s))
+	ctx.Request.Header.SetContentTypeBytes([]byte("multipart/form-data; boundary=----WebKitFormBoundaryJwfATyF8tmxSJnLg"))
+
+	v = ctx.FormValue("f")
+	if string(v) != "fff" {
+		t.Fatalf("unexpected value %q. Expecting %q", v, "fff")
+	}
 }
 
 func TestSetCustomFormValueFunc(t *testing.T) {
@@ -1076,6 +1249,25 @@ func TestForEachKey(t *testing.T) {
 	assert.True(t, ok)
 }
 
+func TestFlush(t *testing.T) {
+	ctx := NewContext(0)
+	err := ctx.Flush()
+	assert.Nil(t, err)
+}
+
+func TestConn(t *testing.T) {
+	ctx := NewContext(0)
+
+	conn := mock.NewConn("")
+
+	ctx.SetConn(conn)
+	connRes := ctx.GetConn()
+
+	val1 := reflect.ValueOf(conn).Pointer()
+	val2 := reflect.ValueOf(connRes).Pointer()
+	assert.DeepEqual(t, val1, val2)
+}
+
 func TestHijackHandler(t *testing.T) {
 	ctx := NewContext(0)
 	handle := func(c network.Conn) {
@@ -1089,6 +1281,32 @@ func TestHijackHandler(t *testing.T) {
 	assert.DeepEqual(t, val1, val2)
 }
 
+func TestGetReader(t *testing.T) {
+	ctx := NewContext(0)
+
+	conn := mock.NewConn("")
+
+	ctx.SetConn(conn)
+	connRes := ctx.GetReader()
+
+	val1 := reflect.ValueOf(conn).Pointer()
+	val2 := reflect.ValueOf(connRes).Pointer()
+	assert.DeepEqual(t, val1, val2)
+}
+
+func TestGetWriter(t *testing.T) {
+	ctx := NewContext(0)
+
+	conn := mock.NewConn("")
+
+	ctx.SetConn(conn)
+	connRes := ctx.GetWriter()
+
+	val1 := reflect.ValueOf(conn).Pointer()
+	val2 := reflect.ValueOf(connRes).Pointer()
+	assert.DeepEqual(t, val1, val2)
+}
+
 func TestIndex(t *testing.T) {
 	ctx := NewContext(0)
 	ctx.ResetWithoutConn()
diff --git a/pkg/app/fs_test.go b/pkg/app/fs_test.go
index 1cd545e..c4ad0e4 100644
--- a/pkg/app/fs_test.go
+++ b/pkg/app/fs_test.go
@@ -53,6 +53,7 @@ import (
 	"testing"
 	"time"
 
+	"github.com/cloudwego/hertz/pkg/common/test/assert"
 	"github.com/cloudwego/hertz/pkg/common/test/mock"
 	"github.com/cloudwego/hertz/pkg/protocol"
 	"github.com/cloudwego/hertz/pkg/protocol/consts"
@@ -223,6 +224,32 @@ func TestServeFileSmallNoReadFrom(t *testing.T) {
 	if body != teststr {
 		t.Fatalf("expected '%s'", teststr)
 	}
+
+	data := make([]byte, len([]byte(teststr)))
+	nn, err := reader.Read(data)
+	assert.DeepEqual(t, len([]byte(teststr)), nn)
+	assert.Nil(t, err)
+	assert.DeepEqual(t, teststr, string(data))
+	assert.DeepEqual(t, reader.startPos, len([]byte(teststr)))
+
+	nn, err = reader.Read(data)
+	assert.DeepEqual(t, 0, nn)
+	assert.DeepEqual(t, io.EOF, err)
+
+	data1 := make([]byte, 2)
+	reader.startPos = len([]byte(teststr)) - 1
+	nn, err = reader.Read(data1)
+	assert.DeepEqual(t, []byte("!"), []byte{data1[0]})
+	assert.DeepEqual(t, 1, nn)
+	assert.DeepEqual(t, nil, err)
+
+	reader.startPos = 0
+	reader.ff.f = nil
+	buf = bytes.NewBuffer(nil)
+	reader.ff.dirIndex = make([]byte, len([]byte(teststr)))
+	n, err = reader.WriteTo(pureWriter{buf})
+	assert.DeepEqual(t, int64(len(teststr)), n)
+	assert.Nil(t, err)
 }
 
 type pureWriter struct {
@@ -660,3 +687,11 @@ func TestServeFileContentType(t *testing.T) {
 		t.Fatalf("Unexpected Content-Type, expected: %q got %q", expected, r.Header.ContentType())
 	}
 }
+
+func TestFileSmallUpdateByteRange(t *testing.T) {
+	r := &fsSmallFileReader{}
+	err := r.UpdateByteRange(1, 1)
+	assert.Nil(t, err)
+	assert.DeepEqual(t, 1, r.startPos)
+	assert.DeepEqual(t, 2, r.endPos)
+}
diff --git a/pkg/app/middlewares/client/sd/options_test.go b/pkg/app/middlewares/client/sd/options_test.go
index 44a7356..d0fad12 100644
--- a/pkg/app/middlewares/client/sd/options_test.go
+++ b/pkg/app/middlewares/client/sd/options_test.go
@@ -20,16 +20,27 @@ import (
 	"context"
 	"testing"
 
+	"github.com/cloudwego/hertz/pkg/app/client/loadbalance"
 	"github.com/cloudwego/hertz/pkg/common/test/assert"
 )
 
 func TestWithCustomizedAddrs(t *testing.T) {
 	var options []ServiceDiscoveryOption
-	options = append(options, WithCustomizedAddrs("127.0.0.1:8080"))
+	options = append(options, WithCustomizedAddrs("127.0.0.1:8080", "/tmp/unix_ss"))
 	opts := &ServiceDiscoveryOptions{}
 	opts.Apply(options)
-	assert.Assert(t, opts.Resolver.Name() == "127.0.0.1:8080")
+	assert.Assert(t, opts.Resolver.Name() == "127.0.0.1:8080,/tmp/unix_ss")
 	res, err := opts.Resolver.Resolve(context.Background(), "")
 	assert.Assert(t, err == nil)
 	assert.Assert(t, res.Instances[0].Address().String() == "127.0.0.1:8080")
+	assert.Assert(t, res.Instances[1].Address().String() == "/tmp/unix_ss")
+}
+
+func TestWithLoadBalanceOptions(t *testing.T) {
+	balance := loadbalance.NewWeightedBalancer()
+	var options []ServiceDiscoveryOption
+	options = append(options, WithLoadBalanceOptions(balance, loadbalance.DefaultLbOpts))
+	opts := &ServiceDiscoveryOptions{}
+	opts.Apply(options)
+	assert.Assert(t, opts.Balancer.Name() == "weight_random")
 }
diff --git a/pkg/app/server/binding/binding_test.go b/pkg/app/server/binding/binding_test.go
index 84c5980..a9050f9 100644
--- a/pkg/app/server/binding/binding_test.go
+++ b/pkg/app/server/binding/binding_test.go
@@ -164,6 +164,19 @@ func TestJsonBind(t *testing.T) {
 	// NOTE: The default does not support string to go int conversion in json.
 	// You can add "string" tags or use other json unmarshal libraries that support this feature
 	assert.DeepEqual(t, 100, req.D)
+
+	req = Test{}
+	UseStdJSONUnmarshaler()
+	err = BindAndValidate(r, &req, nil)
+	if err != nil {
+		t.Fatalf("unexpected error: %v", err)
+	}
+	assert.DeepEqual(t, "aaa", req.A)
+	assert.DeepEqual(t, 2, len(req.B))
+	assert.DeepEqual(t, "ccc", req.C)
+	// NOTE: The default does not support string to go int conversion in json.
+	// You can add "string" tags or use other json unmarshal libraries that support this feature
+	assert.DeepEqual(t, 100, req.D)
 }
 
 // TestQueryParamInconsistency tests the Inconsistency for GetQuery(), the other unit test for GetFunc() in request.go  are similar to it
diff --git a/pkg/app/server/binding/request_test.go b/pkg/app/server/binding/request_test.go
new file mode 100644
index 0000000..b3bb705
--- /dev/null
+++ b/pkg/app/server/binding/request_test.go
@@ -0,0 +1,235 @@
+/*
+ * 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 binding
+
+import (
+	"bytes"
+	"fmt"
+	"testing"
+
+	"github.com/cloudwego/hertz/pkg/common/test/assert"
+	"github.com/cloudwego/hertz/pkg/protocol"
+	"github.com/cloudwego/hertz/pkg/protocol/consts"
+)
+
+func TestGetQuery(t *testing.T) {
+	r := protocol.NewRequest("GET", "/foo", nil)
+	r.SetRequestURI("/foo/bar?para1=hertz&para2=query1&para2=query2&para3=1&para3=2")
+
+	bindReq := bindRequest{
+		req: r,
+	}
+
+	values := bindReq.GetQuery()
+
+	assert.DeepEqual(t, []string{"hertz"}, values["para1"])
+	assert.DeepEqual(t, []string{"query1", "query2"}, values["para2"])
+	assert.DeepEqual(t, []string{"1", "2"}, values["para3"])
+}
+
+func TestGetPostForm(t *testing.T) {
+	data := "a=aaa&b=b1&b=b2&c=ccc&d=100"
+	mr := bytes.NewBufferString(data)
+
+	r := protocol.NewRequest("POST", "/foo", mr)
+	r.Header.SetContentTypeBytes([]byte(consts.MIMEApplicationHTMLForm))
+	r.Header.SetContentLength(len(data))
+
+	bindReq := bindRequest{
+		req: r,
+	}
+
+	values, err := bindReq.GetPostForm()
+	if err != nil {
+		t.Fatalf("unexpected error: %v", err)
+	}
+
+	assert.DeepEqual(t, []string{"aaa"}, values["a"])
+	assert.DeepEqual(t, []string{"b1", "b2"}, values["b"])
+	assert.DeepEqual(t, []string{"ccc"}, values["c"])
+	assert.DeepEqual(t, []string{"100"}, values["d"])
+}
+
+func TestGetForm(t *testing.T) {
+	data := "a=aaa&b=b1&b=b2&c=ccc&d=100"
+	mr := bytes.NewBufferString(data)
+
+	r := protocol.NewRequest("POST", "/foo", mr)
+	r.SetRequestURI("/foo/bar?para1=hertz&para2=query1&para2=query2&para3=1&para3=2")
+	r.Header.SetContentTypeBytes([]byte(consts.MIMEApplicationHTMLForm))
+	r.Header.SetContentLength(len(data))
+
+	bindReq := bindRequest{
+		req: r,
+	}
+
+	values, err := bindReq.GetForm()
+	if err != nil {
+		t.Fatalf("unexpected error: %v", err)
+	}
+
+	assert.DeepEqual(t, []string{"aaa"}, values["a"])
+	assert.DeepEqual(t, []string{"b1", "b2"}, values["b"])
+	assert.DeepEqual(t, []string{"ccc"}, values["c"])
+	assert.DeepEqual(t, []string{"100"}, values["d"])
+	assert.DeepEqual(t, []string{"hertz"}, values["para1"])
+	assert.DeepEqual(t, []string{"query1", "query2"}, values["para2"])
+	assert.DeepEqual(t, []string{"1", "2"}, values["para3"])
+}
+
+func TestGetCookies(t *testing.T) {
+	r := protocol.NewRequest("POST", "/foo", nil)
+	r.SetCookie("cookie1", "cookies1")
+	r.SetCookie("cookie2", "cookies2")
+
+	bindReq := bindRequest{
+		req: r,
+	}
+
+	values := bindReq.GetCookies()
+
+	assert.DeepEqual(t, "cookies1", values[0].Value)
+	assert.DeepEqual(t, "cookies2", values[1].Value)
+}
+
+func TestGetHeader(t *testing.T) {
+	headers := map[string]string{
+		"Header1": "headers1",
+		"Header2": "headers2",
+	}
+
+	r := protocol.NewRequest("GET", "/foo", nil)
+	r.SetHeaders(headers)
+	r.SetHeader("Header3", "headers3")
+
+	bindReq := bindRequest{
+		req: r,
+	}
+
+	values := bindReq.GetHeader()
+
+	assert.DeepEqual(t, []string{"headers1"}, values["Header1"])
+	assert.DeepEqual(t, []string{"headers2"}, values["Header2"])
+	assert.DeepEqual(t, []string{"headers3"}, values["Header3"])
+}
+
+func TestGetMethod(t *testing.T) {
+	r := protocol.NewRequest("GET", "/foo", nil)
+
+	bindReq := bindRequest{
+		req: r,
+	}
+
+	values := bindReq.GetMethod()
+
+	assert.DeepEqual(t, "GET", values)
+}
+
+func TestGetContentType(t *testing.T) {
+	data := "a=aaa&b=b1&b=b2&c=ccc&d=100"
+	mr := bytes.NewBufferString(data)
+
+	r := protocol.NewRequest("POST", "/foo", mr)
+	r.Header.SetContentTypeBytes([]byte(consts.MIMEApplicationHTMLForm))
+	r.Header.SetContentLength(len(data))
+
+	bindReq := bindRequest{
+		req: r,
+	}
+
+	values := bindReq.GetContentType()
+
+	assert.DeepEqual(t, consts.MIMEApplicationHTMLForm, values)
+}
+
+func TestGetBody(t *testing.T) {
+	data := "a=aaa&b=b1&b=b2&c=ccc&d=100"
+	mr := bytes.NewBufferString(data)
+
+	r := protocol.NewRequest("POST", "/foo", mr)
+	r.Header.SetContentTypeBytes([]byte(consts.MIMEApplicationHTMLForm))
+	r.Header.SetContentLength(len(data))
+
+	bindReq := bindRequest{
+		req: r,
+	}
+
+	values, err := bindReq.GetBody()
+	if err != nil {
+		t.Fatalf("unexpected error: %v", err)
+	}
+
+	assert.DeepEqual(t, []byte("a=aaa&b=b1&b=b2&c=ccc&d=100"), values)
+}
+
+func TestGetFileHeaders(t *testing.T) {
+	s := `------WebKitFormBoundaryJwfATyF8tmxSJnLg
+Content-Disposition: form-data; name="f"
+
+fff
+------WebKitFormBoundaryJwfATyF8tmxSJnLg
+Content-Disposition: form-data; name="F1"; filename="TODO1"
+Content-Type: application/octet-stream
+
+- SessionClient with referer and cookies support.
+- Client with requests' pipelining support.
+- ProxyHandler similar to FSHandler.
+- WebSockets. See https://tools.ietf.org/html/rfc6455 .
+- HTTP/2.0. See https://tools.ietf.org/html/rfc7540 .
+------WebKitFormBoundaryJwfATyF8tmxSJnLg
+Content-Disposition: form-data; name="F1"; filename="TODO2"
+Content-Type: application/octet-stream
+
+- SessionClient with referer and cookies support.
+- Client with requests' pipelining support.
+- ProxyHandler similar to FSHandler.
+- WebSockets. See https://tools.ietf.org/html/rfc6455 .
+- HTTP/2.0. See https://tools.ietf.org/html/rfc7540 .
+------WebKitFormBoundaryJwfATyF8tmxSJnLg
+Content-Disposition: form-data; name="F2"; filename="TODO3"
+Content-Type: application/octet-stream
+
+- SessionClient with referer and cookies support.
+- Client with requests' pipelining support.
+- ProxyHandler similar to FSHandler.
+- WebSockets. See https://tools.ietf.org/html/rfc6455 .
+- HTTP/2.0. See https://tools.ietf.org/html/rfc7540 .
+
+------WebKitFormBoundaryJwfATyF8tmxSJnLg--
+tailfoobar`
+
+	mr := bytes.NewBufferString(s)
+
+	r := protocol.NewRequest("POST", "/foo", mr)
+	r.Header.SetContentTypeBytes([]byte("multipart/form-data; boundary=----WebKitFormBoundaryJwfATyF8tmxSJnLg"))
+	r.Header.SetContentLength(len(s))
+
+	bindReq := bindRequest{
+		req: r,
+	}
+
+	values, err := bindReq.GetFileHeaders()
+	if err != nil {
+		t.Fatalf("unexpected error: %v", err)
+	}
+
+	fmt.Printf("%v\n", values)
+
+	assert.DeepEqual(t, "TODO1", values["F1"][0].Filename)
+	assert.DeepEqual(t, "TODO2", values["F1"][1].Filename)
+	assert.DeepEqual(t, "TODO3", values["F2"][0].Filename)
+}
diff --git a/pkg/app/server/hertz_test.go b/pkg/app/server/hertz_test.go
index bc3e1a0..3ce91a8 100644
--- a/pkg/app/server/hertz_test.go
+++ b/pkg/app/server/hertz_test.go
@@ -50,7 +50,7 @@ import (
 )
 
 func TestHertz_Run(t *testing.T) {
-	hertz := New(WithHostPorts("127.0.0.1:6666"))
+	hertz := Default(WithHostPorts("127.0.0.1:6666"))
 	hertz.GET("/test", func(c context.Context, ctx *app.RequestContext) {
 		time.Sleep(time.Second)
 		path := ctx.Request.URI().PathOriginal()
@@ -62,6 +62,8 @@ func TestHertz_Run(t *testing.T) {
 		atomic.StoreUint32(&testint, 1)
 	})
 
+	assert.Assert(t, len(hertz.Handlers) == 1)
+
 	go hertz.Spin()
 	time.Sleep(100 * time.Millisecond)
 
@@ -145,7 +147,7 @@ func TestHertz_GracefulShutdown(t *testing.T) {
 }
 
 func TestLoadHTMLGlob(t *testing.T) {
-	engine := New(WithMaxRequestBodySize(15), WithHostPorts("127.0.0.1:8890"))
+	engine := New(WithMaxRequestBodySize(15), WithHostPorts("127.0.0.1:8893"))
 	engine.Delims("{[{", "}]}")
 	engine.LoadHTMLGlob("../../common/testdata/template/index.tmpl")
 	engine.GET("/index", func(c context.Context, ctx *app.RequestContext) {
@@ -155,7 +157,7 @@ func TestLoadHTMLGlob(t *testing.T) {
 	})
 	go engine.Run()
 	time.Sleep(200 * time.Millisecond)
-	resp, _ := http.Get("http://127.0.0.1:8890/index")
+	resp, _ := http.Get("http://127.0.0.1:8893/index")
 	assert.DeepEqual(t, consts.StatusOK, resp.StatusCode)
 	b := make([]byte, 100)
 	n, _ := resp.Body.Read(b)
@@ -690,7 +692,7 @@ type CloseWithoutResetBuffer interface {
 }
 
 func TestOnprepare(t *testing.T) {
-	h := New(
+	h1 := New(
 		WithHostPorts("localhost:9229"),
 		WithOnConnect(func(ctx context.Context, conn network.Conn) context.Context {
 			b, err := conn.Peek(3)
@@ -703,42 +705,42 @@ func TestOnprepare(t *testing.T) {
 			}
 			return ctx
 		}))
-	h.GET("/ping", func(ctx context.Context, c *app.RequestContext) {
+	h1.GET("/ping", func(ctx context.Context, c *app.RequestContext) {
 		c.JSON(consts.StatusOK, utils.H{"ping": "pong"})
 	})
 
-	go h.Spin()
+	go h1.Spin()
 	time.Sleep(time.Second)
 	_, _, err := c.Get(context.Background(), nil, "http://127.0.0.1:9229/ping")
 	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(
+	h2 := New(
 		WithOnAccept(func(conn net.Conn) context.Context {
 			conn.Close()
 			return context.Background()
 		}),
 		WithHostPorts("localhost:9230"))
-	h.GET("/ping", func(ctx context.Context, c *app.RequestContext) {
+	h2.GET("/ping", func(ctx context.Context, c *app.RequestContext) {
 		c.JSON(consts.StatusOK, utils.H{"ping": "pong"})
 	})
-	go h.Spin()
+	go h2.Spin()
 	time.Sleep(time.Second)
 	_, _, err = c.Get(context.Background(), nil, "http://127.0.0.1:9230/ping")
 	if err == nil {
 		t.Fatalf("err should not be nil")
 	}
 
-	h = New(
+	h3 := 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) {
+	h3.GET("/ping", func(ctx context.Context, c *app.RequestContext) {
 		c.JSON(consts.StatusOK, utils.H{"ping": "pong"})
 	})
-	go h.Spin()
+	go h3.Spin()
 	time.Sleep(time.Second)
 	c.Get(context.Background(), nil, "http://127.0.0.1:9231/ping")
 }
diff --git a/pkg/app/server/option_test.go b/pkg/app/server/option_test.go
index d9abda2..aef554c 100644
--- a/pkg/app/server/option_test.go
+++ b/pkg/app/server/option_test.go
@@ -17,6 +17,10 @@
 package server
 
 import (
+	"context"
+	"net"
+	"reflect"
+	"syscall"
 	"testing"
 	"time"
 
@@ -25,6 +29,7 @@ import (
 	"github.com/cloudwego/hertz/pkg/common/test/assert"
 	"github.com/cloudwego/hertz/pkg/common/tracer/stats"
 	"github.com/cloudwego/hertz/pkg/common/utils"
+	"github.com/cloudwego/hertz/pkg/network"
 )
 
 func TestOptions(t *testing.T) {
@@ -33,6 +38,12 @@ func TestOptions(t *testing.T) {
 		Weight:      10,
 		Addr:        utils.NewNetAddr("tcp", ":8888"),
 	}
+	cfg := &net.ListenConfig{Control: func(network, address string, c syscall.RawConn) error {
+		return c.Control(func(fd uintptr) {})
+	}}
+	transporter := func(options *config.Options) network.Transporter {
+		return &mockTransporter{}
+	}
 	opt := config.NewOptions([]config.Option{
 		WithReadTimeout(time.Second),
 		WithWriteTimeout(time.Second),
@@ -62,6 +73,8 @@ func TestOptions(t *testing.T) {
 		WithTraceLevel(stats.LevelDisabled),
 		WithRegistry(nil, info),
 		WithAutoReloadRender(true, 5*time.Second),
+		WithListenConfig(cfg),
+		WithAltTransport(transporter),
 	})
 	assert.DeepEqual(t, opt.ReadTimeout, time.Second)
 	assert.DeepEqual(t, opt.WriteTimeout, time.Second)
@@ -92,6 +105,8 @@ func TestOptions(t *testing.T) {
 	assert.DeepEqual(t, opt.Registry, nil)
 	assert.DeepEqual(t, opt.AutoReloadRender, true)
 	assert.DeepEqual(t, opt.AutoReloadInterval, 5*time.Second)
+	assert.DeepEqual(t, opt.ListenConfig, cfg)
+	assert.Assert(t, reflect.TypeOf(opt.AltTransporterNewer) == reflect.TypeOf(transporter))
 }
 
 func TestDefaultOptions(t *testing.T) {
@@ -125,3 +140,17 @@ func TestDefaultOptions(t *testing.T) {
 	assert.DeepEqual(t, opt.AutoReloadRender, false)
 	assert.DeepEqual(t, opt.AutoReloadInterval, time.Duration(0))
 }
+
+type mockTransporter struct{}
+
+func (m *mockTransporter) ListenAndServe(onData network.OnData) (err error) {
+	panic("implement me")
+}
+
+func (m *mockTransporter) Close() error {
+	panic("implement me")
+}
+
+func (m *mockTransporter) Shutdown(ctx context.Context) error {
+	panic("implement me")
+}
diff --git a/pkg/app/server/render/html_test.go b/pkg/app/server/render/html_test.go
index eb8f36e..d474f1c 100644
--- a/pkg/app/server/render/html_test.go
+++ b/pkg/app/server/render/html_test.go
@@ -17,14 +17,24 @@
 package render
 
 import (
+	"html/template"
 	"io/ioutil"
 	"os"
 	"testing"
 	"time"
+
+	"github.com/cloudwego/hertz/pkg/common/test/assert"
+	"github.com/cloudwego/hertz/pkg/common/utils"
+	"github.com/cloudwego/hertz/pkg/protocol"
 )
 
 func TestHTMLDebug_StartChecker_timer(t *testing.T) {
-	render := &HTMLDebug{RefreshInterval: time.Second}
+	render := &HTMLDebug{
+		RefreshInterval: time.Second,
+		Delims:          Delims{Left: "{[{", Right: "}]}"},
+		FuncMap:         template.FuncMap{},
+		Files:           []string{"../../../common/testdata/template/index.tmpl"},
+	}
 	select {
 	case <-render.reloadCh:
 		t.Fatalf("should not be triggered")
@@ -35,6 +45,7 @@ func TestHTMLDebug_StartChecker_timer(t *testing.T) {
 	case <-time.After(render.RefreshInterval + 500*time.Millisecond):
 		t.Fatalf("should be triggered in 1.5 second")
 	case <-render.reloadCh:
+		render.reload()
 	}
 }
 
@@ -64,3 +75,55 @@ func TestHTMLDebug_StartChecker_fs_watcher(t *testing.T) {
 	default:
 	}
 }
+
+func TestRenderHTML(t *testing.T) {
+	resp := &protocol.Response{}
+
+	tmpl := template.Must(template.New("").
+		Delims("{[{", "}]}").
+		Funcs(template.FuncMap{}).
+		ParseFiles("../../../common/testdata/template/index.tmpl"))
+
+	r := &HTMLProduction{Template: tmpl}
+
+	html := r.Instance("index.tmpl", utils.H{
+		"title": "Main website",
+	})
+
+	err := r.Close()
+	assert.Nil(t, err)
+
+	html.WriteContentType(resp)
+	assert.DeepEqual(t, []byte("text/html; charset=utf-8"), resp.Header.Peek("Content-Type"))
+
+	err = html.Render(resp)
+
+	assert.Nil(t, err)
+	assert.DeepEqual(t, []byte("text/html; charset=utf-8"), resp.Header.Peek("Content-Type"))
+	assert.DeepEqual(t, []byte("<html><h1>Main website</h1></html>"), resp.Body())
+
+	respDebug := &protocol.Response{}
+
+	rDebug := &HTMLDebug{
+		Template: tmpl,
+		Delims:   Delims{Left: "{[{", Right: "}]}"},
+		FuncMap:  template.FuncMap{},
+		Files:    []string{"../../../common/testdata/template/index.tmpl"},
+	}
+
+	htmlDebug := rDebug.Instance("index.tmpl", utils.H{
+		"title": "Main website",
+	})
+
+	err = rDebug.Close()
+	assert.Nil(t, err)
+
+	htmlDebug.WriteContentType(respDebug)
+	assert.DeepEqual(t, []byte("text/html; charset=utf-8"), respDebug.Header.Peek("Content-Type"))
+
+	err = htmlDebug.Render(respDebug)
+
+	assert.Nil(t, err)
+	assert.DeepEqual(t, []byte("text/html; charset=utf-8"), respDebug.Header.Peek("Content-Type"))
+	assert.DeepEqual(t, []byte("<html><h1>Main website</h1></html>"), respDebug.Body())
+}
diff --git a/pkg/app/server/render/render_test.go b/pkg/app/server/render/render_test.go
index 338d151..0669cb4 100644
--- a/pkg/app/server/render/render_test.go
+++ b/pkg/app/server/render/render_test.go
@@ -47,6 +47,7 @@ import (
 
 	"github.com/bytedance/sonic"
 	"github.com/cloudwego/hertz/pkg/common/test/assert"
+	"github.com/cloudwego/hertz/pkg/common/testdata/proto"
 	"github.com/cloudwego/hertz/pkg/protocol"
 	"github.com/cloudwego/hertz/pkg/protocol/consts"
 )
@@ -96,8 +97,59 @@ func TestRenderJSONError(t *testing.T) {
 	resp := &protocol.Response{}
 	data := make(chan int)
 
+	err := (JSONRender{data}).Render(resp)
+	// json: unsupported type: chan int
+	assert.NotNil(t, err)
+}
+
+func TestRenderPureJSON(t *testing.T) {
+	resp := &protocol.Response{}
+	data := map[string]interface{}{
+		"foo":  "bar",
+		"html": "<b>",
+	}
+
+	(PureJSON{data}).WriteContentType(resp)
+	assert.DeepEqual(t, []byte(consts.MIMEApplicationJSONUTF8), resp.Header.Peek("Content-Type"))
+
+	err := (PureJSON{data}).Render(resp)
+
+	assert.Nil(t, err)
+
+	assert.DeepEqual(t, []byte("{\"foo\":\"bar\",\"html\":\"<b>\"}\n"), resp.Body())
+	assert.DeepEqual(t, []byte(consts.MIMEApplicationJSONUTF8), resp.Header.Peek("Content-Type"))
+}
+
+func TestRenderPureJSONError(t *testing.T) {
+	resp := &protocol.Response{}
+	data := make(chan int)
+
+	err := (PureJSON{data}).Render(resp)
 	// json: unsupported type: chan int
-	assert.NotNil(t, func() { (JSONRender{data}).Render(resp) })
+	assert.NotNil(t, err)
+}
+
+func TestRenderProtobuf(t *testing.T) {
+	resp := &protocol.Response{}
+	data := proto.TestStruct{Body: []byte("Hello World")}
+
+	(ProtoBuf{&data}).WriteContentType(resp)
+	assert.DeepEqual(t, []byte("application/x-protobuf"), resp.Header.Peek("Content-Type"))
+
+	err := (ProtoBuf{&data}).Render(resp)
+
+	assert.Nil(t, err)
+	assert.DeepEqual(t, []byte("\n\vHello World"), resp.Body())
+	assert.DeepEqual(t, []byte("application/x-protobuf"), resp.Header.Peek("Content-Type"))
+}
+
+func TestRenderProtobufError(t *testing.T) {
+	resp := &protocol.Response{}
+	data := proto.Test{}
+
+	err := (ProtoBuf{&data}).Render(resp)
+
+	assert.NotNil(t, err)
 }
 
 func TestRenderString(t *testing.T) {
@@ -162,6 +214,15 @@ func TestRenderXML(t *testing.T) {
 	assert.DeepEqual(t, []byte(consts.MIMEApplicationXMLUTF8), resp.Header.Peek("Content-Type"))
 }
 
+func TestRenderXMLError(t *testing.T) {
+	resp := &protocol.Response{}
+	data := make(chan int)
+
+	err := (XML{data}).Render(resp)
+
+	assert.NotNil(t, err)
+}
+
 func TestRenderIndentedJSON(t *testing.T) {
 	data := map[string]interface{}{
 		"foo":  "bar",
diff --git a/pkg/common/hlog/default_test.go b/pkg/common/hlog/default_test.go
index ec8310c..36f063f 100644
--- a/pkg/common/hlog/default_test.go
+++ b/pkg/common/hlog/default_test.go
@@ -107,6 +107,16 @@ func TestCtxLogger(t *testing.T) {
 		"[Error] work failed\n", string(w.b))
 }
 
+func TestFormatLoggerWithEscapedCharacters(t *testing.T) {
+	initTestLogger()
+
+	var w byteSliceWriter
+	SetOutput(&w)
+
+	Infof("http://localhost:8080/ping?f=http://localhost:3000/hello?c=%E5%A4%A7hi%E5%93%A6%E5%95%8A%E8%AF%B4%E5%BE%97%E5%A5%BD")
+	assert.DeepEqual(t, "[Info] http://localhost:8080/ping?f=http://localhost:3000/hello?c=%E5%A4%A7hi%E5%93%A6%E5%95%8A%E8%AF%B4%E5%BE%97%E5%A5%BD\n", string(w.b))
+}
+
 func TestSetLevel(t *testing.T) {
 	setLogger := &defaultLogger{
 		stdlog: log.New(os.Stderr, "", log.LstdFlags|log.Lshortfile|log.Lmicroseconds),
diff --git a/pkg/common/test/mock/reader.go b/pkg/common/test/mock/reader.go
index 7a35265..b67d503 100644
--- a/pkg/common/test/mock/reader.go
+++ b/pkg/common/test/mock/reader.go
@@ -65,6 +65,13 @@ func NewZeroCopyReader(r string) ZeroCopyReader {
 	return ZeroCopyReader{br}
 }
 
+func NewLimitReader(r *bytes.Buffer) io.LimitedReader {
+	return io.LimitedReader{
+		R: r,
+		N: int64(r.Len()),
+	}
+}
+
 type EOFReader struct{}
 
 func (e *EOFReader) Peek(n int) ([]byte, error) {
diff --git a/pkg/common/utils/chunk_test.go b/pkg/common/utils/chunk_test.go
index ffeafa1..d9c8570 100644
--- a/pkg/common/utils/chunk_test.go
+++ b/pkg/common/utils/chunk_test.go
@@ -35,6 +35,33 @@ func TestChunkParseChunkSizeGetCorrect(t *testing.T) {
 	}
 }
 
+func TestChunkParseChunkSizeGetError(t *testing.T) {
+	// test err from -----n, err := bytesconv.ReadHexInt(r)-----
+	chunkSizeBody := ""
+	zr := mock.NewZeroCopyReader(chunkSizeBody)
+	chunkSize, err := ParseChunkSize(zr)
+	assert.NotNil(t, err)
+	assert.DeepEqual(t, -1, chunkSize)
+	// test err from -----c, err := r.ReadByte()-----
+	chunkSizeBody = "0"
+	zr = mock.NewZeroCopyReader(chunkSizeBody)
+	chunkSize, err = ParseChunkSize(zr)
+	assert.NotNil(t, err)
+	assert.DeepEqual(t, -1, chunkSize)
+	// test err from -----c, err := r.ReadByte()-----
+	chunkSizeBody = "0" + "\r"
+	zr = mock.NewZeroCopyReader(chunkSizeBody)
+	chunkSize, err = ParseChunkSize(zr)
+	assert.NotNil(t, err)
+	assert.DeepEqual(t, -1, chunkSize)
+	// test err from -----c, err := r.ReadByte()-----
+	chunkSizeBody = "0" + "\r" + "\r"
+	zr = mock.NewZeroCopyReader(chunkSizeBody)
+	chunkSize, err = ParseChunkSize(zr)
+	assert.NotNil(t, err)
+	assert.DeepEqual(t, -1, chunkSize)
+}
+
 func TestChunkParseChunkSizeCorrectWhiteSpace(t *testing.T) {
 	// test the whitespace
 	whiteSpace := ""
diff --git a/pkg/common/utils/ioutil_test.go b/pkg/common/utils/ioutil_test.go
index c0d020c..e9a573b 100644
--- a/pkg/common/utils/ioutil_test.go
+++ b/pkg/common/utils/ioutil_test.go
@@ -18,12 +18,81 @@ package utils
 
 import (
 	"bytes"
+	"io"
 	"testing"
 
+	"github.com/cloudwego/hertz/pkg/common/test/mock"
+
 	"github.com/cloudwego/hertz/pkg/common/test/assert"
 	"github.com/cloudwego/hertz/pkg/network"
 )
 
+type writeReadTest interface {
+	Write(p []byte) (n int, err error)
+	Malloc(n int) (buf []byte, err error)
+	WriteBinary(b []byte) (n int, err error)
+	Flush() error
+}
+
+type readerTest interface {
+	ReadFrom(r io.Reader) (n int64, err error)
+	Malloc(n int) (buf []byte, err error)
+	WriteBinary(b []byte) (n int, err error)
+	Flush() error
+}
+
+type testWriter struct {
+	w io.Writer
+}
+
+func (t testWriter) Write(p []byte) (n int, err error) {
+	return
+}
+
+func (t testWriter) Malloc(n int) (buf []byte, err error) {
+	return
+}
+
+func (t testWriter) WriteBinary(b []byte) (n int, err error) {
+	return
+}
+
+func (t testWriter) Flush() error {
+	return nil
+}
+
+type testReader struct {
+	r io.ReaderFrom
+}
+
+func (t testReader) ReadFrom(r io.Reader) (n int64, err error) {
+	return
+}
+
+func (t testReader) Malloc(n int) (buf []byte, err error) {
+	return
+}
+
+func (t testReader) WriteBinary(b []byte) (n int, err error) {
+	return
+}
+
+func (t testReader) Flush() error {
+	return nil
+}
+
+func newTestWriter(w io.Writer) writeReadTest {
+	return &testWriter{
+		w: w,
+	}
+}
+
+func newTestReaderForm(r io.ReaderFrom) readerTest {
+	return &testReader{
+		r: r,
+	}
+}
+
 func TestIoutilCopyBuffer(t *testing.T) {
 	var writeBuffer bytes.Buffer
 	str := string("hertz is very good!!!")
@@ -37,6 +106,70 @@ func TestIoutilCopyBuffer(t *testing.T) {
 	assert.DeepEqual(t, written, srcLen)
 	assert.DeepEqual(t, err, nil)
 	assert.DeepEqual(t, []byte(str), writeBuffer.Bytes())
+
+	// Test when no data is readable
+	writeBuffer.Reset()
+	emptySrc := bytes.NewBufferString("")
+	written, err = CopyBuffer(dst, emptySrc, buf)
+	assert.DeepEqual(t, written, int64(0))
+	assert.Nil(t, err)
+	assert.DeepEqual(t, []byte(""), writeBuffer.Bytes())
+
+	// Test a LimitedReader
+	writeBuffer.Reset()
+	limit := int64(5)
+	limitedSrc := io.LimitedReader{R: bytes.NewBufferString(str), N: limit}
+	written, err = CopyBuffer(dst, &limitedSrc, buf)
+	assert.DeepEqual(t, written, limit)
+	assert.Nil(t, err)
+	assert.DeepEqual(t, []byte(str[:limit]), writeBuffer.Bytes())
+}
+
+func TestIoutilCopyBufferWithIoWriter(t *testing.T) {
+	var writeBuffer bytes.Buffer
+	str := "hertz is very good!!!"
+	var buf []byte
+	src := bytes.NewBuffer([]byte(str))
+	ioWriter := newTestWriter(&writeBuffer)
+	// to show example about -----w, ok := dst.(io.Writer)-----
+	_, ok := ioWriter.(io.Writer)
+	assert.DeepEqual(t, true, ok)
+	written, err := CopyBuffer(ioWriter, src, buf)
+	assert.DeepEqual(t, written, int64(0))
+	assert.NotNil(t, err)
+	assert.DeepEqual(t, []byte(nil), writeBuffer.Bytes())
+}
+
+func TestIoutilCopyBufferWithIoReaderFrom(t *testing.T) {
+	var writeBuffer bytes.Buffer
+	str := "hertz is very good!!!"
+	var buf []byte
+	src := bytes.NewBufferString(str)
+	ioReaderFrom := newTestReaderForm(&writeBuffer)
+	// to show example about -----rf, ok := dst.(io.ReaderFrom)-----
+	_, ok := ioReaderFrom.(io.Writer)
+	assert.DeepEqual(t, false, ok)
+	_, ok = ioReaderFrom.(io.ReaderFrom)
+	assert.DeepEqual(t, true, ok)
+	written, err := CopyBuffer(ioReaderFrom, src, buf)
+	assert.DeepEqual(t, written, int64(0))
+	assert.NotNil(t, err)
+	assert.DeepEqual(t, []byte(nil), writeBuffer.Bytes())
+}
+
+func TestIoutilCopyBufferWithPanic(t *testing.T) {
+	var writeBuffer bytes.Buffer
+	str := "hertz is very good!!!"
+	var buf []byte
+	defer func() {
+		if r := recover(); r != nil {
+			assert.DeepEqual(t, "empty buffer in io.CopyBuffer", r)
+		}
+	}()
+	src := bytes.NewBufferString(str)
+	dst := network.NewWriter(&writeBuffer)
+	buf = make([]byte, 0)
+	_, _ = CopyBuffer(dst, src, buf)
 }
 
 func TestIoutilCopyBufferWithNilBuffer(t *testing.T) {
@@ -49,13 +182,40 @@ func TestIoutilCopyBufferWithNilBuffer(t *testing.T) {
 	written, err := CopyBuffer(dst, src, nil)
 
 	assert.DeepEqual(t, written, srcLen)
-	assert.DeepEqual(t, err, nil)
+	assert.NotNil(t, err)
+	assert.DeepEqual(t, []byte(str), writeBuffer.Bytes())
+}
+
+func TestIoutilCopyBufferWithNilBufferAndIoLimitedReader(t *testing.T) {
+	var writeBuffer bytes.Buffer
+	str := "hertz is very good!!!"
+	src := bytes.NewBufferString(str)
+	reader := mock.NewLimitReader(src)
+	dst := network.NewWriter(&writeBuffer)
+	srcLen := int64(src.Len())
+	written, err := CopyBuffer(dst, &reader, nil)
+
+	assert.DeepEqual(t, written, srcLen)
+	assert.NotNil(t, err)
+	assert.DeepEqual(t, []byte(str), writeBuffer.Bytes())
+
+	// test l.N < 1
+	writeBuffer.Reset()
+	str = ""
+	src = bytes.NewBufferString(str)
+	reader = mock.NewLimitReader(src)
+	dst = network.NewWriter(&writeBuffer)
+	srcLen = int64(src.Len())
+	written, err = CopyBuffer(dst, &reader, nil)
+
+	assert.DeepEqual(t, written, srcLen)
+	assert.NotNil(t, err)
 	assert.DeepEqual(t, []byte(str), writeBuffer.Bytes())
 }
 
 func TestIoutilCopyZeroAlloc(t *testing.T) {
 	var writeBuffer bytes.Buffer
-	str := string("hertz is very good!!!")
+	str := "hertz is very good!!!"
 	src := bytes.NewBufferString(str)
 	dst := network.NewWriter(&writeBuffer)
 	srcLen := int64(src.Len())
@@ -64,4 +224,49 @@ func TestIoutilCopyZeroAlloc(t *testing.T) {
 	assert.DeepEqual(t, written, srcLen)
 	assert.DeepEqual(t, err, nil)
 	assert.DeepEqual(t, []byte(str), writeBuffer.Bytes())
+
+	// Test when no data is readable
+	writeBuffer.Reset()
+	emptySrc := bytes.NewBufferString("")
+	written, err = CopyZeroAlloc(dst, emptySrc)
+	assert.DeepEqual(t, written, int64(0))
+	assert.Nil(t, err)
+	assert.DeepEqual(t, []byte(""), writeBuffer.Bytes())
+}
+
+func TestIoutilCopyBufferWithEmptyBuffer(t *testing.T) {
+	var writeBuffer bytes.Buffer
+	str := "hertz is very good!!!"
+	src := bytes.NewBufferString(str)
+	dst := network.NewWriter(&writeBuffer)
+	// Use a non-empty buffer of length 0
+	emptyBuf := make([]byte, 0)
+	func() {
+		defer func() {
+			if r := recover(); r != nil {
+				assert.DeepEqual(t, "empty buffer in io.CopyBuffer", r)
+			}
+		}()
+
+		written, err := CopyBuffer(dst, src, emptyBuf)
+		assert.Nil(t, err)
+		assert.DeepEqual(t, written, int64(len(str)))
+		assert.DeepEqual(t, []byte(str), writeBuffer.Bytes())
+	}()
+}
+
+func TestIoutilCopyBufferWithLimitedReader(t *testing.T) {
+	var writeBuffer bytes.Buffer
+	str := "hertz is very good!!!"
+	src := bytes.NewBufferString(str)
+	limit := int64(5)
+	limitedSrc := io.LimitedReader{R: src, N: limit}
+	dst := network.NewWriter(&writeBuffer)
+	var buf []byte
+
+	// Test LimitedReader status
+	written, err := CopyBuffer(dst, &limitedSrc, buf)
+	assert.Nil(t, err)
+	assert.DeepEqual(t, written, limit)
+	assert.DeepEqual(t, []byte(str[:limit]), writeBuffer.Bytes())
 }
diff --git a/pkg/common/utils/network_test.go b/pkg/common/utils/network_test.go
index 232e35a..78be77d 100644
--- a/pkg/common/utils/network_test.go
+++ b/pkg/common/utils/network_test.go
@@ -41,3 +41,14 @@ func TestTLSRecordHeaderLooksLikeHTTP(t *testing.T) {
 		assert.DeepEqual(t, expectedResult, TLSRecordHeaderLooksLikeHTTP(value))
 	}
 }
+
+func TestLocalIP(t *testing.T) {
+	// Mock the localIP variable for testing purposes.
+	localIP = "192.168.0.1"
+
+	// Ensure that LocalIP() returns the expected local IP.
+	expectedIP := "192.168.0.1"
+	if got := LocalIP(); got != expectedIP {
+		assert.DeepEqual(t, got, expectedIP)
+	}
+}
diff --git a/pkg/common/utils/path_test.go b/pkg/common/utils/path_test.go
index 6ebb843..b98a8c7 100644
--- a/pkg/common/utils/path_test.go
+++ b/pkg/common/utils/path_test.go
@@ -82,6 +82,11 @@ func TestPathCleanPath(t *testing.T) {
 	expectedMultiSlashPath := "/Foo/Bar/go/src/github.com/cloudwego"
 	cleanMultiSlashPath := CleanPath(multiSlashPath)
 	assert.DeepEqual(t, expectedMultiSlashPath, cleanMultiSlashPath)
+
+	inputPath := "/Foo/Bar/go/src/github.com/cloudwego/hertz/pkg/common/utils/path_test.go/."
+	expectedPath := "/Foo/Bar/go/src/github.com/cloudwego/hertz/pkg/common/utils/path_test.go/"
+	cleanedPath := CleanPath(inputPath)
+	assert.DeepEqual(t, expectedPath, cleanedPath)
 }
 
 // The Function AddMissingPort can only add the missed port, don't consider the other error case.
diff --git a/pkg/protocol/header_test.go b/pkg/protocol/header_test.go
index 1fb0211..8d768fd 100644
--- a/pkg/protocol/header_test.go
+++ b/pkg/protocol/header_test.go
@@ -209,6 +209,7 @@ func TestRequestHeaderDel(t *testing.T) {
 	var h RequestHeader
 	h.Set("Foo-Bar", "baz")
 	h.Set("aaa", "bbb")
+	h.Set("ccc", "ddd")
 	h.Set(consts.HeaderConnection, "keep-alive")
 	h.Set(consts.HeaderContentType, "aaa")
 	h.Set(consts.HeaderServer, "aaabbb")
@@ -226,11 +227,16 @@ func TestRequestHeaderDel(t *testing.T) {
 	h.del([]byte("Host"))
 	h.del([]byte(consts.HeaderTrailer))
 	h.DelCookie("foo")
+	h.Del("ccc")
 
 	hv := h.Peek("aaa")
 	if string(hv) != "bbb" {
 		t.Fatalf("unexpected header value: %q. Expecting %q", hv, "bbb")
 	}
+	hv = h.Peek("ccc")
+	if string(hv) != "" {
+		t.Fatalf("unexpected header value: %q. Expecting %q", hv, "")
+	}
 	hv = h.Peek("Foo-Bar")
 	if len(hv) > 0 {
 		t.Fatalf("non-zero header value: %q", hv)
diff --git a/pkg/protocol/http1/client_test.go b/pkg/protocol/http1/client_test.go
index 7bc389b..7c45d14 100644
--- a/pkg/protocol/http1/client_test.go
+++ b/pkg/protocol/http1/client_test.go
@@ -536,3 +536,43 @@ func TestConnNotRetry(t *testing.T) {
 	assert.True(t, logbuf.String() == "")
 	protocol.ReleaseResponse(resp)
 }
+
+type countCloseConn struct {
+	network.Conn
+	isClose bool
+}
+
+func (c *countCloseConn) Close() error {
+	c.isClose = true
+	return nil
+}
+
+func newCountCloseConn(s string) *countCloseConn {
+	return &countCloseConn{
+		Conn: mock.NewConn(s),
+	}
+}
+
+func TestStreamNoContent(t *testing.T) {
+	conn := newCountCloseConn("HTTP/1.1 204 Foo Bar\r\nContent-Type: aab\r\nTrailer: Foo\r\nContent-Encoding: deflate\r\nTransfer-Encoding: chunked\r\n\r\n0\r\nFoo: bar\r\n\r\nHTTP/1.2")
+
+	c := &HostClient{
+		ClientOptions: &ClientOptions{
+			Dialer: newSlowConnDialer(func(network, addr string) (network.Conn, error) {
+				return conn, nil
+			}),
+		},
+		Addr: "foobar",
+	}
+
+	c.ResponseBodyStream = true
+
+	req := protocol.AcquireRequest()
+	req.SetRequestURI("http://foobar/baz")
+	req.Header.SetConnectionClose(true)
+	resp := protocol.AcquireResponse()
+
+	c.Do(context.Background(), req, resp)
+
+	assert.True(t, conn.isClose)
+}
diff --git a/pkg/protocol/http1/ext/common_test.go b/pkg/protocol/http1/ext/common_test.go
index 78a9fed..9bc1936 100644
--- a/pkg/protocol/http1/ext/common_test.go
+++ b/pkg/protocol/http1/ext/common_test.go
@@ -24,6 +24,7 @@ import (
 	"testing"
 
 	errs "github.com/cloudwego/hertz/pkg/common/errors"
+	"github.com/cloudwego/hertz/pkg/common/hlog"
 	"github.com/cloudwego/hertz/pkg/common/test/assert"
 	"github.com/cloudwego/hertz/pkg/common/test/mock"
 	"github.com/cloudwego/hertz/pkg/protocol"
@@ -123,6 +124,9 @@ func TestReadRawHeaders(t *testing.T) {
 }
 
 func TestBodyChunked(t *testing.T) {
+	var log bytes.Buffer
+	hlog.SetOutput(&log)
+
 	body := "foobar baz aaa bbb ccc"
 	chunk := "16\r\nfoobar baz aaa bbb ccc\r\n0\r\n"
 	b := bytes.NewBufferString(body)
@@ -137,6 +141,22 @@ func TestBodyChunked(t *testing.T) {
 	rb, err := ReadBody(zr, -1, 0, nil)
 	assert.Nil(t, err)
 	assert.DeepEqual(t, body, string(rb))
+
+	assert.DeepEqual(t, 0, log.Len())
+}
+
+func TestBrokenBodyChunked(t *testing.T) {
+	brokenReader := mock.NewBrokenConn("")
+	var log bytes.Buffer
+	hlog.SetOutput(&log)
+
+	var w bytes.Buffer
+	zw := netpoll.NewWriter(&w)
+	err := WriteBodyChunked(zw, brokenReader)
+	assert.Nil(t, err)
+
+	assert.DeepEqual(t, []byte("0\r\n"), w.Bytes())
+	assert.True(t, bytes.Contains(log.Bytes(), []byte("writing chunked response body encountered an error from the reader")))
 }
 
 func TestBodyFixedSize(t *testing.T) {
diff --git a/pkg/protocol/http1/req/header_test.go b/pkg/protocol/http1/req/header_test.go
index 489e0aa..d0978b3 100644
--- a/pkg/protocol/http1/req/header_test.go
+++ b/pkg/protocol/http1/req/header_test.go
@@ -421,3 +421,117 @@ func TestRequestHeaderError(t *testing.T) {
 	err := ReadHeader(&rh, &er)
 	assert.True(t, errors.Is(err, errs.ErrNothingRead))
 }
+
+func TestReadHeader(t *testing.T) {
+	s := "P"
+	zr := mock.NewZeroCopyReader(s)
+	rh := protocol.RequestHeader{}
+	err := ReadHeader(&rh, zr)
+	assert.NotNil(t, err)
+}
+
+func TestParseHeaders(t *testing.T) {
+	rh := protocol.RequestHeader{}
+	_, err := parseHeaders(&rh, []byte{' '})
+	assert.NotNil(t, err)
+}
+
+func TestTryRead(t *testing.T) {
+	rh := protocol.RequestHeader{}
+	s := "P"
+	zr := mock.NewZeroCopyReader(s)
+	err := tryRead(&rh, zr, 0)
+	assert.NotNil(t, err)
+}
+
+func TestParseFirstLine(t *testing.T) {
+	tests := []struct {
+		input    []byte
+		method   string
+		uri      string
+		protocol string
+		err      error
+	}{
+		// Test case 1: n < 0
+		{
+			input:    []byte("GET /path/to/resource HTTP/1.0\r\n"),
+			method:   "GET",
+			uri:      "/path/to/resource",
+			protocol: "HTTP/1.0",
+			err:      nil,
+		},
+		// Test case 2: n == 0
+		{
+			input:    []byte(" /path/to/resource HTTP/1.1\r\n"),
+			method:   "",
+			uri:      "",
+			protocol: "",
+			err:      fmt.Errorf("requestURI cannot be empty in"),
+		},
+		// Test case 3: !bytes.Equal(b[n+1:], bytestr.StrHTTP11)
+		{
+			input:    []byte("POST /path/to/resource HTTP/1.2\r\n"),
+			method:   "POST",
+			uri:      "/path/to/resource",
+			protocol: "HTTP/1.0",
+			err:      nil,
+		},
+	}
+
+	for _, tc := range tests {
+		header := &protocol.RequestHeader{}
+		_, err := parseFirstLine(header, tc.input)
+		assert.NotNil(t, err)
+	}
+}
+
+func TestParse(t *testing.T) {
+	tests := []struct {
+		name     string
+		input    []byte
+		expected int
+		wantErr  bool
+	}{
+		// normal test
+		{
+			name:     "normal",
+			input:    []byte("GET /path/to/resource HTTP/1.1\r\nHost: example.com\r\n\r\n"),
+			expected: len([]byte("GET /path/to/resource HTTP/1.1\r\nHost: example.com\r\n\r\n")),
+			wantErr:  false,
+		},
+		// parseFirstLine error
+		{
+			name:     "parseFirstLine error",
+			input:    []byte("INVALID_LINE\r\nHost: example.com\r\n\r\n"),
+			expected: 0,
+			wantErr:  true,
+		},
+		// ext.ReadRawHeaders error
+		{
+			name:     "ext.ReadRawHeaders error",
+			input:    []byte("GET /path/to/resource HTTP/1.1\r\nINVALID_HEADER\r\n\r\n"),
+			expected: 0,
+			wantErr:  true,
+		},
+		// parseHeaders error
+		{
+			name:     "parseHeaders error",
+			input:    []byte("GET /path/to/resource HTTP/1.1\r\nHost: example.com\r\nINVALID_HEADER\r\n"),
+			expected: 0,
+			wantErr:  true,
+		},
+	}
+
+	for _, tc := range tests {
+		t.Run(tc.name, func(t *testing.T) {
+			header := &protocol.RequestHeader{}
+			bytesRead, err := parse(header, tc.input)
+			if (err != nil) != tc.wantErr {
+				t.Errorf("Expected error: %v, but got: %v", tc.wantErr, err)
+			}
+			if bytesRead != tc.expected {
+				t.Errorf("Expected bytes read: %d, but got: %d", tc.expected, bytesRead)
+			}
+		})
+	}
+}
diff --git a/pkg/protocol/http1/resp/writer_test.go b/pkg/protocol/http1/resp/writer_test.go
index bca2d43..b57281a 100644
--- a/pkg/protocol/http1/resp/writer_test.go
+++ b/pkg/protocol/http1/resp/writer_test.go
@@ -51,3 +51,16 @@ func TestNewChunkedBodyWriter1(t *testing.T) {
 	assert.True(t, strings.Contains(string(out), "5"+string(bytestr.StrCRLF)+"hello"))
 	assert.True(t, strings.Contains(string(out), "0"+string(bytestr.StrCRLF)+string(bytestr.StrCRLF)))
 }
+
+func TestNewChunkedBodyWriterNoData(t *testing.T) {
+	response := protocol.AcquireResponse()
+	response.Header.Set("Foo", "Bar")
+	mockConn := mock.NewConn("")
+	w := NewChunkedBodyWriter(response, mockConn)
+	w.Finalize()
+	w.Flush()
+	out, _ := mockConn.WriterRecorder().ReadBinary(mockConn.WriterRecorder().WroteLen())
+	assert.True(t, strings.Contains(string(out), "Transfer-Encoding: chunked"))
+	assert.True(t, strings.Contains(string(out), "Foo: Bar"))
+	assert.True(t, strings.Contains(string(out), "0"+string(bytestr.StrCRLF)+string(bytestr.StrCRLF)))
+}
diff --git a/pkg/protocol/multipart_test.go b/pkg/protocol/multipart_test.go
index 86995d2..6b96ae8 100644
--- a/pkg/protocol/multipart_test.go
+++ b/pkg/protocol/multipart_test.go
@@ -44,6 +44,7 @@ package protocol
 import (
 	"bytes"
 	"mime/multipart"
+	"net/textproto"
 	"os"
 	"strings"
 	"testing"
@@ -80,6 +81,15 @@ Content-Type: application/json
 		err = WriteMultipartForm(&w, form, "")
 	})
 
+	// call WriteField as twice
+	var body bytes.Buffer
+	mw := multipart.NewWriter(&body)
+	if err = mw.WriteField("field1", "value1"); err != nil {
+		t.Fatal(err)
+	}
+	err = WriteMultipartForm(&w, form, s)
+	assert.NotNil(t, err)
+
 	// normal test
 	err = WriteMultipartForm(&w, form, "foo")
 	if err != nil {
@@ -238,3 +248,28 @@ Content-Type: application/json
 	_, err = MarshalMultipartForm(form, " ")
 	assert.NotNil(t, err)
 }
+
+func TestAddFile(t *testing.T) {
+	t.Parallel()
+	bodyBuffer := &bytes.Buffer{}
+	w := multipart.NewWriter(bodyBuffer)
+	// add null file
+	err := AddFile(w, "test", "/test")
+	assert.NotNil(t, err)
+}
+
+func TestCreateMultipartHeader(t *testing.T) {
+	t.Parallel()
+
+	// filename == Null
+	hdr1 := make(textproto.MIMEHeader)
+	hdr1.Set("Content-Disposition", `form-data; name="test"`)
+	hdr1.Set("Content-Type", "application/json")
+	assert.DeepEqual(t, hdr1, CreateMultipartHeader("test", "", "application/json"))
+
+	// normal test
+	hdr2 := make(textproto.MIMEHeader)
+	hdr2.Set("Content-Disposition", `form-data; name="test"; filename="/test.go"`)
+	hdr2.Set("Content-Type", "application/json")
+	assert.DeepEqual(t, hdr2, CreateMultipartHeader("test", "/test.go", "application/json"))
+}
diff --git a/pkg/route/engine_test.go b/pkg/route/engine_test.go
index fe528b4..350da81 100644
--- a/pkg/route/engine_test.go
+++ b/pkg/route/engine_test.go
@@ -502,6 +502,14 @@ func TestRenderHtmlOfFilesWithAutoRender(t *testing.T) {
 	assert.DeepEqual(t, "text/html; charset=utf-8", rr.Header().Get("Content-Type"))
 }
 
+func TestSetEngineRun(t *testing.T) {
+	e := NewEngine(config.NewOptions(nil))
+	e.Init()
+	assert.True(t, !e.IsRunning())
+	e.MarkAsRunning()
+	assert.True(t, e.IsRunning())
+}
+
 type mockConn struct{}
 
 func (m *mockConn) SetWriteTimeout(t time.Duration) error {