Repository: cloudwego/hertz
Pull request #357 (2022-11-09 09:16:50)

optimize: zero idle timeout acts the same
#### What type of PR is this?
optimize

#### Check the PR title.
- [x] This PR title match the format: \<type\>(optional scope): \<description\>
- [x] The description of this PR title is user-oriented and clear enough for others to understand.


#### (Optional) Translate the PR title into Chinese.
设置 idle timeout 为 0 在不同的网络库下行为一致

#### (Optional) More detail description for this PR(en: English/zh: Chinese).
en: before this, standard network cannot process requests properly when idle timeout is set to zero, this may be confusing to users. 
zh(optional): 在此之前，标准网络在空闲超时设置为零时无法正确处理请求，这可能会让用户感到困惑。

#### Which issue(s) this PR fixes:
None


Patch:
diff --git a/README.md b/README.md
index 6a55598..554319c 100644
--- a/README.md
+++ b/README.md
@@ -64,7 +64,7 @@ Hertz [həːts] is a high-usability, high-performance and high-extensibility Gol
 | [Pprof](https://github.com/hertz-contrib/pprof)                                                    | Extension for Hertz integration with Pprof.                                                                                                                             |
 | [Sessions](https://github.com/hertz-contrib/sessions)                                              | Session middleware with multi-state store support.                                                                                                                      |
 | [Obs-opentelemetry](https://github.com/hertz-contrib/obs-opentelemetry)                            | Hertz's Opentelemetry extension that supports Metric, Logger, Tracing and works out of the box.                                                                         |
-| [Registry](https://github.com/hertz-contrib/registry)                                              | Provides service registry and discovery functions. So far, the supported service discovery extensions are nacos, consul, etcd, eureka, polaris, servicecomb, zookeeper. |
+| [Registry](https://github.com/hertz-contrib/registry)                                              | Provides service registry and discovery functions. So far, the supported service discovery extensions are nacos, consul, etcd, eureka, polaris, servicecomb, zookeeper, redis. |
 | [Keyauth](https://github.com/hertz-contrib/keyauth)                                                | Provides token-based authentication.                                                                                                                                    |
 | [Secure](https://github.com/hertz-contrib/secure)                                                  | Secure middleware with multiple configuration items.                                                                                                                    |
 | [Sentry](https://github.com/hertz-contrib/hertzsentry)                                             | Sentry extension provides some unified interfaces to help users perform real-time error monitoring.                                                                     |
@@ -83,10 +83,14 @@ Hertz [həːts] is a high-usability, high-performance and high-extensibility Gol
 | [Recovery](https://github.com/cloudwego/hertz/tree/develop/pkg/app/middlewares/server/recovery)    | Recovery middleware for Hertz.                                                                                                                                          |
 | [Basicauth](https://github.com/cloudwego/hertz/tree/develop/pkg/app/middlewares/server/basic_auth) | Basicauth middleware can provide HTTP basic authentication.                                                                                                             |
 | [Lark](https://github.com/hertz-contrib/lark-hertz)                                                | Use hertz handle Lark/Feishu card message and event callback.                                                                                                           |
-| [Logger](https://github.com/hertz-contrib/logger)                                                  | Logger extension for Hertz, which provides support for third-party logging frameworks.                                                                                  |
+| [Logger](https://github.com/hertz-contrib/logger)                                                  | Logger extension for Hertz, which provides support for zap, logrus, zerologs logging frameworks.                                                                        |
 
 ## Blogs
 - [ByteDance Practice on Go Network Library](https://www.cloudwego.io/blog/2021/10/09/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/)
 ## Contributing
 
 [Contributing](https://github.com/cloudwego/hertz/blob/main/CONTRIBUTING.md)
diff --git a/README_cn.md b/README_cn.md
index e1f5527..942b743 100644
--- a/README_cn.md
+++ b/README_cn.md
@@ -64,7 +64,7 @@ Hertz[həːts] 是一个 Golang 微服务 HTTP 框架，在设计之初参考了
 | [Pprof](https://github.com/hertz-contrib/pprof)                                                    | Hertz 集成 Pprof 的扩展。                                                                        |
 | [Sessions](https://github.com/hertz-contrib/sessions)                                              | 具有多状态存储支持的 Session 中间件。                                                                    |
 | [Obs-opentelemetry](https://github.com/hertz-contrib/obs-opentelemetry)                            | Hertz 的 Opentelemetry 扩展，支持 Metric、Logger、Tracing并且达到开箱即用。                                 |
-| [Registry](https://github.com/hertz-contrib/registry)                                              | 提供服务注册与发现功能。到现在为止，支持的服务发现拓展有 nacos， consul， etcd， eureka， polaris， servicecomb， zookeeper。 |
+| [Registry](https://github.com/hertz-contrib/registry)                                              | 提供服务注册与发现功能。到现在为止，支持的服务发现拓展有 nacos， consul， etcd， eureka， polaris， servicecomb， zookeeper， redis。 |
 | [Keyauth](https://github.com/hertz-contrib/keyauth)                                                | 提供基于 token 的身份验证。                                                                          |
 | [Secure](https://github.com/hertz-contrib/secure)                                                  | 具有多配置项的 Secure 中间件。                                                                        |
 | [Sentry](https://github.com/hertz-contrib/hertzsentry)                                             | Sentry 拓展提供了一些统一的接口来帮助用户进行实时的错误监控。                                                         |
@@ -83,10 +83,14 @@ Hertz[həːts] 是一个 Golang 微服务 HTTP 框架，在设计之初参考了
 | [Recovery](https://github.com/cloudwego/hertz/tree/develop/pkg/app/middlewares/server/recovery)    | Hertz 的异常恢复中间件。                                                                            |
 | [Basicauth](https://github.com/cloudwego/hertz/tree/develop/pkg/app/middlewares/server/basic_auth) | Basicauth 中间件能够提供 HTTP 基本身份验证。                                                             |
 | [Lark](https://github.com/hertz-contrib/lark-hertz)                                                | 在 Hertz 中处理 Lark/飞书的卡片消息和事件的回调。                                                            |
-| [Logger](https://github.com/hertz-contrib/logger)                                                  | Hertz 的日志拓展，提供了对第三方日志框架的支持。                                                                |
+| [Logger](https://github.com/hertz-contrib/logger)                                                  | Hertz 的日志拓展，提供了对 zap、logrus、zerologs 日志框架的支持。                                              |
 
 ## 相关文章
 - [字节跳动在 Go 网络库上的实践](https://www.cloudwego.io/blog/2021/10/09/bytedance-practices-on-go-network-library/)
+- [超大规模的企业级微服务 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/)
 ## 贡献代码
   [Contributing](https://github.com/cloudwego/hertz/blob/main/CONTRIBUTING.md)
 ## RoadMap
diff --git a/cmd/hz/go.sum b/cmd/hz/go.sum
index 40a7ff2..7838c6a 100644
--- a/cmd/hz/go.sum
+++ b/cmd/hz/go.sum
@@ -90,6 +90,7 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
 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=
 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135 h1:5Beo0mZN8dRzgrMMkDp0jc8YXQKx9DiJ2k1dkvGsn5A=
 golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
 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=
diff --git a/cmd/hz/internal/generator/handler.go b/cmd/hz/internal/generator/handler.go
index b0a2005..1b8883b 100644
--- a/cmd/hz/internal/generator/handler.go
+++ b/cmd/hz/internal/generator/handler.go
@@ -21,7 +21,6 @@ import (
 	"fmt"
 	"io/ioutil"
 	"path/filepath"
-	"regexp"
 	"strings"
 
 	"github.com/cloudwego/hertz/cmd/hz/internal/generator/model"
@@ -111,23 +110,16 @@ func (pkgGen *HttpPackageGenerator) updateHandler(handler interface{}, handlerTp
 		return err
 	}
 
-	hertzImport := regexp.MustCompile(`import \(\n`)
 	// insert new imports
 	for alias, model := range handler.(Handler).Imports {
 		if bytes.Contains(file, []byte(model.Package)) {
 			continue
 		}
-
-		subIndexImport := hertzImport.FindSubmatchIndex(file)
-		if len(subIndexImport) != 2 || subIndexImport[0] < 1 {
-			return fmt.Errorf("\"import (\" not found in %s", string(file))
+		newFile, err := util.AddImport(filePath, alias, model.Package)
+		if err != nil {
+			return err
 		}
-
-		buf := bytes.NewBuffer(nil)
-		buf.Write(file[:subIndexImport[1]])
-		buf.WriteString("\n\t" + fmt.Sprintf("%s \"%s\"\n", alias, model.Package))
-		buf.Write(file[subIndexImport[1]:])
-		file = buf.Bytes()
+		file = []byte(newFile)
 	}
 
 	// insert new handler
diff --git a/cmd/hz/internal/generator/router.go b/cmd/hz/internal/generator/router.go
index 25d00e1..3081b1e 100644
--- a/cmd/hz/internal/generator/router.go
+++ b/cmd/hz/internal/generator/router.go
@@ -231,20 +231,12 @@ func (pkgGen *HttpPackageGenerator) updateRegister(pkg, rDir, pkgName string) er
 		return fmt.Errorf("read register '%s' failed, err: %v", registerPath, err.Error())
 	}
 
-	insertImport := register.PkgAlias + " " + "\"" + register.Pkg + "\"\n"
 	if !bytes.Contains(file, []byte(register.Pkg)) {
-
-		subIndexImport := regImport.FindSubmatchIndex(file)
-		if len(subIndexImport) != 2 || subIndexImport[0] < 1 {
-			return fmt.Errorf("wrong format %s: 'import (' not found", string(file))
+		newFile, err := util.AddImport(registerPath, register.PkgAlias, register.Pkg)
+		if err != nil {
+			return err
 		}
-
-		bufImport := bytes.NewBuffer(nil)
-		bufImport.Write(file[:subIndexImport[1]])
-		bufImport.WriteString("\n\t" + insertImport)
-		bufImport.Write(file[subIndexImport[1]:])
-
-		file = bufImport.Bytes()
+		file = []byte(newFile)
 
 		insertReg := register.PkgAlias + ".Register(r)\n"
 		if bytes.Contains(file, []byte(insertReg)) {
diff --git a/cmd/hz/internal/generator/template.go b/cmd/hz/internal/generator/template.go
index b0c5c46..ed792cf 100644
--- a/cmd/hz/internal/generator/template.go
+++ b/cmd/hz/internal/generator/template.go
@@ -208,13 +208,21 @@ func (tg *TemplateGenerator) Persist() error {
 				return fmt.Errorf("mkdir %s failed, err: %v", abDir, err.Error())
 			}
 		}
-		file, err := os.OpenFile(abPath, os.O_CREATE|os.O_TRUNC|os.O_RDWR, os.FileMode(0o755))
-		defer file.Close()
+
+		err = func() error {
+			file, err := os.OpenFile(abPath, os.O_CREATE|os.O_TRUNC|os.O_RDWR, os.FileMode(0o755))
+			defer file.Close()
+			if err != nil {
+				return fmt.Errorf("open file '%s' failed, err: %v", abPath, err.Error())
+			}
+			if _, err = file.WriteString(data.Content); err != nil {
+				return fmt.Errorf("write file '%s' failed, err: %v", abPath, err.Error())
+			}
+
+			return nil
+		}()
 		if err != nil {
-			return fmt.Errorf("open file '%s' failed, err: %v", abPath, err.Error())
-		}
-		if _, err = file.WriteString(data.Content); err != nil {
-			return fmt.Errorf("write file '%s' failed, err: %v", abPath, err.Error())
+			return err
 		}
 	}
 
diff --git a/cmd/hz/internal/protobuf/tags.go b/cmd/hz/internal/protobuf/tags.go
index c2b2b48..148689c 100644
--- a/cmd/hz/internal/protobuf/tags.go
+++ b/cmd/hz/internal/protobuf/tags.go
@@ -258,7 +258,7 @@ func m2s(mt model.Tag) (ret [2]string) {
 
 func reflectJsonTag(f protoreflect.FieldDescriptor) (ret model.Tag) {
 	ret.Key = "json"
-	ret.Value = checkSnakeName(f.JSONName())
+	ret.Value = checkSnakeName(f.Name())
 	if v := checkFirstOption(api.E_Body, f.Options()); v != nil {
 		ret.Value += ",string"
 	}
diff --git a/cmd/hz/internal/util/ast.go b/cmd/hz/internal/util/ast.go
new file mode 100644
index 0000000..9c2350e
--- /dev/null
+++ b/cmd/hz/internal/util/ast.go
@@ -0,0 +1,49 @@
+/*
+ * 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 util
+
+import (
+	"bytes"
+	"fmt"
+	"go/format"
+	"go/parser"
+	"go/token"
+	"path/filepath"
+
+	"golang.org/x/tools/go/ast/astutil"
+)
+
+func AddImport(file, alias, impt string) (string, error) {
+	fset := token.NewFileSet()
+	path, _ := filepath.Abs(file)
+	f, err := parser.ParseFile(fset, path, nil, parser.ParseComments)
+	if err != nil {
+		return "", fmt.Errorf("can not parse ast for file: %s", path)
+	}
+	added := astutil.AddNamedImport(fset, f, alias, impt)
+	if !added {
+		return "", fmt.Errorf("can not add import \"%s\" for file: %s", impt, path)
+	}
+	var output []byte
+	buffer := bytes.NewBuffer(output)
+	err = format.Node(buffer, fset, f)
+	if err != nil {
+		return "", fmt.Errorf("can add import for file: %s", path)
+	}
+
+	return buffer.String(), nil
+}
diff --git a/go.mod b/go.mod
index f5be276..450f1f6 100644
--- a/go.mod
+++ b/go.mod
@@ -6,7 +6,7 @@ require (
 	github.com/bytedance/go-tagexpr/v2 v2.9.2
 	github.com/bytedance/gopkg v0.0.0-20220413063733-65bf48ffb3a7
 	github.com/bytedance/sonic v1.5.0
-	github.com/cloudwego/netpoll v0.2.6
+	github.com/cloudwego/netpoll v0.3.1
 	github.com/fsnotify/fsnotify v1.5.4
 	github.com/tidwall/gjson v1.13.0 // indirect
 	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
diff --git a/go.sum b/go.sum
index 0d17981..f9885f8 100644
--- a/go.sum
+++ b/go.sum
@@ -6,8 +6,8 @@ github.com/bytedance/sonic v1.5.0 h1:XWdTi8bwPgxIML+eNV1IwNuTROK6EUrQ65ey8yd6fRQ
 github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
 github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06 h1:1sDoSuDPWzhkdzNVxCxtIaKiAe96ESVPv8coGwc1gZ4=
 github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
-github.com/cloudwego/netpoll v0.2.6 h1:vzN8cyayoa9RdCOG87tqkYO/j2hA4SMLC+vkcNUq6uI=
-github.com/cloudwego/netpoll v0.2.6/go.mod h1:1T2WVuQ+MQw6h6DpE45MohSvDTKdy2DlzCx2KsnPI4E=
+github.com/cloudwego/netpoll v0.3.1 h1:xByoORmCLIyKZ8gS+da06WDo3j+jvmhaqS2KeKejtBk=
+github.com/cloudwego/netpoll v0.3.1/go.mod h1:1T2WVuQ+MQw6h6DpE45MohSvDTKdy2DlzCx2KsnPI4E=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
diff --git a/pkg/app/client/option.go b/pkg/app/client/option.go
index 343ce62..8a64ed8 100644
--- a/pkg/app/client/option.go
+++ b/pkg/app/client/option.go
@@ -140,3 +140,10 @@ func WithRetryConfig(opts ...retry.Option) config.ClientOption {
 		o.RetryConfig = retryCfg
 	}}
 }
+
+// WithWriteTimeout sets write timeout.
+func WithWriteTimeout(t time.Duration) config.ClientOption {
+	return config.ClientOption{F: func(o *config.ClientOptions) {
+		o.WriteTimeout = t
+	}}
+}
diff --git a/pkg/app/server/option.go b/pkg/app/server/option.go
index 9447734..4a4a8c6 100644
--- a/pkg/app/server/option.go
+++ b/pkg/app/server/option.go
@@ -47,6 +47,15 @@ func WithReadTimeout(t time.Duration) config.Option {
 	}}
 }
 
+// WithWriteTimeout sets write timeout.
+//
+// Connection will be closed when write request timeout.
+func WithWriteTimeout(t time.Duration) config.Option {
+	return config.Option{F: func(o *config.Options) {
+		o.WriteTimeout = t
+	}}
+}
+
 // WithIdleTimeout sets idle timeout.
 //
 // Close the connection when the successive request timeout (in keepalive mode).
diff --git a/pkg/common/config/option.go b/pkg/common/config/option.go
index 15037f0..3b24f82 100644
--- a/pkg/common/config/option.go
+++ b/pkg/common/config/option.go
@@ -43,6 +43,7 @@ const (
 type Options struct {
 	KeepAliveTimeout             time.Duration
 	ReadTimeout                  time.Duration
+	WriteTimeout                 time.Duration
 	IdleTimeout                  time.Duration
 	RedirectTrailingSlash        bool
 	MaxRequestBodySize           int
diff --git a/pkg/common/config/request_option.go b/pkg/common/config/request_option.go
index 8d4bc5a..3737ac5 100644
--- a/pkg/common/config/request_option.go
+++ b/pkg/common/config/request_option.go
@@ -16,11 +16,17 @@
 
 package config
 
+import "time"
+
 var preDefinedOpts []RequestOption
 
 type RequestOptions struct {
 	tags map[string]string
 	isSD bool
+
+	dialTimeout  time.Duration
+	readTimeout  time.Duration
+	writeTimeout time.Duration
 }
 
 // RequestOption is the only struct to set request-level options.
@@ -55,6 +61,40 @@ func WithSD(b bool) RequestOption {
 	}}
 }
 
+// WithDialTimeout sets dial timeout.
+//
+// This is the request level configuration. It has a higher
+// priority than the client level configuration
+// Note: it won't take effect in the case of the number of
+// connections in the connection pool exceeds the maximum
+// number of connections and needs to establish a connection
+// while waiting.
+func WithDialTimeout(t time.Duration) RequestOption {
+	return RequestOption{F: func(o *RequestOptions) {
+		o.dialTimeout = t
+	}}
+}
+
+// WithReadTimeout sets read timeout.
+//
+// This is the request level configuration. It has a higher
+// priority than the client level configuration
+func WithReadTimeout(t time.Duration) RequestOption {
+	return RequestOption{F: func(o *RequestOptions) {
+		o.readTimeout = t
+	}}
+}
+
+// WithWriteTimeout sets write timeout.
+//
+// This is the request level configuration. It has a higher
+// priority than the client level configuration
+func WithWriteTimeout(t time.Duration) RequestOption {
+	return RequestOption{F: func(o *RequestOptions) {
+		o.writeTimeout = t
+	}}
+}
+
 func (o *RequestOptions) Apply(opts []RequestOption) {
 	for _, op := range opts {
 		op.F(o)
@@ -73,6 +113,18 @@ func (o *RequestOptions) IsSD() bool {
 	return o.isSD
 }
 
+func (o *RequestOptions) DialTimeout() time.Duration {
+	return o.dialTimeout
+}
+
+func (o *RequestOptions) ReadTimeout() time.Duration {
+	return o.readTimeout
+}
+
+func (o *RequestOptions) WriteTimeout() time.Duration {
+	return o.writeTimeout
+}
+
 func (o *RequestOptions) CopyTo(dst *RequestOptions) {
 	if dst.tags == nil {
 		dst.tags = make(map[string]string)
@@ -83,6 +135,9 @@ func (o *RequestOptions) CopyTo(dst *RequestOptions) {
 	}
 
 	dst.isSD = o.isSD
+	dst.readTimeout = o.readTimeout
+	dst.writeTimeout = o.writeTimeout
+	dst.dialTimeout = o.dialTimeout
 }
 
 // SetPreDefinedOpts Pre define some RequestOption here
diff --git a/pkg/common/errors/errors.go b/pkg/common/errors/errors.go
index dd247be..dc3f4bb 100644
--- a/pkg/common/errors/errors.go
+++ b/pkg/common/errors/errors.go
@@ -55,6 +55,9 @@ var (
 	ErrHijacked        = errors.New("connection has been hijacked")
 	ErrIdleTimeout     = errors.New("idle timeout")
 	ErrTimeout         = errors.New("timeout")
+	ErrReadTimeout     = errors.New("read timeout")
+	ErrWriteTimeout    = errors.New("write timeout")
+	ErrDialTimeout     = errors.New("dial timeout")
 	ErrNothingRead     = errors.New("nothing read")
 	ErrShortConnection = errors.New("short connection")
 )
diff --git a/pkg/common/hlog/system.go b/pkg/common/hlog/system.go
index e931517..cb8a145 100644
--- a/pkg/common/hlog/system.go
+++ b/pkg/common/hlog/system.go
@@ -41,30 +41,37 @@ func (ll *systemLogger) SetLevel(lv Level) {
 }
 
 func (ll *systemLogger) Fatal(v ...interface{}) {
+	v = append([]interface{}{ll.prefix}, v...)
 	ll.logger.Fatal(v...)
 }
 
 func (ll *systemLogger) Error(v ...interface{}) {
+	v = append([]interface{}{ll.prefix}, v...)
 	ll.logger.Error(v...)
 }
 
 func (ll *systemLogger) Warn(v ...interface{}) {
+	v = append([]interface{}{ll.prefix}, v...)
 	ll.logger.Warn(v...)
 }
 
 func (ll *systemLogger) Notice(v ...interface{}) {
+	v = append([]interface{}{ll.prefix}, v...)
 	ll.logger.Notice(v...)
 }
 
 func (ll *systemLogger) Info(v ...interface{}) {
+	v = append([]interface{}{ll.prefix}, v...)
 	ll.logger.Info(v...)
 }
 
 func (ll *systemLogger) Debug(v ...interface{}) {
+	v = append([]interface{}{ll.prefix}, v...)
 	ll.logger.Debug(v...)
 }
 
 func (ll *systemLogger) Trace(v ...interface{}) {
+	v = append([]interface{}{ll.prefix}, v...)
 	ll.logger.Trace(v...)
 }
 
diff --git a/pkg/network/connection.go b/pkg/network/connection.go
index 9a5e358..c963f2b 100644
--- a/pkg/network/connection.go
+++ b/pkg/network/connection.go
@@ -83,6 +83,7 @@ type Conn interface {
 
 	// SetReadTimeout should work for every Read process
 	SetReadTimeout(t time.Duration) error
+	SetWriteTimeout(t time.Duration) error
 }
 
 type ConnTLSer interface {
diff --git a/pkg/network/netpoll/transport.go b/pkg/network/netpoll/transport.go
index 844445e..1ea41f4 100644
--- a/pkg/network/netpoll/transport.go
+++ b/pkg/network/netpoll/transport.go
@@ -37,6 +37,7 @@ type transporter struct {
 	addr             string
 	keepAliveTimeout time.Duration
 	readTimeout      time.Duration
+	writeTimeout     time.Duration
 	listener         net.Listener
 	eventLoop        netpoll.EventLoop
 	listenConfig     *net.ListenConfig
@@ -49,6 +50,7 @@ func NewTransporter(options *config.Options) network.Transporter {
 		addr:             options.Addr,
 		keepAliveTimeout: options.KeepAliveTimeout,
 		readTimeout:      options.ReadTimeout,
+		writeTimeout:     options.WriteTimeout,
 		listener:         nil,
 		eventLoop:        nil,
 		listenConfig:     options.ListenConfig,
@@ -74,6 +76,9 @@ func (t *transporter) ListenAndServe(onReq network.OnData) (err error) {
 		netpoll.WithIdleTimeout(t.keepAliveTimeout),
 		netpoll.WithOnPrepare(func(conn netpoll.Connection) context.Context {
 			conn.SetReadTimeout(t.readTimeout) // nolint:errcheck
+			if t.writeTimeout > 0 {
+				conn.SetWriteTimeout(t.writeTimeout)
+			}
 			return context.Background()
 		}),
 	}
diff --git a/pkg/network/standard/connection.go b/pkg/network/standard/connection.go
index d15f060..b1d96e0 100644
--- a/pkg/network/standard/connection.go
+++ b/pkg/network/standard/connection.go
@@ -44,6 +44,13 @@ type Conn struct {
 	maxSize      int      // history max malloc size
 }
 
+func (c *Conn) SetWriteTimeout(t time.Duration) error {
+	if t <= 0 {
+		return c.c.SetWriteDeadline(time.Time{})
+	}
+	return c.c.SetWriteDeadline(time.Now().Add(t))
+}
+
 func (c *Conn) SetReadTimeout(t time.Duration) error {
 	if t <= 0 {
 		return c.c.SetReadDeadline(time.Time{})
diff --git a/pkg/protocol/http1/client.go b/pkg/protocol/http1/client.go
index cb12bc7..a1f18cb 100644
--- a/pkg/protocol/http1/client.go
+++ b/pkg/protocol/http1/client.go
@@ -57,6 +57,7 @@ import (
 	"github.com/cloudwego/hertz/internal/bytestr"
 	"github.com/cloudwego/hertz/internal/nocopy"
 	"github.com/cloudwego/hertz/pkg/app/client/retry"
+	"github.com/cloudwego/hertz/pkg/common/config"
 	errs "github.com/cloudwego/hertz/pkg/common/errors"
 	"github.com/cloudwego/hertz/pkg/common/hlog"
 	"github.com/cloudwego/hertz/pkg/common/timer"
@@ -408,6 +409,33 @@ 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,
+	}
+	if o.ReadTimeout() > 0 {
+		rc.readTimeout = o.ReadTimeout()
+	}
+
+	if o.WriteTimeout() > 0 {
+		rc.writeTimeout = o.WriteTimeout()
+	}
+
+	if o.DialTimeout() > 0 {
+		rc.dialTimeout = o.DialTimeout()
+	}
+
+	return rc
+}
+
 func (c *HostClient) doNonNilReqResp(req *protocol.Request, resp *protocol.Response) (bool, error) {
 	if req == nil {
 		panic("BUG: req cannot be nil")
@@ -418,9 +446,10 @@ 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
 	customSkipBody := resp.SkipBody
 	resp.Reset()
@@ -429,7 +458,7 @@ func (c *HostClient) doNonNilReqResp(req *protocol.Request, resp *protocol.Respo
 	if c.DisablePathNormalizing {
 		req.URI().DisablePathNormalizing = true
 	}
-	cc, err := c.acquireConn()
+	cc, err := c.acquireConn(rc.dialTimeout)
 	// if getting connection error, fast fail
 	if err != nil {
 		return false, err
@@ -445,11 +474,8 @@ func (c *HostClient) doNonNilReqResp(req *protocol.Request, resp *protocol.Respo
 
 	resp.ParseNetAddr(conn)
 
-	if c.WriteTimeout > 0 {
-		// Set Deadline every time, since golang has fixed the performance issue
-		// See https://github.com/golang/go/issues/15133#issuecomment-271571395 for details
-		currentTime := time.Now()
-		if err = conn.SetWriteDeadline(currentTime.Add(c.WriteTimeout)); err != nil {
+	if rc.writeTimeout > 0 {
+		if err = conn.SetWriteTimeout(rc.writeTimeout); err != nil {
 			c.closeConn(cc)
 			// try another connection if retry is enabled
 			return true, err
@@ -486,10 +512,10 @@ func (c *HostClient) doNonNilReqResp(req *protocol.Request, resp *protocol.Respo
 		return true, err
 	}
 
-	if c.ReadTimeout > 0 {
+	if rc.readTimeout > 0 {
 		// 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(c.ReadTimeout); err != nil {
+		if err = conn.SetReadTimeout(rc.readTimeout); err != nil {
 			c.closeConn(cc)
 			// try another connection if retry is enabled
 			return true, err
@@ -543,7 +569,7 @@ func (c *HostClient) SetMaxConns(newMaxConns int) {
 	c.connsLock.Unlock()
 }
 
-func (c *HostClient) acquireConn() (cc *clientConn, err error) {
+func (c *HostClient) acquireConn(dialTimeout time.Duration) (cc *clientConn, err error) {
 	createConn := false
 	startCleaner := false
 
@@ -594,6 +620,11 @@ func (c *HostClient) acquireConn() (cc *clientConn, err error) {
 			}
 		}()
 
+		// Note: In the case of setting MaxConnWaitTimeout, if the number
+		// of connections in the connection pool exceeds the maximum
+		// number of connections and needs to establish a connection while
+		// waiting, the dialtimeout on the hostclient is used instead of
+		// the dialtimeout in request options.
 		c.queueForIdle(w)
 
 		select {
@@ -608,7 +639,7 @@ func (c *HostClient) acquireConn() (cc *clientConn, err error) {
 		go c.connsCleaner()
 	}
 
-	conn, err := c.dialHostHard()
+	conn, err := c.dialHostHard(dialTimeout)
 	if err != nil {
 		c.decConnsCount()
 		return nil, err
@@ -629,7 +660,7 @@ func (c *HostClient) queueForIdle(w *wantConn) {
 }
 
 func (c *HostClient) dialConnFor(w *wantConn) {
-	conn, err := c.dialHostHard()
+	conn, err := c.dialHostHard(c.DialTimeout)
 	if err != nil {
 		w.tryDeliver(nil, err)
 		c.decConnsCount()
@@ -858,7 +889,7 @@ func (c *HostClient) nextAddr() string {
 	return addr
 }
 
-func (c *HostClient) dialHostHard() (conn network.Conn, err error) {
+func (c *HostClient) dialHostHard(dialTimeout time.Duration) (conn network.Conn, err error) {
 	// attempt to dial all the available hosts before giving up.
 
 	c.addrsLock.Lock()
@@ -870,12 +901,11 @@ func (c *HostClient) dialHostHard() (conn network.Conn, err error) {
 		n = 1
 	}
 
-	timeout := c.DialTimeout
-	deadline := time.Now().Add(timeout)
+	deadline := time.Now().Add(dialTimeout)
 	for n > 0 {
 		addr := c.nextAddr()
 		tlsConfig := c.cachedTLSConfig(addr)
-		conn, err = dialAddr(addr, c.Dialer, c.DialDualStack, tlsConfig, timeout, c.ProxyURI, c.IsTLS)
+		conn, err = dialAddr(addr, c.Dialer, c.DialDualStack, tlsConfig, dialTimeout, c.ProxyURI, c.IsTLS)
 		if err == nil {
 			return conn, nil
 		}
@@ -919,7 +949,7 @@ func dialAddr(addr string, dial network.Dialer, dialDualStack bool, tlsConfig *t
 	var conn network.Conn
 	var err error
 	if dial == nil {
-		hlog.SystemLogger().Warnf("HostClient: no dialer specified, trying to use default dialer")
+		hlog.SystemLogger().Warn("HostClient: no dialer specified, trying to use default dialer")
 		dial = dialer.DefaultDialer()
 	}
 	dialFunc := dial.DialConnection
diff --git a/pkg/protocol/http1/server.go b/pkg/protocol/http1/server.go
index b4e9b4e..8df3320 100644
--- a/pkg/protocol/http1/server.go
+++ b/pkg/protocol/http1/server.go
@@ -330,7 +330,8 @@ func (s Server) Serve(c context.Context, conn network.Conn) (err error) {
 		if connectionClose {
 			return errShortConnection
 		}
-
+		// Back to network layer to trigger.
+		// For now, only netpoll network mode has this feature.
 		if s.IdleTimeout == 0 {
 			return
 		}
diff --git a/pkg/route/engine.go b/pkg/route/engine.go
index 8a272a8..0096eb5 100644
--- a/pkg/route/engine.go
+++ b/pkg/route/engine.go
@@ -353,10 +353,6 @@ func (engine *Engine) Run() (err error) {
 }
 
 func (engine *Engine) Init() error {
-	if !h2Enable(engine.options) {
-		engine.protocolSuite.Delete(suite.HTTP2)
-	}
-
 	// add built-in http1 server by default
 	if !engine.HasServer(suite.HTTP1) {
 		engine.AddProtocol(suite.HTTP1, factory.NewServerFactory(newHttp1OptionFromEngine(engine)))
@@ -371,7 +367,6 @@ func (engine *Engine) Init() error {
 
 	if engine.alpnEnable() {
 		engine.options.TLS.NextProtos = append(engine.options.TLS.NextProtos, suite.HTTP1)
-		engine.options.TLS.NextProtos = append(engine.options.TLS.NextProtos, suite.HTTP2)
 	}
 
 	if !atomic.CompareAndSwapUint32(&engine.status, 0, statusInitialized) {
@@ -499,7 +494,7 @@ func (engine *Engine) Serve(c context.Context, conn network.Conn) (err error) {
 		if bytes.Equal(buf, bytestr.StrClientPreface) && engine.protocolServers[suite.HTTP2] != nil {
 			return engine.protocolServers[suite.HTTP2].Serve(c, conn)
 		}
-		hlog.SystemLogger().Warnf("HTTP2 server is not loaded, request is going to fallback to HTTP1 server")
+		hlog.SystemLogger().Warn("HTTP2 server is not loaded, request is going to fallback to HTTP1 server")
 	}
 
 	// ALPN path
@@ -584,10 +579,6 @@ func initTrace(engine *Engine) stats.Level {
 	return traceLevel
 }
 
-func h2Enable(opt *config.Options) bool {
-	return opt.H2C || (opt.TLS != nil && opt.ALPN)
-}
-
 func debugPrintRoute(httpMethod, absolutePath string, handlers app.HandlersChain) {
 	nuHandlers := len(handlers)
 	handlerName := app.GetHandlerName(handlers.Last())
@@ -954,7 +945,7 @@ func iterate(method string, routes RoutesInfo, root *node) RoutesInfo {
 
 // for built-in http1 impl only.
 func newHttp1OptionFromEngine(engine *Engine) *http1.Option {
-	return &http1.Option{
+	opt := &http1.Option{
 		StreamRequestBody:            engine.options.StreamRequestBody,
 		GetOnly:                      engine.options.GetOnly,
 		DisablePreParseMultipartForm: engine.options.DisablePreParseMultipartForm,
@@ -970,4 +961,10 @@ func newHttp1OptionFromEngine(engine *Engine) *http1.Option {
 		EnableTrace:                  engine.IsTraceEnable(),
 		HijackConnHandle:             engine.HijackConnHandle,
 	}
+	// Idle timeout of standard network must not be zero. Set it to -1 seconds if it is zero.
+	// Due to the different triggering ways of the network library, see the actual use of this value for the detailed reasons.
+	if opt.IdleTimeout == 0 && engine.GetTransporterName() == "standard" {
+		opt.IdleTimeout = -1
+	}
+	return opt
 }


Test patch:
diff --git a/cmd/hz/internal/util/ast_test.go b/cmd/hz/internal/util/ast_test.go
new file mode 100644
index 0000000..8a7f54d
--- /dev/null
+++ b/cmd/hz/internal/util/ast_test.go
@@ -0,0 +1,93 @@
+/*
+ * 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 util
+
+import (
+	"bytes"
+	"go/format"
+	"go/parser"
+	"go/token"
+	"testing"
+
+	"golang.org/x/tools/go/ast/astutil"
+)
+
+func TestAddImport(t *testing.T) {
+	inserts := [][]string{
+		{
+			"ctx",
+			"context",
+		},
+		{
+			"",
+			"context",
+		},
+	}
+	files := [][]string{
+		{
+			`package foo
+
+import (
+	"fmt"
+	"time"
+)
+`,
+			`package foo
+
+import (
+	ctx "context"
+	"fmt"
+	"time"
+)
+`,
+		},
+		{
+			`package foo
+
+import (
+	"fmt"
+	"time"
+)
+`,
+			`package foo
+
+import (
+	"context"
+	"fmt"
+	"time"
+)
+`,
+		},
+	}
+	for idx, file := range files {
+		fset := token.NewFileSet()
+		f, err := parser.ParseFile(fset, "", file[0], parser.ImportsOnly)
+		if err != nil {
+			t.Fatalf("can not parse ast for file")
+		}
+		astutil.AddNamedImport(fset, f, inserts[idx][0], inserts[idx][1])
+		var output []byte
+		buffer := bytes.NewBuffer(output)
+		err = format.Node(buffer, fset, f)
+		if err != nil {
+			t.Fatalf("can add import for file")
+		}
+		if buffer.String() != file[1] {
+			t.Fatalf("insert import fialed")
+		}
+	}
+}
diff --git a/pkg/app/client/option_test.go b/pkg/app/client/option_test.go
index 149d6a7..39ac268 100644
--- a/pkg/app/client/option_test.go
+++ b/pkg/app/client/option_test.go
@@ -43,6 +43,7 @@ func TestClientOptions(t *testing.T) {
 			retry.WithMaxJitter(1*time.Second),
 			retry.WithDelayPolicy(retry.CombineDelay(retry.FixedDelayPolicy, retry.BackOffDelayPolicy, retry.RandomDelayPolicy)),
 		),
+		WithWriteTimeout(time.Second),
 	})
 	assert.DeepEqual(t, 100*time.Millisecond, opt.DialTimeout)
 	assert.DeepEqual(t, 128, opt.MaxConnsPerHost)
@@ -51,6 +52,7 @@ func TestClientOptions(t *testing.T) {
 	assert.DeepEqual(t, 5*time.Second, opt.MaxConnWaitTimeout)
 	assert.DeepEqual(t, false, opt.KeepAlive)
 	assert.DeepEqual(t, 1*time.Second, opt.ReadTimeout)
+	assert.DeepEqual(t, 1*time.Second, opt.WriteTimeout)
 	assert.DeepEqual(t, true, opt.ResponseBodyStream)
 	assert.DeepEqual(t, uint(2), opt.RetryConfig.MaxAttemptTimes)
 	assert.DeepEqual(t, 100*time.Millisecond, opt.RetryConfig.Delay)
diff --git a/pkg/app/server/option_test.go b/pkg/app/server/option_test.go
index c68e2c3..5c23fec 100644
--- a/pkg/app/server/option_test.go
+++ b/pkg/app/server/option_test.go
@@ -35,6 +35,7 @@ func TestOptions(t *testing.T) {
 	}
 	opt := config.NewOptions([]config.Option{
 		WithReadTimeout(time.Second),
+		WithWriteTimeout(time.Second),
 		WithIdleTimeout(time.Second),
 		WithKeepAliveTimeout(time.Second),
 		WithRedirectTrailingSlash(false),
@@ -62,6 +63,7 @@ func TestOptions(t *testing.T) {
 		WithAutoReloadRender(true, 5*time.Second),
 	})
 	assert.DeepEqual(t, opt.ReadTimeout, time.Second)
+	assert.DeepEqual(t, opt.WriteTimeout, time.Second)
 	assert.DeepEqual(t, opt.IdleTimeout, time.Second)
 	assert.DeepEqual(t, opt.KeepAliveTimeout, time.Second)
 	assert.DeepEqual(t, opt.RedirectTrailingSlash, false)
diff --git a/pkg/common/config/option_test.go b/pkg/common/config/option_test.go
index 71eeae7..49ea661 100644
--- a/pkg/common/config/option_test.go
+++ b/pkg/common/config/option_test.go
@@ -18,6 +18,7 @@ package config
 
 import (
 	"testing"
+	"time"
 
 	"github.com/cloudwego/hertz/pkg/app/server/registry"
 	"github.com/cloudwego/hertz/pkg/common/test/assert"
@@ -30,6 +31,7 @@ func TestDefaultOptions(t *testing.T) {
 	assert.DeepEqual(t, defaultKeepAliveTimeout, options.KeepAliveTimeout)
 	assert.DeepEqual(t, defaultReadTimeout, options.ReadTimeout)
 	assert.DeepEqual(t, defaultReadTimeout, options.IdleTimeout)
+	assert.DeepEqual(t, time.Duration(0), options.WriteTimeout)
 	assert.True(t, options.RedirectTrailingSlash)
 	assert.True(t, options.RedirectTrailingSlash)
 	assert.False(t, options.HandleMethodNotAllowed)
diff --git a/pkg/common/config/request_option_test.go b/pkg/common/config/request_option_test.go
index c044c43..8041f58 100644
--- a/pkg/common/config/request_option_test.go
+++ b/pkg/common/config/request_option_test.go
@@ -18,6 +18,7 @@ package config
 
 import (
 	"testing"
+	"time"
 
 	"github.com/cloudwego/hertz/pkg/common/test/assert"
 )
@@ -29,10 +30,16 @@ func TestRequestOptions(t *testing.T) {
 		WithTag("c", "d"),
 		WithTag("e", "f"),
 		WithSD(true),
+		WithDialTimeout(time.Second),
+		WithReadTimeout(time.Second),
+		WithWriteTimeout(time.Second),
 	})
 	assert.DeepEqual(t, "b", opt.Tag("a"))
 	assert.DeepEqual(t, "d", opt.Tag("c"))
 	assert.DeepEqual(t, "f", opt.Tag("e"))
+	assert.DeepEqual(t, time.Second, opt.DialTimeout())
+	assert.DeepEqual(t, time.Second, opt.ReadTimeout())
+	assert.DeepEqual(t, time.Second, opt.WriteTimeout())
 	assert.True(t, opt.IsSD())
 }
 
@@ -52,6 +59,9 @@ func TestRequestOptionsWithDefaultOpts(t *testing.T) {
 	assert.False(t, opt.IsSD())
 	SetPreDefinedOpts()
 	assert.Nil(t, preDefinedOpts)
+	assert.DeepEqual(t, time.Duration(0), opt.WriteTimeout())
+	assert.DeepEqual(t, time.Duration(0), opt.ReadTimeout())
+	assert.DeepEqual(t, time.Duration(0), opt.DialTimeout())
 }
 
 // TestRequestOptions_CopyTo test request options copy to another one
diff --git a/pkg/common/hlog/system_test.go b/pkg/common/hlog/system_test.go
index 59c5019..b48e96d 100644
--- a/pkg/common/hlog/system_test.go
+++ b/pkg/common/hlog/system_test.go
@@ -47,12 +47,12 @@ func TestSysLogger(t *testing.T) {
 	sysLogger.Warn("work may fail")
 	sysLogger.Error("work failed")
 
-	assert.DeepEqual(t, "[Trace] trace work\n"+
-		"[Debug] received work order\n"+
-		"[Info] starting work\n"+
-		"[Notice] something happens in work\n"+
-		"[Warn] work may fail\n"+
-		"[Error] work failed\n", string(w.b))
+	assert.DeepEqual(t, "[Trace] HERTZ: trace work\n"+
+		"[Debug] HERTZ: received work order\n"+
+		"[Info] HERTZ: starting work\n"+
+		"[Notice] HERTZ: something happens in work\n"+
+		"[Warn] HERTZ: work may fail\n"+
+		"[Error] HERTZ: work failed\n", string(w.b))
 }
 
 func TestSysFormatLogger(t *testing.T) {
diff --git a/pkg/common/test/mock/network.go b/pkg/common/test/mock/network.go
index 29777ad..bb08cb4 100644
--- a/pkg/common/test/mock/network.go
+++ b/pkg/common/test/mock/network.go
@@ -34,14 +34,28 @@ type Conn struct {
 	zw          network.ReadWriter
 }
 
+func (m *Conn) SetWriteTimeout(t time.Duration) error {
+	// TODO implement me
+	panic("implement me")
+}
+
 type SlowReadConn struct {
 	*Conn
 }
 
+func (m *SlowReadConn) SetWriteTimeout(t time.Duration) error {
+	// TODO implement me
+	panic("implement me")
+}
+
 func SlowReadDialer(addr string) (network.Conn, error) {
 	return NewSlowReadConn(""), nil
 }
 
+func SlowWriteDialer(addr string) (network.Conn, error) {
+	return NewSlowWriteConn(""), nil
+}
+
 func (m *Conn) ReadBinary(n int) (p []byte, err error) {
 	return m.zr.(netpoll.Reader).ReadBinary(n)
 }
@@ -61,6 +75,10 @@ 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 {
+			// simulate timeout forever
+			select {}
+		}
 		time.Sleep(m.readTimeout)
 		return nil, errs.ErrTimeout
 	}
@@ -100,7 +118,7 @@ func (m *SlowReadConn) Peek(i int) ([]byte, error) {
 	time.Sleep(100 * time.Millisecond)
 	if err != nil || len(b) != i {
 		time.Sleep(m.readTimeout)
-		return nil, errs.ErrTimeout
+		return nil, errs.ErrReadTimeout
 	}
 	return b, err
 }
@@ -119,6 +137,30 @@ func NewSlowReadConn(source string) *SlowReadConn {
 	return &SlowReadConn{NewConn(source)}
 }
 
+type SlowWriteConn struct {
+	*Conn
+	writeTimeout time.Duration
+}
+
+func (m *SlowWriteConn) SetWriteTimeout(t time.Duration) error {
+	m.writeTimeout = t
+	return nil
+}
+
+func NewSlowWriteConn(source string) *SlowWriteConn {
+	return &SlowWriteConn{NewConn(source), 0}
+}
+
+func (m *SlowWriteConn) Flush() error {
+	err := m.zw.Flush()
+	time.Sleep(100 * time.Millisecond)
+	if err == nil {
+		time.Sleep(m.writeTimeout)
+		return errs.ErrWriteTimeout
+	}
+	return err
+}
+
 func (m *Conn) Close() error {
 	return nil
 }
diff --git a/pkg/network/netpoll/connection_test.go b/pkg/network/netpoll/connection_test.go
index 79dc80b..893b328 100644
--- a/pkg/network/netpoll/connection_test.go
+++ b/pkg/network/netpoll/connection_test.go
@@ -85,6 +85,11 @@ type mockConn struct {
 	off int
 }
 
+func (m *mockConn) SetWriteTimeout(timeout time.Duration) error {
+	// TODO implement me
+	panic("implement me")
+}
+
 // mockConn's methods is simplified for unit test
 // Peek returns the next n bytes without advancing the reader
 func (m *mockConn) Peek(n int) (b []byte, err error) {
diff --git a/pkg/protocol/http1/client_test.go b/pkg/protocol/http1/client_test.go
index 299a028..fa6d6c9 100644
--- a/pkg/protocol/http1/client_test.go
+++ b/pkg/protocol/http1/client_test.go
@@ -55,6 +55,9 @@ import (
 	"testing"
 	"time"
 
+	"github.com/cloudwego/hertz/pkg/common/config"
+	"github.com/cloudwego/hertz/pkg/common/test/assert"
+
 	errs "github.com/cloudwego/hertz/pkg/common/errors"
 	"github.com/cloudwego/hertz/pkg/common/test/mock"
 	"github.com/cloudwego/hertz/pkg/network"
@@ -223,3 +226,100 @@ func (m *mockDialer) DialTimeout(network, address string, timeout time.Duration,
 func (m *mockDialer) AddTLS(conn network.Conn, tlsConfig *tls.Config) (network.Conn, error) {
 	return nil, nil
 }
+
+type slowDialer struct {
+	*mockDialer
+}
+
+func (s *slowDialer) DialConnection(network, address string, timeout time.Duration, tlsConfig *tls.Config) (conn network.Conn, err error) {
+	time.Sleep(timeout)
+	return nil, errs.ErrDialTimeout
+}
+
+func TestReadTimeoutPriority(t *testing.T) {
+	c := &HostClient{
+		ClientOptions: &ClientOptions{
+			Dialer: newSlowConnDialer(func(network, addr string) (network.Conn, error) {
+				return mock.SlowReadDialer(addr)
+			}),
+			MaxConns:           1,
+			MaxConnWaitTimeout: 50 * time.Millisecond,
+			ReadTimeout:        time.Second * 3,
+		},
+		Addr: "foobar",
+	}
+
+	req := protocol.AcquireRequest()
+	req.SetRequestURI("http://foobar/baz")
+	req.SetOptions(config.WithReadTimeout(time.Second * 1))
+	resp := protocol.AcquireResponse()
+
+	ch := make(chan error, 1)
+	go func() {
+		ch <- c.Do(context.Background(), req, resp)
+	}()
+	select {
+	case <-time.After(time.Second * 2):
+		t.Fatalf("should use readTimeout in request options")
+	case err := <-ch:
+		assert.DeepEqual(t, errs.ErrTimeout.Error(), err.Error())
+	}
+}
+
+func TestWriteTimeoutPriority(t *testing.T) {
+	c := &HostClient{
+		ClientOptions: &ClientOptions{
+			Dialer: newSlowConnDialer(func(network, addr string) (network.Conn, error) {
+				return mock.SlowWriteDialer(addr)
+			}),
+			MaxConns:           1,
+			MaxConnWaitTimeout: 50 * time.Millisecond,
+			WriteTimeout:       time.Second * 3,
+		},
+		Addr: "foobar",
+	}
+
+	req := protocol.AcquireRequest()
+	req.SetRequestURI("http://foobar/baz")
+	req.SetOptions(config.WithWriteTimeout(time.Second * 1))
+	resp := protocol.AcquireResponse()
+
+	ch := make(chan error, 1)
+	go func() {
+		ch <- c.Do(context.Background(), req, resp)
+	}()
+	select {
+	case <-time.After(time.Second * 2000):
+		t.Fatalf("should use writeTimeout in request options")
+	case err := <-ch:
+		assert.DeepEqual(t, errs.ErrWriteTimeout.Error(), err.Error())
+	}
+}
+
+func TestDialTimeoutPriority(t *testing.T) {
+	c := &HostClient{
+		ClientOptions: &ClientOptions{
+			Dialer:             &slowDialer{},
+			MaxConns:           1,
+			MaxConnWaitTimeout: 50 * time.Millisecond,
+			DialTimeout:        time.Second * 3,
+		},
+		Addr: "foobar",
+	}
+
+	req := protocol.AcquireRequest()
+	req.SetRequestURI("http://foobar/baz")
+	req.SetOptions(config.WithDialTimeout(time.Second * 1))
+	resp := protocol.AcquireResponse()
+
+	ch := make(chan error, 1)
+	go func() {
+		ch <- c.Do(context.Background(), req, resp)
+	}()
+	select {
+	case <-time.After(time.Second * 2000):
+		t.Fatalf("should use dialTimeout in request options")
+	case err := <-ch:
+		assert.DeepEqual(t, errs.ErrDialTimeout.Error(), err.Error())
+	}
+}
diff --git a/pkg/route/engine_test.go b/pkg/route/engine_test.go
index f3007ac..379b53f 100644
--- a/pkg/route/engine_test.go
+++ b/pkg/route/engine_test.go
@@ -257,6 +257,39 @@ func TestIdleTimeout01(t *testing.T) {
 	}
 }
 
+func TestIdleTimeout03(t *testing.T) {
+	engine := NewEngine(config.NewOptions(nil))
+	engine.options.IdleTimeout = 0
+	engine.transport = standard.NewTransporter(engine.options)
+	atomic.StoreUint32(&engine.status, statusRunning)
+	engine.Init()
+	atomic.StoreUint32(&engine.status, statusRunning)
+	engine.GET("/foo", func(c context.Context, ctx *app.RequestContext) {
+		time.Sleep(50 * time.Millisecond)
+		ctx.String(200, "ok")
+	})
+
+	conn := mock.NewConn("GET /foo HTTP/1.1\r\nHost: google.com\r\n\r\n" +
+		"GET /foo HTTP/1.1\r\nHost: google.com\r\nConnection: close\r\n\r\n")
+
+	ch := make(chan error)
+	startCh := make(chan error)
+	go func() {
+		<-startCh
+		ch <- engine.Serve(context.Background(), conn)
+	}()
+	close(startCh)
+	select {
+	case err := <-ch:
+		if !errors.Is(err, errs.ErrShortConnection) {
+			t.Errorf("err should be ErrShortConnection, but got %s", err)
+		}
+		return
+	case <-time.Tick(120 * time.Millisecond):
+		t.Errorf("timeout! should have been finished in 120ms...")
+	}
+}
+
 func TestEngine_Routes(t *testing.T) {
 	engine := NewEngine(config.NewOptions(nil))
 	engine.GET("/", handlerTest1)
@@ -455,6 +488,11 @@ func TestRenderHtmlOfFilesWithAutoRender(t *testing.T) {
 
 type mockConn struct{}
 
+func (m *mockConn) SetWriteTimeout(t time.Duration) error {
+	// TODO implement me
+	panic("implement me")
+}
+
 func (m *mockConn) ReadBinary(n int) (p []byte, err error) {
 	panic("implement me")
 }