Repository: cloudwego/hertz
Pull request #191 (2022-08-11 14:18:00)

chore: update sonic version
#### What type of PR is this?
chore

<!--
Add one of the following kinds:

build: Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)
ci: Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)
docs: Documentation only changes
feat: A new feature
optimize: A new optimization
fix: A bug fix
perf: A code change that improves performance
refactor: A code change that neither fixes a bug nor adds a feature
style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
test: Adding missing tests or correcting existing tests
chore: Changes to the build process or auxiliary tools and libraries such as documentation generation
-->

#### What this PR does / why we need it (English/Chinese):
- en: update sonic version
- ch: 更新 sonic 版本
<!--
The description will be attached in Release Notes, 
so please describe it from user-oriented.
-->

#### Which issue(s) this PR fixes:

<!--
*Automatically closes linked issue when PR is merged.
Eg: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
-->



Patch:
diff --git a/.codecov.yml b/.codecov.yml
new file mode 100644
index 0000000..5cd2fc3
--- /dev/null
+++ b/.codecov.yml
@@ -0,0 +1,4 @@
+ignore:
+  - "images/.*"
+  - "examples/.*"
+  - "cmd/.*"
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index b603978..4641e32 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -1,5 +1,4 @@
 #### What type of PR is this?
-
 <!--
 Add one of the following kinds:
 
@@ -11,21 +10,33 @@ optimize: A new optimization
 fix: A bug fix
 perf: A code change that improves performance
 refactor: A code change that neither fixes a bug nor adds a feature
-style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
+style: Changes that do not affect the meaning of the code (white space, formatting, missing semi-colons, etc)
 test: Adding missing tests or correcting existing tests
 chore: Changes to the build process or auxiliary tools and libraries such as documentation generation
 -->
 
-#### What this PR does / why we need it (English/Chinese):
+#### Check the PR title.
+<!--
+The description of the title will be attached in Release Notes, 
+so please describe it from user-oriented, what this PR does / why we need it.
+Please check your PR title with the below requirements:
+-->
+- [ ] This PR title match the format: \<type\>(optional scope): \<description\>
+- [ ] The description of this PR title is user-oriented and clear enough for others to understand.
+
 
+#### (Optional) Translate the PR title into Chinese.
+
+
+#### (Optional) More detail description for this PR(en: English/zh: Chinese).
 <!--
-The description will be attached in Release Notes, 
-so please describe it from user-oriented.
+Provide more detailed info for review. If it is a perf type PR, perf data is suggested to give.
 -->
+en:
+zh(optional):
 
 #### Which issue(s) this PR fixes:
-
 <!--
-*Automatically closes linked issue when PR is merged.
+Automatically closes linked issue when PR is merged.
 Eg: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
 -->
diff --git a/_typos.toml b/_typos.toml
index f83f8e3..113fc63 100644
--- a/_typos.toml
+++ b/_typos.toml
@@ -13,3 +13,7 @@ TestCompressFlateConcurrent = "TestCompressFlateConcurrent"
 trUe = "trUe"
 OPTIO = "OPTIO"
 contant = "contant"
+referer = "referer"
+HeaderReferer = "HeaderReferer"
+expectedReferer = "expectedReferer"
+Referer = "Referer"
\ No newline at end of file
diff --git a/cmd/hz/internal/generator/router.go b/cmd/hz/internal/generator/router.go
index 9e43dc5..25d00e1 100644
--- a/cmd/hz/internal/generator/router.go
+++ b/cmd/hz/internal/generator/router.go
@@ -23,7 +23,6 @@ import (
 	"path/filepath"
 	"regexp"
 	"sort"
-	"strconv"
 	"strings"
 
 	"github.com/cloudwego/hertz/cmd/hz/internal/util"
@@ -73,9 +72,6 @@ func (routerNode *RouterNode) Update(method *HttpMethod, handlerType string) err
 	if paths[0] == "" {
 		paths = paths[1:]
 	}
-	if paths[len(paths)-1] == "" {
-		paths = paths[:len(paths)-1]
-	}
 	parent, last := routerNode.FindNearest(paths)
 	if last == len(paths) {
 		return fmt.Errorf("path '%s' has been registered", method.Path)
@@ -89,7 +85,6 @@ func (routerNode *RouterNode) Update(method *HttpMethod, handlerType string) err
 // DyeGroupName traverses the routing tree in depth and names the middleware for each node.
 func (routerNode *RouterNode) DyeGroupName() error {
 	groups := []string{"root"}
-	unique := map[string]bool{}
 
 	hook := func(layer int, node *RouterNode) error {
 		node.GroupName = groups[layer]
@@ -102,26 +97,14 @@ func (routerNode *RouterNode) DyeGroupName() error {
 				handleName := strings.Split(node.Handler, ".")
 				pname = handleName[len(handleName)-1]
 			}
-			origin := util.ToVarName([]string{pname})
-			pname = origin
-			// There may be a node with the same name on the routing tree.
-			// In order to prevent the generated routing groups from having the same name,
-			// the names of these routing groups are renamed.
-			// 10000 is a large number that cannot be repeated.
-			for i := 0; i < 10000; i++ {
-				if _, ok := unique[pname]; ok {
-					pname = origin + strconv.Itoa(i)
-					if i == 9999 {
-						return fmt.Errorf("cannot generate a unique name for the routing group. "+
-							"The sequence number of the routing group for %s with the same name cannot exceed 9999. "+
-							"\"+\n\t\t\t\t\t\t\t\"Please reduce the use of routing groups with the same name", origin)
-					}
-				} else {
-					unique[pname] = true
-					break
-				}
+			pname = util.ToVarName([]string{pname})
+			// The tolow operation is placed here, unifying the middleware raw name
+			pname = strings.ToLower(pname)
+			pname, err := util.GetMiddlewareUniqueName(pname)
+			if err != nil {
+				return fmt.Errorf("get unique name for middleware '%s' failed, err: %v", pname, err)
 			}
-			node.MiddleWare = "_" + strings.ToLower(pname)
+			node.MiddleWare = "_" + pname
 		}
 		if layer >= len(groups)-1 {
 			groups = append(groups, node.MiddleWare)
diff --git a/cmd/hz/internal/meta/const.go b/cmd/hz/internal/meta/const.go
index d6d2dd8..8996510 100644
--- a/cmd/hz/internal/meta/const.go
+++ b/cmd/hz/internal/meta/const.go
@@ -19,7 +19,7 @@ package meta
 import "runtime"
 
 // Version hz version
-const Version = "v0.2.0"
+const Version = "v0.2.1"
 
 // Mode hz run modes
 type Mode int
diff --git a/cmd/hz/internal/protobuf/plugin.go b/cmd/hz/internal/protobuf/plugin.go
index b434c7c..11dc7f9 100644
--- a/cmd/hz/internal/protobuf/plugin.go
+++ b/cmd/hz/internal/protobuf/plugin.go
@@ -503,7 +503,7 @@ func (plugin *Plugin) getIdlInfo(ast *descriptorpb.FileDescriptorProto, deps map
 	}
 	rs, err := NewResolver(ast, fileInfo, main, map[string]string{})
 	if err != nil {
-		return nil, err
+		return nil, fmt.Errorf("new protobuf resolver failed, err:%v", err)
 	}
 	err = rs.LoadAll(ast)
 	if err != nil {
diff --git a/cmd/hz/internal/protobuf/resolver.go b/cmd/hz/internal/protobuf/resolver.go
index 6e4317c..074c23a 100644
--- a/cmd/hz/internal/protobuf/resolver.go
+++ b/cmd/hz/internal/protobuf/resolver.go
@@ -66,22 +66,28 @@ type PackageReference struct {
 	Referred    bool
 }
 
-func getReferPkgMap(pkgMap map[string]string, incs []*descriptorpb.FileDescriptorProto) map[string]*PackageReference {
+func getReferPkgMap(pkgMap map[string]string, incs []*descriptorpb.FileDescriptorProto) (map[string]*PackageReference, error) {
+	var err error
 	out := make(map[string]*PackageReference, len(pkgMap))
 	for _, inc := range incs {
 		pkg := getGoPackage(inc, pkgMap)
 		path := inc.GetName()
 		base := util.BaseName(path, ".proto")
-		// space := inc.GetPackage()
 		fileName := inc.GetName()
+		pkgName := util.BaseName(pkg, "")
+		pkgName, err = util.GetPackageUniqueName(pkgName)
+		if err != nil {
+			return nil, fmt.Errorf("get package unique name failed, err: %v", err)
+		}
+
 		out[fileName] = &PackageReference{base, path, &model.Model{
 			FilePath:    path,
 			Package:     pkg,
-			PackageName: util.BaseName(pkg, ""),
+			PackageName: pkgName,
 		}, inc, false}
 	}
 
-	return out
+	return out, nil
 }
 
 type FileInfos struct {
@@ -146,7 +152,10 @@ func NewResolver(ast *descriptorpb.FileDescriptorProto, files FileInfos, model *
 			return nil, fmt.Errorf("%s not found", dep)
 		}
 	}
-	pm := getReferPkgMap(pkgMap, incs)
+	pm, err := getReferPkgMap(pkgMap, incs)
+	if err != nil {
+		return nil, fmt.Errorf("get package map failed, err: %v", err)
+	}
 	return &Resolver{
 		root:    make(NameSpace),
 		deps:    make(map[string]NameSpace),
diff --git a/cmd/hz/internal/thrift/plugin.go b/cmd/hz/internal/thrift/plugin.go
index 6da61d4..97c8ffc 100644
--- a/cmd/hz/internal/thrift/plugin.go
+++ b/cmd/hz/internal/thrift/plugin.go
@@ -230,8 +230,11 @@ func (plugin *Plugin) getPackageInfo() (*generator.HttpPackage, error) {
 		Package:     pkg,
 		PackageName: util.SplitPackageName(pkg, ""),
 	}
-	rs := NewResolver(ast, main, pkgMap)
-	err := rs.LoadAll(ast)
+	rs, err := NewResolver(ast, main, pkgMap)
+	if err != nil {
+		return nil, fmt.Errorf("new thrift resolver failed, err:%v", err)
+	}
+	err = rs.LoadAll(ast)
 	if err != nil {
 		return nil, err
 	}
diff --git a/cmd/hz/internal/thrift/resolver.go b/cmd/hz/internal/thrift/resolver.go
index db7e066..b105cf0 100644
--- a/cmd/hz/internal/thrift/resolver.go
+++ b/cmd/hz/internal/thrift/resolver.go
@@ -54,20 +54,27 @@ type PackageReference struct {
 	Referred    bool
 }
 
-func getReferPkgMap(pkgMap map[string]string, incs []*parser.Include) map[string]*PackageReference {
+func getReferPkgMap(pkgMap map[string]string, incs []*parser.Include) (map[string]*PackageReference, error) {
+	var err error
 	out := make(map[string]*PackageReference, len(pkgMap))
 	for _, inc := range incs {
 		pkg := getGoPackage(inc.Reference, pkgMap)
 		impt := inc.GetPath()
 		base := util.BaseNameAndTrim(impt)
+		pkgName := util.SplitPackageName(pkg, "")
+		pkgName, err = util.GetPackageUniqueName(pkgName)
+		if err != nil {
+			return nil, fmt.Errorf("get package unique name failed, err: %v", err)
+		}
+
 		out[base] = &PackageReference{base, impt, &model.Model{
 			FilePath:    inc.Path,
 			Package:     pkg,
-			PackageName: util.SplitPackageName(pkg, ""),
+			PackageName: pkgName,
 		}, inc.Reference, false}
 	}
 
-	return out
+	return out, nil
 }
 
 type Symbol struct {
@@ -89,8 +96,11 @@ type Resolver struct {
 	refPkgs map[string]*PackageReference
 }
 
-func NewResolver(ast *parser.Thrift, model *model.Model, pkgMap map[string]string) *Resolver {
-	pm := getReferPkgMap(pkgMap, ast.GetIncludes())
+func NewResolver(ast *parser.Thrift, model *model.Model, pkgMap map[string]string) (*Resolver, error) {
+	pm, err := getReferPkgMap(pkgMap, ast.GetIncludes())
+	if err != nil {
+		return nil, fmt.Errorf("get package map failed, err: %v", err)
+	}
 	file := ast.GetFilename()
 	return &Resolver{
 		root:    make(NameSpace),
@@ -103,7 +113,7 @@ func NewResolver(ast *parser.Thrift, model *model.Model, pkgMap map[string]strin
 			Ast:         ast,
 			Referred:    false,
 		},
-	}
+	}, nil
 }
 
 func (resolver *Resolver) GetRefModel(includeBase string) (*model.Model, error) {
diff --git a/cmd/hz/internal/util/data.go b/cmd/hz/internal/util/data.go
index ad3a052..6f6f768 100644
--- a/cmd/hz/internal/util/data.go
+++ b/cmd/hz/internal/util/data.go
@@ -24,6 +24,8 @@ import (
 	"reflect"
 	"strconv"
 	"strings"
+
+	"github.com/cloudwego/hertz/cmd/hz/internal/util/logs"
 )
 
 func CopyStringSlice(from, to *[]string) {
@@ -362,3 +364,49 @@ func SubDir(root, subPkg string) string {
 	}
 	return filepath.Join(root, ImportToPath(subPkg, ""))
 }
+
+var (
+	uniquePackageName    = map[string]bool{}
+	uniqueMiddlewareName = map[string]bool{}
+)
+
+// GetPackageUniqueName can get a non-repeating variable name for package alias
+func GetPackageUniqueName(name string) (string, error) {
+	name, err := getUniqueName(name, uniquePackageName)
+	if err != nil {
+		return "", fmt.Errorf("can not generate unique name for package '%s', err: %v", name, err)
+	}
+
+	return name, nil
+}
+
+// GetMiddlewareUniqueName can get a non-repeating variable name for middleware name
+func GetMiddlewareUniqueName(name string) (string, error) {
+	name, err := getUniqueName(name, uniqueMiddlewareName)
+	if err != nil {
+		return "", fmt.Errorf("can not generate routing group for path '%s', err: %v", name, err)
+	}
+
+	return name, nil
+}
+
+// getUniqueName can get a non-repeating variable name
+func getUniqueName(name string, uniqueNameSet map[string]bool) (string, error) {
+	uniqueName := name
+	if _, exist := uniqueNameSet[uniqueName]; exist {
+		for i := 0; i < 10000; i++ {
+			uniqueName = uniqueName + fmt.Sprintf("%d", i)
+			if _, exist := uniqueNameSet[uniqueName]; !exist {
+				logs.Infof("There is a package name with the same name, change %s to %s", name, uniqueName)
+				break
+			}
+			uniqueName = name
+			if i == 9999 {
+				return "", fmt.Errorf("there is too many same package for %s", name)
+			}
+		}
+	}
+	uniqueNameSet[uniqueName] = true
+
+	return uniqueName, nil
+}
diff --git a/examples/html_rendering/index.tmpl b/examples/html_rendering/index.tmpl
new file mode 100644
index 0000000..5e5a261
--- /dev/null
+++ b/examples/html_rendering/index.tmpl
@@ -0,0 +1,5 @@
+<html>
+<h1>
+    {[{ .title }]}
+</h1>
+</html>
\ No newline at end of file
diff --git a/examples/html_rendering/main.go b/examples/html_rendering/main.go
new file mode 100644
index 0000000..ad5c080
--- /dev/null
+++ b/examples/html_rendering/main.go
@@ -0,0 +1,60 @@
+/*
+ * 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 main
+
+import (
+	"context"
+	"fmt"
+	"html/template"
+	"time"
+
+	"github.com/cloudwego/hertz/pkg/app"
+	"github.com/cloudwego/hertz/pkg/app/server"
+	"github.com/cloudwego/hertz/pkg/common/utils"
+	"github.com/cloudwego/hertz/pkg/protocol/consts"
+)
+
+func formatAsDate(t time.Time) string {
+	year, month, day := t.Date()
+	return fmt.Sprintf("%d/%02d/%02d", year, month, day)
+}
+
+func main() {
+	// set interval to 0 means using fs-watching mechanism.
+	h := server.Default(server.WithAutoReloadRender(true, 0))
+
+	h.Delims("{[{", "}]}")
+
+	h.SetFuncMap(template.FuncMap{
+		"formatAsDate": formatAsDate,
+	})
+	h.LoadHTMLGlob("./examples/html_rendering/*")
+
+	h.GET("/index", func(c context.Context, ctx *app.RequestContext) {
+		ctx.HTML(consts.StatusOK, "index.tmpl", utils.H{
+			"title": "Main website",
+		})
+	})
+
+	h.GET("/raw", func(c context.Context, ctx *app.RequestContext) {
+		ctx.HTML(consts.StatusOK, "template.html", utils.H{
+			"now": time.Date(2017, 0o7, 0o1, 0, 0, 0, 0, time.UTC),
+		})
+	})
+
+	h.Spin()
+}
diff --git a/examples/html_rendering/template.html b/examples/html_rendering/template.html
new file mode 100644
index 0000000..b37104a
--- /dev/null
+++ b/examples/html_rendering/template.html
@@ -0,0 +1,1 @@
+<h1>Date: {[{.now | formatAsDate}]}</h1>
\ No newline at end of file
diff --git a/go.mod b/go.mod
index 9cf0e61..7f9b9b5 100644
--- a/go.mod
+++ b/go.mod
@@ -5,9 +5,10 @@ go 1.16
 require (
 	github.com/bytedance/go-tagexpr/v2 v2.9.2
 	github.com/bytedance/gopkg v0.0.0-20220413063733-65bf48ffb3a7
-	github.com/bytedance/sonic v1.3.0
+	github.com/bytedance/sonic v1.3.5
 	github.com/cloudwego/netpoll v0.2.4
+	github.com/fsnotify/fsnotify v1.5.4
 	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
-	golang.org/x/sys v0.0.0-20220110181412-a018aaa089fe
+	golang.org/x/sys v0.0.0-20220412211240-33da011f77ad
 	google.golang.org/protobuf v1.27.1
 )
diff --git a/go.sum b/go.sum
index 8444b8e..4ec04ce 100644
--- a/go.sum
+++ b/go.sum
@@ -4,6 +4,8 @@ github.com/bytedance/gopkg v0.0.0-20220413063733-65bf48ffb3a7 h1:PtwsQyQJGxf8iaP
 github.com/bytedance/gopkg v0.0.0-20220413063733-65bf48ffb3a7/go.mod h1:2ZlV9BaUH4+NXIBF0aMdKKAnHTzqH+iMU4KUjAbL23Q=
 github.com/bytedance/sonic v1.3.0 h1:T2rlvNytw6bTmczlAXvGqmuMzIqGJBOsJKYwRPWR7Y8=
 github.com/bytedance/sonic v1.3.0/go.mod h1:V973WhNhGmvHxW6nQmsHEfHaoU9F3zTF+93rH03hcUQ=
+github.com/bytedance/sonic v1.3.5 h1:xfBNhsG3QCC+AMCmCHxNQg0StI5IM/B9Jtwjqi5WlI0=
+github.com/bytedance/sonic v1.3.5/go.mod h1:V973WhNhGmvHxW6nQmsHEfHaoU9F3zTF+93rH03hcUQ=
 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.4 h1:Kbo2HA1cXEgoy/bu1jSNrjcqZj2diENcJqLy6vKiROU=
@@ -11,6 +13,8 @@ github.com/cloudwego/netpoll v0.2.4/go.mod h1:1T2WVuQ+MQw6h6DpE45MohSvDTKdy2DlzC
 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/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
+github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
 github.com/goccy/go-json v0.9.4 h1:L8MLKG2mvVXiQu07qB6hmfqeSYQdOnqPot2GhsIwIaI=
 github.com/goccy/go-json v0.9.4/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
 github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -58,8 +62,9 @@ golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VA
 golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sys v0.0.0-20220110181412-a018aaa089fe h1:W8vbETX/n8S6EmY0Pu4Ix7VvpsJUESTwl0oCK8MJOgk=
 golang.org/x/sys v0.0.0-20220110181412-a018aaa089fe/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0=
+golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 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=
diff --git a/pkg/app/client/client.go b/pkg/app/client/client.go
index 325e91f..9800638 100644
--- a/pkg/app/client/client.go
+++ b/pkg/app/client/client.go
@@ -53,7 +53,7 @@ import (
 	"github.com/cloudwego/hertz/pkg/common/config"
 	"github.com/cloudwego/hertz/pkg/common/errors"
 	"github.com/cloudwego/hertz/pkg/common/utils"
-	"github.com/cloudwego/hertz/pkg/network"
+	"github.com/cloudwego/hertz/pkg/network/dialer"
 	"github.com/cloudwego/hertz/pkg/protocol"
 	"github.com/cloudwego/hertz/pkg/protocol/client"
 	"github.com/cloudwego/hertz/pkg/protocol/consts"
@@ -271,11 +271,6 @@ func (c *Client) SetRetryIf(fn func(request *protocol.Request) bool) {
 	c.RetryIf = fn
 }
 
-// SetDialFunc is used to set custom dial func.
-func (c *Client) SetDialFunc(dial func(addr string) (network.Conn, error)) {
-	c.options.Dial = dial
-}
-
 // Get returns the status code and body of url.
 //
 // The contents of dst will be replaced by the body and returned, if the dst
@@ -548,6 +543,9 @@ func (c *Client) SetClientFactory(cf suite.ClientFactory) {
 // NewClient return a client with options
 func NewClient(opts ...config.ClientOption) (*Client, error) {
 	opt := config.NewClientOptions(opts)
+	if opt.Dialer == nil {
+		opt.Dialer = dialer.DefaultDialer()
+	}
 	c := &Client{
 		options: opt,
 	}
@@ -569,7 +567,7 @@ func newHttp1OptionFromClient(c *Client) *http1.ClientOptions {
 	return &http1.ClientOptions{
 		Name:                          c.options.Name,
 		NoDefaultUserAgentHeader:      c.options.NoDefaultUserAgentHeader,
-		Dial:                          c.options.Dial,
+		Dialer:                        c.options.Dialer,
 		DialTimeout:                   c.options.DialTimeout,
 		DialDualStack:                 c.options.DialDualStack,
 		TLSConfig:                     c.options.TLSConfig,
diff --git a/pkg/app/client/option.go b/pkg/app/client/option.go
index af149f4..80a74cc 100644
--- a/pkg/app/client/option.go
+++ b/pkg/app/client/option.go
@@ -22,9 +22,7 @@ import (
 
 	"github.com/cloudwego/hertz/pkg/common/config"
 	"github.com/cloudwego/hertz/pkg/network"
-	"github.com/cloudwego/hertz/pkg/network/dialer"
 	"github.com/cloudwego/hertz/pkg/network/standard"
-	"github.com/cloudwego/hertz/pkg/protocol/client"
 )
 
 // WithDialTimeout sets dial timeout.
@@ -87,14 +85,14 @@ func WithClientReadTimeout(t time.Duration) config.ClientOption {
 func WithTLSConfig(cfg *tls.Config) config.ClientOption {
 	return config.ClientOption{F: func(o *config.ClientOptions) {
 		o.TLSConfig = cfg
-		dialer.SetDialer(standard.NewDialer())
+		o.Dialer = standard.NewDialer()
 	}}
 }
 
 // WithDialer sets the specific dialer.
 func WithDialer(d network.Dialer) config.ClientOption {
 	return config.ClientOption{F: func(o *config.ClientOptions) {
-		dialer.SetDialer(d)
+		o.Dialer = d
 	}}
 }
 
@@ -105,15 +103,6 @@ func WithResponseBodyStream(b bool) config.ClientOption {
 	}}
 }
 
-// WithDialFunc is used to set dialer function
-//
-// NOTE: By default, hertz client uses dialer.DialConnection as DialFunc.
-func WithDialFunc(f client.DialFunc) config.ClientOption {
-	return config.ClientOption{F: func(o *config.ClientOptions) {
-		o.Dial = f
-	}}
-}
-
 // WithDisableHeaderNamesNormalizing is used to set whether disable header names normalizing.
 func WithDisableHeaderNamesNormalizing(disable bool) config.ClientOption {
 	return config.ClientOption{F: func(o *config.ClientOptions) {
diff --git a/pkg/app/context.go b/pkg/app/context.go
index 8409e5e..b146425 100644
--- a/pkg/app/context.go
+++ b/pkg/app/context.go
@@ -47,6 +47,7 @@ import (
 	"io"
 	"mime/multipart"
 	"net"
+	"net/url"
 	"os"
 	"reflect"
 	"strings"
@@ -950,6 +951,40 @@ func (ctx *RequestContext) Cookie(key string) []byte {
 	return ctx.Request.Header.Cookie(key)
 }
 
+// SetCookie adds a Set-Cookie header to the Response's headers.
+//  Parameter introduce:
+//  name and value is used to set cookie's name and value, eg. Set-Cookie: name=value
+//  maxAge is use to set cookie's expiry date, eg. Set-Cookie: name=value; max-age=1
+//  path and domain is used to set the scope of a cookie, eg. Set-Cookie: name=value;domain=localhost; path=/;
+//  secure and httpOnly is used to sent cookies securely; eg. Set-Cookie: name=value;HttpOnly; secure;
+//  sameSite let servers specify whether/when cookies are sent with cross-site requests; eg. Set-Cookie: name=value;HttpOnly; secure; SameSite=Lax;
+//
+//  For example:
+//  1. ctx.SetCookie("user", "hertz", 1, "/", "localhost",protocol.CookieSameSiteLaxMode, true, true)
+//  add response header --->  Set-Cookie: user=hertz; max-age=1; domain=localhost; path=/; HttpOnly; secure; SameSite=Lax;
+//  2. ctx.SetCookie("user", "hertz", 10, "/", "localhost",protocol.CookieSameSiteLaxMode, false, false)
+//  add response header --->  Set-Cookie: user=hertz; max-age=10; domain=localhost; path=/; SameSite=Lax;
+//  3. ctx.SetCookie("", "hertz", 10, "/", "localhost",protocol.CookieSameSiteLaxMode, false, false)
+//  add response header --->  Set-Cookie: hertz; max-age=10; domain=localhost; path=/; SameSite=Lax;
+//  4. ctx.SetCookie("user", "", 10, "/", "localhost",protocol.CookieSameSiteLaxMode, false, false)
+//  add response header --->  Set-Cookie: user=; max-age=10; domain=localhost; path=/; SameSite=Lax;
+func (ctx *RequestContext) SetCookie(name, value string, maxAge int, path, domain string, sameSite protocol.CookieSameSite, secure, httpOnly bool) {
+	if path == "" {
+		path = "/"
+	}
+	cookie := protocol.AcquireCookie()
+	defer protocol.ReleaseCookie(cookie)
+	cookie.SetKey(name)
+	cookie.SetValue(url.QueryEscape(value))
+	cookie.SetMaxAge(maxAge)
+	cookie.SetPath(path)
+	cookie.SetDomain(domain)
+	cookie.SetSecure(secure)
+	cookie.SetHTTPOnly(httpOnly)
+	cookie.SetSameSite(sameSite)
+	ctx.Response.Header.SetCookie(cookie)
+}
+
 // UserAgent returns the value of the request user_agent.
 func (ctx *RequestContext) UserAgent() []byte {
 	return ctx.Request.Header.UserAgent()
diff --git a/pkg/app/server/hertz.go b/pkg/app/server/hertz.go
index 96854a7..b8edc92 100644
--- a/pkg/app/server/hertz.go
+++ b/pkg/app/server/hertz.go
@@ -57,7 +57,6 @@ func Default(opts ...config.Option) *Hertz {
 func (h *Hertz) Spin() {
 	errCh := make(chan error)
 	h.initOnRunHooks(errCh)
-	h.initOnShutdownHooks()
 	go func() {
 		errCh <- h.Run()
 	}()
@@ -133,13 +132,3 @@ func (h *Hertz) initOnRunHooks(errChan chan error) {
 		return nil
 	})
 }
-
-func (h *Hertz) initOnShutdownHooks() {
-	opt := h.GetOptions()
-	// add deregister func to shutdownHooks
-	h.OnShutdown = append(h.OnShutdown, func(ctx context.Context) {
-		if err := opt.Registry.Deregister(opt.RegistryInfo); err != nil {
-			hlog.Errorf("HERTZ: Deregister error=%v", err)
-		}
-	})
-}
diff --git a/pkg/app/server/option.go b/pkg/app/server/option.go
index c67ddcc..ccfa97a 100644
--- a/pkg/app/server/option.go
+++ b/pkg/app/server/option.go
@@ -277,3 +277,14 @@ func WithRegistry(r registry.Registry, info *registry.Info) config.Option {
 		o.RegistryInfo = info
 	}}
 }
+
+// WithAutoReloadRender sets the config of auto reload render.
+// If auto reload render is enabled:
+// 1. interval = 0 means reload render according to file watch mechanism.(recommended)
+// 2. interval > 0 means reload render every interval.
+func WithAutoReloadRender(b bool, interval time.Duration) config.Option {
+	return config.Option{F: func(o *config.Options) {
+		o.AutoReloadRender = b
+		o.AutoReloadInterval = interval
+	}}
+}
diff --git a/pkg/app/server/render/html.go b/pkg/app/server/render/html.go
index 12f503f..03cf687 100644
--- a/pkg/app/server/render/html.go
+++ b/pkg/app/server/render/html.go
@@ -43,8 +43,13 @@ package render
 
 import (
 	"html/template"
+	"log"
+	"sync"
+	"time"
 
+	"github.com/cloudwego/hertz/pkg/common/hlog"
 	"github.com/cloudwego/hertz/pkg/protocol"
+	"github.com/fsnotify/fsnotify"
 )
 
 // Delims represents a set of Left and Right delimiters for HTML template rendering.
@@ -59,12 +64,12 @@ type Delims struct {
 type HTMLRender interface {
 	// Instance returns an HTML instance.
 	Instance(string, interface{}) Render
+	Close() error
 }
 
 // HTMLProduction contains template reference and its delims.
 type HTMLProduction struct {
 	Template *template.Template
-	Delims   Delims
 }
 
 // HTML contains template reference and its name with given interface object.
@@ -85,6 +90,10 @@ func (r HTMLProduction) Instance(name string, data interface{}) Render {
 	}
 }
 
+func (r HTMLProduction) Close() error {
+	return nil
+}
+
 // Render (HTML) executes template and writes its result with custom ContentType for response.
 func (r HTML) Render(resp *protocol.Response) error {
 	r.WriteContentType(resp)
@@ -99,3 +108,105 @@ func (r HTML) Render(resp *protocol.Response) error {
 func (r HTML) WriteContentType(resp *protocol.Response) {
 	writeContentType(resp, htmlContentType)
 }
+
+type HTMLDebug struct {
+	sync.Once
+	Template        *template.Template
+	RefreshInterval time.Duration
+	updateTimeStamp time.Time
+
+	Files   []string
+	FuncMap template.FuncMap
+	Delims  Delims
+
+	reloadCh chan struct{}
+	watcher  *fsnotify.Watcher
+}
+
+func (h *HTMLDebug) Instance(name string, data interface{}) Render {
+	h.Do(func() {
+		h.startChecker()
+	})
+
+	select {
+	case <-h.reloadCh:
+		h.reload()
+	default:
+	}
+
+	return HTML{
+		Template: h.Template,
+		Name:     name,
+		Data:     data,
+	}
+}
+
+func (h *HTMLDebug) Close() error {
+	if h.watcher == nil {
+		return nil
+	}
+	return h.watcher.Close()
+}
+
+func (h *HTMLDebug) reload() {
+	h.Template = template.Must(template.New("").
+		Delims(h.Delims.Left, h.Delims.Right).
+		Funcs(h.FuncMap).
+		ParseFiles(h.Files...))
+}
+
+func (h *HTMLDebug) startChecker() {
+	h.reloadCh = make(chan struct{})
+
+	if h.RefreshInterval > 0 {
+		go func() {
+			hlog.Debugf("HERTZ[HTMLDebug]: HTML template reloader started with interval %v", h.RefreshInterval)
+			for {
+				n := time.Now()
+				if n.UTC().Sub(h.updateTimeStamp.UTC()) > h.RefreshInterval {
+					hlog.Debugf("HERTZ[HTMLDebug]: triggering HTML template reloader")
+					h.reloadCh <- struct{}{}
+					hlog.Debugf("HERTZ[HTMLDebug]: HTML template has been reloaded, next reload in %v", h.RefreshInterval)
+					h.updateTimeStamp = time.Now()
+				}
+			}
+		}()
+		return
+	}
+
+	watcher, err := fsnotify.NewWatcher()
+	if err != nil {
+		log.Fatal(err)
+	}
+	h.watcher = watcher
+	for _, f := range h.Files {
+		err := watcher.Add(f)
+		hlog.Debugf("HERTZ[HTMLDebug]: watching file: %s", f)
+		if err != nil {
+			hlog.Errorf("HERTZ[HTMLDebug]: add watching file: %s, error happened: %v", f, err)
+		}
+
+	}
+
+	go func() {
+		hlog.Debugf("HERTZ[HTMLDebug]: HTML template reloader started with file watcher")
+		for {
+			select {
+			case event, ok := <-watcher.Events:
+				if !ok {
+					return
+				}
+				if event.Op&fsnotify.Write == fsnotify.Write {
+					hlog.Debugf("HERTZ[HTMLDebug]: modified file: %s, html render template will be reloaded at the next rendering", event.Name)
+					h.reloadCh <- struct{}{}
+					hlog.Debugf("HERTZ[HTMLDebug]: HTML template has been reloaded")
+				}
+			case err, ok := <-watcher.Errors:
+				if !ok {
+					return
+				}
+				hlog.Errorf("HERTZ: error happened when watching the rendering files: %v", err)
+			}
+		}
+	}()
+}
diff --git a/pkg/common/config/client_option.go b/pkg/common/config/client_option.go
index 4ecc757..ec6f5e6 100644
--- a/pkg/common/config/client_option.go
+++ b/pkg/common/config/client_option.go
@@ -53,15 +53,14 @@ type ClientOptions struct {
 	// User-Agent header to be excluded from the Request.
 	NoDefaultUserAgentHeader bool
 
-	// Callback for establishing new connections to hosts.
-	//
-	// Default Dial is used if not set.
-	Dial func(addr string) (network.Conn, error)
+	// Dialer is the custom dialer used to establish connection.
+	// Default Dialer is used if not set.
+	Dialer network.Dialer
 
 	// Attempt to connect to both ipv4 and ipv6 addresses if set to true.
 	//
 	// This option is used only if default TCP dialer is used,
-	// i.e. if Dial is blank.
+	// i.e. if Dialer is blank.
 	//
 	// By default client connects only to ipv4 addresses,
 	// since unfortunately ipv6 remains broken in many networks worldwide :)
diff --git a/pkg/common/config/option.go b/pkg/common/config/option.go
index 868b476..1fc2789 100644
--- a/pkg/common/config/option.go
+++ b/pkg/common/config/option.go
@@ -71,6 +71,12 @@ type Options struct {
 	Registry registry.Registry
 	// RegistryInfo is base info used for service registry.
 	RegistryInfo *registry.Info
+	// Enable automatically HTML template reloading mechanism.
+	AutoReloadRender bool
+	// If AutoReloadInterval is set to 0(default).
+	// The HTML template will reload according to files' changing event
+	// otherwise it will reload after AutoReloadInterval.
+	AutoReloadInterval time.Duration
 }
 
 func (o *Options) Apply(opts []Option) {
diff --git a/pkg/common/utils/network.go b/pkg/common/utils/network.go
index 0daa2ce..f3b2fcb 100644
--- a/pkg/common/utils/network.go
+++ b/pkg/common/utils/network.go
@@ -31,16 +31,25 @@ func LocalIP() string {
 
 // getLocalIp enumerates local net interfaces to find local ip, it should only be called in init phase
 func getLocalIp() string {
-	addrs, err := net.InterfaceAddrs()
+	inters, err := net.Interfaces()
 	if err != nil {
 		return UNKNOWN_IP_ADDR
 	}
-
-	for _, addr := range addrs {
-		if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
-			return ipnet.IP.String()
+	for _, inter := range inters {
+		if inter.Flags&net.FlagLoopback != net.FlagLoopback &&
+			inter.Flags&net.FlagUp != 0 {
+			addrs, err := inter.Addrs()
+			if err != nil {
+				return UNKNOWN_IP_ADDR
+			}
+			for _, addr := range addrs {
+				if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
+					return ipnet.IP.String()
+				}
+			}
 		}
 	}
+
 	return UNKNOWN_IP_ADDR
 }
 
diff --git a/pkg/common/utils/path.go b/pkg/common/utils/path.go
index a162f11..fa2b176 100644
--- a/pkg/common/utils/path.go
+++ b/pkg/common/utils/path.go
@@ -186,9 +186,16 @@ func bufApp(buf *[]byte, s string, w int, c byte) {
 	b[w] = c
 }
 
+// AddMissingPort adds a port to a host if it is missing.
+// A literal IPv6 address in hostport must be enclosed in square
+// brackets, as in "[::1]:80", "[::1%lo0]:80".
 func AddMissingPort(addr string, isTLS bool) string {
 	if strings.IndexByte(addr, ':') >= 0 {
-		return addr
+		endOfV6 := strings.IndexByte(addr, ']')
+		// we do not care about the validity of the address, just check if it has more bytes after ']'
+		if endOfV6 < len(addr)-1 {
+			return addr
+		}
 	}
 	if !isTLS {
 		return addr + ":80"
diff --git a/pkg/network/dialer/dialer.go b/pkg/network/dialer/dialer.go
index 9678bd0..aea05e0 100644
--- a/pkg/network/dialer/dialer.go
+++ b/pkg/network/dialer/dialer.go
@@ -26,10 +26,16 @@ import (
 
 var defaultDialer network.Dialer
 
+// SetDialer is used to set the global default dialer.
+// Deprecated: use WithDialer instead.
 func SetDialer(dialer network.Dialer) {
 	defaultDialer = dialer
 }
 
+func DefaultDialer() network.Dialer {
+	return defaultDialer
+}
+
 func DialConnection(network, address string, timeout time.Duration, tlsConfig *tls.Config) (conn network.Conn, err error) {
 	return defaultDialer.DialConnection(network, address, timeout, tlsConfig)
 }
diff --git a/pkg/protocol/client/client.go b/pkg/protocol/client/client.go
index fab2bf9..962abfb 100644
--- a/pkg/protocol/client/client.go
+++ b/pkg/protocol/client/client.go
@@ -51,7 +51,6 @@ import (
 	"github.com/cloudwego/hertz/pkg/common/config"
 	"github.com/cloudwego/hertz/pkg/common/errors"
 	"github.com/cloudwego/hertz/pkg/common/timer"
-	"github.com/cloudwego/hertz/pkg/network"
 	"github.com/cloudwego/hertz/pkg/protocol"
 	"github.com/cloudwego/hertz/pkg/protocol/consts"
 )
@@ -97,7 +96,6 @@ type HostClientConfig struct {
 	MaxIdempotentCallAttempts int
 	MaxResponseBodySize       int
 
-	Dial               DialFunc
 	RetryIf            RetryIfFunc
 	ResponseBodyStream bool
 
@@ -116,20 +114,6 @@ type DynamicConfig struct {
 	IsTLS    bool
 }
 
-// DialFunc must establish connection to addr.
-//
-// There is no need in establishing TLS (SSL) connection for https.
-// The client automatically converts connection to TLS
-// if HostClient.IsTLS is set.
-//
-// TCP address passed to DialFunc always contains host and port.
-// Example TCP addr values:
-//
-//   - foobar.com:80
-//   - foobar.com:443
-//   - foobar.com:8080
-type DialFunc func(addr string) (network.Conn, error)
-
 // RetryIfFunc signature of retry if function
 //
 // Request argument passed to RetryIfFunc, if there are any request errors.
diff --git a/pkg/protocol/consts/default.go b/pkg/protocol/consts/default.go
index 82b5b28..ae8fbc2 100644
--- a/pkg/protocol/consts/default.go
+++ b/pkg/protocol/consts/default.go
@@ -52,7 +52,7 @@ const (
 
 	// *** Client default value start from here ***
 
-	// DefaultDialTimeout is timeout used by Dial and DialDualStack
+	// DefaultDialTimeout is timeout used by Dialer and DialDualStack
 	// for establishing TCP connections.
 	DefaultDialTimeout = time.Second
 
diff --git a/pkg/protocol/http1/client.go b/pkg/protocol/http1/client.go
index d179a79..12d2ccc 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"
 	errs "github.com/cloudwego/hertz/pkg/common/errors"
+	"github.com/cloudwego/hertz/pkg/common/hlog"
 	"github.com/cloudwego/hertz/pkg/common/timer"
 	"github.com/cloudwego/hertz/pkg/network"
 	"github.com/cloudwego/hertz/pkg/network/dialer"
@@ -851,7 +852,7 @@ func (c *HostClient) dialHostHard() (conn network.Conn, err error) {
 	for n > 0 {
 		addr := c.nextAddr()
 		tlsConfig := c.cachedTLSConfig(addr)
-		conn, err = dialAddr(addr, c.Dial, c.DialDualStack, tlsConfig, timeout, c.ProxyURI, c.IsTLS)
+		conn, err = dialAddr(addr, c.Dialer, c.DialDualStack, tlsConfig, timeout, c.ProxyURI, c.IsTLS)
 		if err == nil {
 			return conn, nil
 		}
@@ -891,29 +892,32 @@ func (c *HostClient) cachedTLSConfig(addr string) *tls.Config {
 	return cfg
 }
 
-func dialAddr(addr string, dial client.DialFunc, dialDualStack bool, tlsConfig *tls.Config, timeout time.Duration, proxyURI *protocol.URI, isTLS bool) (network.Conn, error) {
+func dialAddr(addr string, dial network.Dialer, dialDualStack bool, tlsConfig *tls.Config, timeout time.Duration, proxyURI *protocol.URI, isTLS bool) (network.Conn, error) {
 	var conn network.Conn
 	var err error
-	if dial != nil {
-		conn, err = dial(addr)
+	if dial == nil {
+		hlog.Warnf("HERTZ: HostClient: no dialer specified, trying to use default dialer")
+		dial = dialer.DefaultDialer()
+	}
+	dialFunc := dial.DialConnection
+
+	// addr has already been added port, no need to do it here
+	if proxyURI != nil {
+		// use tcp connection first, proxy will AddTLS to it
+		conn, err = dialFunc("tcp", string(proxyURI.Host()), timeout, nil)
 	} else {
-		// addr has already been added port, no need to do it here
-		if proxyURI != nil {
-			// use tcp connection first, proxy will AddTLS to it
-			conn, err = dialer.DialConnection("tcp", string(proxyURI.Host()), timeout, nil)
-		} else {
-			conn, err = dialer.DialConnection("tcp", addr, timeout, tlsConfig)
-		}
+		conn, err = dialFunc("tcp", addr, timeout, tlsConfig)
 	}
+
 	if err != nil {
 		return nil, err
 	}
 	if conn == nil {
-		panic("BUG: DialFunc returned (nil, nil)")
+		panic("BUG: dial.DialConnection returned (nil, nil)")
 	}
 
 	if proxyURI != nil {
-		conn, err = proxy.SetupProxy(conn, addr, proxyURI, tlsConfig, isTLS)
+		conn, err = proxy.SetupProxy(conn, addr, proxyURI, tlsConfig, isTLS, dial)
 	}
 
 	// conn must be nil when got error, so doesn't need to close it
@@ -1041,7 +1045,7 @@ func NewHostClient(c *ClientOptions) client.HostClient {
 
 type ClientOptions struct {
 	// Comma-separated list of upstream HTTP server host addresses,
-	// which are passed to Dial in a round-robin manner.
+	// which are passed to Dialer in a round-robin manner.
 	//
 	// Each address may contain port if default dialer is used.
 	// For example,
@@ -1060,8 +1064,8 @@ type ClientOptions struct {
 
 	// Callback for establishing new connection to the host.
 	//
-	// Default Dial is used if not set.
-	Dial client.DialFunc
+	// Default Dialer is used if not set.
+	Dialer network.Dialer
 
 	// Timeout for establishing new connections to hosts.
 	//
@@ -1072,7 +1076,7 @@ type ClientOptions struct {
 	// if set to true.
 	//
 	// This option is used only if default TCP dialer is used,
-	// i.e. if Dial is blank.
+	// i.e. if Dialer is blank.
 	//
 	// By default client connects only to ipv4 addresses,
 	// since unfortunately ipv6 remains broken in many networks worldwide :)
diff --git a/pkg/protocol/http1/proxy/proxy.go b/pkg/protocol/http1/proxy/proxy.go
index c144f1f..faf6803 100644
--- a/pkg/protocol/http1/proxy/proxy.go
+++ b/pkg/protocol/http1/proxy/proxy.go
@@ -34,14 +34,13 @@ import (
 	"github.com/cloudwego/hertz/internal/bytesconv"
 	"github.com/cloudwego/hertz/internal/bytestr"
 	"github.com/cloudwego/hertz/pkg/network"
-	"github.com/cloudwego/hertz/pkg/network/dialer"
 	"github.com/cloudwego/hertz/pkg/protocol"
 	"github.com/cloudwego/hertz/pkg/protocol/consts"
 	reqI "github.com/cloudwego/hertz/pkg/protocol/http1/req"
 	respI "github.com/cloudwego/hertz/pkg/protocol/http1/resp"
 )
 
-func SetupProxy(conn network.Conn, addr string, proxyURI *protocol.URI, tlsConfig *tls.Config, isTLS bool) (network.Conn, error) {
+func SetupProxy(conn network.Conn, addr string, proxyURI *protocol.URI, tlsConfig *tls.Config, isTLS bool, dialer network.Dialer) (network.Conn, error) {
 	var err error
 	if bytes.Equal(proxyURI.Scheme(), bytestr.StrHTTPS) {
 		conn, err = dialer.AddTLS(conn, tlsConfig)
diff --git a/pkg/route/engine.go b/pkg/route/engine.go
index d6c12fb..06f1f62 100644
--- a/pkg/route/engine.go
+++ b/pkg/route/engine.go
@@ -47,6 +47,7 @@ import (
 	"fmt"
 	"html/template"
 	"io"
+	"path/filepath"
 	"reflect"
 	"runtime"
 	"strings"
@@ -271,6 +272,13 @@ func (engine *Engine) Shutdown(ctx context.Context) (err error) {
 		}(i)
 	}
 
+	if opt := engine.options; opt != nil && opt.Registry != nil {
+		if err = opt.Registry.Deregister(opt.RegistryInfo); err != nil {
+			hlog.Errorf("HERTZ: Deregister error=%v", err)
+			return err
+		}
+	}
+
 	// call transport shutdown
 	if err := engine.transport.Shutdown(ctx); err != ctx.Err() {
 		return err
@@ -412,6 +420,9 @@ func getRemoteAddrFromCloser(conn io.Closer) string {
 }
 
 func (engine *Engine) Close() error {
+	if engine.htmlRender != nil {
+		engine.htmlRender.Close() //nolint:errcheck
+	}
 	return engine.transport.Close()
 }
 
@@ -747,23 +758,54 @@ func (engine *Engine) Use(middleware ...app.HandlerFunc) IRoutes {
 // LoadHTMLGlob loads HTML files identified by glob pattern
 // and associates the result with HTML renderer.
 func (engine *Engine) LoadHTMLGlob(pattern string) {
-	left := engine.delims.Left
-	right := engine.delims.Right
-	templ := template.Must(template.New("").Delims(left, right).Funcs(engine.funcMap).ParseGlob(pattern))
+	tmpl := template.Must(template.New("").
+		Delims(engine.delims.Left, engine.delims.Right).
+		Funcs(engine.funcMap).
+		ParseGlob(pattern))
+
+	if engine.options.AutoReloadRender {
+		files, err := filepath.Glob(pattern)
+		if err != nil {
+			hlog.Errorf("LoadHTMLGlob: %v", err)
+			return
+		}
+		engine.SetAutoReloadHTMLTemplate(tmpl, files)
+		return
+	}
 
-	engine.SetHTMLTemplate(templ)
+	engine.SetHTMLTemplate(tmpl)
 }
 
 // LoadHTMLFiles loads a slice of HTML files
 // and associates the result with HTML renderer.
 func (engine *Engine) LoadHTMLFiles(files ...string) {
-	templ := template.Must(template.New("").Delims(engine.delims.Left, engine.delims.Right).Funcs(engine.funcMap).ParseFiles(files...))
-	engine.SetHTMLTemplate(templ)
+	tmpl := template.Must(template.New("").
+		Delims(engine.delims.Left, engine.delims.Right).
+		Funcs(engine.funcMap).
+		ParseFiles(files...))
+
+	if engine.options.AutoReloadRender {
+		engine.SetAutoReloadHTMLTemplate(tmpl, files)
+		return
+	}
+
+	engine.SetHTMLTemplate(tmpl)
 }
 
 // SetHTMLTemplate associate a template with HTML renderer.
-func (engine *Engine) SetHTMLTemplate(templ *template.Template) {
-	engine.htmlRender = render.HTMLProduction{Template: templ.Funcs(engine.funcMap)}
+func (engine *Engine) SetHTMLTemplate(tmpl *template.Template) {
+	engine.htmlRender = render.HTMLProduction{Template: tmpl.Funcs(engine.funcMap)}
+}
+
+// SetAutoReloadHTMLTemplate associate a template with HTML renderer.
+func (engine *Engine) SetAutoReloadHTMLTemplate(tmpl *template.Template, files []string) {
+	engine.htmlRender = &render.HTMLDebug{
+		Template:        tmpl,
+		Files:           files,
+		FuncMap:         engine.funcMap,
+		Delims:          engine.delims,
+		RefreshInterval: engine.options.AutoReloadInterval,
+	}
 }
 
 // SetFuncMap sets the funcMap used for template.funcMap.
diff --git a/pkg/route/tree.go b/pkg/route/tree.go
index 4a648b3..774c8a3 100644
--- a/pkg/route/tree.go
+++ b/pkg/route/tree.go
@@ -146,7 +146,7 @@ func checkPathValid(path string) (valid bool) {
 			}
 		}
 	}
-	return false
+	return true
 }
 
 // addRoute adds a node with the given handle to the path.
diff --git a/version.go b/version.go
index 88f4c4d..d71e454 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.2.1"
+	Version = "v0.3.0"
 )


Test patch:
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 45d099b..7f46f75 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -27,4 +27,7 @@ jobs:
           test -z "$(gofumpt -l -extra .)" 
 
       - name: Unit Test
-        run: go test -race -covermode=atomic -coverprofile=coverage.out ./...
+        run: go test -race -covermode=atomic -coverprofile=coverage.txt ./...
+
+      - name: Codecov
+        run: bash <(curl -s https://codecov.io/bash)
diff --git a/cmd/hz/internal/util/data_test.go b/cmd/hz/internal/util/data_test.go
new file mode 100644
index 0000000..22ec51f
--- /dev/null
+++ b/cmd/hz/internal/util/data_test.go
@@ -0,0 +1,78 @@
+/*
+ * 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 "testing"
+
+func TestUniqueName(t *testing.T) {
+	type UniqueName struct {
+		Name         string
+		ExpectedName string
+		ActualName   string
+	}
+
+	nameList := []UniqueName{
+		{
+			Name:         "aaa",
+			ExpectedName: "aaa",
+		},
+		{
+			Name:         "aaa",
+			ExpectedName: "aaa0",
+		},
+		{
+			Name:         "aaa0",
+			ExpectedName: "aaa00",
+		},
+		{
+			Name:         "aaa0",
+			ExpectedName: "aaa01",
+		},
+		{
+			Name:         "aaa00",
+			ExpectedName: "aaa000",
+		},
+		{
+			Name:         "aaa",
+			ExpectedName: "aaa1",
+		},
+		{
+			Name:         "aaa",
+			ExpectedName: "aaa2",
+		},
+		{
+			Name:         "aaa",
+			ExpectedName: "aaa3",
+		},
+		{
+			Name:         "aaa",
+			ExpectedName: "aaa4",
+		},
+	}
+	for _, name := range nameList {
+		name.ActualName, _ = getUniqueName(name.Name, uniquePackageName)
+		if name.ActualName != name.ExpectedName {
+			t.Errorf("%s name expected unique name '%s', actually get '%s'", name.Name, name.ExpectedName, name.ActualName)
+		}
+	}
+	for _, name := range nameList {
+		name.ActualName, _ = getUniqueName(name.Name, uniqueMiddlewareName)
+		if name.ActualName != name.ExpectedName {
+			t.Errorf("%s name expected unique name '%s', actually get '%s'", name.Name, name.ExpectedName, name.ActualName)
+		}
+	}
+}
diff --git a/pkg/app/client/client_test.go b/pkg/app/client/client_test.go
index 3fbfb05..0efe406 100644
--- a/pkg/app/client/client_test.go
+++ b/pkg/app/client/client_test.go
@@ -43,11 +43,13 @@ package client
 
 import (
 	"context"
+	"crypto/tls"
 	"encoding/base64"
 	"errors"
 	"fmt"
 	"io"
 	"io/ioutil"
+	"math/rand"
 	"net"
 	"net/http"
 	"net/http/httptest"
@@ -67,6 +69,7 @@ import (
 	errs "github.com/cloudwego/hertz/pkg/common/errors"
 	"github.com/cloudwego/hertz/pkg/network"
 	"github.com/cloudwego/hertz/pkg/network/dialer"
+	"github.com/cloudwego/hertz/pkg/network/netpoll"
 	"github.com/cloudwego/hertz/pkg/network/standard"
 	"github.com/cloudwego/hertz/pkg/protocol"
 	"github.com/cloudwego/hertz/pkg/protocol/consts"
@@ -93,9 +96,7 @@ func TestCloseIdleConnections(t *testing.T) {
 	}()
 	time.Sleep(time.Millisecond * 500)
 
-	c, _ := NewClient(WithDialFunc(func(addr string) (network.Conn, error) {
-		return dialer.DialConnection(opt.Network, opt.Addr, time.Second, nil)
-	}))
+	c, _ := NewClient(WithDialer(newMockDialerWithCustomFunc(opt.Network, opt.Addr, 1*time.Second, nil)))
 
 	if _, _, err := c.Get(context.Background(), nil, "http://google.com"); err != nil {
 		t.Fatal(err)
@@ -139,9 +140,7 @@ func TestClientInvalidURI(t *testing.T) {
 	}()
 	time.Sleep(time.Millisecond * 500)
 
-	c, _ := NewClient(WithDialFunc(func(addr string) (network.Conn, error) {
-		return dialer.DialConnection(opt.Network, opt.Addr, time.Second, nil)
-	}))
+	c, _ := NewClient(WithDialer(newMockDialerWithCustomFunc(opt.Network, opt.Addr, 1*time.Second, nil)))
 	req, res := protocol.AcquireRequest(), protocol.AcquireResponse()
 	defer func() {
 		protocol.ReleaseRequest(req)
@@ -175,9 +174,7 @@ func TestClientGetWithBody(t *testing.T) {
 	}()
 	time.Sleep(time.Millisecond * 500)
 
-	c, _ := NewClient(WithDialFunc(func(addr string) (network.Conn, error) {
-		return dialer.DialConnection(opt.Network, opt.Addr, time.Second, nil)
-	}))
+	c, _ := NewClient(WithDialer(newMockDialerWithCustomFunc(opt.Network, opt.Addr, 1*time.Second, nil)))
 	req, res := protocol.AcquireRequest(), protocol.AcquireResponse()
 	defer func() {
 		protocol.ReleaseRequest(req)
@@ -220,9 +217,7 @@ func TestClientURLAuth(t *testing.T) {
 	}()
 	time.Sleep(time.Millisecond * 500)
 
-	c, _ := NewClient(WithDialFunc(func(addr string) (network.Conn, error) {
-		return dialer.DialConnection(opt.Network, opt.Addr, time.Second, nil)
-	}))
+	c, _ := NewClient(WithDialer(newMockDialerWithCustomFunc(opt.Network, opt.Addr, 1*time.Second, nil)))
 	for up, expected := range cases {
 		req := protocol.AcquireRequest()
 		req.Header.SetMethod(consts.MethodGet)
@@ -254,9 +249,7 @@ func TestClientNilResp(t *testing.T) {
 	}()
 	time.Sleep(time.Millisecond * 500)
 
-	c, _ := NewClient(WithDialFunc(func(addr string) (network.Conn, error) {
-		return dialer.DialConnection(opt.Network, opt.Addr, time.Second, nil)
-	}))
+	c, _ := NewClient(WithDialer(newMockDialerWithCustomFunc(opt.Network, opt.Addr, 1*time.Second, nil)))
 
 	req := protocol.AcquireRequest()
 	req.Header.SetMethod(consts.MethodGet)
@@ -279,9 +272,7 @@ func TestClientParseConn(t *testing.T) {
 	go engine.Run()
 	time.Sleep(time.Millisecond * 500)
 
-	c, _ := NewClient(WithDialFunc(func(addr string) (network.Conn, error) {
-		return dialer.DialConnection(opt.Network, opt.Addr, time.Second, nil)
-	}))
+	c, _ := NewClient(WithDialer(newMockDialerWithCustomFunc(opt.Network, opt.Addr, 1*time.Second, nil)))
 	req, res := protocol.AcquireRequest(), protocol.AcquireResponse()
 	defer func() {
 		protocol.ReleaseRequest(req)
@@ -322,9 +313,7 @@ func TestClientPostArgs(t *testing.T) {
 		engine.Close()
 	}()
 	time.Sleep(time.Millisecond * 500)
-	c, _ := NewClient(WithDialFunc(func(addr string) (network.Conn, error) {
-		return dialer.DialConnection(opt.Network, opt.Addr, time.Second, nil)
-	}))
+	c, _ := NewClient(WithDialer(newMockDialerWithCustomFunc(opt.Network, opt.Addr, 1*time.Second, nil)))
 	req, res := protocol.AcquireRequest(), protocol.AcquireResponse()
 	defer func() {
 		protocol.ReleaseRequest(req)
@@ -367,9 +356,7 @@ func TestClientHeaderCase(t *testing.T) {
 	}()
 	time.Sleep(time.Second)
 
-	c, _ := NewClient(WithDialFunc(func(addr string) (network.Conn, error) {
-		return dialer.DialConnection(opt.Network, opt.Addr, time.Second, nil)
-	}), WithDisableHeaderNamesNormalizing(true))
+	c, _ := NewClient(WithDialer(newMockDialerWithCustomFunc(opt.Network, opt.Addr, time.Second, nil)), WithDisableHeaderNamesNormalizing(true))
 	code, body, err := c.Get(context.Background(), nil, "http://example.com")
 	if err != nil {
 		t.Error(err)
@@ -404,12 +391,10 @@ func TestClientReadTimeout(t *testing.T) {
 		ClientOptions: &http1.ClientOptions{
 			ReadTimeout:               time.Second * 4,
 			MaxIdempotentCallAttempts: 1,
-			Dial: func(addr string) (network.Conn, error) {
-				return dialer.DialConnection(opt.Network, opt.Addr, time.Second, nil)
-			},
+			Dialer:                    standard.NewDialer(),
+			Addr:                      opt.Addr,
 		},
 	}
-	dialer.SetDialer(standard.NewDialer())
 
 	req := protocol.AcquireRequest()
 	res := protocol.AcquireResponse()
@@ -472,9 +457,7 @@ func TestClientDefaultUserAgent(t *testing.T) {
 	}()
 	time.Sleep(time.Millisecond * 500)
 
-	c, _ := NewClient(WithDialFunc(func(addr string) (network.Conn, error) {
-		return dialer.DialConnection(opt.Network, opt.Addr, time.Second, nil)
-	}))
+	c, _ := NewClient(WithDialer(newMockDialerWithCustomFunc(opt.Network, opt.Addr, 1*time.Second, nil)))
 	req := protocol.AcquireRequest()
 	res := protocol.AcquireResponse()
 
@@ -509,9 +492,7 @@ func TestClientSetUserAgent(t *testing.T) {
 	time.Sleep(time.Millisecond * 500)
 
 	userAgent := "I'm not hertz"
-	c, _ := NewClient(WithDialFunc(func(addr string) (network.Conn, error) {
-		return dialer.DialConnection(opt.Network, opt.Addr, time.Second, nil)
-	}), WithName(userAgent))
+	c, _ := NewClient(WithDialer(newMockDialerWithCustomFunc(opt.Network, opt.Addr, time.Second, nil)), WithName(userAgent))
 	req := protocol.AcquireRequest()
 	res := protocol.AcquireResponse()
 
@@ -540,9 +521,7 @@ func TestClientNoUserAgent(t *testing.T) {
 		engine.Close()
 	}()
 	time.Sleep(time.Millisecond * 500)
-	c, _ := NewClient(WithDialFunc(func(addr string) (network.Conn, error) {
-		return dialer.DialConnection(opt.Network, opt.Addr, time.Second, nil)
-	}), WithNoDefaultUserAgentHeader(true))
+	c, _ := NewClient(WithDialer(newMockDialerWithCustomFunc(opt.Network, opt.Addr, time.Second, nil)), WithDialTimeout(1*time.Second), WithNoDefaultUserAgentHeader(true))
 
 	req := protocol.AcquireRequest()
 	res := protocol.AcquireResponse()
@@ -625,9 +604,7 @@ func TestClientDoWithCustomHeaders(t *testing.T) {
 	time.Sleep(time.Millisecond * 500)
 
 	// make sure that the client sends all the request headers and body.
-	c, _ := NewClient(WithDialFunc(func(addr string) (network.Conn, error) {
-		return dialer.DialConnection(opt.Network, opt.Addr, time.Second, nil)
-	}))
+	c, _ := NewClient(WithDialer(newMockDialerWithCustomFunc(opt.Network, opt.Addr, 1*time.Second, nil)))
 
 	var req protocol.Request
 	req.Header.SetMethod(consts.MethodPost)
@@ -670,9 +647,7 @@ func TestClientDoTimeoutDisablePathNormalizing(t *testing.T) {
 		engine.Close()
 	}()
 	time.Sleep(time.Millisecond * 500)
-	c, _ := NewClient(WithDialFunc(func(addr string) (network.Conn, error) {
-		return dialer.DialConnection(opt.Network, opt.Addr, time.Second, nil)
-	}), WithDisablePathNormalizing(true))
+	c, _ := NewClient(WithDialer(newMockDialerWithCustomFunc(opt.Network, opt.Addr, time.Second, nil)), WithDisablePathNormalizing(true))
 
 	urlWithEncodedPath := "http://example.com/encoded/Y%2BY%2FY%3D/stuff"
 
@@ -714,10 +689,8 @@ func TestHostClientPendingRequests(t *testing.T) {
 
 	c := &http1.HostClient{
 		ClientOptions: &http1.ClientOptions{
-			Addr: "foobar",
-			Dial: func(addr string) (network.Conn, error) {
-				return dialer.DialConnection(opt.Network, opt.Addr, time.Second, nil)
-			},
+			Addr:   "foobar",
+			Dialer: newMockDialerWithCustomFunc(opt.Network, opt.Addr, time.Second, nil),
 		},
 	}
 
@@ -807,10 +780,8 @@ func TestHostClientMaxConnsWithDeadline(t *testing.T) {
 
 	c := &http1.HostClient{
 		ClientOptions: &http1.ClientOptions{
-			Addr: "foobar",
-			Dial: func(addr string) (network.Conn, error) {
-				return dialer.DialConnection(opt.Network, opt.Addr, time.Second, nil)
-			},
+			Addr:     "foobar",
+			Dialer:   newMockDialerWithCustomFunc(opt.Network, opt.Addr, time.Second, nil),
 			MaxConns: 1,
 		},
 	}
@@ -878,10 +849,8 @@ func TestHostClientMaxConnDuration(t *testing.T) {
 
 	c := &http1.HostClient{
 		ClientOptions: &http1.ClientOptions{
-			Addr: "foobar",
-			Dial: func(addr string) (network.Conn, error) {
-				return dialer.DialConnection(opt.Network, opt.Addr, time.Second, nil)
-			},
+			Addr:            "foobar",
+			Dialer:          newMockDialerWithCustomFunc(opt.Network, opt.Addr, time.Second, nil),
 			MaxConnDuration: 10 * time.Millisecond,
 		},
 	}
@@ -927,10 +896,9 @@ func TestHostClientMultipleAddrs(t *testing.T) {
 	c := &http1.HostClient{
 		ClientOptions: &http1.ClientOptions{
 			Addr: "foo,bar,baz",
-			Dial: func(addr string) (network.Conn, error) {
+			Dialer: newMockDialerWithCustomFunc(opt.Network, opt.Addr, 1*time.Second, func(network, addr string, timeout time.Duration, tlsConfig *tls.Config) {
 				dialsCount[addr]++
-				return dialer.DialConnection(opt.Network, opt.Addr, time.Second, nil)
-			},
+			}),
 		},
 	}
 
@@ -993,10 +961,8 @@ func TestClientFollowRedirects(t *testing.T) {
 
 	c := &http1.HostClient{
 		ClientOptions: &http1.ClientOptions{
-			Addr: "xxx",
-			Dial: func(addr string) (network.Conn, error) {
-				return dialer.DialConnection(opt.Network, opt.Addr, time.Second, nil)
-			},
+			Addr:   "xxx",
+			Dialer: newMockDialerWithCustomFunc(opt.Network, opt.Addr, 1*time.Second, nil),
 		},
 	}
 
@@ -1089,10 +1055,8 @@ func TestHostClientMaxConnWaitTimeoutSuccess(t *testing.T) {
 
 	c := &http1.HostClient{
 		ClientOptions: &http1.ClientOptions{
-			Addr: "foobar",
-			Dial: func(addr string) (network.Conn, error) {
-				return dialer.DialConnection(opt.Network, opt.Addr, time.Second, nil)
-			},
+			Addr:               "foobar",
+			Dialer:             newMockDialerWithCustomFunc(opt.Network, opt.Addr, time.Second, nil),
 			MaxConns:           1,
 			MaxConnWaitTimeout: 200 * time.Millisecond,
 		},
@@ -1160,10 +1124,8 @@ func TestHostClientMaxConnWaitTimeoutError(t *testing.T) {
 
 	c := &http1.HostClient{
 		ClientOptions: &http1.ClientOptions{
-			Addr: "foobar",
-			Dial: func(addr string) (network.Conn, error) {
-				return dialer.DialConnection(opt.Network, opt.Addr, time.Second, nil)
-			},
+			Addr:               "foobar",
+			Dialer:             newMockDialerWithCustomFunc(opt.Network, opt.Addr, time.Second, nil),
 			MaxConns:           1,
 			MaxConnWaitTimeout: 10 * time.Millisecond,
 		},
@@ -1889,38 +1851,31 @@ func TestClientReadResponseBodyStreamWithDoubleRequest(t *testing.T) {
 	}
 }
 
-func TestClientDomainPort(t *testing.T) {
-	opt := config.NewOptions([]config.Option{})
-	opt.Addr = "unix-test-10021"
-	opt.Network = "unix"
-	engine := route.NewEngine(opt)
-	go engine.Run()
-	defer func() {
-		engine.Close()
-	}()
-	expectedAddrMap := map[string]struct{}{
-		"example1.com:443":  {},
-		"example2.com:8888": {},
-		"example3.com:80":   {},
-	}
-	time.Sleep(time.Millisecond * 500)
-	c, _ := NewClient(WithDialFunc(func(addr string) (network.Conn, error) {
-		if _, ok := expectedAddrMap[addr]; !ok {
-			t.Fatalf("not expected addr:%s", addr)
-		}
-		return dialer.DialConnection(opt.Network, opt.Addr, time.Second, nil)
-	}))
+type mockDialer struct {
+	network.Dialer
+	customDialerFunc func(network, address string, timeout time.Duration, tlsConfig *tls.Config)
+	network          string
+	address          string
+	timeout          time.Duration
+}
 
-	_, _, err := c.Get(context.Background(), nil, "https://example1.com")
-	if err != nil {
-		t.Fatal(err)
-	}
-	_, _, err = c.Get(context.Background(), nil, "https://example2.com:8888")
-	if err != nil {
-		t.Fatal(err)
+func (m *mockDialer) DialConnection(network, address string, timeout time.Duration, tlsConfig *tls.Config) (conn network.Conn, err error) {
+	if m.customDialerFunc != nil {
+		m.customDialerFunc(network, address, timeout, tlsConfig)
 	}
-	_, _, err = c.Get(context.Background(), nil, "http://example3.com")
-	if err != nil {
-		t.Fatal(err)
+	return m.Dialer.DialConnection(m.network, m.address, m.timeout, tlsConfig)
+}
+
+func newMockDialerWithCustomFunc(network, address string, timeout time.Duration, f func(network, address string, timeout time.Duration, tlsConfig *tls.Config)) network.Dialer {
+	dialer := standard.NewDialer()
+	if rand.Intn(2) == 0 {
+		dialer = netpoll.NewDialer()
+	}
+	return &mockDialer{
+		Dialer:           dialer,
+		customDialerFunc: f,
+		network:          network,
+		address:          address,
+		timeout:          timeout,
 	}
 }
diff --git a/pkg/app/context_test.go b/pkg/app/context_test.go
index 0a4202b..c83ee6e 100644
--- a/pkg/app/context_test.go
+++ b/pkg/app/context_test.go
@@ -919,3 +919,15 @@ func TestBindAndValidate(t *testing.T) {
 		t.Fatalf("unexpected nil, expected an error")
 	}
 }
+
+func TestRequestContext_SetCookie(t *testing.T) {
+	c := NewContext(0)
+	c.SetCookie("user", "hertz", 1, "/", "localhost", protocol.CookieSameSiteLaxMode, true, true)
+	assert.DeepEqual(t, "user=hertz; max-age=1; domain=localhost; path=/; HttpOnly; secure; SameSite=Lax", c.Response.Header.Get("Set-Cookie"))
+}
+
+func TestRequestContext_SetCookiePathEmpty(t *testing.T) {
+	c := NewContext(0)
+	c.SetCookie("user", "hertz", 1, "", "localhost", protocol.CookieSameSiteDisabled, true, true)
+	assert.DeepEqual(t, "user=hertz; max-age=1; domain=localhost; path=/; HttpOnly; secure", c.Response.Header.Get("Set-Cookie"))
+}
diff --git a/pkg/app/server/option_test.go b/pkg/app/server/option_test.go
index 4f3a885..f45eb22 100644
--- a/pkg/app/server/option_test.go
+++ b/pkg/app/server/option_test.go
@@ -56,6 +56,7 @@ func TestOptions(t *testing.T) {
 		WithALPN(true),
 		WithTraceLevel(stats.LevelDisabled),
 		WithRegistry(nil, info),
+		WithAutoReloadRender(true, 5*time.Second),
 	})
 	assert.DeepEqual(t, opt.ReadTimeout, time.Second)
 	assert.DeepEqual(t, opt.IdleTimeout, time.Second)
@@ -79,6 +80,8 @@ func TestOptions(t *testing.T) {
 	assert.DeepEqual(t, opt.TraceLevel, stats.LevelDisabled)
 	assert.DeepEqual(t, opt.RegistryInfo, info)
 	assert.DeepEqual(t, opt.Registry, nil)
+	assert.DeepEqual(t, opt.AutoReloadRender, true)
+	assert.DeepEqual(t, opt.AutoReloadInterval, 5*time.Second)
 }
 
 func TestDefaultOptions(t *testing.T) {
@@ -103,5 +106,8 @@ func TestDefaultOptions(t *testing.T) {
 	assert.DeepEqual(t, opt.ReadBufferSize, 4096)
 	assert.DeepEqual(t, opt.ALPN, false)
 	assert.DeepEqual(t, opt.Registry, registry.NoopRegistry)
+	assert.DeepEqual(t, opt.AutoReloadRender, false)
 	assert.Assert(t, opt.RegistryInfo == nil)
+	assert.DeepEqual(t, opt.AutoReloadRender, false)
+	assert.DeepEqual(t, opt.AutoReloadInterval, time.Duration(0))
 }
diff --git a/pkg/app/server/render/html_test.go b/pkg/app/server/render/html_test.go
new file mode 100644
index 0000000..86fa981
--- /dev/null
+++ b/pkg/app/server/render/html_test.go
@@ -0,0 +1,73 @@
+/*
+ * 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 render
+
+import (
+	"os"
+	"testing"
+	"time"
+)
+
+func TestHTMLDebug_StartChecker_timer(t *testing.T) {
+	render := &HTMLDebug{RefreshInterval: 2 * time.Second}
+	select {
+	case <-render.reloadCh:
+		t.Fatalf("should not be triggered")
+	default:
+	}
+	render.startChecker()
+	select {
+	case <-time.After(50 * time.Millisecond):
+		t.Fatalf("should be triggered immediately")
+	case <-render.reloadCh:
+
+	}
+	select {
+	// some ci servers have poor computing power, so we add some extra time here.
+	case <-time.After(2*time.Second + 50*time.Millisecond):
+		t.Fatalf("should be triggered before 2 seconds")
+	case <-render.reloadCh:
+		t.Logf("paas")
+	}
+}
+
+func TestHTMLDebug_StartChecker_fs_watcher(t *testing.T) {
+	f, _ := os.CreateTemp("./", "test.tmpl")
+	defer func() {
+		f.Close()
+		os.Remove(f.Name())
+	}()
+	render := &HTMLDebug{Files: []string{f.Name()}}
+	select {
+	case <-render.reloadCh:
+		t.Fatalf("should not be triggered")
+	default:
+	}
+	render.startChecker()
+	f.Write([]byte("hello"))
+
+	select {
+	case <-time.After(50 * time.Millisecond):
+		t.Fatalf("should be triggered immediately")
+	case <-render.reloadCh:
+	}
+	select {
+	case <-render.reloadCh:
+		t.Fatalf("should not be triggered")
+	default:
+	}
+}
diff --git a/pkg/common/utils/path_test.go b/pkg/common/utils/path_test.go
index 5c0d539..475e439 100644
--- a/pkg/common/utils/path_test.go
+++ b/pkg/common/utils/path_test.go
@@ -98,26 +98,20 @@ func TestPathCleanPath(t *testing.T) {
 
 // The Function AddMissingPort can only add the missed port, don't consider the other error case.
 func TestPathAddMissingPort(t *testing.T) {
-	isTLS := true
-	notTLS := false
-	ipAddress := "127.0.0.1"
-	expectedNormalHTTPAddress := "127.0.0.1:80"
-	expectedNormalHTTPsAddress := "127.0.0.1:443"
-	httpAddress := AddMissingPort(ipAddress, notTLS)
-	httpsAddress := AddMissingPort(ipAddress, isTLS)
-
-	if httpAddress != expectedNormalHTTPAddress {
-		t.Fatalf("Unexpected address: %s. Expecting address: %s", httpAddress, expectedNormalHTTPAddress)
-	}
-	if httpsAddress != expectedNormalHTTPsAddress {
-		t.Fatalf("Unexpected address: %s. Expecting address: %s", httpsAddress, expectedNormalHTTPsAddress)
-	}
-
-	normalAddress := "127.0.0.1:80"
-	expectedNormalAddress := "127.0.0.1:80"
-	addAddress := AddMissingPort(normalAddress, notTLS)
-
-	if addAddress != expectedNormalAddress {
-		t.Fatalf("Unexpected address: %s. Expecting address: %s", addAddress, expectedNormalAddress)
+	ipList := []string{"127.0.0.1", "111.111.1.1", "[0:0:0:0:0:ffff:192.1.56.10]", "[0:0:0:0:0:ffff:c0a8:101]", "www.foobar.com"}
+	for _, ip := range ipList {
+		if AddMissingPort(ip, true) != ip+":443" {
+			t.Fatalf("Unexpected address: %s. Expecting address: %s", AddMissingPort(ip, true), ip+":443")
+		}
+		if AddMissingPort(ip, false) != ip+":80" {
+			t.Fatalf("Unexpected address: %s. Expecting address: %s", AddMissingPort(ip, false), ip+":80")
+		}
+		customizedPort := ":8080"
+		if AddMissingPort(ip+customizedPort, true) != ip+customizedPort {
+			t.Fatalf("Unexpected address: %s. Expecting address: %s", AddMissingPort(ip+customizedPort, false), ip+customizedPort)
+		}
+		if AddMissingPort(ip+customizedPort, false) != ip+customizedPort {
+			t.Fatalf("Unexpected address: %s. Expecting address: %s", AddMissingPort(ip+customizedPort, true), ip+customizedPort)
+		}
 	}
 }
diff --git a/pkg/protocol/http1/client_test.go b/pkg/protocol/http1/client_test.go
index f334889..ae0871a 100644
--- a/pkg/protocol/http1/client_test.go
+++ b/pkg/protocol/http1/client_test.go
@@ -44,9 +44,11 @@ package http1
 import (
 	"bytes"
 	"context"
+	"crypto/tls"
 	"errors"
 	"fmt"
 	"io/ioutil"
+	"net"
 	"strings"
 	"sync"
 	"sync/atomic"
@@ -55,6 +57,7 @@ import (
 
 	errs "github.com/cloudwego/hertz/pkg/common/errors"
 	"github.com/cloudwego/hertz/pkg/common/test/mock"
+	"github.com/cloudwego/hertz/pkg/network"
 	"github.com/cloudwego/hertz/pkg/protocol"
 	"github.com/cloudwego/hertz/pkg/protocol/consts"
 	"github.com/cloudwego/hertz/pkg/protocol/http1/resp"
@@ -71,8 +74,10 @@ func TestHostClientMaxConnWaitTimeoutWithEarlierDeadline(t *testing.T) {
 
 	c := &HostClient{
 		ClientOptions: &ClientOptions{
-			Addr:               "foobar",
-			Dial:               mock.SlowReadDialer,
+			Addr: "foobar",
+			Dialer: newSlowConnDialer(func(network, addr string) (network.Conn, error) {
+				return mock.SlowReadDialer(addr)
+			}),
 			MaxConns:           1,
 			MaxConnWaitTimeout: 50 * time.Millisecond,
 		},
@@ -198,3 +203,23 @@ func testContinueReadResponseBodyStream(t *testing.T, header, body string, maxBo
 		t.Fatalf("should left %d bytes in original reader. got %q", bytesLeftInReader, len(left))
 	}
 }
+
+func newSlowConnDialer(dialer func(network, addr string) (network.Conn, error)) network.Dialer {
+	return &mockDialer{customDialConn: dialer}
+}
+
+type mockDialer struct {
+	customDialConn func(network, addr string) (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)
+}
+
+func (m *mockDialer) 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) {
+	return nil, nil
+}
diff --git a/pkg/protocol/http1/req/header_test.go b/pkg/protocol/http1/req/header_test.go
index 45115c4..500b745 100644
--- a/pkg/protocol/http1/req/header_test.go
+++ b/pkg/protocol/http1/req/header_test.go
@@ -203,7 +203,7 @@ func TestRequestHeaderCookie(t *testing.T) {
 	h.SetCookie("привет", "мир")
 
 	if string(h.Cookie("foo")) != "bar" {
-		t.Fatalf("Unexpected cookie value %q. Exepcted %q", h.Cookie("foo"), "bar")
+		t.Fatalf("Unexpected cookie value %q. Expected %q", h.Cookie("foo"), "bar")
 	}
 	if string(h.Cookie("привет")) != "мир" {
 		t.Fatalf("Unexpected cookie value %q. Expected %q", h.Cookie("привет"), "мир")
@@ -226,7 +226,7 @@ func TestRequestHeaderCookie(t *testing.T) {
 	}
 
 	if !bytes.Equal(h1.Cookie("foo"), h.Cookie("foo")) {
-		t.Fatalf("Unexpected cookie value %q. Exepcted %q", h1.Cookie("foo"), h.Cookie("foo"))
+		t.Fatalf("Unexpected cookie value %q. Expected %q", h1.Cookie("foo"), h.Cookie("foo"))
 	}
 	h1.DelCookie("foo")
 	if len(h1.Cookie("foo")) > 0 {
diff --git a/pkg/route/engine_test.go b/pkg/route/engine_test.go
index 451ea35..9b99180 100644
--- a/pkg/route/engine_test.go
+++ b/pkg/route/engine_test.go
@@ -58,6 +58,9 @@ import (
 	errs "github.com/cloudwego/hertz/pkg/common/errors"
 	"github.com/cloudwego/hertz/pkg/common/test/assert"
 	"github.com/cloudwego/hertz/pkg/common/test/mock"
+	"github.com/cloudwego/hertz/pkg/network"
+	"github.com/cloudwego/hertz/pkg/network/netpoll"
+	"github.com/cloudwego/hertz/pkg/network/standard"
 )
 
 func TestNew_Engine(t *testing.T) {
@@ -361,6 +364,75 @@ func TestRenderHtml(t *testing.T) {
 	assert.DeepEqual(t, "text/html; charset=utf-8", rr.Header().Get("Content-Type"))
 }
 
+func TestTransporterName(t *testing.T) {
+	SetTransporter(netpoll.NewTransporter)
+	assert.DeepEqual(t, "netpoll", GetTransporterName())
+
+	SetTransporter(standard.NewTransporter)
+	assert.DeepEqual(t, "standard", GetTransporterName())
+
+	SetTransporter(func(options *config.Options) network.Transporter {
+		return &mockTransporter{}
+	})
+	assert.DeepEqual(t, "route", GetTransporterName())
+}
+
+type mockTransporter struct{}
+
+func (m *mockTransporter) ListenAndServe(onData network.OnData) (err error) {
+	panic("implement me")
+}
+
+func (m *mockTransporter) Close() error {
+	panic("implement me")
+}
+
+func (m *mockTransporter) Shutdown(ctx context.Context) error {
+	panic("implement me")
+}
+
+func TestRenderHtmlOfGlobWithAutoRender(t *testing.T) {
+	opt := config.NewOptions([]config.Option{})
+	opt.AutoReloadRender = true
+	e := NewEngine(opt)
+	e.Delims("{[{", "}]}")
+	e.SetFuncMap(template.FuncMap{
+		"formatAsDate": formatAsDate,
+	})
+	e.LoadHTMLGlob("../common/testdata/template/htmltemplate.html")
+	e.GET("/templateName", func(c context.Context, ctx *app.RequestContext) {
+		ctx.HTML(http.StatusOK, "htmltemplate.html", map[string]interface{}{
+			"now": time.Date(2017, 0o7, 0o1, 0, 0, 0, 0, time.UTC),
+		})
+	})
+	rr := performRequest(e, "GET", "/templateName")
+	b, _ := ioutil.ReadAll(rr.Body)
+	assert.DeepEqual(t, 200, rr.Code)
+	assert.DeepEqual(t, []byte("<h1>Date: 2017/07/01</h1>"), b)
+	assert.DeepEqual(t, "text/html; charset=utf-8", rr.Header().Get("Content-Type"))
+}
+
+func TestRenderHtmlOfFilesWithAutoRender(t *testing.T) {
+	opt := config.NewOptions([]config.Option{})
+	opt.AutoReloadRender = true
+	e := NewEngine(opt)
+	e.Delims("{[{", "}]}")
+	e.SetFuncMap(template.FuncMap{
+		"formatAsDate": formatAsDate,
+	})
+	e.LoadHTMLFiles("../common/testdata/template/htmltemplate.html")
+	e.GET("/templateName", func(c context.Context, ctx *app.RequestContext) {
+		ctx.HTML(http.StatusOK, "htmltemplate.html", map[string]interface{}{
+			"now": time.Date(2017, 0o7, 0o1, 0, 0, 0, 0, time.UTC),
+		})
+	})
+	rr := performRequest(e, "GET", "/templateName")
+	b, _ := ioutil.ReadAll(rr.Body)
+	assert.DeepEqual(t, 200, rr.Code)
+	assert.DeepEqual(t, []byte("<h1>Date: 2017/07/01</h1>"), b)
+	assert.DeepEqual(t, "text/html; charset=utf-8", rr.Header().Get("Content-Type"))
+}
+
 type mockConn struct{}
 
 func (m *mockConn) ReadBinary(n int) (p []byte, err error) {