Repository: cloudwego/hertz
Pull request #1325 (2025-04-09 03:15:53)

chore: release v0.9.7


Patch:
diff --git a/.github/workflows/invalid_question.yml b/.github/workflows/invalid_question.yml
index 9589f42..4d8237f 100644
--- a/.github/workflows/invalid_question.yml
+++ b/.github/workflows/invalid_question.yml
@@ -1,7 +1,7 @@
 name: "Close Invalid Issue"
 on:
   schedule:
-    - cron: "0 * * * *"
+    - cron: "0 0,8,16 * * *"
 
 permissions:
   contents: read
diff --git a/.typos.toml b/.typos.toml
new file mode 100644
index 0000000..5fe071f
--- /dev/null
+++ b/.typos.toml
@@ -0,0 +1,13 @@
+# Typo check: https://github.com/crate-ci/typos
+
+[files]
+extend-exclude = ["go.mod", "go.sum"]
+
+[default.extend-identifiers]
+pn = "pn" # packageName in cmd/hz
+ConnTLSer = "ConnTLSer"
+OPTIO = "OPTIO" # b[:5] for OPTION
+
+[default.extend-words]
+typ = "typ"   # type
+Flate = "Flate" # flate algorithm
diff --git a/README.md b/README.md
index 181f3f7..f1fffde 100644
--- a/README.md
+++ b/README.md
@@ -92,11 +92,12 @@ Hertz [həːts] is a high-usability, high-performance and high-extensibility Gol
 | [Cache](https://github.com/hertz-contrib/cache)                                                    | Hertz middleware for cache HTTP response with multi-backend support                                                                                                            |
 
 ## Blogs
-- [ByteDance Practice on Go Network Library](https://www.cloudwego.io/blog/2021/10/09/bytedance-practices-on-go-network-library/)
+- [ByteDance Practice on Go Network Library](https://www.cloudwego.io/blog/2020/05/24/bytedance-practices-on-go-network-library/)
 - [Ultra-large-scale Enterprise-level Microservice HTTP Framework — Hertz is Officially Open Source!](https://www.cloudwego.io/zh/blog/2022/06/21/%E8%B6%85%E5%A4%A7%E8%A7%84%E6%A8%A1%E7%9A%84%E4%BC%81%E4%B8%9A%E7%BA%A7%E5%BE%AE%E6%9C%8D%E5%8A%A1-http-%E6%A1%86%E6%9E%B6-hertz-%E6%AD%A3%E5%BC%8F%E5%BC%80%E6%BA%90/)
 - [ByteDance Open Source Go HTTP Framework Hertz Design Practice](https://www.cloudwego.io/zh/blog/2022/06/21/%E5%AD%97%E8%8A%82%E8%B7%B3%E5%8A%A8%E5%BC%80%E6%BA%90-go-http-%E6%A1%86%E6%9E%B6-hertz-%E8%AE%BE%E8%AE%A1%E5%AE%9E%E8%B7%B5/)
 - [Help ByteDance Reduce Costs and Increase Efficiency, the Design Practice for Large-scale Enterprise-level HTTP Framework Hertz](https://www.cloudwego.io/zh/blog/2022/09/27/%E5%8A%A9%E5%8A%9B%E5%AD%97%E8%8A%82%E9%99%8D%E6%9C%AC%E5%A2%9E%E6%95%88%E5%A4%A7%E8%A7%84%E6%A8%A1%E4%BC%81%E4%B8%9A%E7%BA%A7-http-%E6%A1%86%E6%9E%B6-hertz-%E8%AE%BE%E8%AE%A1%E5%AE%9E%E8%B7%B5/)
-- [A Practical Introduction to the HTTP Framework Hertz: A Guide to Performance Testing](https://www.cloudwego.io/zh/blog/2022/11/01/http-%E6%A1%86%E6%9E%B6-hertz-%E5%AE%9E%E8%B7%B5%E5%85%A5%E9%97%A8%E6%80%A7%E8%83%BD%E6%B5%8B%E8%AF%95%E6%8C%87%E5%8D%97/)
+- [Getting Started with Hertz: Performance Testing Guide](https://www.cloudwego.io/blog/2023/02/24/getting-started-with-hertz-performance-testing-guide/)
+
 ## Contributing
 
 [Contributing](https://github.com/cloudwego/hertz/blob/main/CONTRIBUTING.md)
diff --git a/README_cn.md b/README_cn.md
index 47fe662..65c8b1e 100644
--- a/README_cn.md
+++ b/README_cn.md
@@ -93,11 +93,12 @@ Hertz[həːts] 是一个 Golang 微服务 HTTP 框架，在设计之初参考了
 | [Cache](https://github.com/hertz-contrib/cache)                                                    | 用于缓存 HTTP 接口内容的 Hertz 中间件，支持多种客户端。                                                                |
 
 ## 相关文章
-- [字节跳动在 Go 网络库上的实践](https://www.cloudwego.io/blog/2021/10/09/bytedance-practices-on-go-network-library/)
+- [字节跳动在 Go 网络库上的实践](https://www.cloudwego.io/zh/blog/2020/05/24/%E5%AD%97%E8%8A%82%E8%B7%B3%E5%8A%A8%E5%9C%A8-go-%E7%BD%91%E7%BB%9C%E5%BA%93%E4%B8%8A%E7%9A%84%E5%AE%9E%E8%B7%B5/)
 - [超大规模的企业级微服务 HTTP 框架 — Hertz 正式开源！](https://www.cloudwego.io/zh/blog/2022/06/21/%E8%B6%85%E5%A4%A7%E8%A7%84%E6%A8%A1%E7%9A%84%E4%BC%81%E4%B8%9A%E7%BA%A7%E5%BE%AE%E6%9C%8D%E5%8A%A1-http-%E6%A1%86%E6%9E%B6-hertz-%E6%AD%A3%E5%BC%8F%E5%BC%80%E6%BA%90/)
 - [字节跳动开源 Go HTTP 框架 Hertz 设计实践](https://www.cloudwego.io/zh/blog/2022/06/21/%E5%AD%97%E8%8A%82%E8%B7%B3%E5%8A%A8%E5%BC%80%E6%BA%90-go-http-%E6%A1%86%E6%9E%B6-hertz-%E8%AE%BE%E8%AE%A1%E5%AE%9E%E8%B7%B5/)
 - [助力字节降本增效，大规模企业级 HTTP 框架 Hertz 设计实践](https://www.cloudwego.io/zh/blog/2022/09/27/%E5%8A%A9%E5%8A%9B%E5%AD%97%E8%8A%82%E9%99%8D%E6%9C%AC%E5%A2%9E%E6%95%88%E5%A4%A7%E8%A7%84%E6%A8%A1%E4%BC%81%E4%B8%9A%E7%BA%A7-http-%E6%A1%86%E6%9E%B6-hertz-%E8%AE%BE%E8%AE%A1%E5%AE%9E%E8%B7%B5/)
-- [HTTP 框架 Hertz 实践入门：性能测试指南](https://www.cloudwego.io/zh/blog/2022/11/01/http-%E6%A1%86%E6%9E%B6-hertz-%E5%AE%9E%E8%B7%B5%E5%85%A5%E9%97%A8%E6%80%A7%E8%83%BD%E6%B5%8B%E8%AF%95%E6%8C%87%E5%8D%97/)
+- [HTTP 框架 Hertz 实践入门：性能测试指南](https://www.cloudwego.io/zh/blog/2023/02/24/http-%E6%A1%86%E6%9E%B6-hertz-%E5%AE%9E%E8%B7%B5%E5%85%A5%E9%97%A8%E6%80%A7%E8%83%BD%E6%B5%8B%E8%AF%95%E6%8C%87%E5%8D%97/)
+
 ## 贡献代码
   [Contributing](https://github.com/cloudwego/hertz/blob/main/CONTRIBUTING.md)
 ## RoadMap
diff --git a/ROADMAP.md b/ROADMAP.md
index 0e29e72..f65f2ee 100644
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -1,22 +1,14 @@
 # Hertz RoadMap
-This document shows key roadmap of Hertz development from the year of 2022 to 2023. It may help users know more about the future features. But the actual work is driven by real-world needs, we may adjust our goals sometimes.
 
-# New Features:
-- Community Build
-    - [x] Support more middlewares for users, like sessions、gzip.
-    - [x] Support reverse proxy.
-    - Support swagger.
-- Protocol
-    - [x] Support Websocket.
-    - [x] Support HTTP2.
-    - [x] Support HTTP3.
-- Service Governance
-    - Support more extension for users.
-- Performance Optimization
-    - Improve the server throughput in tiny packet case.
-- User Experience Optimization
-    - [x] Provide good development practices for users to develop with Hertz more easily.
-    - [x] Improve code generation tool(hz) usability.
+From 2025, instead of developing new features we will focus on optimizing core functionalities and user experience.
 
+The following is a list of planned projects.
 
-All developers are welcome to contribute your extension to [hertz-contrib](https://github.com/hertz-contrib).
+- [ ] Optimize `RequestContext` issues under concurrency.
+- [ ] Refactor binding and validator for better extensibility, and deprecate built-in implementations.
+- [ ] Make `netpoll` optional, and `pkg/network/standard` the default.
+- [ ] Enhance Content-Encoding extension interface for better extensibility.
+- [ ] Optimize `pkg/common/adaptor`, and deprecate implementations with `net/http` alternatives.
+- [ ] Deprecate the built-in protobuf code generator, use [cloudwego/prutal](https://github.com/cloudwego/prutal)
+
+All users are encouraged to provide suggestions on the projects listed above, or to submit proposals for enhancing current features.
diff --git a/_typos.toml b/_typos.toml
deleted file mode 100644
index 50baf65..0000000
--- a/_typos.toml
+++ /dev/null
@@ -1,25 +0,0 @@
-# Typo check: https://github.com/crate-ci/typos
-
-[files]
-extend-exclude = ["go.mod", "go.sum"]
-
-[default.extend-identifiers]
-# *sigh* this just isn't worth the cost of fixing
-ConnTLSer = "ConnTLSer"
-flate = "flate"
-TestCompressFlateSerial = "TestCompressFlateSerial"
-testCompressFlate = "testCompressFlate"
-TestCompressFlateConcurrent = "TestCompressFlateConcurrent"
-trUe = "trUe"
-OPTIO = "OPTIO"
-contant = "contant"
-referer = "referer"
-HeaderReferer = "HeaderReferer"
-expectedReferer = "expectedReferer"
-Referer = "Referer"
-O_WRONLY = "O_WRONLY"
-WRONLY = "WRONLY"
-ome = "ome"
-ifModifiedSice = "ifModifiedSice"
-hd = "hd"
-pn = "pn"
\ No newline at end of file
diff --git a/go.mod b/go.mod
index 48d18ca..3469bd7 100644
--- a/go.mod
+++ b/go.mod
@@ -1,11 +1,11 @@
 module github.com/cloudwego/hertz
 
-go 1.17
+go 1.19
 
 require (
 	github.com/bytedance/gopkg v0.1.0
 	github.com/bytedance/mockey v1.2.12
-	github.com/bytedance/sonic v1.12.7
+	github.com/bytedance/sonic v1.13.2
 	github.com/cloudwego/netpoll v0.6.4
 	github.com/fsnotify/fsnotify v1.5.4
 	github.com/nyaruka/phonenumbers v1.0.55
@@ -17,8 +17,8 @@ require (
 )
 
 require (
-	github.com/bytedance/sonic/loader v0.2.2 // indirect
-	github.com/cloudwego/base64x v0.1.4 // indirect
+	github.com/bytedance/sonic/loader v0.2.4 // indirect
+	github.com/cloudwego/base64x v0.1.5 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
 	github.com/golang/protobuf v1.5.0 // indirect
 	github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 // indirect
diff --git a/go.sum b/go.sum
index 1e0a861..a527451 100644
--- a/go.sum
+++ b/go.sum
@@ -2,13 +2,13 @@ github.com/bytedance/gopkg v0.1.0 h1:aAxB7mm1qms4Wz4sp8e1AtKDOeFLtdqvGiUe7aonRJs
 github.com/bytedance/gopkg v0.1.0/go.mod h1:FtQG3YbQG9L/91pbKSw787yBQPutC+457AvDW77fgUQ=
 github.com/bytedance/mockey v1.2.12 h1:aeszOmGw8CPX8CRx1DZ/Glzb1yXvhjDh6jdFBNZjsU4=
 github.com/bytedance/mockey v1.2.12/go.mod h1:3ZA4MQasmqC87Tw0w7Ygdy7eHIc2xgpZ8Pona5rsYIk=
-github.com/bytedance/sonic v1.12.7 h1:CQU8pxOy9HToxhndH0Kx/S1qU/CuS9GnKYrGioDcU1Q=
-github.com/bytedance/sonic v1.12.7/go.mod h1:tnbal4mxOMju17EGfknm2XyYcpyCnIROYOEYuemj13I=
+github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ=
+github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
 github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
-github.com/bytedance/sonic/loader v0.2.2 h1:jxAJuN9fOot/cyz5Q6dUuMJF5OqQ6+5GfA8FjjQ0R4o=
-github.com/bytedance/sonic/loader v0.2.2/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
-github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
-github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
+github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY=
+github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
+github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
+github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
 github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
 github.com/cloudwego/netpoll v0.6.4 h1:z/dA4sOTUQof6zZIO4QNnLBXsDFFFEos9OOGloR6kno=
 github.com/cloudwego/netpoll v0.6.4/go.mod h1:BtM+GjKTdwKoC8IOzD08/+8eEn2gYoiNLipFca6BVXQ=
@@ -54,7 +54,6 @@ github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
 github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
 github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
 github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
-golang.org/x/arch v0.0.0-20201008161808-52c3e6f60cff/go.mod h1:flIaEI6LNU6xOCD5PaJvn9wGP0agmIOqjrtsKGRguv4=
 golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU=
 golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
@@ -84,4 +83,3 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
-rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
diff --git a/internal/bytesconv/bytesconv.go b/internal/bytesconv/bytesconv.go
index fd355ec..e2d1ca5 100644
--- a/internal/bytesconv/bytesconv.go
+++ b/internal/bytesconv/bytesconv.go
@@ -43,7 +43,6 @@ package bytesconv
 
 import (
 	"net/http"
-	"reflect"
 	"sync"
 	"time"
 	"unsafe"
@@ -71,23 +70,23 @@ func LowercaseBytes(b []byte) {
 // Note it may break if string and/or slice header will change
 // in the future go versions.
 func B2s(b []byte) string {
-	/* #nosec G103 */
 	return *(*string)(unsafe.Pointer(&b))
 }
 
+type sliceHeader struct {
+	Data unsafe.Pointer
+	Len  int
+	Cap  int
+}
+
 // S2b converts string to a byte slice without memory allocation.
 //
 // Note it may break if string and/or slice header will change
 // in the future go versions.
 func S2b(s string) (b []byte) {
-	/* #nosec G103 */
-	bh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
-	/* #nosec G103 */
-	sh := (*reflect.StringHeader)(unsafe.Pointer(&s))
-	bh.Data = sh.Data
-	bh.Len = sh.Len
-	bh.Cap = sh.Len
-	return b
+	*(*string)(unsafe.Pointer(&b)) = s
+	(*sliceHeader)(unsafe.Pointer(&b)).Cap = len(s)
+	return
 }
 
 func WriteHexInt(w network.Writer, n int) error {
diff --git a/pkg/app/client/client.go b/pkg/app/client/client.go
index e750096..09a371a 100644
--- a/pkg/app/client/client.go
+++ b/pkg/app/client/client.go
@@ -532,7 +532,7 @@ func (c *Client) do(ctx context.Context, req *protocol.Request, resp *protocol.R
 	c.mLock.Unlock()
 
 	if startCleaner {
-		go c.mCleaner()
+		go c.cleaner(isTLS)
 	}
 
 	return hc.Do(ctx, req, resp)
@@ -550,21 +550,25 @@ func (c *Client) CloseIdleConnections() {
 	c.mLock.Unlock()
 }
 
-func (c *Client) mCleaner() {
+func (c *Client) cleaner(isTLS bool) {
 	for {
 		time.Sleep(10 * time.Second)
-		if c.mClean() {
+		if c.cleanHostClients(isTLS) {
 			break
 		}
 	}
 }
 
-func (c *Client) mClean() bool {
+func (c *Client) cleanHostClients(isTLS bool) bool {
 	c.mLock.Lock()
 	defer c.mLock.Unlock()
-	for k, v := range c.m {
+	m := c.m
+	if isTLS {
+		m = c.ms
+	}
+	for k, v := range m {
 		if v.ShouldRemove() {
-			delete(c.m, k)
+			delete(m, k)
 			if f, ok := v.(io.Closer); ok {
 				err := f.Close()
 				if err != nil {
@@ -573,7 +577,7 @@ func (c *Client) mClean() bool {
 			}
 		}
 	}
-	return len(c.m) == 0
+	return len(m) == 0
 }
 
 func (c *Client) SetClientFactory(cf suite.ClientFactory) {
diff --git a/pkg/app/server/hertz.go b/pkg/app/server/hertz.go
index 1115ac8..b4a3f9c 100644
--- a/pkg/app/server/hertz.go
+++ b/pkg/app/server/hertz.go
@@ -91,8 +91,7 @@ func (h *Hertz) SetCustomSignalWaiter(f func(err chan error) error) {
 }
 
 // Default implementation for signal waiter.
-// SIGTERM triggers immediately close.
-// SIGHUP|SIGINT triggers graceful shutdown.
+// SIGHUP|SIGINT|SIGTERM triggers graceful shutdown.
 func waitSignal(errCh chan error) error {
 	signalToNotify := []os.Signal{syscall.SIGINT, syscall.SIGHUP, syscall.SIGTERM}
 	if signal.Ignored(syscall.SIGHUP) {
diff --git a/pkg/common/bytebufferpool/bytebuffer.go b/pkg/common/bytebufferpool/bytebuffer.go
index 6561746..2254cbb 100644
--- a/pkg/common/bytebufferpool/bytebuffer.go
+++ b/pkg/common/bytebufferpool/bytebuffer.go
@@ -61,6 +61,10 @@ func (b *ByteBuffer) Len() int {
 	return len(b.B)
 }
 
+func (b *ByteBuffer) Cap() int {
+	return cap(b.B)
+}
+
 // ReadFrom implements io.ReaderFrom.
 //
 // The function appends all the data read from r to b.
diff --git a/pkg/network/netpoll/transport.go b/pkg/network/netpoll/transport.go
index 7450292..badf6cf 100644
--- a/pkg/network/netpoll/transport.go
+++ b/pkg/network/netpoll/transport.go
@@ -47,18 +47,19 @@ func cancelContext(ctx context.Context) context.Context {
 }
 
 type transporter struct {
-	sync.RWMutex
 	senseClientDisconnection bool
 	network                  string
 	addr                     string
 	keepAliveTimeout         time.Duration
 	readTimeout              time.Duration
 	writeTimeout             time.Duration
-	listener                 net.Listener
-	eventLoop                netpoll.EventLoop
 	listenConfig             *net.ListenConfig
 	OnAccept                 func(conn net.Conn) context.Context
 	OnConnect                func(ctx context.Context, conn network.Conn) context.Context
+
+	mu sync.RWMutex
+	ln net.Listener
+	el netpoll.EventLoop
 }
 
 // For transporter switch
@@ -70,23 +71,31 @@ func NewTransporter(options *config.Options) network.Transporter {
 		keepAliveTimeout:         options.KeepAliveTimeout,
 		readTimeout:              options.ReadTimeout,
 		writeTimeout:             options.WriteTimeout,
-		listener:                 nil,
-		eventLoop:                nil,
 		listenConfig:             options.ListenConfig,
 		OnAccept:                 options.OnAccept,
 		OnConnect:                options.OnConnect,
 	}
 }
 
+func (t *transporter) Listener() net.Listener {
+	t.mu.RLock()
+	defer t.mu.RUnlock()
+	return t.ln
+}
+
 // ListenAndServe binds listen address and keep serving, until an error occurs
 // or the transport shutdowns
 func (t *transporter) ListenAndServe(onReq network.OnData) (err error) {
 	network.UnlinkUdsFile(t.network, t.addr) //nolint:errcheck
+
+	t.mu.Lock()
 	if t.listenConfig != nil {
-		t.listener, err = t.listenConfig.Listen(context.Background(), t.network, t.addr)
+		t.ln, err = t.listenConfig.Listen(context.Background(), t.network, t.addr)
 	} else {
-		t.listener, err = net.Listen(t.network, t.addr)
+		t.ln, err = net.Listen(t.network, t.addr)
 	}
+	ln := t.ln
+	t.mu.Unlock()
 
 	if err != nil {
 		panic("create netpoll listener fail: " + err.Error())
@@ -127,20 +136,19 @@ func (t *transporter) ListenAndServe(onReq network.OnData) (err error) {
 	}
 
 	// Create EventLoop
-	t.Lock()
-	t.eventLoop, err = netpoll.NewEventLoop(func(ctx context.Context, connection netpoll.Connection) error {
+	t.mu.Lock()
+	t.el, err = netpoll.NewEventLoop(func(ctx context.Context, connection netpoll.Connection) error {
 		return onReq(ctx, newConn(connection))
 	}, opts...)
-	t.Unlock()
+	eventLoop := t.el
+	t.mu.Unlock()
 	if err != nil {
 		panic("create netpoll event-loop fail")
 	}
 
 	// Start Server
-	hlog.SystemLogger().Infof("HTTP server listening on address=%s", t.listener.Addr().String())
-	t.RLock()
-	err = t.eventLoop.Serve(t.listener)
-	t.RUnlock()
+	hlog.SystemLogger().Infof("HTTP server listening on address=%s", ln.Addr().String())
+	err = eventLoop.Serve(ln)
 	if err != nil {
 		panic("netpoll server exit")
 	}
@@ -160,11 +168,11 @@ func (t *transporter) Close() error {
 func (t *transporter) Shutdown(ctx context.Context) error {
 	defer func() {
 		network.UnlinkUdsFile(t.network, t.addr) //nolint:errcheck
-		t.RUnlock()
+		t.mu.RUnlock()
 	}()
-	t.RLock()
-	if t.eventLoop == nil {
+	t.mu.RLock()
+	if t.el == nil {
 		return nil
 	}
-	return t.eventLoop.Shutdown(ctx)
+	return t.el.Shutdown(ctx)
 }
diff --git a/pkg/network/standard/transport.go b/pkg/network/standard/transport.go
index b5f169e..10ee991 100644
--- a/pkg/network/standard/transport.go
+++ b/pkg/network/standard/transport.go
@@ -19,8 +19,10 @@ package standard
 import (
 	"context"
 	"crypto/tls"
+	"errors"
 	"net"
 	"sync"
+	"sync/atomic"
 	"time"
 
 	"github.com/cloudwego/hertz/pkg/common/config"
@@ -42,40 +44,54 @@ type transport struct {
 	keepAliveTimeout time.Duration
 	readTimeout      time.Duration
 	handler          network.OnData
-	ln               net.Listener
 	tls              *tls.Config
 	listenConfig     *net.ListenConfig
-	lock             sync.Mutex
 	OnAccept         func(conn net.Conn) context.Context
 	OnConnect        func(ctx context.Context, conn network.Conn) context.Context
+
+	// active connections. it +1 after accept and -1 after handler returns
+	active int32
+
+	mu sync.RWMutex
+	ln net.Listener
+}
+
+func (t *transport) Listener() net.Listener {
+	t.mu.RLock()
+	defer t.mu.RUnlock()
+	return t.ln
 }
 
 func (t *transport) serve() (err error) {
 	network.UnlinkUdsFile(t.network, t.addr) //nolint:errcheck
-	t.lock.Lock()
+	t.mu.Lock()
 	if t.listenConfig != nil {
 		t.ln, err = t.listenConfig.Listen(context.Background(), t.network, t.addr)
 	} else {
 		t.ln, err = net.Listen(t.network, t.addr)
 	}
-	t.lock.Unlock()
+	// fix concurrency issue
+	// normally listener must not be changed during serve()
+	ln := t.ln
+	t.mu.Unlock()
 	if err != nil {
 		return err
 	}
-	hlog.SystemLogger().Infof("HTTP server listening on address=%s", t.ln.Addr().String())
+	hlog.SystemLogger().Infof("HTTP server listening on address=%s", ln.Addr().String())
 	for {
 		ctx := context.Background()
-		conn, err := t.ln.Accept()
-		var c network.Conn
+		conn, err := ln.Accept()
 		if err != nil {
 			hlog.SystemLogger().Errorf("Error=%s", err.Error())
 			return err
 		}
+		t.updateActive(1)
 
 		if t.OnAccept != nil {
 			ctx = t.OnAccept(conn)
 		}
 
+		var c network.Conn
 		if t.tls != nil {
 			c = newTLSConn(tls.Server(conn, t.tls), t.readBufferSize)
 		} else {
@@ -85,10 +101,17 @@ func (t *transport) serve() (err error) {
 		if t.OnConnect != nil {
 			ctx = t.OnConnect(ctx, c)
 		}
-		go t.handler(ctx, c)
+		go func(ctx context.Context, conn network.Conn) {
+			t.handler(ctx, conn)
+			t.updateActive(-1)
+		}(ctx, c)
 	}
 }
 
+func (t *transport) updateActive(delta int32) int32 {
+	return atomic.AddInt32(&t.active, delta)
+}
+
 func (t *transport) ListenAndServe(onData network.OnData) (err error) {
 	t.handler = onData
 	return t.serve()
@@ -100,17 +123,47 @@ func (t *transport) Close() error {
 	return t.Shutdown(ctx)
 }
 
+var (
+	shutdownTimeout = 30 * time.Second
+	shutdownTicker  = 10 * time.Millisecond
+
+	errShutdownTimeout = errors.New("shutdown timeout")
+)
+
 func (t *transport) Shutdown(ctx context.Context) error {
 	defer func() {
 		network.UnlinkUdsFile(t.network, t.addr) //nolint:errcheck
 	}()
-	t.lock.Lock()
-	if t.ln != nil {
-		_ = t.ln.Close()
+	if ln := t.Listener(); ln != nil {
+		_ = ln.Close()
+	}
+
+	tk := time.NewTicker(shutdownTicker)
+	defer tk.Stop()
+
+	// make sure t.active is updated correctly under concurrency
+	<-tk.C
+
+	// luckily the server is idle, no more active connections
+	if t.updateActive(0) <= 0 {
+		return nil
+	}
+
+	// check periodically to see if all connections closed
+	t0 := time.Now()
+	for {
+		select {
+		case now := <-tk.C:
+			if t.updateActive(0) <= 0 {
+				return nil
+			}
+			if now.Sub(t0) > shutdownTimeout {
+				return errShutdownTimeout
+			}
+		case <-ctx.Done():
+			return ctx.Err()
+		}
 	}
-	t.lock.Unlock()
-	<-ctx.Done()
-	return nil
 }
 
 // For transporter switch
diff --git a/pkg/protocol/args.go b/pkg/protocol/args.go
index fa2a670..abdd1cb 100644
--- a/pkg/protocol/args.go
+++ b/pkg/protocol/args.go
@@ -54,8 +54,6 @@ const (
 	ArgsHasValue = false
 )
 
-var nilByteSlice = []byte{}
-
 type argsScanner struct {
 	b []byte
 }
@@ -167,9 +165,15 @@ func allocArg(h []argsKV) ([]argsKV, *argsKV) {
 	n := len(h)
 	if cap(h) > n {
 		h = h[:n+1]
-	} else {
-		h = append(h, argsKV{})
+		kv := &h[n]
+		if kv.value == nil {
+			// bytes in value would be reused, and it's not always nil
+			// only set to empty when it's nil
+			kv.value = []byte{}
+		}
+		return h, kv
 	}
+	h = append(h, argsKV{value: []byte{}})
 	return h, &h[n]
 }
 
@@ -228,10 +232,7 @@ func peekArgBytes(h []argsKV, k []byte) []byte {
 	for i, n := 0, len(h); i < n; i++ {
 		kv := &h[i]
 		if bytes.Equal(kv.key, k) {
-			if kv.value != nil {
-				return kv.value
-			}
-			return nilByteSlice
+			return kv.value
 		}
 	}
 	return nil
diff --git a/pkg/protocol/header.go b/pkg/protocol/header.go
index 8888548..e68ec5c 100644
--- a/pkg/protocol/header.go
+++ b/pkg/protocol/header.go
@@ -258,8 +258,8 @@ func (h *RequestHeader) Add(key, value string) {
 	if h.setSpecialHeader(bytesconv.S2b(key), bytesconv.S2b(value)) {
 		return
 	}
-
-	k := getHeaderKeyBytes(&h.bufKV, key, h.disableNormalizing)
+	k := []byte(key)
+	utils.NormalizeHeaderKey(k, h.disableNormalizing)
 	h.h = appendArg(h.h, bytesconv.B2s(k), value, ArgsHasValue)
 }
 
@@ -336,7 +336,7 @@ func (h *RequestHeader) GetBufValue() []byte {
 // HasAcceptEncodingBytes returns true if the header contains
 // the given Accept-Encoding value.
 func (h *RequestHeader) HasAcceptEncodingBytes(acceptEncoding []byte) bool {
-	ae := h.peek(bytestr.StrAcceptEncoding)
+	ae := h.peek(consts.HeaderAcceptEncoding)
 	n := bytes.Index(ae, acceptEncoding)
 	if n < 0 {
 		return false
@@ -352,7 +352,7 @@ func (h *RequestHeader) HasAcceptEncodingBytes(acceptEncoding []byte) bool {
 }
 
 func (h *RequestHeader) PeekIfModifiedSinceBytes() []byte {
-	return h.peek(bytestr.StrIfModifiedSince)
+	return h.peek(consts.HeaderIfModifiedSince)
 }
 
 // RequestURI returns RequestURI from the first HTTP request line.
@@ -551,8 +551,8 @@ func (h *ResponseHeader) Add(key, value string) {
 	if h.setSpecialHeader(bytesconv.S2b(key), bytesconv.S2b(value)) {
 		return
 	}
-
-	k := getHeaderKeyBytes(&h.bufKV, key, h.disableNormalizing)
+	k := []byte(key)
+	utils.NormalizeHeaderKey(k, h.disableNormalizing)
 	h.h = appendArg(h.h, bytesconv.B2s(k), value, ArgsHasValue)
 }
 
@@ -622,9 +622,9 @@ func (h *ResponseHeader) DelCookieBytes(key []byte) {
 
 // DelBytes deletes header with the given key.
 func (h *ResponseHeader) DelBytes(key []byte) {
-	h.bufKV.key = append(h.bufKV.key[:0], key...)
-	utils.NormalizeHeaderKey(h.bufKV.key, h.disableNormalizing)
-	h.del(h.bufKV.key)
+	k := []byte(string(key))
+	utils.NormalizeHeaderKey(k, h.disableNormalizing)
+	h.del(k)
 }
 
 // Header returns response header representation.
@@ -636,7 +636,7 @@ func (h *ResponseHeader) Header() []byte {
 }
 
 func (h *ResponseHeader) PeekLocation() []byte {
-	return h.peek(bytestr.StrLocation)
+	return h.peek(consts.HeaderLocation)
 }
 
 // DelClientCookie instructs the client to remove the given cookie.
@@ -684,8 +684,9 @@ func (h *ResponseHeader) DelClientCookieBytes(key []byte) {
 // Returned value is valid until the next call to ResponseHeader.
 // Do not store references to returned value. Make copies instead.
 func (h *ResponseHeader) Peek(key string) []byte {
-	k := getHeaderKeyBytes(&h.bufKV, key, h.disableNormalizing)
-	return h.peek(k)
+	k := []byte(key)
+	utils.NormalizeHeaderKey(k, h.disableNormalizing)
+	return h.peek(bytesconv.B2s(k))
 }
 
 func (h *ResponseHeader) IsDisableNormalizing() bool {
@@ -699,8 +700,8 @@ func (h *ResponseHeader) ParseSetCookie(value []byte) {
 	kv.value = append(kv.value[:0], value...)
 }
 
-func (h *ResponseHeader) peek(key []byte) []byte {
-	switch string(key) {
+func (h *ResponseHeader) peek(key string) []byte {
+	switch key {
 	case consts.HeaderContentType:
 		return h.ContentType()
 	case consts.HeaderContentEncoding:
@@ -711,7 +712,7 @@ func (h *ResponseHeader) peek(key []byte) []byte {
 		if h.ConnectionClose() {
 			return bytestr.StrClose
 		}
-		return peekArgBytes(h.h, key)
+		return peekArgStr(h.h, key)
 	case consts.HeaderContentLength:
 		return h.contentLengthBytes
 	case consts.HeaderSetCookie:
@@ -719,7 +720,7 @@ func (h *ResponseHeader) peek(key []byte) []byte {
 	case consts.HeaderTrailer:
 		return h.Trailer().GetBytes()
 	default:
-		return peekArgBytes(h.h, key)
+		return peekArgStr(h.h, key)
 	}
 }
 
@@ -730,7 +731,8 @@ func (h *ResponseHeader) peek(key []byte) []byte {
 // Any future calls to the Peek* will modify the returned value.
 // Do not store references to returned value. Use ResponseHeader.GetAll(key) instead.
 func (h *ResponseHeader) PeekAll(key string) [][]byte {
-	k := getHeaderKeyBytes(&h.bufKV, key, h.disableNormalizing)
+	k := []byte(key)
+	utils.NormalizeHeaderKey(k, h.disableNormalizing)
 	return h.peekAll(k)
 }
 
@@ -772,7 +774,8 @@ func (h *ResponseHeader) peekAll(key []byte) [][]byte {
 // Any future calls to the Peek* will modify the returned value.
 // Do not store references to returned value. Use RequestHeader.GetAll(key) instead.
 func (h *RequestHeader) PeekAll(key string) [][]byte {
-	k := getHeaderKeyBytes(&h.bufKV, key, h.disableNormalizing)
+	k := []byte(key)
+	utils.NormalizeHeaderKey(k, h.disableNormalizing)
 	return h.peekAll(k)
 }
 
@@ -984,7 +987,8 @@ func (h *ResponseHeader) StatusCode() int {
 
 // Del deletes header with the given key.
 func (h *ResponseHeader) Del(key string) {
-	k := getHeaderKeyBytes(&h.bufKV, key, h.disableNormalizing)
+	k := []byte(key)
+	utils.NormalizeHeaderKey(k, h.disableNormalizing)
 	h.del(k)
 }
 
@@ -1013,7 +1017,8 @@ func (h *ResponseHeader) del(key []byte) {
 //
 // Use AddBytesV for setting multiple header values under the same key.
 func (h *ResponseHeader) SetBytesV(key string, value []byte) {
-	k := getHeaderKeyBytes(&h.bufKV, key, h.disableNormalizing)
+	k := []byte(key)
+	utils.NormalizeHeaderKey(k, h.disableNormalizing)
 	h.SetCanonical(k, value)
 }
 
@@ -1064,14 +1069,15 @@ func (h *RequestHeader) SetByteRange(startPos, endPos int) {
 
 // DelBytes deletes header with the given key.
 func (h *RequestHeader) DelBytes(key []byte) {
-	h.bufKV.key = append(h.bufKV.key[:0], key...)
-	utils.NormalizeHeaderKey(h.bufKV.key, h.disableNormalizing)
-	h.del(h.bufKV.key)
+	k := []byte(string(key))
+	utils.NormalizeHeaderKey(k, h.disableNormalizing)
+	h.del(k)
 }
 
 // Del deletes header with the given key.
 func (h *RequestHeader) Del(key string) {
-	k := getHeaderKeyBytes(&h.bufKV, key, h.disableNormalizing)
+	k := []byte(key)
+	utils.NormalizeHeaderKey(k, h.disableNormalizing)
 	h.del(k)
 }
 
@@ -1128,8 +1134,9 @@ func (h *RequestHeader) CopyTo(dst *RequestHeader) {
 // Returned value is valid until the next call to RequestHeader.
 // Do not store references to returned value. Make copies instead.
 func (h *RequestHeader) Peek(key string) []byte {
-	k := getHeaderKeyBytes(&h.bufKV, key, h.disableNormalizing)
-	return h.peek(k)
+	k := []byte(key)
+	utils.NormalizeHeaderKey(k, h.disableNormalizing)
+	return h.peek(bytesconv.B2s(k))
 }
 
 // SetMultipartFormBoundary sets the following Content-Type:
@@ -1277,9 +1284,9 @@ func (h *RequestHeader) SetRequestURIBytes(requestURI []byte) {
 //
 // Use AddBytesKV for setting multiple header values under the same key.
 func (h *RequestHeader) SetBytesKV(key, value []byte) {
-	h.bufKV.key = append(h.bufKV.key[:0], key...)
-	utils.NormalizeHeaderKey(h.bufKV.key, h.disableNormalizing)
-	h.SetCanonical(h.bufKV.key, value)
+	k := []byte(string(key))
+	utils.NormalizeHeaderKey(k, h.disableNormalizing)
+	h.SetCanonical(k, value)
 }
 
 func (h *RequestHeader) AddArgBytes(key, value []byte, noValue bool) {
@@ -1324,11 +1331,11 @@ func (h *RequestHeader) Cookies() []*Cookie {
 }
 
 func (h *RequestHeader) PeekRange() []byte {
-	return h.peek(bytestr.StrRange)
+	return h.peek(consts.HeaderRange)
 }
 
 func (h *RequestHeader) PeekContentEncoding() []byte {
-	return h.peek(bytestr.StrContentEncoding)
+	return h.peek(consts.HeaderContentEncoding)
 }
 
 // FullCookie returns complete cookie bytes
@@ -1409,7 +1416,8 @@ func (h *RequestHeader) Set(key, value string) {
 }
 
 func initHeaderKV(kv *argsKV, key, value string, disableNormalizing bool) {
-	kv.key = getHeaderKeyBytes(kv, key, disableNormalizing)
+	kv.key = append(kv.key[:0], key...)
+	utils.NormalizeHeaderKey(kv.key, disableNormalizing)
 	kv.value = append(kv.value[:0], value...)
 }
 
@@ -1598,8 +1606,8 @@ func appendArg(args []argsKV, key, value string, noValue bool) []argsKV {
 	return args
 }
 
-func (h *RequestHeader) peek(key []byte) []byte {
-	switch string(key) {
+func (h *RequestHeader) peek(key string) []byte {
+	switch key {
 	case consts.HeaderHost:
 		return h.Host()
 	case consts.HeaderContentType:
@@ -1610,18 +1618,18 @@ func (h *RequestHeader) peek(key []byte) []byte {
 		if h.ConnectionClose() {
 			return bytestr.StrClose
 		}
-		return peekArgBytes(h.h, key)
+		return peekArgStr(h.h, key)
 	case consts.HeaderContentLength:
 		return h.contentLengthBytes
 	case consts.HeaderCookie:
 		if h.cookiesCollected {
 			return appendRequestCookieBytes(nil, h.cookies)
 		}
-		return peekArgBytes(h.h, key)
+		return peekArgStr(h.h, key)
 	case consts.HeaderTrailer:
 		return h.Trailer().GetBytes()
 	default:
-		return peekArgBytes(h.h, key)
+		return peekArgStr(h.h, key)
 	}
 }
 
@@ -1693,12 +1701,6 @@ func refreshServerDate() {
 	ServerDate.Store(b)
 }
 
-func getHeaderKeyBytes(kv *argsKV, key string, disableNormalizing bool) []byte {
-	kv.key = append(kv.key[:0], key...)
-	utils.NormalizeHeaderKey(kv.key, disableNormalizing)
-	return kv.key
-}
-
 // SetMethodBytes sets HTTP request method.
 func (h *RequestHeader) SetMethodBytes(method []byte) {
 	h.method = append(h.method[:0], method...)
diff --git a/pkg/protocol/http1/client.go b/pkg/protocol/http1/client.go
index 5bac3e9..eb57d0d 100644
--- a/pkg/protocol/http1/client.go
+++ b/pkg/protocol/http1/client.go
@@ -363,6 +363,10 @@ func (c *HostClient) DoRedirects(ctx context.Context, req *protocol.Request, res
 // It is recommended obtaining req and resp via AcquireRequest
 // and AcquireResponse in performance-critical code.
 func (c *HostClient) Do(ctx context.Context, req *protocol.Request, resp *protocol.Response) error {
+	if ctx == nil {
+		panic("ctx is nil")
+	}
+
 	var (
 		err                error
 		canIdempotentRetry bool
diff --git a/pkg/protocol/request.go b/pkg/protocol/request.go
index 8e4b40b..1bec09f 100644
--- a/pkg/protocol/request.go
+++ b/pkg/protocol/request.go
@@ -172,7 +172,7 @@ func (req *Request) BodyBuffer() *bytebufferpool.ByteBuffer {
 //     with ContinueReadBody.
 //   - Or close the connection.
 func (req *Request) MayContinue() bool {
-	return bytes.Equal(req.Header.peek(bytestr.StrExpect), bytestr.Str100Continue)
+	return bytes.Equal(req.Header.peek(consts.HeaderExpect), bytestr.Str100Continue)
 }
 
 // Scheme returns the scheme of the request.
@@ -238,7 +238,7 @@ func (req *Request) MultipartForm() (*multipart.Form, error) {
 		return nil, errors.ErrNoMultipartForm
 	}
 
-	ce := req.Header.peek(bytestr.StrContentEncoding)
+	ce := req.Header.peek(consts.HeaderContentEncoding)
 	var err error
 	var f *multipart.Form
 
@@ -411,7 +411,7 @@ func (req *Request) ResetBody() {
 	req.RemoveMultipartFormFiles()
 	req.CloseBodyStream() //nolint:errcheck
 	if req.body != nil {
-		if req.body.Len() <= req.maxKeepBodySize {
+		if req.body.Cap() <= req.maxKeepBodySize {
 			req.body.Reset()
 			return
 		}
diff --git a/pkg/protocol/response.go b/pkg/protocol/response.go
index ccace13..174ae4f 100644
--- a/pkg/protocol/response.go
+++ b/pkg/protocol/response.go
@@ -296,7 +296,7 @@ func (resp *Response) ResetBody() {
 	resp.bodyRaw = nil
 	resp.CloseBodyStream() //nolint:errcheck
 	if resp.body != nil {
-		if resp.body.Len() <= resp.maxKeepBodySize {
+		if resp.body.Cap() <= resp.maxKeepBodySize {
 			resp.body.Reset()
 			return
 		}
diff --git a/pkg/protocol/trailer.go b/pkg/protocol/trailer.go
index 6820f87..ff90e26 100644
--- a/pkg/protocol/trailer.go
+++ b/pkg/protocol/trailer.go
@@ -41,13 +41,15 @@ func (t *Trailer) Get(key string) string {
 // Returned value is valid until the next call to Trailer.
 // Do not store references to returned value. Make copies instead.
 func (t *Trailer) Peek(key string) []byte {
-	k := getHeaderKeyBytes(&t.bufKV, key, t.disableNormalizing)
+	k := []byte(key)
+	utils.NormalizeHeaderKey(k, t.disableNormalizing)
 	return peekArgBytes(t.h, k)
 }
 
 // Del deletes trailer with the given key.
 func (t *Trailer) Del(key string) {
-	k := getHeaderKeyBytes(&t.bufKV, key, t.disableNormalizing)
+	k := []byte(key)
+	utils.NormalizeHeaderKey(k, t.disableNormalizing)
 	t.h = delAllArgsBytes(t.h, k)
 }
 
@@ -163,7 +165,7 @@ func (t *Trailer) SetTrailers(trailers []byte) (err error) {
 		}
 
 		utils.NormalizeHeaderKey(trailerKey, t.disableNormalizing)
-		err = t.addArgBytes(trailerKey, nilByteSlice, argsNoValue)
+		err = t.addArgBytes(trailerKey, nil, argsNoValue)
 	}
 	return
 }
diff --git a/pkg/route/engine.go b/pkg/route/engine.go
index 2a7b60c..7c87826 100644
--- a/pkg/route/engine.go
+++ b/pkg/route/engine.go
@@ -47,6 +47,7 @@ import (
 	"fmt"
 	"html/template"
 	"io"
+	"net"
 	"path/filepath"
 	"reflect"
 	"runtime"
@@ -258,7 +259,18 @@ func (engine *Engine) IsStreamRequestBody() bool {
 }
 
 func (engine *Engine) IsRunning() bool {
-	return atomic.LoadUint32(&engine.status) == statusRunning
+	if atomic.LoadUint32(&engine.status) != statusRunning {
+		return false
+	}
+	// double check listener
+	type ListenerIface interface {
+		Listener() net.Listener
+	}
+	v, ok := engine.transport.(ListenerIface)
+	if ok {
+		return v.Listener() != nil
+	}
+	return true // default behavior if no ListenerIface
 }
 
 func (engine *Engine) HijackConnHandle(c network.Conn, h app.HijackHandler) {
diff --git a/version.go b/version.go
index 1173b73..58c3022 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.9.6"
+	Version = "v0.9.7"
 )


Test patch:
diff --git a/.github/workflows/cmd-tests.yml b/.github/workflows/cmd-tests.yml
new file mode 100644
index 0000000..047785b
--- /dev/null
+++ b/.github/workflows/cmd-tests.yml
@@ -0,0 +1,55 @@
+name: Cmd Tests
+
+on:
+  push:
+    paths:
+      - 'cmd/**'
+  pull_request:
+    paths:
+      - 'cmd/**'
+
+jobs:
+  hz-test-unix:
+    runs-on: [ self-hosted, Linux, X64 ]
+    steps:
+      - uses: actions/checkout@v4
+      - name: Set up Go
+        uses: actions/setup-go@v5
+        with:
+          go-version: stable
+          cache: false # don't use cache for self-hosted runners
+
+      - name: Setup Environment
+        run: |
+          echo "GOPATH=$(go env GOPATH)" >> $GITHUB_ENV
+          echo "$(go env GOPATH)/bin" >> $GITHUB_PATH
+
+      - name: Hz Test
+        run: |
+          cd cmd/hz
+          sh test_hz_unix.sh
+
+
+  hz-test-windows:
+    runs-on: windows-latest
+    steps:
+      - uses: actions/checkout@v4
+      - name: Set up Go
+        uses: actions/setup-go@v5
+        with:
+          go-version: stable
+
+      - name: Install Protobuf
+        shell: pwsh
+        run: |
+          Invoke-WebRequest https://github.com/protocolbuffers/protobuf/releases/download/v3.19.4/protoc-3.19.4-win64.zip -OutFile protoc-3.19.4-win64.zip
+          Expand-Archive protoc-3.19.4-win64.zip -DestinationPath protoc-3.19.4-win64
+          $GOPATH=go env GOPATH
+          Copy-Item -Path protoc-3.19.4-win64\bin\protoc.exe -Destination $GOPATH\bin
+          Copy-Item -Path protoc-3.19.4-win64\include\* -Destination cmd\hz\testdata\include\google -Recurse
+          protoc --version
+
+      - name: Hz Test
+        run: |
+          cd cmd/hz
+          sh test_hz_windows.sh
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
deleted file mode 100644
index 4eac500..0000000
--- a/.github/workflows/tests.yml
+++ /dev/null
@@ -1,121 +0,0 @@
-name: Tests
-
-on: [push, pull_request]
-
-jobs:
-  compat-test:
-    strategy:
-      matrix:
-        version: ["1.17", "1.18", "1.19"]
-    runs-on: [self-hosted, Linux, X64]
-    steps:
-      - uses: actions/checkout@v4
-      - name: Set up Go
-        uses: actions/setup-go@v5
-        with:
-          go-version: ${{ matrix.version }}
-          cache: false # don't use cache for self-hosted runners
-
-      # Just build tests without running them
-      - name: Build Test
-        run: go test -run=nope ./...
-
-  unit-test:
-    strategy:
-      matrix:
-        version: ["1.20", "1.21", "1.22", "1.23"]
-    runs-on: [self-hosted, Linux, X64]
-    steps:
-      - uses: actions/checkout@v4
-      - name: Set up Go
-        uses: actions/setup-go@v5
-        with:
-          go-version: ${{ matrix.version }}
-          cache: false # don't use cache for self-hosted runners
-
-      - name: Unit Test
-        run: go test -race ./...
-
-  ut-windows:
-    runs-on: [self-hosted, Windows]
-    steps:
-      - uses: actions/checkout@v4
-      - name: Set up Go
-        uses: actions/setup-go@v5
-        with:
-          go-version: stable
-          cache: false # don't use cache for self-hosted runners
-
-      - name: Unit Test
-        run: go test -race ./...
-
-  code-cov:
-    runs-on: [self-hosted, Linux, X64]
-    steps:
-      - uses: actions/checkout@v4
-      - name: Set up Go
-        uses: actions/setup-go@v5
-        with:
-          go-version: stable
-          cache: false # don't use cache for self-hosted runners
-
-      - name: Unit Test
-        run: go test -covermode=atomic -coverprofile=coverage.txt ./...
-
-      - name: Codecov
-        run: bash <(curl -s https://codecov.io/bash)
-
-  hz-test-unix:
-    runs-on: [ self-hosted, Linux, X64 ]
-    steps:
-      - uses: actions/checkout@v4
-      - name: Set up Go
-        uses: actions/setup-go@v5
-        with:
-          go-version: stable
-          cache: false # don't use cache for self-hosted runners
-
-      - name: Setup Environment
-        run: |
-          echo "GOPATH=$(go env GOPATH)" >> $GITHUB_ENV
-          echo "$(go env GOPATH)/bin" >> $GITHUB_PATH
-
-      - name: Get changed files
-        id: changed-files
-        uses: tj-actions/changed-files@v41
-
-      - name: Hz Test
-        if: contains(steps.changed-files.outputs.all_changed_files, 'cmd/hz')
-        run: |
-          cd cmd/hz
-          sh test_hz_unix.sh
-
-
-  hz-test-windows:
-    runs-on: windows-latest
-    steps:
-      - uses: actions/checkout@v4
-      - name: Set up Go
-        uses: actions/setup-go@v5
-        with:
-          go-version: stable
-
-      - name: Get changed files
-        id: changed-files
-        uses: tj-actions/changed-files@v41
-
-      - name: Install Protobuf
-        shell: pwsh
-        run: |
-          Invoke-WebRequest https://github.com/protocolbuffers/protobuf/releases/download/v3.19.4/protoc-3.19.4-win64.zip -OutFile protoc-3.19.4-win64.zip
-          Expand-Archive protoc-3.19.4-win64.zip -DestinationPath protoc-3.19.4-win64
-          $GOPATH=go env GOPATH
-          Copy-Item -Path protoc-3.19.4-win64\bin\protoc.exe -Destination $GOPATH\bin
-          Copy-Item -Path protoc-3.19.4-win64\include\* -Destination cmd\hz\testdata\include\google -Recurse
-          protoc --version
-
-      - name: Hz Test
-        if: contains(steps.changed-files.outputs.all_changed_files, 'cmd/hz')
-        run: |
-          cd cmd/hz
-          sh test_hz_windows.sh
diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml
new file mode 100644
index 0000000..601ba0f
--- /dev/null
+++ b/.github/workflows/unit-tests.yml
@@ -0,0 +1,66 @@
+name: Unit Tests
+
+on: [push, pull_request]
+
+jobs:
+  unit-test-x64:
+    strategy:
+      matrix:
+        version: ["1.19", "1.20", oldstable, stable]
+    runs-on: [self-hosted, Linux, X64]
+    steps:
+      - uses: actions/checkout@v4
+      - name: Set up Go
+        uses: actions/setup-go@v5
+        with:
+          go-version: ${{ matrix.version }}
+          cache: false # don't use cache for self-hosted runners
+
+      - name: Unit Test
+        run: go test -race ./...
+
+  unit-test-arm64:
+    strategy:
+      matrix:
+        version: ["1.19", "1.20", oldstable, stable]
+    runs-on: [self-hosted, ARM64]
+    steps:
+      - uses: actions/checkout@v4
+      - name: Set up Go
+        uses: actions/setup-go@v5
+        with:
+          go-version: ${{ matrix.version }}
+          cache: false # don't use cache for self-hosted runners
+
+      - name: Unit Test
+        run: go test -race ./...
+
+  ut-windows:
+    runs-on: [self-hosted, Windows]
+    steps:
+      - uses: actions/checkout@v4
+      - name: Set up Go
+        uses: actions/setup-go@v5
+        with:
+          go-version: stable
+          cache: false # don't use cache for self-hosted runners
+
+      - name: Unit Test
+        run: go test -race ./...
+
+  code-cov:
+    runs-on: [self-hosted, Linux, X64]
+    steps:
+      - uses: actions/checkout@v4
+      - name: Set up Go
+        uses: actions/setup-go@v5
+        with:
+          go-version: stable
+          cache: false # don't use cache for self-hosted runners
+
+      - name: Unit Test
+        run: go test -covermode=atomic -coverprofile=coverage.txt ./...
+
+      - uses: codecov/codecov-action@v5
+        with:
+          fail_ci_if_error: true
diff --git a/internal/testutils/testutils.go b/internal/testutils/testutils.go
new file mode 100644
index 0000000..4de5835
--- /dev/null
+++ b/internal/testutils/testutils.go
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2025 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 testutils
+
+import (
+	"net"
+	"path"
+	"reflect"
+	"unsafe"
+
+	"github.com/cloudwego/hertz/pkg/network"
+	"github.com/cloudwego/hertz/pkg/route"
+)
+
+var (
+	engineType    = reflect.TypeOf((*route.Engine)(nil)).Elem()
+	transportType = reflect.TypeOf((*network.Transporter)(nil)).Elem()
+)
+
+func unwrapValue(rv reflect.Value) reflect.Value {
+	for rv.Kind() == reflect.Ptr || rv.Kind() == reflect.Interface {
+		rv = rv.Elem()
+	}
+	return rv
+}
+
+func getEngine(rv reflect.Value) reflect.Value {
+	rv = unwrapValue(rv)
+	if rv.Type() == engineType {
+		return rv
+	}
+	for i := 0; i < rv.NumField(); i++ {
+		f := unwrapValue(rv.Field(i))
+		if f.Type() == engineType {
+			return f
+		}
+	}
+	panic("not found *route.Engine")
+}
+
+func getNetworkTransporter(rv reflect.Value) reflect.Value {
+	rv = unwrapValue(rv)
+	for i := 0; i < rv.NumField(); i++ {
+		f := rv.Field(i)
+		if f.Type() == transportType {
+			return f
+		}
+	}
+	panic("not found network.Transporter")
+}
+
+func getUnexportedField(f reflect.Value) interface{} {
+	return reflect.NewAt(f.Type(), unsafe.Pointer(f.Addr().Pointer())).Elem().Interface()
+}
+
+// GetListener extracts net.Listener from network.Transporter in route.Engine
+func GetListener(v interface{}) net.Listener {
+	rv := getEngine(reflect.ValueOf(v)) // *route.Engine
+	rv = getNetworkTransporter(rv)      // network.Transporter
+
+	// implemented by network/netpoll & standard
+	type ListenerIface interface {
+		Listener() net.Listener
+	}
+
+	// NOTE: do not access net.Listener directly, it may cause race issue.
+	// use `Listener()` method
+	trans := getUnexportedField(rv)
+	if p, ok := trans.(ListenerIface); ok {
+		return p.Listener()
+	}
+	panic("network.Transporter has no Listener() method")
+}
+
+// GetListenerAddr is shortcut of GetListener(e).Addr().String()
+func GetListenerAddr(v interface{}) string {
+	return GetListener(v).Addr().String()
+}
+
+// GetURL ...
+func GetURL(v interface{}, p string) string {
+	return "http://" + path.Join(GetListenerAddr(v), p)
+}
diff --git a/internal/testutils/testutils_test.go b/internal/testutils/testutils_test.go
new file mode 100644
index 0000000..756eded
--- /dev/null
+++ b/internal/testutils/testutils_test.go
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2025 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 testutils
+
+import (
+	"context"
+	"io"
+	"net/http"
+	"testing"
+	"time"
+
+	"github.com/cloudwego/hertz/pkg/app"
+	"github.com/cloudwego/hertz/pkg/common/config"
+	"github.com/cloudwego/hertz/pkg/protocol/consts"
+	"github.com/cloudwego/hertz/pkg/route"
+)
+
+func TestGetListener(t *testing.T) {
+	msg := "world"
+	e := route.NewEngine(&config.Options{Network: "tcp", Addr: "127.0.0.1:0"})
+	e.GET("/hello", func(ctx context.Context, c *app.RequestContext) {
+		c.String(consts.StatusOK, msg)
+	})
+	defer e.Shutdown(context.Background())
+
+	go e.Run()
+	time.Sleep(20 * time.Millisecond)
+
+	type AppServer struct {
+		*route.Engine
+	}
+	if GetURL(e, "") != GetURL(&AppServer{e}, "") {
+		t.Fatal("ne")
+	}
+
+	resp, err := http.Get(GetURL(e, "/hello"))
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer resp.Body.Close()
+
+	body, err := io.ReadAll(resp.Body)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if s := string(body); s != msg {
+		t.Fatal(s, "!=", msg)
+	}
+}
diff --git a/pkg/app/client/client_test.go b/pkg/app/client/client_test.go
index e466809..5e182f6 100644
--- a/pkg/app/client/client_test.go
+++ b/pkg/app/client/client_test.go
@@ -66,6 +66,7 @@ import (
 	"time"
 
 	"github.com/cloudwego/hertz/internal/bytestr"
+	"github.com/cloudwego/hertz/internal/testutils"
 	"github.com/cloudwego/hertz/pkg/app"
 	"github.com/cloudwego/hertz/pkg/app/client/retry"
 	"github.com/cloudwego/hertz/pkg/common/config"
@@ -75,6 +76,7 @@ import (
 	"github.com/cloudwego/hertz/pkg/network/dialer"
 	"github.com/cloudwego/hertz/pkg/network/standard"
 	"github.com/cloudwego/hertz/pkg/protocol"
+	"github.com/cloudwego/hertz/pkg/protocol/client"
 	"github.com/cloudwego/hertz/pkg/protocol/consts"
 	"github.com/cloudwego/hertz/pkg/protocol/http1"
 	"github.com/cloudwego/hertz/pkg/protocol/http1/req"
@@ -111,25 +113,10 @@ func nextUnixSock() string {
 func waitEngineRunning(e *route.Engine) {
 	for i := 0; i < 100; i++ {
 		if e.IsRunning() {
-			break
+			return
 		}
 		time.Sleep(10 * time.Millisecond)
 	}
-	opts := e.GetOptions()
-	network, addr := opts.Network, opts.Addr
-	if network == "" {
-		network = "tcp"
-	}
-	for i := 0; i < 100; i++ {
-		conn, err := net.Dial(network, addr)
-		if err != nil {
-			time.Sleep(10 * time.Millisecond)
-			continue
-		}
-		conn.Close()
-		return
-	}
-
 	panic("not running")
 }
 
@@ -171,7 +158,7 @@ func TestCloseIdleConnections(t *testing.T) {
 		t.Errorf("expected 0 conns got %d", conns)
 	}
 
-	c.mClean()
+	c.cleanHostClients(false)
 
 	func() {
 		c.mLock.Lock()
@@ -348,7 +335,7 @@ func TestClientNilResp(t *testing.T) {
 
 func TestClientParseConn(t *testing.T) {
 	opt := config.NewOptions([]config.Option{})
-	opt.Addr = "127.0.0.1:10005"
+	opt.Addr = "127.0.0.1:0"
 	engine := route.NewEngine(opt)
 	engine.GET("/", func(c context.Context, ctx *app.RequestContext) {
 	})
@@ -357,6 +344,7 @@ func TestClientParseConn(t *testing.T) {
 		engine.Close()
 	}()
 	waitEngineRunning(engine)
+	opt.Addr = testutils.GetListenerAddr(engine)
 
 	c, _ := NewClient(WithDialer(newMockDialerWithCustomFunc(opt.Network, opt.Addr, 1*time.Second, nil)))
 	req, res := protocol.AcquireRequest(), protocol.AcquireResponse()
@@ -1242,7 +1230,7 @@ func TestHostClientMaxConnWaitTimeoutError(t *testing.T) {
 
 func TestNewClient(t *testing.T) {
 	opt := config.NewOptions([]config.Option{})
-	opt.Addr = "127.0.0.1:10022"
+	opt.Addr = "127.0.0.1:0"
 	engine := route.NewEngine(opt)
 	engine.GET("/ping", func(c context.Context, ctx *app.RequestContext) {
 		ctx.SetBodyString("pong")
@@ -1258,7 +1246,7 @@ func TestNewClient(t *testing.T) {
 		t.Fatal(err)
 		return
 	}
-	status, resp, err := client.Get(context.Background(), nil, "http://127.0.0.1:10022/ping")
+	status, resp, err := client.Get(context.Background(), nil, testutils.GetURL(engine, "/ping"))
 	if err != nil {
 		t.Fatal(err)
 		return
@@ -1271,7 +1259,7 @@ func TestNewClient(t *testing.T) {
 
 func TestUseShortConnection(t *testing.T) {
 	opt := config.NewOptions([]config.Option{})
-	opt.Addr = "127.0.0.1:10023"
+	opt.Addr = "127.0.0.1:0"
 	engine := route.NewEngine(opt)
 	engine.GET("/", func(c context.Context, ctx *app.RequestContext) {
 	})
@@ -1280,6 +1268,7 @@ func TestUseShortConnection(t *testing.T) {
 		engine.Close()
 	}()
 	waitEngineRunning(engine)
+	opt.Addr = testutils.GetListenerAddr(engine)
 
 	c, _ := NewClient(WithKeepAlive(false))
 	var wg sync.WaitGroup
@@ -1287,7 +1276,7 @@ func TestUseShortConnection(t *testing.T) {
 		wg.Add(1)
 		go func() {
 			defer wg.Done()
-			if _, _, err := c.Get(context.Background(), nil, "http://127.0.0.1:10023"); err != nil {
+			if _, _, err := c.Get(context.Background(), nil, testutils.GetURL(engine, "")); err != nil {
 				t.Error(err)
 				return
 			}
@@ -1298,11 +1287,11 @@ func TestUseShortConnection(t *testing.T) {
 		c.mLock.Lock()
 		defer c.mLock.Unlock()
 
-		if _, ok := c.m["127.0.0.1:10023"]; !ok {
+		if _, ok := c.m[opt.Addr]; !ok {
 			return 0
 		}
 
-		return c.m["127.0.0.1:10023"].ConnectionCount()
+		return c.m[opt.Addr].ConnectionCount()
 	}
 
 	if conns := connsLen(); conns > 0 {
@@ -1312,7 +1301,7 @@ func TestUseShortConnection(t *testing.T) {
 
 func TestPostWithFormData(t *testing.T) {
 	opt := config.NewOptions([]config.Option{})
-	opt.Addr = "127.0.0.1:10025"
+	opt.Addr = "127.0.0.1:0"
 	engine := route.NewEngine(opt)
 	engine.POST("/", func(c context.Context, ctx *app.RequestContext) {
 		var ans string
@@ -1348,7 +1337,7 @@ func TestPostWithFormData(t *testing.T) {
 		"a": []string{"d", "e"},
 		"c": []string{"f"},
 	})
-	req.SetRequestURI("http://127.0.0.1:10025")
+	req.SetRequestURI(testutils.GetURL(engine, ""))
 	req.SetMethod(consts.MethodPost)
 	err := client.Do(context.Background(), req, rsp)
 	if err != nil {
@@ -1365,7 +1354,7 @@ func TestPostWithFormData(t *testing.T) {
 
 func TestPostWithMultipartField(t *testing.T) {
 	opt := config.NewOptions([]config.Option{})
-	opt.Addr = "127.0.0.1:10026"
+	opt.Addr = "127.0.0.1:0"
 	engine := route.NewEngine(opt)
 	engine.POST("/", func(c context.Context, ctx *app.RequestContext) {
 		if string(ctx.FormValue("a")) != "1" {
@@ -1394,7 +1383,7 @@ func TestPostWithMultipartField(t *testing.T) {
 		"b": "2",
 	}
 	req.SetMethod(consts.MethodPost)
-	req.SetRequestURI("http://127.0.0.1:10026")
+	req.SetRequestURI(testutils.GetURL(engine, ""))
 	req.SetMultipartFormData(data)
 	req.SetMultipartFormData(map[string]string{
 		"c": "3",
@@ -1407,7 +1396,7 @@ func TestPostWithMultipartField(t *testing.T) {
 
 func TestSetFiles(t *testing.T) {
 	opt := config.NewOptions([]config.Option{})
-	opt.Addr = "127.0.0.1:10027"
+	opt.Addr = "127.0.0.1:0"
 	engine := route.NewEngine(opt)
 	engine.POST("/", func(c context.Context, ctx *app.RequestContext) {
 		form, _ := ctx.MultipartForm()
@@ -1436,7 +1425,7 @@ func TestSetFiles(t *testing.T) {
 		protocol.ReleaseResponse(rsp)
 	}()
 	req.SetMethod(consts.MethodPost)
-	req.SetRequestURI("http://127.0.0.1:10027")
+	req.SetRequestURI(testutils.GetURL(engine, ""))
 	files := []string{"../../common/testdata/test.txt", "../../common/testdata/proto/test.proto", "../../common/testdata/test.png", "../../common/testdata/proto/test.pb.go"}
 	defer func() {
 		for _, file := range files {
@@ -1457,7 +1446,7 @@ func TestSetFiles(t *testing.T) {
 
 func TestSetMultipartFields(t *testing.T) {
 	opt := config.NewOptions([]config.Option{})
-	opt.Addr = "127.0.0.1:10028"
+	opt.Addr = "127.0.0.1:0"
 	engine := route.NewEngine(opt)
 	engine.POST("/", func(c context.Context, ctx *app.RequestContext) {
 		t.Log(req.GetHTTP1Request(&ctx.Request).String())
@@ -1510,7 +1499,7 @@ func TestSetMultipartFields(t *testing.T) {
 	}()
 	req.SetMultipartFields(fields...)
 	req.SetMultipartFormData(map[string]string{"a": "1", "b": "2"})
-	req.SetRequestURI("http://127.0.0.1:10028")
+	req.SetRequestURI(testutils.GetURL(engine, ""))
 	req.SetMethod(consts.MethodPost)
 	err := client.DoTimeout(context.Background(), req, rsp, 1*time.Second)
 	if err != nil {
@@ -1523,7 +1512,7 @@ func TestClientReadResponseBodyStream(t *testing.T) {
 	part2 := "ghij"
 
 	opt := config.NewOptions([]config.Option{})
-	opt.Addr = "127.0.0.1:10033"
+	opt.Addr = "127.0.0.1:0"
 	engine := route.NewEngine(opt)
 	engine.POST("/", func(ctx context.Context, c *app.RequestContext) {
 		c.String(consts.StatusOK, part1+part2)
@@ -1540,7 +1529,7 @@ func TestClientReadResponseBodyStream(t *testing.T) {
 		protocol.ReleaseRequest(req)
 		protocol.ReleaseResponse(resp)
 	}()
-	req.SetRequestURI("http://127.0.0.1:10033")
+	req.SetRequestURI(testutils.GetURL(engine, ""))
 	req.SetMethod(consts.MethodPost)
 	err := client.Do(context.Background(), req, resp)
 	if err != nil {
@@ -1567,7 +1556,7 @@ func TestClientReadResponseBodyStream(t *testing.T) {
 
 func TestWithBasicAuth(t *testing.T) {
 	opt := config.NewOptions([]config.Option{})
-	opt.Addr = "127.0.0.1:10034"
+	opt.Addr = "127.0.0.1:0"
 	engine := route.NewEngine(opt)
 	engine.GET("/", func(c context.Context, ctx *app.RequestContext) {
 		auth := ctx.GetHeader(consts.HeaderAuthorization)
@@ -1597,7 +1586,7 @@ func TestWithBasicAuth(t *testing.T) {
 
 	// Success
 	req.SetBasicAuth("myuser", "basicauth")
-	req.SetRequestURI("http://127.0.0.1:10034")
+	req.SetRequestURI(testutils.GetURL(engine, ""))
 	req.SetMethod(consts.MethodGet)
 	err := client.Do(context.Background(), req, rsp)
 	if err != nil {
@@ -1610,7 +1599,7 @@ func TestWithBasicAuth(t *testing.T) {
 	// Fail
 	req.Reset()
 	rsp.Reset()
-	req.SetRequestURI("http://127.0.0.1:10034")
+	req.SetRequestURI(testutils.GetURL(engine, ""))
 	req.SetMethod(consts.MethodGet)
 	err = client.Do(context.Background(), req, rsp)
 	if err != nil {
@@ -1766,7 +1755,7 @@ func TestClientProxyWithNetpollDialer(t *testing.T) {
 		{false, false},
 		{true, false},
 		{false, true},
-		{false, true},
+		{true, true},
 	}
 	for _, testCase := range testCases {
 		httpsSite := testCase.httpsSite
@@ -1952,7 +1941,7 @@ func TestClientReadResponseBodyStreamWithDoubleRequest(t *testing.T) {
 	part2 := "ghij"
 
 	opt := config.NewOptions([]config.Option{})
-	opt.Addr = "127.0.0.1:10035"
+	opt.Addr = "127.0.0.1:0"
 	engine := route.NewEngine(opt)
 	engine.POST("/", func(ctx context.Context, c *app.RequestContext) {
 		c.String(consts.StatusOK, part1+part2)
@@ -1969,7 +1958,7 @@ func TestClientReadResponseBodyStreamWithDoubleRequest(t *testing.T) {
 		protocol.ReleaseRequest(req)
 		protocol.ReleaseResponse(resp)
 	}()
-	req.SetRequestURI("http://127.0.0.1:10035")
+	req.SetRequestURI(testutils.GetURL(engine, ""))
 	req.SetMethod(consts.MethodPost)
 	err := client.Do(context.Background(), req, resp)
 	if err != nil {
@@ -1996,7 +1985,7 @@ func TestClientReadResponseBodyStreamWithDoubleRequest(t *testing.T) {
 		protocol.ReleaseRequest(req1)
 		protocol.ReleaseResponse(resp1)
 	}()
-	req1.SetRequestURI("http://127.0.0.1:10035")
+	req1.SetRequestURI(testutils.GetURL(engine, ""))
 	req1.SetMethod(consts.MethodPost)
 	err = client.Do(context.Background(), req1, resp1)
 	if err != nil {
@@ -2025,7 +2014,7 @@ func TestClientReadResponseBodyStreamWithConnectionClose(t *testing.T) {
 	}
 
 	opt := config.NewOptions([]config.Option{})
-	opt.Addr = "127.0.0.1:10036"
+	opt.Addr = "127.0.0.1:0"
 	engine := route.NewEngine(opt)
 	engine.POST("/", func(ctx context.Context, c *app.RequestContext) {
 		c.String(consts.StatusOK, part1)
@@ -2046,7 +2035,7 @@ func TestClientReadResponseBodyStreamWithConnectionClose(t *testing.T) {
 	}()
 	req.SetConnectionClose()
 	req.SetMethod(consts.MethodPost)
-	req.SetRequestURI("http://127.0.0.1:10036")
+	req.SetRequestURI(testutils.GetURL(engine, ""))
 
 	err := client.Do(context.Background(), req, resp)
 	if err != nil {
@@ -2063,7 +2052,7 @@ func TestClientReadResponseBodyStreamWithConnectionClose(t *testing.T) {
 	}()
 	req1.SetConnectionClose()
 	req1.SetMethod(consts.MethodPost)
-	req1.SetRequestURI("http://127.0.0.1:10036")
+	req1.SetRequestURI(testutils.GetURL(engine, ""))
 
 	err = client.Do(context.Background(), req1, resp1)
 	if err != nil {
@@ -2371,13 +2360,14 @@ func TestClientDoWithDialFunc(t *testing.T) {
 
 func TestClientState(t *testing.T) {
 	opt := config.NewOptions([]config.Option{})
-	opt.Addr = "127.0.0.1:10037"
+	opt.Addr = "127.0.0.1:0"
 	engine := route.NewEngine(opt)
 	go engine.Run()
 	defer func() {
 		engine.Close()
 	}()
 	waitEngineRunning(engine)
+	opt.Addr = testutils.GetListenerAddr(engine)
 
 	var wg sync.WaitGroup
 	wg.Add(2)
@@ -2390,19 +2380,19 @@ func TestClientState(t *testing.T) {
 				assert.DeepEqual(t, 1, hcs.ConnPoolState().TotalConnNum)
 				assert.DeepEqual(t, 1, hcs.ConnPoolState().PoolConnNum)
 				assert.DeepEqual(t, consts.DefaultMaxConnsPerHost, hcs.ConnPoolState().MaxConns)
-				assert.DeepEqual(t, "127.0.0.1:10037", hcs.ConnPoolState().Addr)
+				assert.DeepEqual(t, opt.Addr, hcs.ConnPoolState().Addr)
 				atomic.StoreInt32(&state, int32(1))
 				wg.Done()
 			case int32(1):
 				assert.DeepEqual(t, 0, hcs.ConnPoolState().TotalConnNum)
 				assert.DeepEqual(t, 0, hcs.ConnPoolState().PoolConnNum)
 				assert.DeepEqual(t, consts.DefaultMaxConnsPerHost, hcs.ConnPoolState().MaxConns)
-				assert.DeepEqual(t, "127.0.0.1:10037", hcs.ConnPoolState().Addr)
+				assert.DeepEqual(t, opt.Addr, hcs.ConnPoolState().Addr)
 				atomic.StoreInt32(&state, int32(2))
 				wg.Done()
 			}
 		}, 50*time.Millisecond))
-	client.Get(context.Background(), nil, "http://127.0.0.1:10037")
+	client.Get(context.Background(), nil, "http://"+opt.Addr)
 	wg.Wait()
 	assert.DeepEqual(t, int32(2), atomic.LoadInt32(&state))
 }
@@ -2410,7 +2400,7 @@ func TestClientState(t *testing.T) {
 func TestClientRetryErr(t *testing.T) {
 	t.Run("200", func(t *testing.T) {
 		opt := config.NewOptions([]config.Option{})
-		opt.Addr = "127.0.0.1:10136"
+		opt.Addr = "127.0.0.1:0"
 		engine := route.NewEngine(opt)
 		var l sync.Mutex
 		retryNum := 0
@@ -2427,7 +2417,7 @@ func TestClientRetryErr(t *testing.T) {
 		waitEngineRunning(engine)
 
 		c, _ := NewClient(WithRetryConfig(retry.WithMaxAttemptTimes(3)))
-		_, _, err := c.Get(context.Background(), nil, "http://127.0.0.1:10136/ping")
+		_, _, err := c.Get(context.Background(), nil, testutils.GetURL(engine, "/ping"))
 		assert.Nil(t, err)
 		l.Lock()
 		assert.DeepEqual(t, 1, retryNum)
@@ -2436,7 +2426,7 @@ func TestClientRetryErr(t *testing.T) {
 
 	t.Run("502", func(t *testing.T) {
 		opt := config.NewOptions([]config.Option{})
-		opt.Addr = "127.0.0.1:10137"
+		opt.Addr = "127.0.0.1:0"
 		engine := route.NewEngine(opt)
 		var l sync.Mutex
 		retryNum := 0
@@ -2456,10 +2446,133 @@ func TestClientRetryErr(t *testing.T) {
 		c.SetRetryIfFunc(func(req *protocol.Request, resp *protocol.Response, err error) bool {
 			return resp.StatusCode() == 502
 		})
-		_, _, err := c.Get(context.Background(), nil, "http://127.0.0.1:10137/ping")
+		_, _, err := c.Get(context.Background(), nil, testutils.GetURL(engine, "/ping"))
 		assert.Nil(t, err)
 		l.Lock()
 		assert.DeepEqual(t, 3, retryNum)
 		l.Unlock()
 	})
 }
+
+type mockHostClient struct {
+	shouldRemove bool
+	closed       bool
+}
+
+func (m *mockHostClient) Do(ctx context.Context, req *protocol.Request, resp *protocol.Response) error {
+	return nil
+}
+
+func (m *mockHostClient) SetDynamicConfig(dc *client.DynamicConfig) {
+}
+
+func (m *mockHostClient) CloseIdleConnections() {
+}
+
+func (m *mockHostClient) ShouldRemove() bool {
+	return m.shouldRemove
+}
+
+func (m *mockHostClient) ConnectionCount() int {
+	return 0
+}
+
+func (m *mockHostClient) Close() error {
+	m.closed = true
+	return nil
+}
+
+func TestCleanHostClients(t *testing.T) {
+	tests := []struct {
+		name         string
+		isTLS        bool
+		initMap      map[string]*mockHostClient
+		expectedKeys []string
+		shouldClose  bool
+		expectedRes  bool
+	}{
+		{
+			name:  "Remove item from c.m",
+			isTLS: false,
+			initMap: map[string]*mockHostClient{
+				"google.com": {shouldRemove: true},
+			},
+			expectedKeys: []string{},
+			shouldClose:  true,
+			expectedRes:  true,
+		},
+		{
+			name:  "Remove item from c.ms",
+			isTLS: true,
+			initMap: map[string]*mockHostClient{
+				"google.com": {shouldRemove: true},
+			},
+			expectedKeys: []string{},
+			shouldClose:  true,
+			expectedRes:  true,
+		},
+		{
+			name:  "Do not remove non-removable client",
+			isTLS: false,
+			initMap: map[string]*mockHostClient{
+				"google.com": {shouldRemove: false},
+			},
+			expectedKeys: []string{"google.com"},
+			shouldClose:  false,
+			expectedRes:  false,
+		},
+		{
+			name:         "Empty map",
+			isTLS:        false,
+			initMap:      map[string]*mockHostClient{},
+			expectedKeys: []string{},
+			shouldClose:  false,
+			expectedRes:  true,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			cli := &Client{
+				mLock: sync.Mutex{},
+				m:     map[string]client.HostClient{},
+				ms:    map[string]client.HostClient{},
+			}
+
+			if tt.isTLS {
+				for k, v := range tt.initMap {
+					cli.ms[k] = v
+				}
+			} else {
+				for k, v := range tt.initMap {
+					cli.m[k] = v
+				}
+			}
+
+			result := cli.cleanHostClients(tt.isTLS)
+
+			var resultMap map[string]client.HostClient
+			if tt.isTLS {
+				resultMap = cli.ms
+			} else {
+				resultMap = cli.m
+			}
+
+			var keys []string
+			for k := range resultMap {
+				keys = append(keys, k)
+			}
+			assert.Assert(t, len(tt.expectedKeys) == len(keys))
+
+			for _, v := range tt.initMap {
+				if v.shouldRemove {
+					assert.Assert(t, v.closed, "Expected client to be closed")
+				} else {
+					assert.Assert(t, !v.closed, "Client should not be closed")
+				}
+			}
+
+			assert.Assert(t, tt.expectedRes == result)
+		})
+	}
+}
diff --git a/pkg/app/context_test.go b/pkg/app/context_test.go
index a5cb00a..e98129d 100644
--- a/pkg/app/context_test.go
+++ b/pkg/app/context_test.go
@@ -117,11 +117,11 @@ func TestIfModifiedSince(t *testing.T) {
 	req.Header.Set(string(bytestr.StrIfModifiedSince), "Mon, 02 Jan 2006 15:04:05 MST")
 	req.CopyTo(&ctx.Request)
 	if !ctx.IfModifiedSince(time.Now()) {
-		t.Fatalf("ifModifiedSice error, expected false, but get true")
+		t.Fatalf("ifModifiedSince error, expected false, but get true")
 	}
 	tt, _ := time.Parse(time.RFC3339, "2004-11-12T11:45:26.371Z")
 	if ctx.IfModifiedSince(tt) {
-		t.Fatalf("ifModifiedSice error, expected true, but get false")
+		t.Fatalf("ifModifiedSince error, expected true, but get false")
 	}
 }
 
diff --git a/pkg/app/server/hertz_test.go b/pkg/app/server/hertz_test.go
index 83b40eb..2a71947 100644
--- a/pkg/app/server/hertz_test.go
+++ b/pkg/app/server/hertz_test.go
@@ -32,6 +32,7 @@ import (
 	"testing"
 	"time"
 
+	"github.com/cloudwego/hertz/internal/testutils"
 	"github.com/cloudwego/hertz/pkg/app"
 	c "github.com/cloudwego/hertz/pkg/app/client"
 	"github.com/cloudwego/hertz/pkg/app/server/binding"
@@ -53,35 +54,20 @@ import (
 
 type routeEngine interface {
 	IsRunning() bool
-	GetOptions() *config.Options
 }
 
 func waitEngineRunning(e routeEngine) {
 	for i := 0; i < 100; i++ {
 		if e.IsRunning() {
-			break
+			return
 		}
 		time.Sleep(10 * time.Millisecond)
 	}
-	opts := e.GetOptions()
-	network, addr := opts.Network, opts.Addr
-	if network == "" {
-		network = "tcp"
-	}
-	for i := 0; i < 100; i++ {
-		conn, err := net.Dial(network, addr)
-		if err != nil {
-			time.Sleep(10 * time.Millisecond)
-			continue
-		}
-		conn.Close()
-		return
-	}
 	panic("not running")
 }
 
 func TestHertz_Run(t *testing.T) {
-	hertz := Default(WithHostPorts("127.0.0.1:6666"))
+	hertz := Default(WithHostPorts("127.0.0.1:0"))
 	hertz.GET("/test", func(c context.Context, ctx *app.RequestContext) {
 		time.Sleep(time.Second)
 		path := ctx.Request.URI().PathOriginal()
@@ -99,7 +85,7 @@ func TestHertz_Run(t *testing.T) {
 	waitEngineRunning(hertz)
 
 	hertz.Close()
-	resp, err := http.Get("http://127.0.0.1:6666/test")
+	resp, err := http.Get(testutils.GetURL(hertz, "/test"))
 	assert.NotNil(t, err)
 	assert.Nil(t, resp)
 	assert.DeepEqual(t, uint32(0), atomic.LoadUint32(&testint))
@@ -108,7 +94,7 @@ func TestHertz_Run(t *testing.T) {
 func TestHertz_GracefulShutdown(t *testing.T) {
 	handling := make(chan struct{})
 	closing := make(chan struct{})
-	engine := New(WithHostPorts("127.0.0.1:6667"))
+	engine := New(WithHostPorts("127.0.0.1:0"))
 	engine.GET("/test", func(c context.Context, ctx *app.RequestContext) {
 		close(handling)
 		<-closing
@@ -143,7 +129,7 @@ func TestHertz_GracefulShutdown(t *testing.T) {
 		defer ticker.Stop()
 		for range ticker.C {
 			t.Logf("[%v]begin listening\n", time.Now())
-			_, err2 := hc.Get("http://127.0.0.1:6667/test2")
+			_, err2 := hc.Get(testutils.GetURL(engine, "/test2"))
 			if err2 != nil {
 				t.Logf("[%v]listening closed: %v", time.Now(), err2)
 				ch2 <- struct{}{}
@@ -153,7 +139,7 @@ func TestHertz_GracefulShutdown(t *testing.T) {
 	}()
 	go func() {
 		t.Logf("[%v]begin request\n", time.Now())
-		resp, err = http.Get("http://127.0.0.1:6667/test")
+		resp, err = http.Get(testutils.GetURL(engine, "/test"))
 		t.Logf("[%v]end request\n", time.Now())
 		ch <- struct{}{}
 	}()
@@ -182,7 +168,7 @@ func TestHertz_GracefulShutdown(t *testing.T) {
 }
 
 func TestLoadHTMLGlob(t *testing.T) {
-	engine := New(WithMaxRequestBodySize(15), WithHostPorts("127.0.0.1:8893"))
+	engine := New(WithMaxRequestBodySize(15), WithHostPorts("127.0.0.1:0"))
 	engine.Delims("{[{", "}]}")
 	engine.LoadHTMLGlob("../../common/testdata/template/index.tmpl")
 	engine.GET("/index", func(c context.Context, ctx *app.RequestContext) {
@@ -196,7 +182,7 @@ func TestLoadHTMLGlob(t *testing.T) {
 	}()
 	waitEngineRunning(engine)
 
-	resp, _ := http.Get("http://127.0.0.1:8893/index")
+	resp, _ := http.Get(testutils.GetURL(engine, "/index"))
 	assert.DeepEqual(t, consts.StatusOK, resp.StatusCode)
 	b := make([]byte, 100)
 	n, _ := resp.Body.Read(b)
@@ -206,7 +192,7 @@ func TestLoadHTMLGlob(t *testing.T) {
 }
 
 func TestLoadHTMLFiles(t *testing.T) {
-	engine := New(WithMaxRequestBodySize(15), WithHostPorts("127.0.0.1:8891"))
+	engine := New(WithMaxRequestBodySize(15), WithHostPorts("127.0.0.1:0"))
 	engine.Delims("{[{", "}]}")
 	engine.SetFuncMap(template.FuncMap{
 		"formatAsDate": formatAsDate,
@@ -224,7 +210,7 @@ func TestLoadHTMLFiles(t *testing.T) {
 	}()
 	waitEngineRunning(engine)
 
-	resp, _ := http.Get("http://127.0.0.1:8891/raw")
+	resp, _ := http.Get(testutils.GetURL(engine, "/raw"))
 	assert.DeepEqual(t, consts.StatusOK, resp.StatusCode)
 	b := make([]byte, 100)
 	n, _ := resp.Body.Read(b)
@@ -259,29 +245,29 @@ func Test_getServerName(t *testing.T) {
 }
 
 func TestServer_Run(t *testing.T) {
-	hertz := New(WithHostPorts("127.0.0.1:8899"))
+	hertz := New(WithHostPorts("127.0.0.1:0"))
 	hertz.GET("/test", func(c context.Context, ctx *app.RequestContext) {
 		path := ctx.Request.URI().PathOriginal()
 		ctx.SetBodyString(string(path))
 	})
 	hertz.POST("/redirect", func(c context.Context, ctx *app.RequestContext) {
-		ctx.Redirect(consts.StatusMovedPermanently, []byte("http://127.0.0.1:8899/test"))
+		ctx.Redirect(consts.StatusMovedPermanently, []byte(testutils.GetURL(hertz, "/test")))
 	})
 	go hertz.Run()
 	waitEngineRunning(hertz)
 
-	resp, err := http.Get("http://127.0.0.1:8899/test")
+	resp, err := http.Get(testutils.GetURL(hertz, "/test"))
 	assert.Nil(t, err)
 	assert.DeepEqual(t, consts.StatusOK, resp.StatusCode)
 	b := make([]byte, 5)
 	resp.Body.Read(b)
 	assert.DeepEqual(t, "/test", string(b))
 
-	resp, err = http.Get("http://127.0.0.1:8899/foo")
+	resp, err = http.Get(testutils.GetURL(hertz, "/foo"))
 	assert.Nil(t, err)
 	assert.DeepEqual(t, consts.StatusNotFound, resp.StatusCode)
 
-	resp, err = http.Post("http://127.0.0.1:8899/redirect", "", nil)
+	resp, err = http.Post(testutils.GetURL(hertz, "/redirect"), "", nil)
 	assert.Nil(t, err)
 	assert.DeepEqual(t, consts.StatusOK, resp.StatusCode)
 	b = make([]byte, 5)
@@ -294,7 +280,7 @@ func TestServer_Run(t *testing.T) {
 }
 
 func TestNotAbsolutePath(t *testing.T) {
-	engine := New(WithHostPorts("127.0.0.1:9990"))
+	engine := New(WithHostPorts("127.0.0.1:0"))
 	engine.POST("/", func(c context.Context, ctx *app.RequestContext) {
 		ctx.Write(ctx.Request.Body())
 	})
@@ -331,7 +317,7 @@ func TestNotAbsolutePath(t *testing.T) {
 }
 
 func TestNotAbsolutePathWithRawPath(t *testing.T) {
-	engine := New(WithHostPorts("127.0.0.1:9991"), WithUseRawPath(true))
+	engine := New(WithHostPorts("127.0.0.1:0"), WithUseRawPath(true))
 	const (
 		MiddlewareKey   = "middleware_key"
 		MiddlewareValue = "middleware_value"
@@ -377,7 +363,7 @@ func TestNotAbsolutePathWithRawPath(t *testing.T) {
 }
 
 func TestNotValidHost(t *testing.T) {
-	engine := New(WithHostPorts("127.0.0.1:9992"))
+	engine := New(WithHostPorts("127.0.0.1:0"))
 	const (
 		MiddlewareKey   = "middleware_key"
 		MiddlewareValue = "middleware_value"
@@ -418,7 +404,7 @@ func TestNotValidHost(t *testing.T) {
 }
 
 func TestWithBasePath(t *testing.T) {
-	engine := New(WithBasePath("/hertz"), WithHostPorts("127.0.0.1:19898"))
+	engine := New(WithBasePath("/hertz"), WithHostPorts("127.0.0.1:0"))
 	engine.POST("/test", func(c context.Context, ctx *app.RequestContext) {
 	})
 	go engine.Run()
@@ -431,13 +417,13 @@ func TestWithBasePath(t *testing.T) {
 	r.ParseForm()
 	r.Form.Add("xxxxxx", "xxx")
 	body := strings.NewReader(r.Form.Encode())
-	resp, err := http.Post("http://127.0.0.1:19898/hertz/test", "application/x-www-form-urlencoded", body)
+	resp, err := http.Post(testutils.GetURL(engine, "/hertz/test"), "application/x-www-form-urlencoded", body)
 	assert.Nil(t, err)
 	assert.DeepEqual(t, consts.StatusOK, resp.StatusCode)
 }
 
 func TestNotEnoughBodySize(t *testing.T) {
-	engine := New(WithMaxRequestBodySize(5), WithHostPorts("127.0.0.1:8889"))
+	engine := New(WithMaxRequestBodySize(5), WithHostPorts("127.0.0.1:0"))
 	engine.POST("/test", func(c context.Context, ctx *app.RequestContext) {
 	})
 	go engine.Run()
@@ -450,7 +436,7 @@ func TestNotEnoughBodySize(t *testing.T) {
 	r.ParseForm()
 	r.Form.Add("xxxxxx", "xxx")
 	body := strings.NewReader(r.Form.Encode())
-	resp, err := http.Post("http://127.0.0.1:8889/test", "application/x-www-form-urlencoded", body)
+	resp, err := http.Post(testutils.GetURL(engine, "/test"), "application/x-www-form-urlencoded", body)
 	assert.Nil(t, err)
 	assert.DeepEqual(t, 413, resp.StatusCode)
 	bodyBytes, _ := ioutil.ReadAll(resp.Body)
@@ -458,7 +444,7 @@ func TestNotEnoughBodySize(t *testing.T) {
 }
 
 func TestEnoughBodySize(t *testing.T) {
-	engine := New(WithMaxRequestBodySize(15), WithHostPorts("127.0.0.1:8892"))
+	engine := New(WithMaxRequestBodySize(15), WithHostPorts("127.0.0.1:0"))
 	engine.POST("/test", func(c context.Context, ctx *app.RequestContext) {
 	})
 	go engine.Run()
@@ -471,7 +457,7 @@ func TestEnoughBodySize(t *testing.T) {
 	r.ParseForm()
 	r.Form.Add("xxxxxx", "xxx")
 	body := strings.NewReader(r.Form.Encode())
-	resp, _ := http.Post("http://127.0.0.1:8892/test", "application/x-www-form-urlencoded", body)
+	resp, _ := http.Post(testutils.GetURL(engine, "/test"), "application/x-www-form-urlencoded", body)
 	assert.DeepEqual(t, consts.StatusOK, resp.StatusCode)
 }
 
@@ -578,7 +564,7 @@ func verifyResponseHeader(t *testing.T, h *protocol.ResponseHeader, expectedStat
 
 func TestParamInconsist(t *testing.T) {
 	mapS := sync.Map{}
-	h := New(WithHostPorts("localhost:10091"))
+	h := New(WithHostPorts("127.0.0.1:0"))
 	h.GET("/:label", func(c context.Context, ctx *app.RequestContext) {
 		label := ctx.Param("label")
 		x, _ := mapS.LoadOrStore(label, label)
@@ -595,13 +581,13 @@ func TestParamInconsist(t *testing.T) {
 	tr := func() {
 		defer wg.Done()
 		for i := 0; i < 500; i++ {
-			client.Get(context.Background(), nil, "http://localhost:10091/test1")
+			client.Get(context.Background(), nil, testutils.GetURL(h, "/test1"))
 		}
 	}
 	ti := func() {
 		defer wg.Done()
 		for i := 0; i < 500; i++ {
-			client.Get(context.Background(), nil, "http://localhost:10091/test2")
+			client.Get(context.Background(), nil, testutils.GetURL(h, "/test2"))
 		}
 	}
 
@@ -614,7 +600,7 @@ func TestParamInconsist(t *testing.T) {
 }
 
 func TestDuplicateReleaseBodyStream(t *testing.T) {
-	h := New(WithStreamBody(true), WithHostPorts("localhost:10092"))
+	h := New(WithStreamBody(true), WithHostPorts("127.0.0.1:0"))
 	h.POST("/test", func(ctx context.Context, c *app.RequestContext) {
 		stream := c.RequestBodyStream()
 		c.Response.SetBodyStream(stream, -1)
@@ -637,7 +623,7 @@ func TestDuplicateReleaseBodyStream(t *testing.T) {
 	wg := sync.WaitGroup{}
 	testFunc := func() {
 		defer wg.Done()
-		r := protocol.NewRequest("POST", "http://localhost:10092/test", nil)
+		r := protocol.NewRequest("POST", testutils.GetURL(h, "/test"), nil)
 		r.SetBodyString(body)
 		resp := protocol.AcquireResponse()
 		err := client.Do(context.Background(), r, resp)
@@ -674,7 +660,7 @@ func TestServiceRegisterFailed(t *testing.T) {
 	}
 	var opts []config.Option
 	opts = append(opts, WithRegistry(mockRegistry, nil))
-	opts = append(opts, WithHostPorts("127.0.0.1:9222"))
+	opts = append(opts, WithHostPorts("127.0.0.1:0"))
 	srv := New(opts...)
 	srv.Spin()
 	assert.Assert(t, atomic.LoadInt32(&rCount) == 1)
@@ -704,7 +690,7 @@ func TestServiceDeregisterFailed(t *testing.T) {
 
 	var opts []config.Option
 	opts = append(opts, WithRegistry(mockRegistry, nil))
-	opts = append(opts, WithHostPorts("127.0.0.1:9223"))
+	opts = append(opts, WithHostPorts("127.0.0.1:0"))
 	srv := New(opts...)
 	go srv.Spin()
 	waitEngineRunning(srv)
@@ -753,7 +739,7 @@ func TestServiceRegistryInfo(t *testing.T) {
 	}
 	var opts []config.Option
 	opts = append(opts, WithRegistry(mockRegistry, registryInfo))
-	opts = append(opts, WithHostPorts("127.0.0.1:9225"))
+	opts = append(opts, WithHostPorts("127.0.0.1:0"))
 	srv := New(opts...)
 	go srv.Spin()
 	waitEngineRunning(srv)
@@ -793,7 +779,7 @@ func TestServiceRegistryNoInitInfo(t *testing.T) {
 	}
 	var opts []config.Option
 	opts = append(opts, WithRegistry(mockRegistry, nil))
-	opts = append(opts, WithHostPorts("127.0.0.1:9227"))
+	opts = append(opts, WithHostPorts("127.0.0.1:0"))
 	srv := New(opts...)
 	go srv.Spin()
 	waitEngineRunning(srv)
@@ -821,7 +807,7 @@ func (t testTracer) Start(ctx context.Context, c *app.RequestContext) context.Co
 func (t testTracer) Finish(ctx context.Context, c *app.RequestContext) {}
 
 func TestReuseCtx(t *testing.T) {
-	h := New(WithTracer(testTracer{}), WithHostPorts("localhost:9228"))
+	h := New(WithTracer(testTracer{}), WithHostPorts("127.0.0.1:0"))
 	h.GET("/ping", func(ctx context.Context, c *app.RequestContext) {
 		assert.DeepEqual(t, 0, ctx.Value("testKey").(int))
 	})
@@ -830,7 +816,7 @@ func TestReuseCtx(t *testing.T) {
 	waitEngineRunning(h)
 
 	for i := 0; i < 1000; i++ {
-		_, _, err := c.Get(context.Background(), nil, "http://127.0.0.1:9228/ping")
+		_, _, err := c.Get(context.Background(), nil, testutils.GetURL(h, "/ping"))
 		assert.Nil(t, err)
 	}
 }
@@ -840,15 +826,9 @@ type CloseWithoutResetBuffer interface {
 }
 
 func TestOnprepare(t *testing.T) {
-	n := int32(0)
 	h1 := New(
-		WithHostPorts("localhost:9333"),
+		WithHostPorts("127.0.0.1:0"),
 		WithOnConnect(func(ctx context.Context, conn network.Conn) context.Context {
-			if atomic.AddInt32(&n, 1) == 1 {
-				// the 1st connection is from waitEngineRunning
-				conn.Close()
-				return ctx
-			}
 			b, err := conn.Peek(3)
 			assert.Nil(t, err)
 			assert.DeepEqual(t, string(b), "GET")
@@ -866,7 +846,7 @@ func TestOnprepare(t *testing.T) {
 	go h1.Spin()
 	waitEngineRunning(h1)
 
-	_, _, err := c.Get(context.Background(), nil, "http://127.0.0.1:9333/ping")
+	_, _, err := c.Get(context.Background(), nil, testutils.GetURL(h1, "/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())
 
 	h2 := New(
@@ -874,24 +854,25 @@ func TestOnprepare(t *testing.T) {
 			conn.Close()
 			return context.Background()
 		}),
-		WithHostPorts("localhost:9331"))
+		WithHostPorts("127.0.0.1:0"))
 	h2.GET("/ping", func(ctx context.Context, c *app.RequestContext) {
 		c.JSON(consts.StatusOK, utils.H{"ping": "pong"})
 	})
 	go h2.Spin()
 	waitEngineRunning(h2)
 
-	_, _, err = c.Get(context.Background(), nil, "http://127.0.0.1:9331/ping")
+	_, _, err = c.Get(context.Background(), nil, testutils.GetURL(h2, "/ping"))
 	if err == nil {
 		t.Fatalf("err should not be nil")
 	}
 
-	h3 := New(
+	var h3 *Hertz
+	h3 = New(
 		WithOnAccept(func(conn net.Conn) context.Context {
-			assert.DeepEqual(t, conn.LocalAddr().String(), "127.0.0.1:9231")
+			assert.DeepEqual(t, conn.LocalAddr().String(), testutils.GetListenerAddr(h3))
 			return context.Background()
 		}),
-		WithHostPorts("localhost:9231"),
+		WithHostPorts("127.0.0.1:0"),
 		WithTransport(standard.NewTransporter))
 	h3.GET("/ping", func(ctx context.Context, c *app.RequestContext) {
 		c.JSON(consts.StatusOK, utils.H{"ping": "pong"})
@@ -899,7 +880,7 @@ func TestOnprepare(t *testing.T) {
 	go h3.Spin()
 	waitEngineRunning(h3)
 
-	c.Get(context.Background(), nil, "http://127.0.0.1:9231/ping")
+	c.Get(context.Background(), nil, testutils.GetURL(h3, "/ping"))
 }
 
 type lockBuffer struct {
@@ -925,7 +906,7 @@ func TestSilentMode(t *testing.T) {
 
 	hlog.SetOutput(b)
 
-	h := New(WithHostPorts("localhost:9232"), WithTransport(standard.NewTransporter))
+	h := New(WithHostPorts("127.0.0.1:0"), WithTransport(standard.NewTransporter))
 	h.GET("/ping", func(c context.Context, ctx *app.RequestContext) {
 		ctx.Write([]byte("hello, world"))
 	})
@@ -933,7 +914,7 @@ func TestSilentMode(t *testing.T) {
 	waitEngineRunning(h)
 
 	d := standard.NewDialer()
-	conn, _ := d.DialConnection("tcp", "127.0.0.1:9232", 0, nil)
+	conn, _ := d.DialConnection("tcp", testutils.GetListenerAddr(h), 0, nil)
 	conn.Write([]byte("aaa"))
 	conn.Close()
 
@@ -944,7 +925,7 @@ func TestSilentMode(t *testing.T) {
 
 func TestHertzDisableHeaderNamesNormalizing(t *testing.T) {
 	h := New(
-		WithHostPorts("localhost:9212"),
+		WithHostPorts("127.0.0.1:0"),
 		WithDisableHeaderNamesNormalizing(true),
 	)
 	headerName := "CASE-senSITive-HEAder-NAME"
@@ -969,7 +950,7 @@ func TestHertzDisableHeaderNamesNormalizing(t *testing.T) {
 
 	cli, _ := c.NewClient(c.WithDisableHeaderNamesNormalizing(true))
 
-	r := protocol.NewRequest("GET", "http://localhost:9212/test", nil)
+	r := protocol.NewRequest("GET", testutils.GetURL(h, "/test"), nil)
 	r.Header.DisableNormalizing()
 	r.Header.Set(headerName, headerValue)
 	res := protocol.AcquireResponse()
@@ -985,7 +966,7 @@ func TestBindConfig(t *testing.T) {
 	bindConfig := binding.NewBindConfig()
 	bindConfig.LooseZeroMode = true
 	h := New(
-		WithHostPorts("localhost:9332"),
+		WithHostPorts("127.0.0.1:0"),
 		WithBindConfig(bindConfig))
 	h.GET("/bind", func(c context.Context, ctx *app.RequestContext) {
 		var req Req
@@ -999,13 +980,13 @@ func TestBindConfig(t *testing.T) {
 	waitEngineRunning(h)
 
 	hc := http.Client{Timeout: time.Second}
-	_, err := hc.Get("http://127.0.0.1:9332/bind?a=")
+	_, err := hc.Get(testutils.GetURL(h, "/bind?a="))
 	assert.Nil(t, err)
 
 	bindConfig = binding.NewBindConfig()
 	bindConfig.LooseZeroMode = false
 	h2 := New(
-		WithHostPorts("localhost:9448"),
+		WithHostPorts("127.0.0.1:0"),
 		WithBindConfig(bindConfig))
 	h2.GET("/bind", func(c context.Context, ctx *app.RequestContext) {
 		var req Req
@@ -1018,7 +999,7 @@ func TestBindConfig(t *testing.T) {
 	go h2.Spin()
 	waitEngineRunning(h2)
 
-	_, err = hc.Get("http://127.0.0.1:9448/bind?a=")
+	_, err = hc.Get(testutils.GetURL(h2, "/bind?a="))
 	assert.Nil(t, err)
 	time.Sleep(100 * time.Millisecond)
 }
@@ -1066,7 +1047,7 @@ func TestCustomBinder(t *testing.T) {
 		A int `query:"a"`
 	}
 	h := New(
-		WithHostPorts("localhost:9334"),
+		WithHostPorts("127.0.0.1:0"),
 		WithCustomBinder(&mockBinder{}))
 	h.GET("/bind", func(c context.Context, ctx *app.RequestContext) {
 		var req Req
@@ -1081,7 +1062,7 @@ func TestCustomBinder(t *testing.T) {
 	waitEngineRunning(h)
 
 	hc := http.Client{Timeout: time.Second}
-	_, err := hc.Get("http://127.0.0.1:9334/bind?a=")
+	_, err := hc.Get(testutils.GetURL(h, "/bind?a="))
 	assert.Nil(t, err)
 	time.Sleep(100 * time.Millisecond)
 }
@@ -1094,8 +1075,7 @@ func TestValidateConfigRegValidateFunc(t *testing.T) {
 	validateConfig.MustRegValidateFunc("f", func(args ...interface{}) error {
 		return fmt.Errorf("test validator")
 	})
-	h := New(
-		WithHostPorts("localhost:9229"))
+	h := New(WithHostPorts("127.0.0.1:0"))
 	h.GET("/bind", func(c context.Context, ctx *app.RequestContext) {
 		var req Req
 		err := ctx.BindAndValidate(&req)
@@ -1109,7 +1089,7 @@ func TestValidateConfigRegValidateFunc(t *testing.T) {
 	waitEngineRunning(h)
 
 	hc := http.Client{Timeout: time.Second}
-	_, err := hc.Get("http://127.0.0.1:9229/bind?a=2")
+	_, err := hc.Get(testutils.GetURL(h, "/bind?a=2"))
 	assert.Nil(t, err)
 	time.Sleep(100 * time.Millisecond)
 }
@@ -1133,7 +1113,7 @@ func TestCustomValidator(t *testing.T) {
 		A int `query:"a" vd:"f($)"`
 	}
 	h := New(
-		WithHostPorts("localhost:9555"),
+		WithHostPorts("127.0.0.1:0"),
 		WithCustomValidator(&mockValidator{}))
 	h.GET("/bind", func(c context.Context, ctx *app.RequestContext) {
 		var req Req
@@ -1147,7 +1127,7 @@ func TestCustomValidator(t *testing.T) {
 	go h.Spin()
 	time.Sleep(100 * time.Millisecond)
 	hc := http.Client{Timeout: time.Second}
-	_, err := hc.Get("http://127.0.0.1:9555/bind?a=2")
+	_, err := hc.Get(testutils.GetURL(h, "/bind?a=2"))
 	assert.Nil(t, err)
 	time.Sleep(100 * time.Millisecond)
 }
@@ -1180,7 +1160,7 @@ func TestValidateConfigSetSetErrorFactory(t *testing.T) {
 	validateConfig := binding.NewValidateConfig()
 	validateConfig.SetValidatorErrorFactory(CustomValidateErrFunc)
 	h := New(
-		WithHostPorts("localhost:9666"),
+		WithHostPorts("127.0.0.1:0"),
 		WithValidateConfig(validateConfig))
 	h.GET("/bind", func(c context.Context, ctx *app.RequestContext) {
 		var req TestValidate
@@ -1195,7 +1175,7 @@ func TestValidateConfigSetSetErrorFactory(t *testing.T) {
 	waitEngineRunning(h)
 
 	hc := http.Client{Timeout: time.Second}
-	_, err := hc.Get("http://127.0.0.1:9666/bind?b=1")
+	_, err := hc.Get(testutils.GetURL(h, "/bind?b=1"))
 	assert.Nil(t, err)
 	time.Sleep(100 * time.Millisecond)
 }
@@ -1207,7 +1187,7 @@ func TestValidateConfigAndBindConfig(t *testing.T) {
 	validateConfig := binding.NewValidateConfig()
 	validateConfig.ValidateTag = "vt"
 	h := New(
-		WithHostPorts("localhost:9876"),
+		WithHostPorts("127.0.0.1:0"),
 		WithValidateConfig(validateConfig))
 	h.GET("/bind", func(c context.Context, ctx *app.RequestContext) {
 		var req Req
@@ -1222,14 +1202,14 @@ func TestValidateConfigAndBindConfig(t *testing.T) {
 	waitEngineRunning(h)
 
 	hc := http.Client{Timeout: time.Second}
-	_, err := hc.Get("http://127.0.0.1:9876/bind?a=135")
+	_, err := hc.Get(testutils.GetURL(h, "/bind?a=135"))
 	assert.Nil(t, err)
 	time.Sleep(100 * time.Millisecond)
 }
 
 func TestWithDisableDefaultDate(t *testing.T) {
 	h := New(
-		WithHostPorts("localhost:8321"),
+		WithHostPorts("127.0.0.1:0"),
 		WithDisableDefaultDate(true),
 	)
 	h.GET("/", func(_ context.Context, c *app.RequestContext) {})
@@ -1237,13 +1217,13 @@ func TestWithDisableDefaultDate(t *testing.T) {
 	waitEngineRunning(h)
 
 	hc := http.Client{Timeout: time.Second}
-	r, _ := hc.Get("http://127.0.0.1:8321") //nolint:errcheck
+	r, _ := hc.Get(testutils.GetURL(h, "")) //nolint:errcheck
 	assert.DeepEqual(t, "", r.Header.Get("Date"))
 }
 
 func TestWithDisableDefaultContentType(t *testing.T) {
 	h := New(
-		WithHostPorts("localhost:8324"),
+		WithHostPorts("127.0.0.1:0"),
 		WithDisableDefaultContentType(true),
 	)
 	h.GET("/", func(_ context.Context, c *app.RequestContext) {})
@@ -1251,6 +1231,6 @@ func TestWithDisableDefaultContentType(t *testing.T) {
 	waitEngineRunning(h)
 
 	hc := http.Client{Timeout: time.Second}
-	r, _ := hc.Get("http://127.0.0.1:8324") //nolint:errcheck
+	r, _ := hc.Get(testutils.GetURL(h, "")) //nolint:errcheck
 	assert.DeepEqual(t, "", r.Header.Get("Content-Type"))
 }
diff --git a/pkg/app/server/hertz_unix_test.go b/pkg/app/server/hertz_unix_test.go
index 7e1d8d1..7e14c94 100644
--- a/pkg/app/server/hertz_unix_test.go
+++ b/pkg/app/server/hertz_unix_test.go
@@ -30,6 +30,7 @@ import (
 	"testing"
 	"time"
 
+	"github.com/cloudwego/hertz/internal/testutils"
 	"github.com/cloudwego/hertz/pkg/app"
 	c "github.com/cloudwego/hertz/pkg/app/client"
 	"github.com/cloudwego/hertz/pkg/common/test/assert"
@@ -82,7 +83,7 @@ func TestReusePorts(t *testing.T) {
 }
 
 func TestHertz_Spin(t *testing.T) {
-	engine := New(WithHostPorts("127.0.0.1:6668"))
+	engine := New(WithHostPorts("127.0.0.1:0"))
 	engine.GET("/test", func(c context.Context, ctx *app.RequestContext) {
 		time.Sleep(40 * time.Millisecond)
 		path := ctx.Request.URI().PathOriginal()
@@ -107,7 +108,7 @@ func TestHertz_Spin(t *testing.T) {
 		ticker := time.NewTicker(10 * time.Millisecond)
 		defer ticker.Stop()
 		for range ticker.C {
-			_, err := hc.Get("http://127.0.0.1:6668/test2")
+			_, err := hc.Get(testutils.GetURL(engine, "/test2"))
 			t.Logf("[%v]begin listening\n", time.Now())
 			if err != nil {
 				t.Logf("[%v]listening closed: %v", time.Now(), err)
@@ -118,7 +119,7 @@ func TestHertz_Spin(t *testing.T) {
 	}()
 	go func() {
 		t.Logf("[%v]begin request\n", time.Now())
-		resp, err = http.Get("http://127.0.0.1:6668/test")
+		resp, err = http.Get(testutils.GetURL(engine, "/test"))
 		t.Logf("[%v]end request\n", time.Now())
 		ch <- struct{}{}
 	}()
@@ -141,7 +142,7 @@ func TestHertz_Spin(t *testing.T) {
 
 func TestWithSenseClientDisconnection(t *testing.T) {
 	var closeFlag int32
-	h := New(WithHostPorts("127.0.0.1:6631"), WithSenseClientDisconnection(true))
+	h := New(WithHostPorts("127.0.0.1:0"), WithSenseClientDisconnection(true))
 	h.GET("/ping", func(c context.Context, ctx *app.RequestContext) {
 		assert.DeepEqual(t, "aa", string(ctx.Host()))
 		ch := make(chan struct{})
@@ -155,7 +156,7 @@ func TestWithSenseClientDisconnection(t *testing.T) {
 	go h.Spin()
 	waitEngineRunning(h)
 
-	con, err := net.Dial("tcp", "127.0.0.1:6631")
+	con, err := net.Dial("tcp", testutils.GetListenerAddr(h))
 	assert.Nil(t, err)
 	_, err = con.Write([]byte("GET /ping HTTP/1.1\r\nHost: aa\r\n\r\n"))
 	assert.Nil(t, err)
@@ -168,7 +169,7 @@ func TestWithSenseClientDisconnection(t *testing.T) {
 
 func TestWithSenseClientDisconnectionAndWithOnConnect(t *testing.T) {
 	var closeFlag int32
-	h := New(WithHostPorts("127.0.0.1:6632"), WithSenseClientDisconnection(true), WithOnConnect(func(ctx context.Context, conn network.Conn) context.Context {
+	h := New(WithHostPorts("127.0.0.1:0"), WithSenseClientDisconnection(true), WithOnConnect(func(ctx context.Context, conn network.Conn) context.Context {
 		return ctx
 	}))
 	h.GET("/ping", func(c context.Context, ctx *app.RequestContext) {
@@ -184,7 +185,7 @@ func TestWithSenseClientDisconnectionAndWithOnConnect(t *testing.T) {
 	go h.Spin()
 	waitEngineRunning(h)
 
-	con, err := net.Dial("tcp", "127.0.0.1:6632")
+	con, err := net.Dial("tcp", testutils.GetListenerAddr(h))
 	assert.Nil(t, err)
 	_, err = con.Write([]byte("GET /ping HTTP/1.1\r\nHost: aa\r\n\r\n"))
 	assert.Nil(t, err)
diff --git a/pkg/common/adaptor/request_test.go b/pkg/common/adaptor/request_test.go
index ba3ec6a..3e0a37d 100644
--- a/pkg/common/adaptor/request_test.go
+++ b/pkg/common/adaptor/request_test.go
@@ -25,6 +25,7 @@ import (
 	"testing"
 	"time"
 
+	"github.com/cloudwego/hertz/internal/testutils"
 	"github.com/cloudwego/hertz/pkg/app"
 	"github.com/cloudwego/hertz/pkg/app/server"
 	"github.com/cloudwego/hertz/pkg/common/test/assert"
@@ -35,8 +36,6 @@ import (
 func TestCompatResponse_WriteHeader(t *testing.T) {
 	var testHeader http.Header
 	var testBody string
-	testUrl1 := "http://127.0.0.1:9000/test1"
-	testUrl2 := "http://127.0.0.1:9000/test2"
 	testStatusCode := 299
 	testCookieValue := "cookie"
 
@@ -48,7 +47,7 @@ func TestCompatResponse_WriteHeader(t *testing.T) {
 
 	testBody = "test body"
 
-	h := server.New(server.WithHostPorts("127.0.0.1:9000"))
+	h := server.New(server.WithHostPorts("127.0.0.1:0"))
 	h.POST("/test1", func(c context.Context, ctx *app.RequestContext) {
 		req, _ := GetCompatRequest(&ctx.Request)
 		resp := GetCompatResponseWriter(&ctx.Response)
@@ -62,8 +61,10 @@ func TestCompatResponse_WriteHeader(t *testing.T) {
 	})
 
 	go h.Spin()
-	time.Sleep(200 * time.Millisecond)
+	time.Sleep(100 * time.Millisecond)
 
+	testUrl1 := testutils.GetURL(h, "/test1")
+	testUrl2 := testutils.GetURL(h, "/test2")
 	makeACall(t, http.MethodPost, testUrl1, testHeader, testBody, testStatusCode, []byte(testCookieValue))
 	makeACall(t, http.MethodPost, testUrl2, testHeader, testBody, consts.StatusOK, []byte(testCookieValue))
 }
diff --git a/pkg/common/bytebufferpool/bytebuffer_test.go b/pkg/common/bytebufferpool/bytebuffer_test.go
index 0cd4bf5..7727354 100644
--- a/pkg/common/bytebufferpool/bytebuffer_test.go
+++ b/pkg/common/bytebufferpool/bytebuffer_test.go
@@ -47,6 +47,8 @@ import (
 	"io"
 	"testing"
 	"time"
+
+	"github.com/cloudwego/hertz/pkg/common/test/assert"
 )
 
 func TestByteBufferReadFrom(t *testing.T) {
@@ -73,6 +75,7 @@ func TestByteBufferReadFrom(t *testing.T) {
 		if bbLen != expectedLen {
 			t.Fatalf("unexpected byteBuffer length: %d. Expecting %d", bbLen, expectedLen)
 		}
+		assert.True(t, bb.Cap() >= int(expectedLen))
 		for j := 0; j < i; j++ {
 			start := prefixLen + int64(j)*expectedN
 			b := bb.B[start : start+expectedN]
@@ -104,6 +107,7 @@ func TestByteBufferWriteTo(t *testing.T) {
 			t.Fatalf("unexpected string written %q. Expecting %q", s, expectedS)
 		}
 		w.Reset()
+		assert.True(t, bb.Cap() >= len(expectedS))
 	}
 }
 
diff --git a/pkg/common/test/assert/assert.go b/pkg/common/test/assert/assert.go
index c82d84c..dbd9caf 100644
--- a/pkg/common/test/assert/assert.go
+++ b/pkg/common/test/assert/assert.go
@@ -18,11 +18,16 @@ package assert
 
 import (
 	"reflect"
-	"testing"
 )
 
+type testingT interface {
+	Helper()
+	Fatal(args ...any)
+	Fatalf(format string, args ...any)
+}
+
 // Assert .
-func Assert(t testing.TB, cond bool, val ...interface{}) {
+func Assert(t testingT, cond bool, val ...interface{}) {
 	t.Helper()
 	if !cond {
 		if len(val) > 0 {
@@ -35,7 +40,7 @@ func Assert(t testing.TB, cond bool, val ...interface{}) {
 }
 
 // Assertf .
-func Assertf(t testing.TB, cond bool, format string, val ...interface{}) {
+func Assertf(t testingT, cond bool, format string, val ...interface{}) {
 	t.Helper()
 	if !cond {
 		t.Fatalf(format, val...)
@@ -43,60 +48,65 @@ func Assertf(t testing.TB, cond bool, format string, val ...interface{}) {
 }
 
 // DeepEqual .
-func DeepEqual(t testing.TB, expected, actual interface{}) {
+func DeepEqual(t testingT, expected, actual interface{}) {
 	t.Helper()
 	if !reflect.DeepEqual(actual, expected) {
 		t.Fatalf("assertion failed, unexpected: %v, expected: %v", actual, expected)
 	}
 }
 
-func Nil(t testing.TB, data interface{}) {
-	t.Helper()
-	if data == nil {
-		return
-	}
-	if !reflect.ValueOf(data).IsNil() {
-		t.Fatalf("assertion failed, unexpected: %v, expected: nil", data)
+func isNil(rv reflect.Value) bool {
+	switch rv.Kind() {
+	case reflect.Chan, reflect.Func, reflect.Map, reflect.Pointer,
+		reflect.UnsafePointer, reflect.Interface, reflect.Slice:
+		if rv.IsNil() {
+			return true
+		}
 	}
+	return false
 }
 
-func NotNil(t testing.TB, data interface{}) {
+func Nil(t testingT, data interface{}) {
 	t.Helper()
-	if data != nil {
+	if data == nil || isNil(reflect.ValueOf(data)) {
 		return
 	}
+	t.Fatalf("assertion failed, unexpected: %v, expected: nil", data)
+}
 
-	if reflect.ValueOf(data).IsNil() {
+func NotNil(t testingT, data interface{}) {
+	t.Helper()
+	if data == nil || isNil(reflect.ValueOf(data)) {
 		t.Fatalf("assertion failed, unexpected: %v, expected: not nil", data)
 	}
 }
 
 // NotEqual .
-func NotEqual(t testing.TB, expected, actual interface{}) {
+func NotEqual(t testingT, expected, actual interface{}) {
 	t.Helper()
 	if expected == nil || actual == nil {
 		if expected == actual {
-			t.Fatalf("assertion failed, unexpected: %v, expected: %v", actual, expected)
+			t.Fatalf("assertion failed: %v == %v", actual, expected)
 		}
 	}
 
 	if reflect.DeepEqual(actual, expected) {
-		t.Fatalf("assertion failed, unexpected: %v, expected: %v", actual, expected)
+		t.Fatalf("assertion failed: %v == %v", actual, expected)
 	}
 }
 
-func True(t testing.TB, obj interface{}) {
+func True(t testingT, obj interface{}) {
 	t.Helper()
 	DeepEqual(t, true, obj)
 }
 
-func False(t testing.TB, obj interface{}) {
+func False(t testingT, obj interface{}) {
 	t.Helper()
 	DeepEqual(t, false, obj)
 }
 
 // Panic .
-func Panic(t testing.TB, fn func()) {
+func Panic(t testingT, fn func()) {
 	t.Helper()
 	defer func() {
 		if err := recover(); err == nil {
@@ -107,11 +117,11 @@ func Panic(t testing.TB, fn func()) {
 }
 
 // NotPanic .
-func NotPanic(t testing.TB, fn func()) {
+func NotPanic(t testingT, fn func()) {
 	t.Helper()
 	defer func() {
 		if err := recover(); err != nil {
-			t.Fatal("assertion failed: did panic")
+			t.Fatal("assertion failed: panicked")
 		}
 	}()
 	fn()
diff --git a/pkg/common/test/assert/assert_test.go b/pkg/common/test/assert/assert_test.go
new file mode 100644
index 0000000..0f5df2d
--- /dev/null
+++ b/pkg/common/test/assert/assert_test.go
@@ -0,0 +1,154 @@
+/*
+ * 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 assert
+
+import (
+	"fmt"
+	"strings"
+	"testing"
+)
+
+type mockTestingT struct {
+	fatalstr string
+}
+
+func (mockTestingT) Helper() {}
+
+func (m *mockTestingT) Reset() {
+	m.fatalstr = ""
+}
+
+func (m *mockTestingT) Fatal(args ...any) {
+	m.fatalstr = fmt.Sprintln(args...)
+}
+
+func (m *mockTestingT) Fatalf(fm string, args ...any) {
+	m.fatalstr = fmt.Sprintf(fm, args...)
+}
+
+func (m *mockTestingT) String() string {
+	return m.fatalstr
+}
+
+func (m *mockTestingT) Expect(t *testing.T, s string) {
+	t.Helper()
+	got := strings.TrimSpace(m.fatalstr)
+	if got != s {
+		t.Fatalf("got: %q expect: %q", got, s)
+	}
+	m.Reset()
+}
+
+func TestAssert(t *testing.T) {
+	m := &mockTestingT{}
+	Assert(m, true)
+	m.Expect(t, "")
+
+	Assert(m, false)
+	m.Expect(t, "assertion failed")
+
+	Assert(m, false, "hello")
+	m.Expect(t, "assertion failed: hello")
+
+	Assertf(m, true, "hello %s", "world")
+	m.Expect(t, "")
+	Assertf(m, false, "hello %s", "world")
+	m.Expect(t, "hello world")
+}
+
+func TestNil(t *testing.T) {
+	m := &mockTestingT{}
+
+	Nil(m, nil)
+	m.Expect(t, "")
+	Nil(m, (*testing.T)(nil))
+	m.Expect(t, "")
+
+	Nil(m, 1)
+	m.Expect(t, "assertion failed, unexpected: 1, expected: nil")
+
+	Nil(m, "hello")
+	m.Expect(t, "assertion failed, unexpected: hello, expected: nil")
+
+	NotNil(m, 1)
+	m.Expect(t, "")
+
+	NotNil(m, "hello")
+	m.Expect(t, "")
+
+	NotNil(m, struct {
+		hello string
+	}{})
+	m.Expect(t, "")
+
+	NotNil(m, (*testing.T)(nil))
+	m.Expect(t, `assertion failed, unexpected: <nil>, expected: not nil`)
+
+}
+
+func TestDeepEqual(t *testing.T) {
+	m := &mockTestingT{}
+
+	DeepEqual(m, 1, 1)
+	m.Expect(t, "")
+
+	DeepEqual(m, 1, 2)
+	m.Expect(t, `assertion failed, unexpected: 2, expected: 1`)
+
+}
+
+func TestNotEqual(t *testing.T) {
+	m := &mockTestingT{}
+
+	NotEqual(m, 1, 2)
+	m.Expect(t, "")
+
+	NotEqual(m, nil, nil)
+	m.Expect(t, `assertion failed: <nil> == <nil>`)
+}
+
+func TestTrueFalse(t *testing.T) {
+	m := &mockTestingT{}
+
+	True(m, true)
+	m.Expect(t, "")
+
+	False(m, false)
+	m.Expect(t, "")
+}
+
+func TestPanic(t *testing.T) {
+	m := &mockTestingT{}
+
+	Panic(m, func() {
+		panic("hello")
+	})
+	m.Expect(t, "")
+	Panic(m, func() {
+	})
+	m.Expect(t, `assertion failed: did not panic`)
+
+	NotPanic(m, func() {
+	})
+	m.Expect(t, "")
+
+	NotPanic(m, func() {
+		panic("hello")
+	})
+	m.Expect(t, `assertion failed: panicked`)
+
+}
diff --git a/pkg/common/utils/utils_test.go b/pkg/common/utils/utils_test.go
index 225d8e4..967d03c 100644
--- a/pkg/common/utils/utils_test.go
+++ b/pkg/common/utils/utils_test.go
@@ -81,7 +81,7 @@ func TestUtilsAssert(t *testing.T) {
 
 func TestUtilsIsTrueString(t *testing.T) {
 	normalTrueStr := "true"
-	upperTrueStr := "trUe"
+	upperTrueStr := "TRUE"
 	otherStr := "hertz"
 
 	assert.DeepEqual(t, true, IsTrueString(normalTrueStr))
@@ -114,7 +114,7 @@ func TestUtilsCaseInsensitiveCompare(t *testing.T) {
 	assert.DeepEqual(t, false, CaseInsensitiveCompare(lessStr, moreStr))
 
 	firstStr := []byte("content-type")
-	secondStr := []byte("contant-type")
+	secondStr := []byte("content0type")
 	assert.DeepEqual(t, false, CaseInsensitiveCompare(firstStr, secondStr))
 }
 
diff --git a/pkg/network/netpoll/dial_test.go b/pkg/network/netpoll/dial_test.go
index 1830d1e..bfea1be 100644
--- a/pkg/network/netpoll/dial_test.go
+++ b/pkg/network/netpoll/dial_test.go
@@ -27,12 +27,17 @@ import (
 	"github.com/cloudwego/hertz/pkg/common/config"
 	"github.com/cloudwego/hertz/pkg/common/test/assert"
 	"github.com/cloudwego/hertz/pkg/common/test/mock"
+	"github.com/cloudwego/hertz/pkg/network"
 )
 
+func getListenerAddr(trans network.Transporter) string {
+	return trans.(*transporter).Listener().Addr().String()
+}
+
 func TestDial(t *testing.T) {
 	t.Run("NetpollDial", func(t *testing.T) {
 		const nw = "tcp"
-		const addr = "localhost:10100"
+		var addr = "127.0.0.1:0"
 		transporter := NewTransporter(&config.Options{
 			Addr:    addr,
 			Network: nw,
@@ -48,6 +53,7 @@ func TestDial(t *testing.T) {
 		_, err := dial.DialConnection("tcp", "localhost:10101", time.Second, nil) // wrong addr
 		assert.NotNil(t, err)
 
+		addr = getListenerAddr(transporter)
 		nwConn, err := dial.DialConnection(nw, addr, time.Second, nil)
 		assert.Nil(t, err)
 		defer nwConn.Close()
diff --git a/pkg/network/netpoll/transport_test.go b/pkg/network/netpoll/transport_test.go
index 5323920..d6383eb 100644
--- a/pkg/network/netpoll/transport_test.go
+++ b/pkg/network/netpoll/transport_test.go
@@ -34,8 +34,8 @@ import (
 
 func TestTransport(t *testing.T) {
 	const nw = "tcp"
-	const addr = "localhost:10103"
 	t.Run("TestDefault", func(t *testing.T) {
+		var addr = "127.0.0.1:0"
 		var onConnFlag, onAcceptFlag, onDataFlag int32
 		transporter := NewTransporter(&config.Options{
 			Addr:    addr,
@@ -57,6 +57,8 @@ func TestTransport(t *testing.T) {
 		defer transporter.Close()
 		time.Sleep(100 * time.Millisecond)
 
+		addr = getListenerAddr(transporter)
+
 		dial := NewDialer()
 		conn, err := dial.DialConnection(nw, addr, time.Second, nil)
 		assert.Nil(t, err)
@@ -70,6 +72,7 @@ func TestTransport(t *testing.T) {
 	})
 
 	t.Run("TestSenseClientDisconnection", func(t *testing.T) {
+		var addr = "127.0.0.1:0"
 		var onReqFlag int32
 		transporter := NewTransporter(&config.Options{
 			Addr:                     addr,
@@ -86,6 +89,8 @@ func TestTransport(t *testing.T) {
 		defer transporter.Close()
 		time.Sleep(100 * time.Millisecond)
 
+		addr = getListenerAddr(transporter)
+
 		dial := NewDialer()
 		conn, err := dial.DialConnection(nw, addr, time.Second, nil)
 		assert.Nil(t, err)
@@ -106,7 +111,7 @@ func TestTransport(t *testing.T) {
 			})
 		}}
 		transporter := NewTransporter(&config.Options{
-			Addr:         addr,
+			Addr:         "127.0.0.1:0",
 			Network:      nw,
 			ListenConfig: listenCfg,
 		})
@@ -119,7 +124,7 @@ func TestTransport(t *testing.T) {
 	t.Run("TestExceptionCase", func(t *testing.T) {
 		assert.Panic(t, func() { // listen err
 			transporter := NewTransporter(&config.Options{
-				Network: "unknow",
+				Network: "unknown",
 			})
 			transporter.ListenAndServe(func(ctx context.Context, conn interface{}) error {
 				return nil
diff --git a/pkg/network/standard/dial_test.go b/pkg/network/standard/dial_test.go
index c7d9fa2..0c7e854 100644
--- a/pkg/network/standard/dial_test.go
+++ b/pkg/network/standard/dial_test.go
@@ -31,11 +31,16 @@ import (
 
 	"github.com/cloudwego/hertz/pkg/common/config"
 	"github.com/cloudwego/hertz/pkg/common/test/assert"
+	"github.com/cloudwego/hertz/pkg/network"
 )
 
+func getListenerAddr(trans network.Transporter) string {
+	return trans.(*transport).Listener().Addr().String()
+}
+
 func TestDial(t *testing.T) {
 	const nw = "tcp"
-	const addr = "localhost:10104"
+	var addr = "127.0.0.1:0"
 	transporter := NewTransporter(&config.Options{
 		Addr:    addr,
 		Network: nw,
@@ -47,6 +52,8 @@ func TestDial(t *testing.T) {
 	defer transporter.Close()
 	time.Sleep(time.Millisecond * 100)
 
+	addr = getListenerAddr(transporter)
+
 	dial := NewDialer()
 	_, err := dial.DialConnection(nw, addr, time.Second, nil)
 	assert.Nil(t, err)
@@ -58,9 +65,9 @@ func TestDial(t *testing.T) {
 
 func TestDialTLS(t *testing.T) {
 	const nw = "tcp"
-	const addr = "localhost:10105"
+	var addr = "127.0.0.1:0"
 	data := []byte("abcdefg")
-	listened := make(chan struct{})
+	listened := make(chan net.Listener)
 	go func() {
 		mockTLSServe(nw, addr, func(conn net.Conn) {
 			defer conn.Close()
@@ -70,7 +77,8 @@ func TestDialTLS(t *testing.T) {
 	}()
 
 	select {
-	case <-listened:
+	case ln := <-listened:
+		addr = ln.Addr().String()
 	case <-time.After(time.Second * 5):
 		t.Fatalf("timeout")
 	}
@@ -99,7 +107,7 @@ func TestDialTLS(t *testing.T) {
 	assert.DeepEqual(t, string(data), string(buf))
 }
 
-func mockTLSServe(nw, addr string, handle func(conn net.Conn), listened chan struct{}) (err error) {
+func mockTLSServe(nw, addr string, handle func(conn net.Conn), listened chan net.Listener) (err error) {
 	certData, keyData, err := generateTestCertificate("")
 	if err != nil {
 		return
@@ -119,7 +127,7 @@ func mockTLSServe(nw, addr string, handle func(conn net.Conn), listened chan str
 	}
 	defer ln.Close()
 
-	listened <- struct{}{}
+	listened <- ln
 	for {
 		conn, err := ln.Accept()
 		if err != nil {
diff --git a/pkg/network/standard/transport_test.go b/pkg/network/standard/transport_test.go
new file mode 100644
index 0000000..57698d4
--- /dev/null
+++ b/pkg/network/standard/transport_test.go
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2025 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 standard
+
+import (
+	"context"
+	"io"
+	"net"
+	"testing"
+	"time"
+
+	"github.com/cloudwego/hertz/pkg/common/config"
+	"github.com/cloudwego/hertz/pkg/network"
+)
+
+func assertWriteRead(t *testing.T, c io.ReadWriter, w string, r string) {
+	t.Helper()
+	_, err := c.Write([]byte(w))
+	if err != nil {
+		t.Fatal("write err", err)
+	}
+	b := make([]byte, len(r))
+	_, err = io.ReadFull(c, b)
+	if err != nil {
+		t.Fatal("read err", err)
+	}
+	if s := string(b); s != r {
+		t.Fatal("read", r)
+	}
+}
+
+func TestTransporter(t *testing.T) {
+	handlerExit := make(chan struct{})
+	req := "hello"
+	resp := "world"
+	trans := NewTransporter(&config.Options{Network: "tcp", Addr: "127.0.0.1:0"}).(*transport)
+	go trans.ListenAndServe(func(ctx context.Context, conn interface{}) error {
+		c := conn.(network.Conn)
+		defer c.Close()
+		assertWriteRead(t, c, resp, req)
+		<-handlerExit
+		return nil
+	})
+	for trans.Listener() == nil { // wait server up
+		time.Sleep(5 * time.Millisecond)
+	}
+
+	// dial and test
+	c, err := net.Dial("tcp", trans.Listener().Addr().String())
+	if err != nil {
+		t.Fatal(err)
+	}
+	assertWriteRead(t, c, req, resp)
+
+	checkActiveConn := func(n int) {
+		if v := trans.updateActive(0); v != int32(n) {
+			t.Helper()
+			t.Fatal("trans active conn", v)
+		}
+	}
+	checkActiveConn(1)
+
+	// make sure shutdownTimeout will be reset after this test
+	defer func(old time.Duration) { shutdownTimeout = old }(shutdownTimeout)
+	defer func(old time.Duration) { shutdownTicker = old }(shutdownTicker)
+
+	shutdownTicker = time.Millisecond // shorter for saving test time
+
+	// case: wait util shutdownTimeout
+	shutdownTimeout = time.Millisecond
+	if err := trans.Shutdown(context.Background()); err != errShutdownTimeout {
+		t.Fatal(err)
+	}
+
+	// case: ctx done
+	shutdownTimeout = time.Second // long enough
+	ctx, cancel := context.WithCancel(context.Background())
+	cancel()
+	err = trans.Shutdown(ctx)
+	if err != ctx.Err() {
+		t.Fatal(err)
+	}
+
+	// case: even after listener is closed, handler may still active
+	checkActiveConn(1)
+
+	// case: Shutdown blocks at ticker and check periodically.
+	shutdownTimeout = time.Second // long enough
+	go trans.Shutdown(context.Background())
+	time.Sleep(30 * time.Millisecond) // make sure Shutdown blocks at for loop
+	close(handlerExit)                // signal handler to return
+	time.Sleep(10 * time.Millisecond) // wait handler returns, and active conn to be updated.
+	checkActiveConn(0)
+}
diff --git a/pkg/protocol/args_test.go b/pkg/protocol/args_test.go
index 0e89f30..70970a9 100644
--- a/pkg/protocol/args_test.go
+++ b/pkg/protocol/args_test.go
@@ -130,6 +130,37 @@ func TestArgsVisitAll(t *testing.T) {
 	assert.DeepEqual(t, []string{"cloudwego", "hertz", "hello", "world"}, s)
 }
 
+func TestArgsCopyTo(t *testing.T) {
+	var a Args
+	a.Add("cloudwego", "")
+	a.Add("hello", "world")
+
+	var b Args
+	a.CopyTo(&b)
+	assert.Assert(t, b.Len() == 2)
+	assert.Assert(t, a.Peek("cloudwego") != nil && len(a.Peek("cloudwego")) == 0)
+	assert.Assert(t, string(a.Peek("hello")) == "world")
+	assert.Assert(t, a.Peek("key-not-exists") == nil)
+}
+
+func TestArgsPeek(t *testing.T) {
+	var a Args
+	a.Add("cloudwego", "")
+	a.Add("hello", "world")
+
+	assert.Assert(t, a.Peek("cloudwego") != nil && len(a.Peek("cloudwego")) == 0)
+	assert.Assert(t, string(a.Peek("hello")) == "world")
+	assert.Assert(t, a.Peek("key-not-exists") == nil)
+
+	// reset and reuse
+	a.Reset()
+	a.Add("cloudwego", "")
+	a.Add("hello", "world")
+	assert.Assert(t, a.Peek("cloudwego") != nil && len(a.Peek("cloudwego")) == 0)
+	assert.Assert(t, string(a.Peek("hello")) == "world")
+	assert.Assert(t, a.Peek("key-not-exists") == nil)
+}
+
 func TestArgsPeekMulti(t *testing.T) {
 	var a Args
 	a.Add("cloudwego", "hertz")
@@ -141,7 +172,7 @@ func TestArgsPeekMulti(t *testing.T) {
 	expectedVV := [][]byte{
 		[]byte("hertz"),
 		[]byte("kitex"),
-		[]byte(nil),
+		[]byte{},
 	}
 	assert.DeepEqual(t, expectedVV, vv)
 
diff --git a/pkg/protocol/header_test.go b/pkg/protocol/header_test.go
index ba2bd69..e9c7403 100644
--- a/pkg/protocol/header_test.go
+++ b/pkg/protocol/header_test.go
@@ -816,3 +816,15 @@ func TestSetTrailerWithROString(t *testing.T) {
 	h1.Add(consts.HeaderTrailer, "foo,bar,hertz")
 	assert.DeepEqual(t, "Foo, Bar, Hertz", h1.Get(consts.HeaderTrailer))
 }
+
+func Benchmark_RequestHeader_Peek(b *testing.B) {
+	h := &RequestHeader{}
+	h.Add("hello", "world")
+	if s := string(h.Peek("hello")); s != "world" {
+		b.Fatal(s)
+	}
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		h.Peek("hello")
+	}
+}
diff --git a/pkg/protocol/http1/client_test.go b/pkg/protocol/http1/client_test.go
index 2ecb764..353cf61 100644
--- a/pkg/protocol/http1/client_test.go
+++ b/pkg/protocol/http1/client_test.go
@@ -656,3 +656,13 @@ func TestDialTimeout(t *testing.T) {
 
 	c.Do(context.Background(), req, resp)
 }
+
+func TestContextNil(t *testing.T) {
+	defer func() {
+		v := recover()
+		assert.NotNil(t, v)
+		assert.True(t, fmt.Sprint(v) == "ctx is nil")
+	}()
+	c := &HostClient{}
+	c.Do(nil, nil, nil) //nolint:staticcheck // SA1012: do not pass a nil Context
+}
diff --git a/pkg/protocol/http1/req/request_test.go b/pkg/protocol/http1/req/request_test.go
index e9451e7..a3b0ee1 100644
--- a/pkg/protocol/http1/req/request_test.go
+++ b/pkg/protocol/http1/req/request_test.go
@@ -219,7 +219,7 @@ func TestRequestSuccess(t *testing.T) {
 	testRequestSuccess(t, consts.MethodPost, "/bbb", "aaa.com", "Chrome aaa", "post body", consts.MethodPost)
 
 	// PUT method with body
-	testRequestSuccess(t, consts.MethodPut, "/aa/bb", "a.com", "ome aaa", "put body", consts.MethodPut)
+	testRequestSuccess(t, consts.MethodPut, "/aa/bb", "a.com", "aaa", "put body", consts.MethodPut)
 
 	// only host is set
 	testRequestSuccess(t, "", "", "gooble.com", "", "", consts.MethodGet)
diff --git a/pkg/protocol/request_test.go b/pkg/protocol/request_test.go
index fda96cd..8902b48 100644
--- a/pkg/protocol/request_test.go
+++ b/pkg/protocol/request_test.go
@@ -703,6 +703,27 @@ func TestRequestSetMaxKeepBodySize(t *testing.T) {
 	assert.DeepEqual(t, 1024, r.maxKeepBodySize)
 }
 
+func TestRequestBodyReuse(t *testing.T) {
+	req := Request{}
+	req.maxKeepBodySize = 1024
+
+	buf := req.BodyBuffer()
+	// set a big body
+	buf.Write(make([]byte, req.maxKeepBodySize+1))
+	req.ResetBody()
+	assert.Nil(t, req.body)
+	// NOTICE: bytebufferpool may not get a big enough buffer,
+	// so we just mock a new one here
+	req.body = &bytebufferpool.ByteBuffer{
+		B: make([]byte, 0, req.maxKeepBodySize+1),
+	}
+	// set a small body
+	buf = req.BodyBuffer()
+	buf.Write(make([]byte, 1))
+	req.ResetBody()
+	assert.Nil(t, req.body)
+}
+
 func TestRequestGetBodyAfterGetBodyStream(t *testing.T) {
 	req := AcquireRequest()
 	req.SetBodyString("abc")
diff --git a/pkg/protocol/response_test.go b/pkg/protocol/response_test.go
index 6b09c2e..5ecf5a0 100644
--- a/pkg/protocol/response_test.go
+++ b/pkg/protocol/response_test.go
@@ -172,6 +172,26 @@ func TestResponseResetBody(t *testing.T) {
 	assert.Nil(t, resp.body)
 }
 
+func TestResponseBodyReuse(t *testing.T) {
+	resp := Response{}
+	resp.maxKeepBodySize = 1024
+
+	buf := resp.BodyBuffer()
+	// set a big body
+	buf.Write(make([]byte, resp.maxKeepBodySize+1))
+	resp.ResetBody()
+	assert.Nil(t, resp.body)
+	// NOTICE: bytebufferpool may not get a big enough buffer,
+	// so we just mock a new one here
+	resp.body = &bytebufferpool.ByteBuffer{
+		B: make([]byte, 0, resp.maxKeepBodySize+1),
+	}
+	// set a small body
+	buf.Write(make([]byte, 1))
+	resp.ResetBody()
+	assert.Nil(t, resp.body)
+}
+
 func testResponseCopyTo(t *testing.T, src *Response) {
 	var dst Response
 	src.CopyTo(&dst)
@@ -226,12 +246,15 @@ func TestResponseSwapResponseBody(t *testing.T) {
 
 func TestResponseAcquireResponse(t *testing.T) {
 	t.Parallel()
-	resp1 := AcquireResponse()
-	assert.NotNil(t, resp1)
-	resp1.SetBody([]byte("test"))
-	resp1.SetStatusCode(consts.StatusOK)
-	ReleaseResponse(resp1)
-	assert.Nil(t, resp1.body)
+	for i := 0; i < 10; i++ {
+		resp1 := AcquireResponse()
+		assert.NotNil(t, resp1)
+		assert.Nil(t, resp1.body)
+
+		resp1.SetBody([]byte("test"))
+		resp1.SetStatusCode(consts.StatusOK)
+		ReleaseResponse(resp1)
+	}
 }
 
 type closeBuffer struct {
@@ -263,6 +286,8 @@ func TestSetBodyStreamNoReset(t *testing.T) {
 
 func TestRespSafeCopy(t *testing.T) {
 	resp := AcquireResponse()
+	defer ReleaseResponse(resp)
+
 	resp.bodyRaw = make([]byte, 1)
 	resps := make([]*Response, 10)
 	for i := 0; i < 10; i++ {
@@ -278,6 +303,8 @@ func TestRespSafeCopy(t *testing.T) {
 
 func TestResponse_HijackWriter(t *testing.T) {
 	resp := AcquireResponse()
+	defer ReleaseResponse(resp)
+
 	buf := new(bytes.Buffer)
 	isFinal := false
 	resp.HijackWriter(&mock.ExtWriter{Buf: buf, IsFinal: &isFinal})
diff --git a/pkg/route/engine_test.go b/pkg/route/engine_test.go
index a5da5dc..2c8c97f 100644
--- a/pkg/route/engine_test.go
+++ b/pkg/route/engine_test.go
@@ -49,6 +49,7 @@ import (
 	"io/ioutil"
 	"net"
 	"net/http"
+	"sync"
 	"sync/atomic"
 	"testing"
 	"time"
@@ -871,48 +872,91 @@ func (e mockDeregsitryErr) Deregister(*registry.Info) error {
 	return errTestDeregsitry
 }
 
+type mockStandardTransporter struct {
+	network.Transporter
+}
+
+func (m *mockStandardTransporter) Shutdown(ctx context.Context) error {
+	// FIXME: standard.Transporter mindlessly blocks on ctx.Done()
+	// This change help tests run faster
+	newctx, cancel := context.WithCancel(ctx)
+	cancel()
+	return m.Transporter.Shutdown(newctx)
+}
+
+func newMockStandardTransporter(opt *config.Options) network.Transporter {
+	return &mockStandardTransporter{standard.NewTransporter(opt)}
+}
+
 func TestEngineShutdown(t *testing.T) {
-	defaultTransporter = standard.NewTransporter
 	mockCtxCallback := func(ctx context.Context) {}
-	// Test case 1: serve not running error
+
 	opt := config.NewOptions(nil)
-	opt.Addr = "127.0.0.1:10027"
-	engine := NewEngine(opt)
-	ctx1, cancel1 := context.WithTimeout(context.Background(), time.Second)
-	defer cancel1()
-	err := engine.Shutdown(ctx1)
-	assert.DeepEqual(t, errStatusNotRunning, err)
-
-	// Test case 2: serve successfully running and shutdown
+	opt.Addr = "127.0.0.1:0"
+	opt.TransporterNewer = newMockStandardTransporter
+
+	var wg sync.WaitGroup
+	var engine *Engine
+
+	runEngine := func() {
+		wg.Add(1)
+		go func() {
+			defer wg.Done()
+			engine.Run()
+		}()
+		// wait for engine to start
+		time.Sleep(100 * time.Millisecond)
+	}
+
+	shutdownEngine := func(ctx context.Context, expectErr error, expectStatus uint32) {
+		t.Helper()
+		err := engine.Shutdown(ctx)
+		if expectErr == nil {
+			assert.Nil(t, err)
+		} else {
+			assert.DeepEqual(t, expectErr, err)
+		}
+		if expectStatus != 0 {
+			if expectStatus == statusShutdown {
+				assert.DeepEqual(t, expectStatus, atomic.LoadUint32(&engine.status))
+				// make sure engine.Run() returns
+				// in case registry fails, it blocks
+				engine.transport.Shutdown(ctx)
+				expectStatus = statusClosed
+			}
+			wg.Wait() // wait engine.Run() returns
+		}
+		assert.DeepEqual(t, expectStatus, atomic.LoadUint32(&engine.status))
+	}
+
+	// case: serve not running error
 	engine = NewEngine(opt)
-	engine.OnShutdown = []CtxCallback{mockCtxCallback}
-	go func() {
-		engine.Run()
-	}()
-	// wait for engine to start
-	time.Sleep(1 * time.Second)
+	shutdownEngine(context.Background(), errStatusNotRunning, 0)
 
-	ctx2, cancel2 := context.WithTimeout(context.Background(), time.Second)
-	defer cancel2()
-	err = engine.Shutdown(ctx2)
-	assert.Nil(t, err)
-	assert.DeepEqual(t, statusClosed, atomic.LoadUint32(&engine.status))
+	// case: serve successfully running and shutdown
+	engine = NewEngine(opt)
+	engine.OnShutdown = []CtxCallback{mockCtxCallback}
+	runEngine()
+	shutdownEngine(context.Background(), nil, statusClosed)
 
-	// Test case 3: serve successfully running and shutdown with deregistry error
+	// case: serve successfully running and shutdown with deregistry error
 	engine = NewEngine(opt)
 	engine.OnShutdown = []CtxCallback{mockCtxCallback}
 	engine.options.Registry = &mockDeregsitryErr{}
-	go func() {
-		engine.Run()
-	}()
-	// wait for engine to start
-	time.Sleep(1 * time.Second)
-
-	ctx3, cancel3 := context.WithTimeout(context.Background(), time.Second)
-	defer cancel3()
-	err = engine.Shutdown(ctx3)
-	assert.DeepEqual(t, errTestDeregsitry, err)
-	assert.DeepEqual(t, statusShutdown, atomic.LoadUint32(&engine.status))
+	runEngine()
+	shutdownEngine(context.Background(), errTestDeregsitry, statusShutdown)
+	engine.options.Registry = nil
+
+	// case: ctx cancelled when Shutdown
+	engine = NewEngine(opt)
+
+	// make sure callback is in progress but ctx cancelled
+	engine.OnShutdown = []CtxCallback{func(ctx context.Context) { time.Sleep(50 * time.Millisecond) }}
+
+	runEngine()
+	ctx, cancel := context.WithCancel(context.Background())
+	cancel()
+	shutdownEngine(ctx, nil, statusClosed)
 }
 
 type mockStreamer struct{}