Repository: cloudwego/hertz
Pull request #1387 (2025-07-02 09:13:06)

chore: release v0.10.1


Patch:
diff --git a/.codecov.yml b/.codecov.yml
index 98ed505..3881dfe 100644
--- a/.codecov.yml
+++ b/.codecov.yml
@@ -5,3 +5,11 @@ ignore:
   - "images/.*"
   - "examples/.*"
   - "cmd/.*"
+
+coverage:
+  status:
+    project:
+      default:
+        # pass if coverage drops by no more than 0.05%
+        # this is possibly caused by unstable coverage.
+        threshold: 0.05%
diff --git a/README.md b/README.md
index f1fffde..2ccc9ea 100644
--- a/README.md
+++ b/README.md
@@ -12,7 +12,7 @@ English | [中文](README_cn.md)
 ![Forks](https://img.shields.io/github/forks/cloudwego/hertz)
 
 
-Hertz [həːts] is a high-usability, high-performance and high-extensibility Golang HTTP framework that helps developers build microservices. It was designed with reference to other open-source frameworks like [fasthttp](https://github.com/valyala/fasthttp), [gin](https://github.com/gin-gonic/gin), [echo](https://github.com/labstack/echo) and combined with the internal requirements in ByteDance. At present, it has been widely used inside ByteDance. Nowadays, more and more microservices use Golang. If you have requirements for microservice performance and hope that the framework can fully meet the internal customizable requirements, Hertz will be a good choice.
+Hertz [həːts] is a high-usability, high-performance and high-extensibility Golang HTTP framework that helps developers build microservices. It was originally a fork of [fasthttp](https://github.com/valyala/fasthttp) and inspired by [gin](https://github.com/gin-gonic/gin), [echo](https://github.com/labstack/echo) and combined with the internal requirements in ByteDance. At present, it has been widely used inside ByteDance. Nowadays, more and more microservices use Golang. If you have requirements for microservice performance and hope that the framework can fully meet the internal customizable requirements, Hertz will be a good choice.
 ## Basic Features
 - High usability
 
diff --git a/README_cn.md b/README_cn.md
index 65c8b1e..2a582c5 100644
--- a/README_cn.md
+++ b/README_cn.md
@@ -11,7 +11,9 @@
 ![Stars](https://img.shields.io/github/stars/cloudwego/hertz)
 ![Forks](https://img.shields.io/github/forks/cloudwego/hertz)
 
-Hertz[həːts] 是一个 Golang 微服务 HTTP 框架，在设计之初参考了其他开源框架 [fasthttp](https://github.com/valyala/fasthttp)、[gin](https://github.com/gin-gonic/gin)、[echo](https://github.com/labstack/echo) 的优势，并结合字节跳动内部的需求，使其具有高易用性、高性能、高扩展性等特点，目前在字节跳动内部已广泛使用。如今越来越多的微服务选择使用 Golang，如果对微服务性能有要求，又希望框架能够充分满足内部的可定制化需求，Hertz 会是一个不错的选择。
+Hertz[həːts] 是一个 Golang 微服务 HTTP 框架，最初fork自[fasthttp](https://github.com/valyala/fasthttp)，并在设计时参考了其他开源框架[gin](https://github.com/gin-gonic/gin)、[echo](https://github.com/labstack/echo) 的优势，并结合字节跳动内部的需求，使其具有高易用性、高
+性能、高扩展性等特点，目前在字节跳动内部已广泛使用。如今越来越多的微服务选择使用 Golang，如果对微服务性能有要求，又希望框架能够充分满足内部的可定制化需求，Hertz 会是一个不错的选择。
+
 ## 框架特点
 - 高易用性
 
diff --git a/cmd/hz/go.mod b/cmd/hz/go.mod
index 712c5a2..b3703fa 100644
--- a/cmd/hz/go.mod
+++ b/cmd/hz/go.mod
@@ -4,11 +4,11 @@ go 1.16
 
 require (
 	github.com/Masterminds/sprig/v3 v3.2.3
-	github.com/cloudwego/thriftgo v0.1.7
+	github.com/cloudwego/thriftgo v0.4.2-0.20250604064713-0e1e704080b1
 	github.com/hashicorp/go-version v1.5.0
 	github.com/jhump/protoreflect v1.12.0
 	github.com/urfave/cli/v2 v2.23.0
-	golang.org/x/tools v0.4.0
+	golang.org/x/tools v0.6.0
 	google.golang.org/protobuf v1.28.0
 	gopkg.in/yaml.v2 v2.4.0
 )
diff --git a/cmd/hz/go.sum b/cmd/hz/go.sum
index de5c00d..18865c4 100644
--- a/cmd/hz/go.sum
+++ b/cmd/hz/go.sum
@@ -7,18 +7,24 @@ github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7Y
 github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
 github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA=
 github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM=
-github.com/apache/thrift v0.13.0 h1:5hryIiq9gtn+MiLVn0wP37kb/uTeRZgN08WoCsAhIhI=
-github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
+github.com/bytedance/gopkg v0.1.1 h1:3azzgSkiaw79u24a+w9arfH8OfnQQ4MHUt9lJFREEaE=
+github.com/bytedance/gopkg v0.1.1/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
-github.com/cloudwego/thriftgo v0.1.7 h1:mTGRv6Dtwfp0hTPZXuIHwm3vtGOuZVTrWarI0xVzUYg=
-github.com/cloudwego/thriftgo v0.1.7/go.mod h1:LzeafuLSiHA9JTiWC8TIMIq64iadeObgRUhmVG1OC/w=
+github.com/cloudwego/gopkg v0.1.4 h1:EoQiCG4sTonTPHxOGE0VlQs+sQR+Hsi2uN0qqwu8O50=
+github.com/cloudwego/gopkg v0.1.4/go.mod h1:FQuXsRWRsSqJLsMVd5SYzp8/Z1y5gXKnVvRrWUOsCMI=
+github.com/cloudwego/thriftgo v0.4.2-0.20250604064713-0e1e704080b1 h1:iuQJK+ZEtb0uA9cTjWW65gj2R0UB03GFZRD8IwAbDaE=
+github.com/cloudwego/thriftgo v0.4.2-0.20250604064713-0e1e704080b1/go.mod h1:/D4zRAEj1t3/Tq1bVGDMnRt3wxpHfalXfZWvq/n4YmY=
 github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
 github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
 github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
+github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
+github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
 github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
@@ -61,8 +67,9 @@ github.com/jhump/protoreflect v1.12.0/go.mod h1:JytZfP5d0r8pVNLZvai7U/MCuTWITgrI
 github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
-github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ=
 github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
 github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY=
@@ -77,9 +84,16 @@ github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFR
 github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
 github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
-github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
+github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
 github.com/urfave/cli/v2 v2.23.0 h1:pkly7gKIeYv3olPAeNajNpLjeJrmTPYCoZWaV+2VfvE=
 github.com/urfave/cli/v2 v2.23.0/go.mod h1:1CNUng3PtjQMtRzJO4FMXBQvkGtuYRxxiR9xMa7jMwI=
 github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
@@ -88,14 +102,16 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5t
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A=
 golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
+golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
+golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
+golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
 golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
-golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -106,8 +122,11 @@ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81R
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
-golang.org/x/net v0.3.0 h1:VWL6FNY2bEEmsGVKabSlHu5Irp34xmMRoqb/9lF9lxk=
-golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
+golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
+golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
+golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
+golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
+golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -115,6 +134,7 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -124,19 +144,27 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
-golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
+golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
-golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
+golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
+golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
+golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
+golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
-golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
-golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
+golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
+golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
@@ -144,8 +172,8 @@ golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3
 golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
-golang.org/x/tools v0.4.0 h1:7mTAgkunk3fr4GAloyyCasadO6h9zSsQZbwvcaIciV4=
-golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ=
+golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
+golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -175,6 +203,7 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ
 google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
 google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
@@ -182,6 +211,8 @@ gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/go.mod b/go.mod
index d35115e..2d4d38f 100644
--- a/go.mod
+++ b/go.mod
@@ -4,7 +4,6 @@ go 1.19
 
 require (
 	github.com/bytedance/gopkg v0.1.1
-	github.com/bytedance/mockey v1.2.12
 	github.com/bytedance/sonic v1.13.2
 	github.com/cloudwego/netpoll v0.7.0
 	github.com/fsnotify/fsnotify v1.5.4
@@ -13,7 +12,7 @@ require (
 	github.com/tidwall/gjson v1.14.4
 	golang.org/x/sync v0.8.0
 	golang.org/x/sys v0.24.0
-	google.golang.org/protobuf v1.27.1
+	google.golang.org/protobuf v1.34.1
 )
 
 require (
@@ -22,12 +21,8 @@ require (
 	github.com/cloudwego/gopkg v0.1.4 // indirect
 	github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
 	github.com/golang/protobuf v1.5.0 // indirect
-	github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 // indirect
-	github.com/jtolds/gls v4.20.0+incompatible // indirect
 	github.com/klauspost/cpuid/v2 v2.0.9 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
-	github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d // indirect
-	github.com/smartystreets/goconvey v1.6.4 // indirect
 	github.com/stretchr/objx v0.5.2 // indirect
 	github.com/tidwall/match v1.1.1 // indirect
 	github.com/tidwall/pretty v1.2.0 // indirect
diff --git a/go.sum b/go.sum
index 1fba56c..b0faaf3 100644
--- a/go.sum
+++ b/go.sum
@@ -1,7 +1,5 @@
 github.com/bytedance/gopkg v0.1.1 h1:3azzgSkiaw79u24a+w9arfH8OfnQQ4MHUt9lJFREEaE=
 github.com/bytedance/gopkg v0.1.1/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
-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.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=
@@ -25,10 +23,6 @@ github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4
 github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
 github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
-github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
-github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
-github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
 github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
 github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
 github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
@@ -41,10 +35,6 @@ github.com/nyaruka/phonenumbers v1.0.55 h1:bj0nTO88Y68KeUQ/n3Lo2KgK7lM1hF7L9NFuw
 github.com/nyaruka/phonenumbers v1.0.55/go.mod h1:sDaTZ/KPX5f8qyV9qN+hIm+4ZBARJrupC6LuhshJq1U=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
-github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
-github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
-github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
 github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
@@ -74,7 +64,6 @@ golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDf
 golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
-golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
@@ -112,7 +101,6 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
 golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
 golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
@@ -120,8 +108,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
-google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
-google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
+google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/pkg/app/server/binding/internal/decoder/base_type_decoder.go b/pkg/app/server/binding/internal/decoder/base_type_decoder.go
index bf26c9c..d99f90a 100644
--- a/pkg/app/server/binding/internal/decoder/base_type_decoder.go
+++ b/pkg/app/server/binding/internal/decoder/base_type_decoder.go
@@ -123,6 +123,11 @@ func (d *baseTypeFieldTextDecoder) Decode(req *protocol.Request, params param.Pa
 	}
 
 	// Non-pointer elems
+	if field.CanAddr() {
+		if tryTextUnmarshaler(field.Addr(), text) {
+			return nil
+		}
+	}
 	err = d.decoder.UnmarshalString(text, field, d.config.LooseZeroMode)
 	if err != nil {
 		return fmt.Errorf("unable to decode '%s' as %s: %w", text, d.fieldType.Name(), err)
diff --git a/pkg/app/server/binding/internal/decoder/tag.go b/pkg/app/server/binding/internal/decoder/tag.go
index 8ca5ae0..58dd56a 100644
--- a/pkg/app/server/binding/internal/decoder/tag.go
+++ b/pkg/app/server/binding/internal/decoder/tag.go
@@ -147,6 +147,10 @@ func getFieldTagInfoByTag(field reflect.StructField, tag string) []TagInfo {
 		if tagValue == "-" {
 			skip = true
 		}
+		defaultVal := ""
+		if val, ok := field.Tag.Lookup(defaultTag); ok {
+			defaultVal = val
+		}
 		var options []string
 		var opt string
 		var required bool
@@ -157,7 +161,7 @@ func getFieldTagInfoByTag(field reflect.StructField, tag string) []TagInfo {
 				required = true
 			}
 		}
-		tagInfos = append(tagInfos, TagInfo{Key: tag, Value: tagValue, Options: options, Required: required, Skip: skip})
+		tagInfos = append(tagInfos, TagInfo{Key: tag, Value: tagValue, Options: options, Required: required, Default: defaultVal, Skip: skip})
 	} else {
 		tagInfos = append(tagInfos, TagInfo{Key: tag, Value: field.Name})
 	}
diff --git a/pkg/app/server/binding/internal/decoder/util.go b/pkg/app/server/binding/internal/decoder/util.go
index be141c2..0464547 100644
--- a/pkg/app/server/binding/internal/decoder/util.go
+++ b/pkg/app/server/binding/internal/decoder/util.go
@@ -17,6 +17,7 @@
 package decoder
 
 import (
+	"encoding"
 	"fmt"
 	"reflect"
 	"strings"
@@ -45,8 +46,7 @@ func toDefaultValue(typ reflect.Type, defaultValue string) string {
 }
 
 // stringToValue is used to dynamically create reflect.Value for 'text'
-func stringToValue(elemType reflect.Type, text string, req *protocol.Request, params param.Params, config *DecodeConfig) (v reflect.Value, err error) {
-	v = reflect.New(elemType).Elem()
+func stringToValue(elemType reflect.Type, text string, req *protocol.Request, params param.Params, config *DecodeConfig) (reflect.Value, error) {
 	if customizedFunc, exist := config.TypeUnmarshalFuncs[elemType]; exist {
 		val, err := customizedFunc(req, params, text)
 		if err != nil {
@@ -54,23 +54,42 @@ func stringToValue(elemType reflect.Type, text string, req *protocol.Request, pa
 		}
 		return val, nil
 	}
+	v := reflect.New(elemType)
+	if tryTextUnmarshaler(v, text) {
+		return v.Elem(), nil
+	}
 	switch elemType.Kind() {
-	case reflect.Struct:
-		err = hJson.Unmarshal(bytesconv.S2b(text), v.Addr().Interface())
-	case reflect.Map:
-		err = hJson.Unmarshal(bytesconv.S2b(text), v.Addr().Interface())
+	case reflect.Struct, reflect.Map:
+		if err := hJson.Unmarshal(bytesconv.S2b(text), v.Interface()); err != nil {
+			return reflect.Value{}, err
+		}
+		return v.Elem(), nil
+
 	case reflect.Array, reflect.Slice:
 		// do nothing
+		return v.Elem(), nil
+
 	default:
 		decoder, err := SelectTextDecoder(elemType)
 		if err != nil {
-			return reflect.Value{}, fmt.Errorf("unsupported type %s for slice/array", elemType.String())
+			return reflect.Value{}, err
 		}
+		v = v.Elem()
 		err = decoder.UnmarshalString(text, v, config.LooseZeroMode)
 		if err != nil {
 			return reflect.Value{}, fmt.Errorf("unable to decode '%s' as %s: %w", text, elemType.String(), err)
 		}
+		return v, nil
 	}
 
-	return v, err
+}
+
+func tryTextUnmarshaler(v reflect.Value, s string) bool {
+	enc, ok := v.Interface().(encoding.TextUnmarshaler)
+	if ok {
+		if err := enc.UnmarshalText(bytesconv.S2b(s)); err == nil {
+			return true
+		}
+	}
+	return false
 }
diff --git a/pkg/app/server/option.go b/pkg/app/server/option.go
index 73188d1..7a77778 100644
--- a/pkg/app/server/option.go
+++ b/pkg/app/server/option.go
@@ -278,7 +278,8 @@ func WithH2C(enable bool) config.Option {
 	}}
 }
 
-// WithReadBufferSize sets the read buffer size which also limit the header size.
+// WithReadBufferSize sets the size of each read buffer node in standard transport.
+// NOTE: this cannot limit the header size.
 func WithReadBufferSize(size int) config.Option {
 	return config.Option{F: func(o *config.Options) {
 		o.ReadBufferSize = size
diff --git a/pkg/network/standard/transport.go b/pkg/network/standard/transport.go
index 1b08509..79d5ea6 100644
--- a/pkg/network/standard/transport.go
+++ b/pkg/network/standard/transport.go
@@ -31,13 +31,8 @@ import (
 )
 
 type transport struct {
-	// Per-connection buffer size for requests' reading.
-	// This also limits the maximum header size.
-	//
-	// Increase this buffer if your clients send multi-KB RequestURIs
-	// and/or multi-KB headers (for example, BIG cookies).
-	//
-	// Default buffer size is used if not set.
+	// The underlying read buffer is a buffer node list, `readBufferSize` is the size of a single node.
+	// `defaultReadBufferSize` (4KB) is used if not set.
 	readBufferSize           int
 	network                  string
 	addr                     string
diff --git a/pkg/protocol/http1/client.go b/pkg/protocol/http1/client.go
index c49da74..442c9b8 100644
--- a/pkg/protocol/http1/client.go
+++ b/pkg/protocol/http1/client.go
@@ -472,51 +472,48 @@ func (c *HostClient) do(req *protocol.Request, resp *protocol.Response) (bool, e
 	return canIdempotentRetry, err
 }
 
-type requestConfig struct {
-	dialTimeout  time.Duration
-	readTimeout  time.Duration
-	writeTimeout time.Duration
-}
-
-func (c *HostClient) preHandleConfig(o *config.RequestOptions) requestConfig {
-	rc := requestConfig{
-		dialTimeout:  c.DialTimeout,
-		readTimeout:  c.ReadTimeout,
-		writeTimeout: c.WriteTimeout,
+func minTimeout(d0, d1 time.Duration) time.Duration {
+	if d0 <= 0 {
+		if d1 <= 0 {
+			return 0
+		}
+		return d1
 	}
-	if o.ReadTimeout() > 0 {
-		rc.readTimeout = o.ReadTimeout()
+	if d1 <= 0 {
+		return d0
 	}
-
-	if o.WriteTimeout() > 0 {
-		rc.writeTimeout = o.WriteTimeout()
+	if d0 < d1 {
+		return d0
 	}
+	return d1
+}
 
-	if o.DialTimeout() > 0 {
-		rc.dialTimeout = o.DialTimeout()
+func timeUntil(deadline time.Time) time.Duration {
+	timeout := time.Until(deadline)
+	if timeout <= 0 {
+		return -1
 	}
-
-	return rc
+	return timeout
 }
 
-func updateReqTimeout(reqTimeout, compareTimeout time.Duration, before time.Time) (shouldCloseConn bool, timeout time.Duration) {
-	if reqTimeout <= 0 {
-		return false, compareTimeout
+// calcTimeout checks deadline and returns timeout for conn.SetXXXTimeout
+//
+// returns 0 which means no timeout
+// returns -1 if deadline exceeded
+func calcTimeout(deadline time.Time, timeout time.Duration) time.Duration {
+	if timeout <= 0 {
+		if deadline.IsZero() {
+			return 0
+		}
+		return timeUntil(deadline)
 	}
-	left := reqTimeout - time.Since(before)
-	if left <= 0 {
-		return true, 0
+	if deadline.IsZero() {
+		return timeout // must > 0
 	}
-
-	if compareTimeout <= 0 {
-		return false, left
+	if d := timeUntil(deadline); d < timeout {
+		return d
 	}
-
-	if left > compareTimeout {
-		return false, compareTimeout
-	}
-
-	return false, left
+	return timeout
 }
 
 func (c *HostClient) doNonNilReqResp(req *protocol.Request, resp *protocol.Response) (bool, error) {
@@ -529,8 +526,6 @@ func (c *HostClient) doNonNilReqResp(req *protocol.Request, resp *protocol.Respo
 
 	atomic.StoreUint32(&c.lastUseTime, uint32(time.Now().Unix()-startTimeUnix))
 
-	rc := c.preHandleConfig(req.Options())
-
 	// Free up resources occupied by response before sending the request,
 	// so the GC may reclaim these resources (e.g. response body).
 	// backing up SkipBody in case it was set explicitly
@@ -541,19 +536,46 @@ func (c *HostClient) doNonNilReqResp(req *protocol.Request, resp *protocol.Respo
 	if c.DisablePathNormalizing {
 		req.URI().DisablePathNormalizing = true
 	}
-	reqTimeout := req.Options().RequestTimeout()
-	begin := req.Options().StartTime()
 
-	dialTimeout := rc.dialTimeout
-	if (reqTimeout > 0 && reqTimeout < dialTimeout) || dialTimeout == 0 {
-		dialTimeout = reqTimeout
+	o := req.Options()
+	deadline := time.Time{}
+	if v := o.RequestTimeout(); v > 0 {
+		deadline = o.StartTime().Add(v)
 	}
-	cc, inPool, err := c.acquireConn(dialTimeout)
-	// if getting connection error, fast fail
+
+	// dial starts
+
+	timeout := calcTimeout(deadline, minTimeout(c.DialTimeout, o.DialTimeout()))
+	if timeout < 0 {
+		return false, errTimeout
+	}
+	cc, inPool, err := c.acquireConn(timeout)
 	if err != nil {
 		return false, err
 	}
 	conn := cc.c
+	resp.ParseNetAddr(conn)
+
+	if c.IsTLS && timeout > 0 { // force handshake using dial timeout
+		// NOTE: Handshake() here is optional as Write would tirigger handshake
+		// but for tls handshake, it writes and reads, and we need to set deadline for that.
+		tlsconn, ok := conn.(network.ConnTLSer)
+		if ok {
+			// currently netpoll doesn't support conn.SetDeadline nor tls, but crypto/tls.Conn does.
+			// in case netpoll supports tls in the future, may need to change this to
+			// call both conn.SetReadTimeout, and conn.SetWriteTimeout
+			err := conn.SetDeadline(time.Now().Add(timeout))
+			if err == nil {
+				err = tlsconn.Handshake()
+				// NOTE: no need conn.SetDeadline(time.Time{})?
+				// we always reset before Write and Read
+			}
+			if err != nil {
+				c.closeConn(cc)
+				return true, err
+			}
+		}
+	}
 
 	usingProxy := false
 	if c.ProxyURI != nil && bytes.Equal(req.Scheme(), bytestr.StrHTTP) {
@@ -561,17 +583,15 @@ func (c *HostClient) doNonNilReqResp(req *protocol.Request, resp *protocol.Respo
 		proxy.SetProxyAuthHeader(&req.Header, c.ProxyURI)
 	}
 
-	resp.ParseNetAddr(conn)
+	// write starts
 
-	shouldClose, timeout := updateReqTimeout(reqTimeout, rc.writeTimeout, begin)
-	if shouldClose {
+	timeout = calcTimeout(deadline, minTimeout(c.WriteTimeout, o.WriteTimeout()))
+	if timeout < 0 {
 		c.closeConn(cc)
 		return false, errTimeout
 	}
-
 	if err = conn.SetWriteTimeout(timeout); err != nil {
 		c.closeConn(cc)
-		// try another connection if retry is enabled
 		return true, err
 	}
 
@@ -595,55 +615,53 @@ func (c *HostClient) doNonNilReqResp(req *protocol.Request, resp *protocol.Respo
 	if resetConnection {
 		req.Header.ResetConnectionClose()
 	}
-
 	if err == nil {
 		err = zw.Flush()
 	}
-	// error happened when writing request, close the connection, and try another connection if retry is enabled
 	if err != nil {
 		defer c.closeConn(cc)
-
 		errNorm, ok := conn.(network.ErrorNormalization)
 		if ok {
 			err = errNorm.ToHertzError(err)
 		}
-
 		if !errors.Is(err, errs.ErrConnectionClosed) {
 			return true, err
 		}
 
-		// set a protection timeout to avoid infinite loop.
-		if conn.SetReadTimeout(time.Second) != nil {
+		// introduced by https://github.com/cloudwego/hertz/pull/412
+		// only for reading 4xx err
+
+		// short period of time (50ms) is enough for this case
+		// NOTE: can't use deadline since it likely already exceeded deadline when write
+		timeout = minTimeout(50*time.Millisecond, minTimeout(c.ReadTimeout, o.ReadTimeout()))
+		if conn.SetReadTimeout(timeout) != nil {
 			return true, err
 		}
-
-		// Only if the connection is closed while writing the request. Try to parse the response and return.
-		// In this case, the request/response is considered as successful.
-		// Otherwise, return the former error.
 		zr := c.acquireReader(conn)
 		defer zr.Release()
 		if respI.ReadHeaderAndLimitBody(resp, zr, c.MaxResponseBodySize) == nil {
-			return false, nil
+			if code := resp.StatusCode(); code >= 400 && code < 600 {
+				// strictly for 4xx only, but 5xx is also acceptable.
+				// both can be considered better response rather than write err
+				return false, nil
+			}
 		}
 
 		if inPool {
 			err = errs.ErrBadPoolConn
 		}
-
 		return true, err
 	}
 
-	shouldClose, timeout = updateReqTimeout(reqTimeout, rc.readTimeout, begin)
-	if shouldClose {
+	// read starts
+
+	timeout = calcTimeout(deadline, minTimeout(c.ReadTimeout, o.ReadTimeout()))
+	if timeout < 0 {
 		c.closeConn(cc)
 		return false, errTimeout
 	}
-
-	// Set Deadline every time, since golang has fixed the performance issue
-	// See https://github.com/golang/go/issues/15133#issuecomment-271571395 for details
 	if err = conn.SetReadTimeout(timeout); err != nil {
 		c.closeConn(cc)
-		// try another connection if retry is enabled
 		return true, err
 	}
 
diff --git a/pkg/protocol/http1/req/header.go b/pkg/protocol/http1/req/header.go
index 442e344..80745f8 100644
--- a/pkg/protocol/http1/req/header.go
+++ b/pkg/protocol/http1/req/header.go
@@ -131,39 +131,76 @@ func parse(h *protocol.RequestHeader, buf []byte) (int, error) {
 	return m + n, nil
 }
 
+const (
+	maxFirstLineLen   = 8 << 10
+	maxCheckMethodLen = 10
+
+	// reuse ValidHeaderFieldNameTable for Method, both are `token`
+	// see:
+	//	https://www.rfc-editor.org/rfc/rfc9110.html#name-methods
+	//	https://www.rfc-editor.org/rfc/rfc9110.html#name-field-names
+	validMethodCharTable = bytesconv.ValidHeaderFieldNameTable
+)
+
+var errMalformedHTTPRequest = errors.New("malformed HTTP request")
+
+// request-line = method SP request-target SP HTTP-version CRLF
 func parseFirstLine(h *protocol.RequestHeader, buf []byte) (int, error) {
-	bNext := buf
-	var b []byte
-	var err error
-	for len(b) == 0 {
-		if b, bNext, err = utils.NextLine(bNext); err != nil {
-			return 0, err
+	b, leftb, err := utils.NextLine(buf)
+	if err != nil {
+		// errs.ErrNeedMore?
+		// check malformed HTTP request before reading more data
+
+		// check len
+		if len(buf) > maxFirstLineLen {
+			return 0, errMalformedHTTPRequest
+		}
+
+		// check method bytes
+		// NOTE:
+		//  only check method bytes if errs.ErrNeedMore for closing malformed connections.
+		//  for performance concern, it won't be checked in the hot path.
+		for i, c := range buf {
+			if c == ' ' || i > maxCheckMethodLen {
+				break // skip if SP or reach maxCheckMethodLen
+			}
+			if validMethodCharTable[c] == 0 {
+				return 0, errMalformedHTTPRequest
+			}
 		}
+		return 0, err
 	}
 
 	// parse method
 	n := bytes.IndexByte(b, ' ')
 	if n <= 0 {
-		return 0, fmt.Errorf("cannot find http request method in %q", ext.BufferSnippet(buf))
+		return 0, errMalformedHTTPRequest
 	}
 	h.SetMethodBytes(b[:n])
 	b = b[n+1:]
 
-	// Set default protocol
-	h.SetProtocol(consts.HTTP11)
-	// parse requestURI
-	n = bytes.LastIndexByte(b, ' ')
-	if n < 0 {
-		h.SetProtocol(consts.HTTP10)
-		n = len(b)
-	} else if n == 0 {
-		return 0, fmt.Errorf("requestURI cannot be empty in %q", buf)
-	} else if !bytes.Equal(b[n+1:], bytestr.StrHTTP11) {
-		h.SetProtocol(consts.HTTP10)
+	// parse request-target (uri)
+	n = bytes.IndexByte(b, ' ')
+	if n <= 0 {
+		return 0, errMalformedHTTPRequest
 	}
 	h.SetRequestURIBytes(b[:n])
+	b = b[n+1:]
 
-	return len(buf) - len(bNext), nil
+	// parse http protocol
+	switch string(b) {
+	case consts.HTTP11: // likely HTTP/1.1
+		h.SetProtocol(consts.HTTP11)
+	case consts.HTTP10:
+		h.SetProtocol(consts.HTTP10)
+	default:
+		if len(b) < 5 || string(b[:5]) != "HTTP/" {
+			return 0, errMalformedHTTPRequest
+		}
+		// XXX: all other cases are considered to be HTTP/1.0 for safe
+		h.SetProtocol(consts.HTTP10)
+	}
+	return len(buf) - len(leftb), nil
 }
 
 // validHeaderFieldValue is equal to httpguts.ValidHeaderFieldValue（shares the same context）
diff --git a/pkg/protocol/http1/resp/writer.go b/pkg/protocol/http1/resp/writer.go
index 02a3b7c..1150826 100644
--- a/pkg/protocol/http1/resp/writer.go
+++ b/pkg/protocol/http1/resp/writer.go
@@ -26,10 +26,10 @@ import (
 	"github.com/cloudwego/hertz/pkg/protocol/http1/ext"
 )
 
-var chunkReaderPool sync.Pool
+var chunkWriterPool sync.Pool
 
 func init() {
-	chunkReaderPool = sync.Pool{
+	chunkWriterPool = sync.Pool{
 		New: func() interface{} {
 			return &chunkedBodyWriter{}
 		},
@@ -58,6 +58,12 @@ func (c *chunkedBodyWriter) Write(p []byte) (n int, err error) {
 	if err := c.writeHeader(); err != nil {
 		return 0, err
 	}
+	if len(p) == 0 {
+		// prevent from sending zero-len chunk which indicates stream ends.
+		// callers may write with zero-len buf unintentionally.
+		// use Finalize() instead.
+		return 0, nil
+	}
 	if err := c.writeChunk(p); err != nil {
 		return 0, err
 	}
@@ -93,11 +99,14 @@ func (c *chunkedBodyWriter) Finalize() error {
 	if c.finalized || c.err != nil {
 		return c.err
 	}
+	c.finalized = true
+	if err := c.writeHeader(); err != nil {
+		return err
+	}
 	// zero-len chunk
-	if _, err := c.Write(nil); err != nil {
+	if err := c.writeChunk(nil); err != nil {
 		return err
 	}
-	c.finalized = true
 	// trailer which ends with \r\n
 	_, c.err = c.w.WriteBinary(c.r.Header.Trailer().Header())
 	if c.err == nil {
@@ -112,12 +121,12 @@ func (c *chunkedBodyWriter) release() {
 	c.err = nil
 	c.finalized = false
 	c.wroteHeader = false
-	chunkReaderPool.Put(c)
+	chunkWriterPool.Put(c)
 }
 
 // NewChunkedBodyWriter creates a new chunked body writer.
 func NewChunkedBodyWriter(r *protocol.Response, w network.Writer) network.ExtWriter {
-	extWriter := chunkReaderPool.Get().(*chunkedBodyWriter)
+	extWriter := chunkWriterPool.Get().(*chunkedBodyWriter)
 	extWriter.r = r
 	extWriter.w = w
 	runtime.SetFinalizer(extWriter, (*chunkedBodyWriter).release)
diff --git a/pkg/protocol/sse/reader.go b/pkg/protocol/sse/reader.go
index b879fee..d6daebe 100644
--- a/pkg/protocol/sse/reader.go
+++ b/pkg/protocol/sse/reader.go
@@ -59,6 +59,7 @@ func NewReader(resp *protocol.Response) (*Reader, error) {
 		r.r = bytes.NewReader(resp.Body())
 	}
 	r.s = bufio.NewScanner(r.r)
+	r.s.Split(scanEOL)
 	return r, nil
 }
 
diff --git a/pkg/protocol/sse/utils.go b/pkg/protocol/sse/utils.go
index 74062b8..ca09451 100644
--- a/pkg/protocol/sse/utils.go
+++ b/pkg/protocol/sse/utils.go
@@ -70,3 +70,35 @@ func hasCRLF(s string) bool {
 	}
 	return false
 }
+
+// https://html.spec.whatwg.org/multipage/server-sent-events.html#parsing-an-event-stream
+// end-of-line   = ( cr lf / cr / lf )
+func scanEOL(data []byte, atEOF bool) (advance int, token []byte, err error) {
+	if atEOF && len(data) == 0 {
+		return 0, nil, nil
+	}
+	i := bytes.IndexByte(data, '\r')
+	j := bytes.IndexByte(data, '\n')
+	if i >= 0 {
+		if i+1 == j { // \r\n
+			return i + 2, data[0:i], nil
+		}
+		if j >= 0 { // choose the nearer \r or \n as EOL
+			if i < j {
+				return i + 1, data[0:i], nil // \r
+			}
+			return j + 1, data[0:j], nil // \n
+		}
+		// if ends with '\r', we need to check the next char is NOT '\n' as per spec
+		// this may cause unexpected blocks on reading more data.
+		if i < len(data)-1 || atEOF {
+			return i + 1, data[0:i], nil
+		}
+	} else if j >= 0 {
+		return j + 1, data[0:j], nil
+	}
+	if atEOF {
+		return len(data), data, nil
+	}
+	return 0, nil, nil // more data
+}
diff --git a/pkg/protocol/sse/writer.go b/pkg/protocol/sse/writer.go
index 511eecb..af4e91b 100644
--- a/pkg/protocol/sse/writer.go
+++ b/pkg/protocol/sse/writer.go
@@ -17,13 +17,11 @@
 package sse
 
 import (
-	"bytes"
 	"errors"
 	"strconv"
-	"strings"
 	"sync"
 
-	"github.com/cloudwego/hertz/internal/bytestr"
+	"github.com/cloudwego/hertz/internal/bytesconv"
 	"github.com/cloudwego/hertz/pkg/app"
 	"github.com/cloudwego/hertz/pkg/common/bytebufferpool"
 	"github.com/cloudwego/hertz/pkg/network"
@@ -41,8 +39,11 @@ type Writer struct {
 
 // NewWriter creates a new SSE writer.
 func NewWriter(c *app.RequestContext) *Writer {
+	// make sure proxies won't cache the data
 	c.Response.Header.Set("Cache-Control", "no-cache")
-	c.Response.Header.SetContentType(string(bytestr.MIMETextEventStream))
+	// browsers may need charset=utf-8 for logging responses
+	// even though it's unnecessary as per spec, coz chunks must be in utf8.
+	c.Response.Header.SetContentType("text/event-stream; charset=utf-8")
 	w := c.Response.GetHijackWriter()
 	if w == nil {
 		w = resp.NewChunkedBodyWriter(&c.Response, c.GetWriter())
@@ -84,16 +85,12 @@ func (w *Writer) WriteComment(s string) error {
 	defer bytebufferpool.Put(p)
 
 	buf := p.B[:0]
-	for len(s) > 0 {
-		i := strings.IndexByte(s, '\n')
-		if i >= 0 {
-			buf = append(buf, ':')
-			buf = append(buf, s[:i+1]...) // it contains '\n' already
-			s = s[i+1:]
-		} else {
-			buf = append(append(append(buf, ':'), s...), '\n')
-			s = ""
-		}
+	for data := bytesconv.S2b(s); len(data) > 0; {
+		i, b, _ := scanEOL(data, true)
+		buf = append(buf, ':')
+		buf = append(buf, b...)
+		buf = append(buf, '\n')
+		data = data[i:]
 	}
 	if len(buf) == 0 {
 		buf = append(buf, ':')
@@ -142,16 +139,13 @@ func (w *Writer) Write(e *Event) error {
 
 	if e.IsSetData() {
 		data := e.Data
+		// replace EOLs with multiple "data: " lines
 		for len(data) > 0 {
-			i := bytes.IndexByte(data, '\n')
-			if i >= 0 {
-				buf = append(buf, "data: "...)
-				buf = append(buf, data[:i+1]...) // it contains '\n' already
-				data = data[i+1:]
-			} else {
-				buf = append(append(append(buf, "data: "...), data...), '\n')
-				data = nil
-			}
+			i, b, _ := scanEOL(data, true)
+			buf = append(buf, "data: "...)
+			buf = append(buf, b...)
+			buf = append(buf, '\n')
+			data = data[i:]
 		}
 	}
 	p.B = append(buf, '\n') // end of event
diff --git a/pkg/route/engine.go b/pkg/route/engine.go
index 6589cf9..502c16d 100644
--- a/pkg/route/engine.go
+++ b/pkg/route/engine.go
@@ -455,17 +455,7 @@ func (engine *Engine) onData(c context.Context, conn interface{}) (err error) {
 	return
 }
 
-func errProcess(conn io.Closer, err error) {
-	if err == nil {
-		return
-	}
-
-	defer func() {
-		if err != nil {
-			conn.Close()
-		}
-	}()
-
+func logError(conn network.Conn, err error) {
 	// Quiet close the connection
 	if errors.Is(err, errs.ErrShortConnection) || errors.Is(err, errs.ErrIdleTimeout) {
 		return
@@ -473,12 +463,14 @@ func errProcess(conn io.Closer, err error) {
 
 	// Do not process the hijack connection error
 	if errors.Is(err, errs.ErrHijacked) {
-		err = nil
 		return
 	}
 
 	// Get remote address
-	rip := getRemoteAddrFromCloser(conn)
+	rip := ""
+	if addr := conn.RemoteAddr(); addr != nil {
+		rip = addr.String()
+	}
 
 	// Handle Specific error
 	if hsp, ok := conn.(network.HandleSpecificError); ok {
@@ -490,15 +482,6 @@ func errProcess(conn io.Closer, err error) {
 	hlog.SystemLogger().Errorf(hlog.EngineErrorFormat, err.Error(), rip)
 }
 
-func getRemoteAddrFromCloser(conn io.Closer) string {
-	if c, ok := conn.(network.Conn); ok {
-		if addr := c.RemoteAddr(); addr != nil {
-			return addr.String()
-		}
-	}
-	return ""
-}
-
 func (engine *Engine) Close() error {
 	if engine.htmlRender != nil {
 		engine.htmlRender.Close() //nolint:errcheck
@@ -523,7 +506,12 @@ func (engine *Engine) GetServerName() []byte {
 
 func (engine *Engine) Serve(c context.Context, conn network.Conn) (err error) {
 	defer func() {
-		errProcess(conn, err)
+		if err != nil {
+			logError(conn, err)
+		}
+		if !errors.Is(err, errs.ErrHijacked) {
+			_ = conn.Close()
+		}
 	}()
 
 	// H2C path
@@ -707,7 +695,7 @@ func (engine *Engine) addRoute(method, path string, handlers app.HandlersChain)
 
 	methodRouter := engine.trees.get(method)
 	if methodRouter == nil {
-		methodRouter = &router{method: method, root: &node{}, hasTsrHandler: make(map[string]bool)}
+		methodRouter = &router{method: method, root: &node{}}
 		engine.trees = append(engine.trees, methodRouter)
 	}
 	methodRouter.addRoute(path, handlers)
diff --git a/pkg/route/tree.go b/pkg/route/tree.go
index 8b63c14..14b5e7b 100644
--- a/pkg/route/tree.go
+++ b/pkg/route/tree.go
@@ -55,9 +55,8 @@ import (
 )
 
 type router struct {
-	method        string
-	root          *node
-	hasTsrHandler map[string]bool
+	method string
+	root   *node
 }
 
 type MethodTrees []*router
diff --git a/version.go b/version.go
index 91da369..a833105 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.10.0"
+	Version = "v0.10.1"
 )


Test patch:
diff --git a/pkg/app/server/binding/binder_test.go b/pkg/app/server/binding/binder_test.go
index 1ea7b1e..4f5f742 100644
--- a/pkg/app/server/binding/binder_test.go
+++ b/pkg/app/server/binding/binder_test.go
@@ -41,7 +41,9 @@
 package binding
 
 import (
+	"encoding"
 	"encoding/json"
+	"errors"
 	"fmt"
 	"mime/multipart"
 	"net/url"
@@ -1665,6 +1667,41 @@ func TestBind_NormalizeContentType(t *testing.T) {
 	assert.DeepEqual(t, "version", result.Version)
 }
 
+type TestEnumType int32
+
+var _ encoding.TextUnmarshaler = (*TestEnumType)(nil)
+
+func (p *TestEnumType) UnmarshalText(v []byte) error {
+	switch string(v) {
+	case "one":
+		*p = 1
+	case "two":
+		*p = 2
+	default:
+		return errors.New("invalid")
+	}
+	return nil
+}
+
+func TestBind_TextUnmarshaler(t *testing.T) {
+	type Query struct {
+		A TestEnumType  `query:"a"`
+		B TestEnumType  `query:"b"`
+		C *TestEnumType `query:"c"`
+		D *TestEnumType `query:"d"`
+	}
+	q := &Query{}
+	req := newMockRequest().SetRequestURI("http://example.com?a=1&b=one&c=2&d=two")
+	err := DefaultBinder().BindQuery(req.Req, q)
+	assert.Nil(t, err)
+	assert.DeepEqual(t, TestEnumType(1), q.A)
+	assert.DeepEqual(t, TestEnumType(1), q.B)
+	assert.NotNil(t, q.C)
+	assert.NotNil(t, q.D)
+	assert.DeepEqual(t, TestEnumType(2), *q.C)
+	assert.DeepEqual(t, TestEnumType(2), *q.D)
+}
+
 func Benchmark_Binding(b *testing.B) {
 	type Req struct {
 		Version string `path:"v"`
diff --git a/pkg/app/server/binding/internal/decoder/util_test.go b/pkg/app/server/binding/internal/decoder/util_test.go
new file mode 100644
index 0000000..0031e58
--- /dev/null
+++ b/pkg/app/server/binding/internal/decoder/util_test.go
@@ -0,0 +1,206 @@
+/*
+ * Copyright 2024 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 decoder
+
+import (
+	"encoding"
+	"errors"
+	"reflect"
+	"testing"
+
+	"github.com/cloudwego/hertz/pkg/common/test/assert"
+	"github.com/cloudwego/hertz/pkg/protocol"
+	"github.com/cloudwego/hertz/pkg/route/param"
+)
+
+type testTextUnmarshaler struct {
+	Value string
+}
+
+func (t *testTextUnmarshaler) UnmarshalText(text []byte) error {
+	t.Value = string(text)
+	return nil
+}
+
+var _ encoding.TextUnmarshaler = (*testTextUnmarshaler)(nil)
+
+func TestStringToValue(t *testing.T) {
+	tests := []struct {
+		name        string
+		elemType    reflect.Type
+		text        string
+		config      *DecodeConfig
+		expectValue interface{}
+		expectError bool
+	}{
+		{
+			name:        "string type",
+			elemType:    reflect.TypeOf(""),
+			text:        "test string",
+			expectValue: "test string",
+		},
+		{
+			name:        "int type",
+			elemType:    reflect.TypeOf(0),
+			text:        "42",
+			expectValue: 42,
+		},
+		{
+			name:        "bool type",
+			elemType:    reflect.TypeOf(false),
+			text:        "true",
+			expectValue: true,
+		},
+		{
+			name:        "float type",
+			elemType:    reflect.TypeOf(0.0),
+			text:        "3.14",
+			expectValue: 3.14,
+		},
+		{
+			name:        "text unmarshaler",
+			elemType:    reflect.TypeOf(testTextUnmarshaler{}),
+			text:        "custom text",
+			expectValue: testTextUnmarshaler{Value: "custom text"},
+		},
+		{
+			name:        "invalid int",
+			elemType:    reflect.TypeOf(0),
+			text:        "not an int",
+			expectError: true,
+		},
+		{
+			name:        "struct type",
+			elemType:    reflect.TypeOf(struct{ Name string }{}),
+			text:        `{"Name":"test"}`,
+			expectValue: struct{ Name string }{Name: "test"},
+		},
+		{
+			name:        "struct type err",
+			elemType:    reflect.TypeOf(struct{ Name string }{}),
+			text:        `{"Name":1}`,
+			expectError: true,
+		},
+		{
+			name:        "list type",
+			elemType:    reflect.TypeOf([]int{}),
+			expectValue: *new([]int),
+		},
+		{
+			name:        "map type",
+			elemType:    reflect.TypeOf(map[string]interface{}{}),
+			text:        `{"key":"value"}`,
+			expectValue: map[string]interface{}{"key": "value"},
+		},
+		{
+			name:        "unsupported type",
+			elemType:    reflect.TypeOf(complex64(0)),
+			expectError: true,
+		},
+		{
+			name:     "custom type unmarshal func",
+			elemType: reflect.TypeOf(testTextUnmarshaler{}),
+			text:     "custom func",
+			config: &DecodeConfig{
+				TypeUnmarshalFuncs: map[reflect.Type]CustomizeDecodeFunc{
+					reflect.TypeOf(testTextUnmarshaler{}): func(req *protocol.Request, params param.Params, text string) (reflect.Value, error) {
+						return reflect.ValueOf(testTextUnmarshaler{Value: "from custom func"}), nil
+					},
+				},
+			},
+			expectValue: testTextUnmarshaler{Value: "from custom func"},
+		},
+		{
+			name:     "custom type unmarshal func err",
+			elemType: reflect.TypeOf(testTextUnmarshaler{}),
+			config: &DecodeConfig{
+				TypeUnmarshalFuncs: map[reflect.Type]CustomizeDecodeFunc{
+					reflect.TypeOf(testTextUnmarshaler{}): func(req *protocol.Request, params param.Params, text string) (reflect.Value, error) {
+						return reflect.Value{}, errors.New("err")
+					},
+				},
+			},
+			expectError: true,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			req := &protocol.Request{}
+			params := param.Params{}
+			config := tt.config
+			if config == nil {
+				config = &DecodeConfig{}
+			}
+			val, err := stringToValue(tt.elemType, tt.text, req, params, config)
+			if tt.expectError {
+				assert.NotNil(t, err)
+				return
+			}
+			assert.Nil(t, err)
+			assert.DeepEqual(t, tt.expectValue, val.Interface())
+		})
+	}
+}
+
+func TestTryTextUnmarshaler(t *testing.T) {
+	tests := []struct {
+		name     string
+		value    interface{}
+		text     string
+		expected bool
+	}{
+		{
+			name:     "text unmarshaler",
+			value:    &testTextUnmarshaler{},
+			text:     "test text",
+			expected: true,
+		},
+		{
+			name:     "non text unmarshaler",
+			value:    &struct{}{},
+			text:     "test text",
+			expected: false,
+		},
+		{
+			name:     "nil value",
+			value:    nil,
+			text:     "test text",
+			expected: false,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			var v reflect.Value
+			if tt.value != nil {
+				v = reflect.ValueOf(tt.value)
+			} else {
+				v = reflect.ValueOf(&tt.value).Elem()
+			}
+
+			result := tryTextUnmarshaler(v, tt.text)
+			assert.DeepEqual(t, tt.expected, result)
+
+			if tt.expected && tt.value != nil {
+				// Verify the value was actually set
+				unmarshaler := tt.value.(*testTextUnmarshaler)
+				assert.DeepEqual(t, tt.text, unmarshaler.Value)
+			}
+		})
+	}
+}
diff --git a/pkg/common/test/mock/network.go b/pkg/common/test/mock/network.go
index 366e335..66f5aba 100644
--- a/pkg/common/test/mock/network.go
+++ b/pkg/common/test/mock/network.go
@@ -18,6 +18,7 @@ package mock
 
 import (
 	"bytes"
+	"crypto/tls"
 	"io"
 	"net"
 	"strings"
@@ -34,10 +35,12 @@ var (
 )
 
 type Conn struct {
-	readTimeout time.Duration
-	zr          network.Reader
-	zw          network.ReadWriter
-	wroteLen    int
+	zr       network.Reader
+	zw       network.ReadWriter
+	wroteLen int
+
+	rtimeout time.Duration
+	wtimeout time.Duration
 }
 
 type Recorder interface {
@@ -59,7 +62,7 @@ func (m *SlowReadConn) SetWriteTimeout(t time.Duration) error {
 }
 
 func (m *SlowReadConn) SetReadTimeout(t time.Duration) error {
-	m.Conn.readTimeout = t
+	m.Conn.rtimeout = t
 	return nil
 }
 
@@ -90,11 +93,11 @@ func (m *Conn) Release() error {
 func (m *Conn) Peek(i int) ([]byte, error) {
 	b, err := m.zr.Peek(i)
 	if err != nil || len(b) != i {
-		if m.readTimeout <= 0 {
+		if m.rtimeout <= 0 {
 			// simulate timeout forever
 			select {}
 		}
-		time.Sleep(m.readTimeout)
+		time.Sleep(m.rtimeout)
 		return nil, errs.ErrTimeout
 	}
 	return b, err
@@ -132,7 +135,11 @@ func (m *Conn) WriterRecorder() Recorder {
 }
 
 func (m *Conn) GetReadTimeout() time.Duration {
-	return m.readTimeout
+	return m.rtimeout
+}
+
+func (m *Conn) GetWriteTimeout() time.Duration {
+	return m.wtimeout
 }
 
 type recorder struct {
@@ -146,8 +153,8 @@ func (r *recorder) WroteLen() int {
 
 func (m *SlowReadConn) Peek(i int) ([]byte, error) {
 	b, err := m.zr.Peek(i)
-	if m.readTimeout > 0 {
-		time.Sleep(m.readTimeout)
+	if m.rtimeout > 0 {
+		time.Sleep(m.rtimeout)
 	} else {
 		time.Sleep(100 * time.Millisecond)
 	}
@@ -283,11 +290,13 @@ func (m *Conn) RemoteAddr() net.Addr {
 }
 
 func (m *Conn) SetDeadline(t time.Time) error {
-	panic("implement me")
+	m.rtimeout = -time.Since(t)
+	m.wtimeout = m.rtimeout
+	return nil
 }
 
 func (m *Conn) SetReadDeadline(t time.Time) error {
-	m.readTimeout = -time.Since(t)
+	m.rtimeout = -time.Since(t)
 	return nil
 }
 
@@ -312,7 +321,7 @@ func (m *Conn) SetIdleTimeout(timeout time.Duration) error {
 }
 
 func (m *Conn) SetReadTimeout(t time.Duration) error {
-	m.readTimeout = t
+	m.rtimeout = t
 	return nil
 }
 
@@ -407,3 +416,23 @@ func (m *MockWriter) Flush() error {
 	}
 	return m.w.Flush()
 }
+
+type TLSConn struct {
+	network.Conn
+
+	HandshakeErr error
+}
+
+var _ network.ConnTLSer = (*TLSConn)(nil)
+
+func (c *TLSConn) Handshake() error {
+	return c.HandshakeErr
+}
+
+func (c *TLSConn) ConnectionState() tls.ConnectionState {
+	return tls.ConnectionState{}
+}
+
+func NewTLSConn(conn network.Conn) *TLSConn {
+	return &TLSConn{Conn: conn}
+}
diff --git a/pkg/common/test/mock/network_test.go b/pkg/common/test/mock/network_test.go
index 34b0769..9839a0d 100644
--- a/pkg/common/test/mock/network_test.go
+++ b/pkg/common/test/mock/network_test.go
@@ -111,9 +111,6 @@ func TestConn(t *testing.T) {
 		assert.DeepEqual(t, nil, conn.LocalAddr())
 		assert.DeepEqual(t, nil, conn.RemoteAddr())
 		assert.DeepEqual(t, nil, conn.SetIdleTimeout(du1))
-		assert.Panic(t, func() {
-			conn.SetDeadline(t1)
-		})
 		assert.Panic(t, func() {
 			conn.SetWriteDeadline(t1)
 		})
@@ -139,7 +136,7 @@ func TestSlowConn(t *testing.T) {
 		conn := NewSlowReadConn(s1)
 		assert.Nil(t, conn.SetWriteTimeout(1))
 		assert.Nil(t, conn.SetReadTimeout(1))
-		assert.DeepEqual(t, time.Duration(1), conn.readTimeout)
+		assert.DeepEqual(t, time.Duration(1), conn.GetReadTimeout())
 
 		b, err := conn.Peek(4)
 		assert.DeepEqual(t, nil, err)
diff --git a/pkg/network/standard/connection_test.go b/pkg/network/standard/connection_test.go
index f0796cf..91ffa7f 100644
--- a/pkg/network/standard/connection_test.go
+++ b/pkg/network/standard/connection_test.go
@@ -25,12 +25,10 @@ import (
 	"net"
 	"runtime"
 	"strings"
-	"sync/atomic"
 	"syscall"
 	"testing"
 	"time"
 
-	. "github.com/bytedance/mockey"
 	"github.com/cloudwego/hertz/pkg/common/test/assert"
 )
 
@@ -383,34 +381,20 @@ func (m *mockAddr) String() string {
 	return m.address
 }
 
-var release_count uint32 = 0
-
-func mockLinkBufferNodeRelease(b *linkBufferNode) {
-	atomic.AddUint32(&release_count, 1)
-
-	if !b.readOnly {
-		free(b.buf)
-	}
-	b.readOnly = false
-	b.buf = nil
-	b.next = nil
-	b.malloc, b.off = 0, 0
-	bufferPool.Put(b)
-}
-
 func TestConnSetFinalizer(t *testing.T) {
-	runtime.GC()
-	time.Sleep(time.Millisecond * 100)
-
-	Mock((*linkBufferNode).Release).To(mockLinkBufferNodeRelease).Build()
-
-	atomic.StoreUint32(&release_count, 0)
-	_ = newConn(&mockConn{}, 4096)
+	defer func(newfunc func() interface{}) {
+		bufferPool.New = newfunc
+	}(bufferPool.New) // reset
 
 	runtime.GC()
-	time.Sleep(time.Millisecond * 100)
+	runtime.GC()
+	for i := 0; i < 1000; i++ {
+		_ = newConn(&mockConn{}, 4096)
+	}
+	runtime.GC()
+	bufferPool.New = nil
+	assert.NotNil(t, bufferPool.Get())
 
-	assert.DeepEqual(t, uint32(2), atomic.LoadUint32(&release_count))
 }
 
 func TestFillReturnErrAndN(t *testing.T) {
diff --git a/pkg/protocol/http1/client_test.go b/pkg/protocol/http1/client_test.go
index f73bea7..17d0649 100644
--- a/pkg/protocol/http1/client_test.go
+++ b/pkg/protocol/http1/client_test.go
@@ -84,7 +84,7 @@ func TestHostClientMaxConnWaitTimeoutWithEarlierDeadline(t *testing.T) {
 
 	c := &HostClient{
 		ClientOptions: &ClientOptions{
-			Dialer: newSlowConnDialer(func(network, addr string, timeout time.Duration) (network.Conn, error) {
+			Dialer: dialerFunc(func(network, addr string, timeout time.Duration) (network.Conn, error) {
 				return mock.SlowReadDialer(addr)
 			}),
 			MaxConns:           1,
@@ -244,28 +244,22 @@ func testContinueReadResponseBodyStream(t *testing.T, header, body string, maxBo
 	}
 }
 
-func newSlowConnDialer(dialer func(network, addr string, timeout time.Duration) (network.Conn, error)) network.Dialer {
-	return &mockDialer{customDialConn: dialer}
-}
-
-type mockDialer struct {
-	customDialConn func(network, addr string, timeout time.Duration) (network.Conn, error)
-}
+type dialerFunc func(network, addr string, timeout time.Duration) (network.Conn, error)
 
-func (m *mockDialer) DialConnection(network, address string, timeout time.Duration, tlsConfig *tls.Config) (conn network.Conn, err error) {
-	return m.customDialConn(network, address, timeout)
+func (f dialerFunc) DialConnection(network, address string, timeout time.Duration, tlsConfig *tls.Config) (conn network.Conn, err error) {
+	return f(network, address, timeout)
 }
 
-func (m *mockDialer) DialTimeout(network, address string, timeout time.Duration, tlsConfig *tls.Config) (conn net.Conn, err error) {
+func (_ dialerFunc) DialTimeout(network, address string, timeout time.Duration, tlsConfig *tls.Config) (conn net.Conn, err error) {
 	return nil, nil
 }
 
-func (m *mockDialer) AddTLS(conn network.Conn, tlsConfig *tls.Config) (network.Conn, error) {
+func (_ dialerFunc) AddTLS(conn network.Conn, tlsConfig *tls.Config) (network.Conn, error) {
 	return nil, nil
 }
 
 type slowDialer struct {
-	*mockDialer
+	network.Dialer
 }
 
 func (s *slowDialer) DialConnection(network, address string, timeout time.Duration, tlsConfig *tls.Config) (conn network.Conn, err error) {
@@ -276,7 +270,7 @@ func (s *slowDialer) DialConnection(network, address string, timeout time.Durati
 func TestReadTimeoutPriority(t *testing.T) {
 	c := &HostClient{
 		ClientOptions: &ClientOptions{
-			Dialer: newSlowConnDialer(func(network, addr string, timeout time.Duration) (network.Conn, error) {
+			Dialer: dialerFunc(func(network, addr string, timeout time.Duration) (network.Conn, error) {
 				return mock.SlowReadDialer(addr)
 			}),
 			MaxConns:           1,
@@ -306,11 +300,8 @@ func TestReadTimeoutPriority(t *testing.T) {
 func TestDoNonNilReqResp(t *testing.T) {
 	c := &HostClient{
 		ClientOptions: &ClientOptions{
-			Dialer: newSlowConnDialer(func(network, addr string, timeout time.Duration) (network.Conn, error) {
-				return &writeErrConn{
-						Conn: mock.NewConn("HTTP/1.1 400 OK\nContent-Length: 6\n\n123456"),
-					},
-					nil
+			Dialer: dialerFunc(func(network, addr string, timeout time.Duration) (network.Conn, error) {
+				return mock.NewConn("HTTP/1.1 400 OK\nContent-Length: 6\n\n123456"), nil
 			}),
 		},
 	}
@@ -324,10 +315,85 @@ func TestDoNonNilReqResp(t *testing.T) {
 	assert.DeepEqual(t, resp.Body(), []byte("123456"))
 }
 
+func TestDoNonNilReqResp_WriteErr(t *testing.T) {
+	c := &HostClient{
+		ClientOptions: &ClientOptions{},
+	}
+	req := protocol.AcquireRequest()
+	resp := protocol.AcquireResponse()
+	req.SetHost("foobar")
+	req.SetConnectionClose() // won't reuse the conn
+
+	// 200 with write err, will return write err
+	c.ClientOptions.Dialer = dialerFunc(func(network, addr string, timeout time.Duration) (network.Conn, error) {
+		return &writeErrConn{mock.NewConn("HTTP/1.1 200 OK\nContent-Length: 6\n\n123456")}, nil
+	})
+	retry, err := c.doNonNilReqResp(req, resp)
+	assert.True(t, retry)
+	assert.NotNil(t, err)
+
+	c = &HostClient{
+		ClientOptions: &ClientOptions{
+			Dialer: dialerFunc(func(network, addr string, timeout time.Duration) (network.Conn, error) {
+				return &writeErrConn{mock.NewConn("HTTP/1.1 400 OK\nContent-Length: 6\n\n123456")}, nil
+			}),
+		},
+	}
+
+	// 400 with write err, will NOT return write err
+	c.ClientOptions.Dialer = dialerFunc(func(network, addr string, timeout time.Duration) (network.Conn, error) {
+		return &writeErrConn{mock.NewConn("HTTP/1.1 400 OK\nContent-Length: 6\n\n123456")}, nil
+	})
+	retry, err = c.doNonNilReqResp(req, resp)
+	assert.False(t, retry)
+	assert.Nil(t, err)
+	assert.DeepEqual(t, resp.StatusCode(), 400)
+	assert.DeepEqual(t, resp.Body(), []byte("123456"))
+}
+
+func TestDoNonNilReqResp_TLS(t *testing.T) {
+	const (
+		dialTimeout = 123 * time.Millisecond
+		dev         = 10 * time.Millisecond
+	)
+	conn := mock.NewConn("HTTP/1.1 200 OK\nContent-Length: 5\n\n54321")
+	tlsconn := mock.NewTLSConn(conn)
+	c := &HostClient{
+		IsTLS: true,
+		ClientOptions: &ClientOptions{
+			DialTimeout: dialTimeout,
+			Dialer: dialerFunc(func(network, addr string, timeout time.Duration) (network.Conn, error) {
+				return tlsconn, nil
+			}),
+		},
+	}
+	req := protocol.AcquireRequest()
+	resp := protocol.AcquireResponse()
+	req.SetHost("foobar")
+
+	// HandshakeErr != nil
+	tlsconn.HandshakeErr = errors.New("testerr")
+	retry, err := c.doNonNilReqResp(req, resp)
+	assert.True(t, retry)
+	assert.True(t, err == tlsconn.HandshakeErr)
+	if diff := conn.GetReadTimeout() - dialTimeout; diff < -dev || diff > dev {
+		t.Fatal("unexpected timeout. got", conn.GetReadTimeout(), "expect", dialTimeout)
+	}
+	assert.True(t, conn.GetReadTimeout() == conn.GetWriteTimeout())
+
+	// HandshakeErr == nil
+	tlsconn.HandshakeErr = nil
+	retry, err = c.doNonNilReqResp(req, resp)
+	assert.False(t, retry)
+	assert.Nil(t, err)
+	assert.DeepEqual(t, resp.StatusCode(), 200)
+	assert.DeepEqual(t, resp.Body(), []byte("54321"))
+}
+
 func TestDoNonNilReqResp_Err(t *testing.T) {
 	c := &HostClient{
 		ClientOptions: &ClientOptions{
-			Dialer: newSlowConnDialer(func(network, addr string, timeout time.Duration) (network.Conn, error) {
+			Dialer: dialerFunc(func(network, addr string, timeout time.Duration) (network.Conn, error) {
 				return peekErrConn{writeErrConn{mock.NewConn("")}}, nil
 			}),
 		},
@@ -439,7 +505,7 @@ func TestStreamResponse_ConnUpgrade(t *testing.T) {
 func TestWriteTimeoutPriority(t *testing.T) {
 	c := &HostClient{
 		ClientOptions: &ClientOptions{
-			Dialer: newSlowConnDialer(func(network, addr string, timeout time.Duration) (network.Conn, error) {
+			Dialer: dialerFunc(func(network, addr string, timeout time.Duration) (network.Conn, error) {
 				return mock.SlowWriteDialer(addr)
 			}),
 			MaxConns:           1,
@@ -501,7 +567,7 @@ func TestStateObserve(t *testing.T) {
 	}{}
 	c := &HostClient{
 		ClientOptions: &ClientOptions{
-			Dialer: newSlowConnDialer(func(network, addr string, timeout time.Duration) (network.Conn, error) {
+			Dialer: dialerFunc(func(network, addr string, timeout time.Duration) (network.Conn, error) {
 				return mock.SlowReadDialer(addr)
 			}),
 			StateObserve: func(hcs config.HostClientState) {
@@ -529,7 +595,7 @@ func TestStateObserve(t *testing.T) {
 func TestCachedTLSConfig(t *testing.T) {
 	c := &HostClient{
 		ClientOptions: &ClientOptions{
-			Dialer: newSlowConnDialer(func(network, addr string, timeout time.Duration) (network.Conn, error) {
+			Dialer: dialerFunc(func(network, addr string, timeout time.Duration) (network.Conn, error) {
 				return mock.SlowReadDialer(addr)
 			}),
 			TLSConfig: &tls.Config{
@@ -551,7 +617,7 @@ func TestRetry(t *testing.T) {
 	var times int32
 	c := &HostClient{
 		ClientOptions: &ClientOptions{
-			Dialer: newSlowConnDialer(func(network, addr string, timeout time.Duration) (network.Conn, error) {
+			Dialer: dialerFunc(func(network, addr string, timeout time.Duration) (network.Conn, error) {
 				times++
 				if times < 3 {
 					return &retryConn{
@@ -619,7 +685,7 @@ func (w retryConn) SetWriteTimeout(t time.Duration) error {
 func TestConnInPoolRetry(t *testing.T) {
 	c := &HostClient{
 		ClientOptions: &ClientOptions{
-			Dialer: newSlowConnDialer(func(network, addr string, timeout time.Duration) (network.Conn, error) {
+			Dialer: dialerFunc(func(network, addr string, timeout time.Duration) (network.Conn, error) {
 				return mock.NewOneTimeConn("HTTP/1.1 200 OK\r\nContent-Length: 10\r\nContent-Type: foo/bar\r\n\r\n0123456789"), nil
 			}),
 		},
@@ -651,7 +717,7 @@ func TestConnInPoolRetry(t *testing.T) {
 func TestConnNotRetry(t *testing.T) {
 	c := &HostClient{
 		ClientOptions: &ClientOptions{
-			Dialer: newSlowConnDialer(func(network, addr string, timeout time.Duration) (network.Conn, error) {
+			Dialer: dialerFunc(func(network, addr string, timeout time.Duration) (network.Conn, error) {
 				return mock.NewBrokenConn(""), nil
 			}),
 		},
@@ -691,7 +757,7 @@ func TestStreamNoContent(t *testing.T) {
 
 	c := &HostClient{
 		ClientOptions: &ClientOptions{
-			Dialer: newSlowConnDialer(func(network, addr string, timeout time.Duration) (network.Conn, error) {
+			Dialer: dialerFunc(func(network, addr string, timeout time.Duration) (network.Conn, error) {
 				return conn, nil
 			}),
 		},
@@ -714,12 +780,10 @@ func TestDialTimeout(t *testing.T) {
 	c := &HostClient{
 		ClientOptions: &ClientOptions{
 			DialTimeout: time.Second * 10,
-			Dialer: &mockDialer{
-				customDialConn: func(network, addr string, timeout time.Duration) (network.Conn, error) {
-					assert.DeepEqual(t, time.Second*10, timeout)
-					return nil, errors.New("test error")
-				},
-			},
+			Dialer: dialerFunc(func(network, addr string, timeout time.Duration) (network.Conn, error) {
+				assert.DeepEqual(t, time.Second*10, timeout)
+				return nil, errors.New("test error")
+			}),
 		},
 		Addr: "foobar",
 	}
@@ -740,3 +804,62 @@ func TestContextNil(t *testing.T) {
 	c := &HostClient{}
 	c.Do(nil, nil, nil) //nolint:staticcheck // SA1012: do not pass a nil Context
 }
+
+func TestMinTimeout(t *testing.T) {
+	tests := []struct {
+		name     string
+		d0       time.Duration
+		d1       time.Duration
+		expected time.Duration
+	}{
+		{"both positive, d0 < d1", 1 * time.Second, 2 * time.Second, 1 * time.Second},
+		{"both positive, d0 > d1", 3 * time.Second, 1 * time.Second, 1 * time.Second},
+		{"both positive, d0 = d1", 2 * time.Second, 2 * time.Second, 2 * time.Second},
+		{"d0 zero, d1 positive", 0, 2 * time.Second, 2 * time.Second},
+		{"d0 negative, d1 positive", -1 * time.Second, 2 * time.Second, 2 * time.Second},
+		{"d0 positive, d1 zero", 2 * time.Second, 0, 2 * time.Second},
+		{"d0 positive, d1 negative", 2 * time.Second, -1 * time.Second, 2 * time.Second},
+		{"both zero", 0, 0, 0},
+		{"both negative", -1 * time.Second, -2 * time.Second, 0},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			result := minTimeout(tt.d0, tt.d1)
+			if result != tt.expected {
+				t.Errorf("minTimeout(%v, %v) = %v, expected %v", tt.d0, tt.d1, result, tt.expected)
+			}
+		})
+	}
+}
+
+func TestCalcimeout(t *testing.T) {
+	now := time.Now()
+
+	tests := []struct {
+		name     string
+		deadline time.Time
+		timeout  time.Duration
+		expected time.Duration
+	}{
+		{"zero deadline, positive timeout", time.Time{}, 5 * time.Second, 5 * time.Second},
+		{"zero deadline, zero timeout", time.Time{}, 0, 0},
+		{"zero deadline, negative timeout", time.Time{}, -1 * time.Second, 0},
+		{"future deadline, zero timeout", now.Add(10 * time.Second), 0, 10 * time.Second},
+		{"future deadline, positive timeout (deadline < timeout)", now.Add(3 * time.Second), 5 * time.Second, 3 * time.Second},
+		{"future deadline, positive timeout (deadline > timeout)", now.Add(8 * time.Second), 5 * time.Second, 5 * time.Second},
+		{"past deadline, zero timeout", now.Add(-5 * time.Second), 0, -1},
+		{"past deadline, positive timeout", now.Add(-5 * time.Second), time.Second, -1},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			result := calcTimeout(tt.deadline, tt.timeout)
+			diff := result - tt.expected
+			if diff < -50*time.Millisecond || diff > 50*time.Millisecond {
+				t.Errorf("calcTimeout(%v, %v) = %v, expected %v",
+					tt.deadline, tt.timeout, result, tt.expected)
+			}
+		})
+	}
+}
diff --git a/pkg/protocol/http1/req/header_test.go b/pkg/protocol/http1/req/header_test.go
index 29192c7..12b0e65 100644
--- a/pkg/protocol/http1/req/header_test.go
+++ b/pkg/protocol/http1/req/header_test.go
@@ -46,8 +46,6 @@ import (
 	"bytes"
 	"errors"
 	"fmt"
-	"net/http"
-	"strings"
 	"testing"
 
 	errs "github.com/cloudwego/hertz/pkg/common/errors"
@@ -82,34 +80,6 @@ func TestRequestHeader_Read(t *testing.T) {
 	assert.DeepEqual(t, []byte("100-continue"), rh.Peek("Expect"))
 }
 
-func TestRequestHeaderMultiLineValue(t *testing.T) {
-	s := "HTTP/1.1 200 OK\r\n" +
-		"EmptyValue1:\r\n" +
-		"Content-Type: foo/bar;\r\n\tnewline;\r\n another/newline\r\n" +
-		"Foo: Bar\r\n" +
-		"Multi-Line: one;\r\n two\r\n" +
-		"Values: v1;\r\n v2; v3;\r\n v4;\tv5\r\n" +
-		"\r\n"
-
-	header := new(protocol.RequestHeader)
-	if _, err := parse(header, []byte(s)); err != nil {
-		t.Fatalf("parse headers with multi-line values failed, %s", err)
-	}
-	response, err := http.ReadResponse(bufio.NewReader(strings.NewReader(s)), nil)
-	if err != nil {
-		t.Fatalf("parse response using net/http failed, %s", err)
-	}
-
-	for name, vals := range response.Header {
-		got := string(header.Peek(name))
-		want := vals[0]
-
-		if got != want {
-			t.Errorf("unexpected %s got: %q want: %q", name, got, want)
-		}
-	}
-}
-
 func TestRequestHeader_Peek(t *testing.T) {
 	s := "PUT /foo/bar HTTP/1.1\r\nExpect: 100-continue\r\nUser-Agent: foo\r\nHost: 127.0.0.1\r\nConnection: Keep-Alive\r\nContent-Length: 5\r\nTransfer-Encoding: foo\r\nContent-Type: foo/bar\r\n\r\nabcdef4343"
 	zr := mock.NewZeroCopyReader(s)
@@ -446,46 +416,68 @@ func TestTryRead(t *testing.T) {
 
 func TestParseFirstLine(t *testing.T) {
 	tests := []struct {
+		name     string
 		input    []byte
 		method   string
 		uri      string
 		protocol string
 		err      error
 	}{
-		// Test case 1: n < 0
 		{
+			name:     "case: normal",
 			input:    []byte("GET /path/to/resource HTTP/1.0\r\n"),
 			method:   "GET",
 			uri:      "/path/to/resource",
 			protocol: "HTTP/1.0",
-			err:      nil,
 		},
-		// Test case 2: n == 0
 		{
-			input:    []byte(" /path/to/resource HTTP/1.1\r\n"),
-			method:   "",
-			uri:      "",
-			protocol: "",
-			err:      fmt.Errorf("requestURI cannot be empty in"),
+			name:  "case: empty uri",
+			input: []byte("GET  HTTP/1.1\r\n"),
+			err:   errMalformedHTTPRequest,
 		},
-		// Test case 3: !bytes.Equal(b[n+1:], bytestr.StrHTTP11)
 		{
+			name:     "case: unknown protocol should use HTTP/1.0",
 			input:    []byte("POST /path/to/resource HTTP/1.2\r\n"),
 			method:   "POST",
 			uri:      "/path/to/resource",
 			protocol: "HTTP/1.0",
-			err:      nil,
+		},
+		{
+			name:  "case: invalid protocol",
+			input: []byte("POST /path/to/resource XTTP/1.1\r\n"),
+			err:   errMalformedHTTPRequest,
+		},
+		{
+			name:  "case: input too large",
+			input: make([]byte, 9<<10),
+			err:   errMalformedHTTPRequest,
+		},
+		{
+			name:  "case: method invalid",
+			input: []byte("< / HTTP/1."),
+			err:   errMalformedHTTPRequest,
+		},
+		{
+			name:  "case: need more err",
+			input: []byte("GET / HTTP/1."),
+			err:   errs.ErrNeedMore,
 		},
 	}
 
 	for _, tc := range tests {
-		header := &protocol.RequestHeader{}
-		_, err := parseFirstLine(header, tc.input)
+		h := &protocol.RequestHeader{}
+		_, err := parseFirstLine(h, tc.input)
 		if tc.err != nil {
-			assert.NotNil(t, err)
-		} else {
-			assert.Nil(t, err)
+			assert.Assert(t, errors.Is(err, tc.err), tc.name, err)
+			continue
 		}
+		assert.Assert(t, err == nil, tc.name, err)
+		if string(h.Method()) != tc.method ||
+			string(h.RequestURI()) != tc.uri ||
+			h.GetProtocol() != tc.protocol {
+			t.Fatal(tc.name, "got", h.String())
+		}
+
 	}
 }
 
diff --git a/pkg/protocol/http1/resp/header_test.go b/pkg/protocol/http1/resp/header_test.go
index 0fad023..488849f 100644
--- a/pkg/protocol/http1/resp/header_test.go
+++ b/pkg/protocol/http1/resp/header_test.go
@@ -42,7 +42,10 @@
 package resp
 
 import (
+	"bufio"
 	"bytes"
+	"net/http"
+	"strings"
 	"testing"
 
 	"github.com/cloudwego/hertz/pkg/protocol"
@@ -179,3 +182,31 @@ func equalCookie(c1, c2 *protocol.Cookie) bool {
 	}
 	return true
 }
+
+func TestResponseHeaderMultiLineValue(t *testing.T) {
+	s := "HTTP/1.1 200 OK\r\n" +
+		"EmptyValue1:\r\n" +
+		"Content-Type: foo/bar;\r\n\tnewline;\r\n another/newline\r\n" +
+		"Foo: Bar\r\n" +
+		"Multi-Line: one;\r\n two\r\n" +
+		"Values: v1;\r\n v2; v3;\r\n v4;\tv5\r\n" +
+		"\r\n"
+
+	header := new(protocol.ResponseHeader)
+	if _, err := parse(header, []byte(s)); err != nil {
+		t.Fatalf("parse headers with multi-line values failed, %s", err)
+	}
+	response, err := http.ReadResponse(bufio.NewReader(strings.NewReader(s)), nil)
+	if err != nil {
+		t.Fatalf("parse response using net/http failed, %s", err)
+	}
+
+	for name, vals := range response.Header {
+		got := string(header.Peek(name))
+		want := vals[0]
+
+		if got != want {
+			t.Errorf("unexpected %s got: %q want: %q", name, got, want)
+		}
+	}
+}
diff --git a/pkg/protocol/http1/resp/writer_test.go b/pkg/protocol/http1/resp/writer_test.go
index 80bec2f..c52b34c 100644
--- a/pkg/protocol/http1/resp/writer_test.go
+++ b/pkg/protocol/http1/resp/writer_test.go
@@ -33,6 +33,7 @@ func TestNewChunkedBodyWriter(t *testing.T) {
 	mockConn := mock.NewConn("")
 	w := NewChunkedBodyWriter(response, mockConn)
 	_, _ = w.Write([]byte("hello"))
+	_, _ = w.Write(nil) // noop
 	assert.Nil(t, w.Flush())
 	out, _ := mockConn.WriterRecorder().Peek(mockConn.WriterRecorder().WroteLen())
 	resp := string(out)
diff --git a/pkg/protocol/sse/reader_test.go b/pkg/protocol/sse/reader_test.go
index 9de6ebf..63af535 100644
--- a/pkg/protocol/sse/reader_test.go
+++ b/pkg/protocol/sse/reader_test.go
@@ -117,7 +117,7 @@ func TestReader_ReadEvent(t *testing.T) {
 		},
 		{
 			name:  "Event with multiline data",
-			input: "id: 123\nevent: update\ndata: line1\ndata: line2\ndata: line3\n\n",
+			input: "id: 123\revent: update\r\ndata: line1\rdata: line2\r\ndata: line3\n\n",
 			expected: func() *Event {
 				e := NewEvent()
 				e.SetID("123")
diff --git a/pkg/protocol/sse/utils_test.go b/pkg/protocol/sse/utils_test.go
index 4c73a3f..cc67e50 100644
--- a/pkg/protocol/sse/utils_test.go
+++ b/pkg/protocol/sse/utils_test.go
@@ -71,7 +71,38 @@ func TestHasCRLF(t *testing.T) {
 	assert.Assert(t, hasCRLF("This is a test string") == false)
 }
 
-func TestSseEventType(t *testing.T) {
+func TestSSEEventType(t *testing.T) {
 	assert.DeepEqual(t, "message", sseEventType([]byte("message")))
 	assert.DeepEqual(t, "custom", sseEventType([]byte("custom")))
 }
+
+func TestScanEOL(t *testing.T) {
+	tests := []struct {
+		data    string
+		atEOF   bool
+		advance int
+		token   string
+	}{
+		{"", true, 0, ""},
+		{"", false, 0, ""},
+		{"hello\r\nworld", false, 7, "hello"},
+		{"hello\rworld", false, 6, "hello"},
+		{"hello\nworld", false, 6, "hello"},
+		{"hello world", false, 0, ""},
+		{"hello world", true, 11, "hello world"},
+		{"\r", false, 0, ""},
+		{"hello\r", false, 0, ""},
+		{"hello\r", true, 6, "hello"},
+		{"\n", false, 1, ""},
+		{"\r\nhello", false, 2, ""},
+		{"\r\n", false, 2, ""},
+	}
+
+	for _, tc := range tests {
+		advance, token, _ := scanEOL([]byte(tc.data), tc.atEOF)
+		if advance != tc.advance || string(token) != tc.token {
+			t.Fatalf("scanLines(data=%q, atEOF=%v) returns (%d, %q) expect (%d, %q)",
+				tc.data, tc.atEOF, advance, string(token), tc.advance, tc.token)
+		}
+	}
+}
diff --git a/pkg/protocol/sse/writer_test.go b/pkg/protocol/sse/writer_test.go
index 3c2c693..44fb9e5 100644
--- a/pkg/protocol/sse/writer_test.go
+++ b/pkg/protocol/sse/writer_test.go
@@ -142,11 +142,11 @@ func TestWriter_Write(t *testing.T) {
 			name: "Multiline data",
 			event: func() *Event {
 				e := NewEvent()
-				e.SetData([]byte("line1\nline2\nline3"))
+				e.SetData([]byte("line1\rline2\nline3\r\nline4"))
 				return e
 			}(),
 			wantErr:  false,
-			expected: "data: line1\ndata: line2\ndata: line3\n\n",
+			expected: "data: line1\ndata: line2\ndata: line3\ndata: line4\n\n",
 		},
 		{
 			name: "Write error",
@@ -198,7 +198,7 @@ func TestNewWriter(t *testing.T) {
 	w := NewWriter(c)
 	assert.Assert(t, w != nil)
 	assert.DeepEqual(t, "no-cache", string(c.Response.Header.Peek("Cache-Control")))
-	assert.DeepEqual(t, "text/event-stream", string(c.Response.Header.Peek("Content-Type")))
+	assert.DeepEqual(t, "text/event-stream; charset=utf-8", string(c.Response.Header.Peek("Content-Type")))
 }
 
 func TestWriter_WriteComment(t *testing.T) {
diff --git a/pkg/route/routes_timing_test.go b/pkg/route/routes_timing_test.go
index 4beb71d..d0f18a4 100644
--- a/pkg/route/routes_timing_test.go
+++ b/pkg/route/routes_timing_test.go
@@ -54,7 +54,7 @@ type Route struct {
 }
 
 func BenchmarkTree_FindStatic(b *testing.B) {
-	tree := &router{method: "GET", root: &node{}, hasTsrHandler: make(map[string]bool)}
+	tree := &router{method: "GET", root: &node{}}
 
 	static := []*Route{
 		{"/"},
@@ -230,7 +230,7 @@ func BenchmarkTree_FindStatic(b *testing.B) {
 }
 
 func BenchmarkTree_FindGithub(b *testing.B) {
-	tree := &router{method: "GET", root: &node{}, hasTsrHandler: make(map[string]bool)}
+	tree := &router{method: "GET", root: &node{}}
 
 	static := []*Route{
 		// OAuth Authorizations
@@ -508,7 +508,7 @@ func BenchmarkTree_FindGithub(b *testing.B) {
 }
 
 func BenchmarkTree_FindStaticTsr(b *testing.B) {
-	tree := &router{method: "GET", root: &node{}, hasTsrHandler: make(map[string]bool)}
+	tree := &router{method: "GET", root: &node{}}
 
 	routes := [...]string{
 		"/doc/foo/go_faq.html/",
@@ -530,7 +530,7 @@ func BenchmarkTree_FindStaticTsr(b *testing.B) {
 }
 
 func BenchmarkTree_FindParam(b *testing.B) {
-	tree := &router{method: "GET", root: &node{}, hasTsrHandler: make(map[string]bool)}
+	tree := &router{method: "GET", root: &node{}}
 
 	routes := [...]string{
 		"/hi/:key1/foo/:key2",
@@ -552,7 +552,7 @@ func BenchmarkTree_FindParam(b *testing.B) {
 }
 
 func BenchmarkTree_FindParamTsr(b *testing.B) {
-	tree := &router{method: "GET", root: &node{}, hasTsrHandler: make(map[string]bool)}
+	tree := &router{method: "GET", root: &node{}}
 
 	routes := [...]string{
 		"/hi/:key1/foo/:key2/",
@@ -574,7 +574,7 @@ func BenchmarkTree_FindParamTsr(b *testing.B) {
 }
 
 func BenchmarkTree_FindAny(b *testing.B) {
-	tree := &router{method: "GET", root: &node{}, hasTsrHandler: make(map[string]bool)}
+	tree := &router{method: "GET", root: &node{}}
 
 	routes := [...]string{
 		"/hi/*key1",
@@ -596,7 +596,7 @@ func BenchmarkTree_FindAny(b *testing.B) {
 }
 
 func BenchmarkTree_FindAnyFallback(b *testing.B) {
-	tree := &router{method: "GET", root: &node{}, hasTsrHandler: make(map[string]bool)}
+	tree := &router{method: "GET", root: &node{}}
 
 	routes := [...]string{
 		"/hi/a/b/c/d/e/*key1",
diff --git a/pkg/route/tree_test.go b/pkg/route/tree_test.go
index 296edfa..9da57ed 100644
--- a/pkg/route/tree_test.go
+++ b/pkg/route/tree_test.go
@@ -94,7 +94,7 @@ func TestCountParams(t *testing.T) {
 }
 
 func TestEmptyPath(t *testing.T) {
-	tree := &router{method: "GET", root: &node{}, hasTsrHandler: make(map[string]bool)}
+	tree := &router{method: "GET", root: &node{}}
 
 	routes := [...]string{
 		"",
@@ -113,7 +113,7 @@ func TestEmptyPath(t *testing.T) {
 }
 
 func TestTreeAddAndGet(t *testing.T) {
-	tree := &router{method: "GET", root: &node{}, hasTsrHandler: make(map[string]bool)}
+	tree := &router{method: "GET", root: &node{}}
 
 	routes := [...]string{
 		"/hi",
@@ -150,7 +150,7 @@ func TestTreeAddAndGet(t *testing.T) {
 }
 
 func TestTreeWildcard(t *testing.T) {
-	tree := &router{method: "GET", root: &node{}, hasTsrHandler: make(map[string]bool)}
+	tree := &router{method: "GET", root: &node{}}
 
 	routes := [...]string{
 		"/",
@@ -198,7 +198,7 @@ func TestTreeWildcard(t *testing.T) {
 }
 
 func TestUnescapeParameters(t *testing.T) {
-	tree := &router{method: "GET", root: &node{}, hasTsrHandler: make(map[string]bool)}
+	tree := &router{method: "GET", root: &node{}}
 
 	routes := [...]string{
 		"/",
@@ -247,7 +247,7 @@ type testRoute struct {
 }
 
 func testRoutes(t *testing.T, routes []testRoute) {
-	tree := &router{method: "GET", root: &node{}, hasTsrHandler: make(map[string]bool)}
+	tree := &router{method: "GET", root: &node{}}
 
 	for _, route := range routes {
 		recv := catchPanic(func() {
@@ -306,7 +306,7 @@ func TestTreeChildConflict(t *testing.T) {
 }
 
 func TestTreeDuplicatePath(t *testing.T) {
-	tree := &router{method: "GET", root: &node{}, hasTsrHandler: make(map[string]bool)}
+	tree := &router{method: "GET", root: &node{}}
 
 	routes := [...]string{
 		"/",
@@ -342,7 +342,7 @@ func TestTreeDuplicatePath(t *testing.T) {
 }
 
 func TestEmptyWildcardName(t *testing.T) {
-	tree := &router{method: "GET", root: &node{}, hasTsrHandler: make(map[string]bool)}
+	tree := &router{method: "GET", root: &node{}}
 
 	routes := [...]string{
 		"/user:",
@@ -372,7 +372,7 @@ func TestTreeCatchAllConflict(t *testing.T) {
 }
 
 func TestTreeCatchMaxParams(t *testing.T) {
-	tree := &router{method: "GET", root: &node{}, hasTsrHandler: make(map[string]bool)}
+	tree := &router{method: "GET", root: &node{}}
 	route := "/cmd/*filepath"
 	tree.addRoute(route, fakeHandler(route))
 }
@@ -387,7 +387,7 @@ func TestTreeDoubleWildcard(t *testing.T) {
 	}
 
 	for _, route := range routes {
-		tree := &router{method: "GET", root: &node{}, hasTsrHandler: make(map[string]bool)}
+		tree := &router{method: "GET", root: &node{}}
 		recv := catchPanic(func() {
 			tree.addRoute(route, nil)
 		})
@@ -399,7 +399,7 @@ func TestTreeDoubleWildcard(t *testing.T) {
 }
 
 func TestTreeTrailingSlashRedirect2(t *testing.T) {
-	tree := &router{method: "GET", root: &node{}, hasTsrHandler: make(map[string]bool)}
+	tree := &router{method: "GET", root: &node{}}
 
 	routes := [...]string{
 		"/api/:version/seller/locales/get",
@@ -444,7 +444,7 @@ func TestTreeTrailingSlashRedirect2(t *testing.T) {
 }
 
 func TestTreeTrailingSlashRedirect(t *testing.T) {
-	tree := &router{method: "GET", root: &node{}, hasTsrHandler: make(map[string]bool)}
+	tree := &router{method: "GET", root: &node{}}
 
 	routes := [...]string{
 		"/hi",
@@ -543,7 +543,7 @@ func TestTreeTrailingSlashRedirect(t *testing.T) {
 }
 
 func TestTreeRootTrailingSlashRedirect(t *testing.T) {
-	tree := &router{method: "GET", root: &node{}, hasTsrHandler: make(map[string]bool)}
+	tree := &router{method: "GET", root: &node{}}
 
 	recv := catchPanic(func() {
 		tree.addRoute("/:test", fakeHandler("/:test"))
@@ -561,7 +561,7 @@ func TestTreeRootTrailingSlashRedirect(t *testing.T) {
 }
 
 func TestTreeFindCaseInsensitivePath(t *testing.T) {
-	tree := &router{method: "GET", root: &node{}, hasTsrHandler: make(map[string]bool)}
+	tree := &router{method: "GET", root: &node{}}
 
 	longPath := "/l" + strings.Repeat("o", 128) + "ng"
 	lOngPath := "/l" + strings.Repeat("O", 128) + "ng/"
@@ -708,7 +708,7 @@ func TestTreeFindCaseInsensitivePath(t *testing.T) {
 }
 
 func TestTreeParamNotOptimize(t *testing.T) {
-	tree := &router{method: "GET", root: &node{}, hasTsrHandler: make(map[string]bool)}
+	tree := &router{method: "GET", root: &node{}}
 	routes := [...]string{
 		"/:parama/start",
 		"/:paramb",
@@ -722,7 +722,7 @@ func TestTreeParamNotOptimize(t *testing.T) {
 	})
 
 	// other sequence
-	tree = &router{method: "GET", root: &node{}, hasTsrHandler: make(map[string]bool)}
+	tree = &router{method: "GET", root: &node{}}
 	routes = [...]string{
 		"/:paramb",
 		"/:parama/start",