Repository: go-kratos/kratos
Pull request #2411 (2022-09-28 09:59:53)

fix: call `defer` in `for` loop
<!--
🎉 Thanks for sending a pull request to Kratos! Here are some tips for you:

1. If this is your first time contributing to Kratos, please read our contribution guide: https://go-kratos.dev/en/docs/community/contribution/
2. Ensure you have added or ran the appropriate tests and lint for your PR, please use `make lint` and `make test` before filing your PR, use `make clean` to tidy your go mod.
3. If the PR is unfinished, you may need to mark it as a WIP(Work In Progress) PR or draft PR
4. Please use a semantic commits format title, such as `<type>[optional scope]: <description>`, see: https://go-kratos.dev/docs/community/contribution#type
5. at the same time, please note that similar work should be submitted in one PR as far as possible to reduce the workload of reviewers. Do not split a work into multiple PR unless it should.
-->

<!--
🎉 感谢您向 Kratos 发送 PR 请求！以下是一些提示：
如果这是你第一次为 Kratos 贡献，请阅读我们的贡献指南：https://go-kratos.dev/en/docs/community/contribution/
2、确保您已经为您的 PR 添加或运行了适当的测试和lint，请在提交PR之前使用“make lint”和“make test”，使用“make clean”整理您的 go.mod。
3、如果请购单未完成，您可能需要将其标记为 WIP（在制品）PR 或 draft PR
4、请使用语义提交格式标题，如“<类型>[可选范围]：<说明>`，请参阅：https://go-kratos.dev/docs/community/contribution#type
5. 同时请注意，同类的工作请尽量在一个PR中提交，以减轻 review 者的工作负担，不要把一项工作拆分成很多个PR，除非它应该这样做。
-->

<!--
-->

#### Description (what this PR does / why we need it):
<!--
* The description should include the motivation for this PR or contrast this with previous behavior
-->
Resource leakage may occur. Call 'defer' in the 'for' loop.
case: https://go.dev/doc/effective_go#defer

#### Which issue(s) this PR fixes (resolves / be part of):
<!--
* Automatically closes linked issue when PR is merged.
* If your PR is not fully resolved the issue, please use `part of #<issue number>` instead.

Usage: `fixes/resolves #<issue number>`, or `fixes/resolves (paste link of issue)`.
-->


#### Other special notes for the reviewers:
<!--
* Somethings that need extra attention for the reviewers
* Some additional notes, TODO list, etc.
-->


codecov-commenter: # [Codecov](https://codecov.io/gh/go-kratos/kratos/pull/2411?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos) Report
> Merging [#2411](https://codecov.io/gh/go-kratos/kratos/pull/2411?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos) (41ca554) into [main](https://codecov.io/gh/go-kratos/kratos/commit/0ecc2b422f280cddcfaae2ff63b81aec2f04cb69?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos) (0ecc2b4) will **not change** coverage.
> The diff coverage is `n/a`.

```diff
@@           Coverage Diff           @@
##             main    #2411   +/-   ##
=======================================
  Coverage   85.31%   85.31%           
=======================================
  Files          88       88           
  Lines        3765     3765           
=======================================
  Hits         3212     3212           
  Misses        386      386           
  Partials      167      167           
```



Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos)


Patch:
diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml
index 552fef4a..d2a527c6 100644
--- a/.github/workflows/go.yml
+++ b/.github/workflows/go.yml
@@ -13,7 +13,7 @@ jobs:
   build:
     strategy:
       matrix:
-        go: [1.16, 1.17, 1.18]
+        go: [1.16, 1.17, 1.18, 1.19]
     name: build & test
     runs-on: ubuntu-latest
     services:
diff --git a/.golangci.yml b/.golangci.yml
index 441db5ee..06ee200c 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -31,6 +31,7 @@ linters:
     - varcheck
     - whitespace
     - wastedassign
+    - unconvert
 
   # don't enable:
   # - asciicheck
@@ -66,3 +67,5 @@ linters-settings:
   gocyclo:
     # recommend 10-20
     min-complexity: 50
+  goimports:
+    local-prefixes: github.com/go-kratos/kratos # Put imports beginning with prefix after 3rd-party packages
diff --git a/api/metadata/server.go b/api/metadata/server.go
index 39e468ec..3600fc14 100644
--- a/api/metadata/server.go
+++ b/api/metadata/server.go
@@ -14,7 +14,7 @@ import (
 	"google.golang.org/grpc/status"
 	"google.golang.org/protobuf/proto"
 	"google.golang.org/protobuf/reflect/protodesc"
-	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+	"google.golang.org/protobuf/reflect/protoreflect"
 	"google.golang.org/protobuf/reflect/protoregistry"
 	dpb "google.golang.org/protobuf/types/descriptorpb"
 
diff --git a/cmd/kratos/internal/base/vcs_url.go b/cmd/kratos/internal/base/vcs_url.go
index ef76db30..6d3aef12 100644
--- a/cmd/kratos/internal/base/vcs_url.go
+++ b/cmd/kratos/internal/base/vcs_url.go
@@ -8,7 +8,7 @@ import (
 )
 
 var (
-	scpSyntaxRe = regexp.MustCompile(`^([a-zA-Z0-9_]+)@([a-zA-Z0-9._-]+):(.*)$`)
+	scpSyntaxRe = regexp.MustCompile(`^(\w+)@([\w.-]+):(.*)$`)
 	scheme      = []string{"git", "https", "http", "git+ssh", "ssh", "file", "ftp", "ftps"}
 )
 
diff --git a/cmd/kratos/version.go b/cmd/kratos/version.go
index 9d205cbc..a616f715 100644
--- a/cmd/kratos/version.go
+++ b/cmd/kratos/version.go
@@ -1,4 +1,4 @@
 package main
 
 // release is the current kratos tool version.
-const release = "v2.5.0"
+const release = "v2.5.1"
diff --git a/cmd/protoc-gen-go-errors/version.go b/cmd/protoc-gen-go-errors/version.go
index 71fced3e..8061323f 100644
--- a/cmd/protoc-gen-go-errors/version.go
+++ b/cmd/protoc-gen-go-errors/version.go
@@ -1,4 +1,4 @@
 package main
 
 // release is the current protoc-gen-go-errors version.
-const release = "v2.5.0"
+const release = "v2.5.1"
diff --git a/cmd/protoc-gen-go-http/http.go b/cmd/protoc-gen-go-http/http.go
index d8827722..a9eff9a3 100644
--- a/cmd/protoc-gen-go-http/http.go
+++ b/cmd/protoc-gen-go-http/http.go
@@ -213,7 +213,7 @@ func buildPathVars(path string) (res map[string]*string) {
 	if strings.HasSuffix(path, "/") {
 		fmt.Fprintf(os.Stderr, "\u001B[31mWARN\u001B[m: Path %s should not end with \"/\" \n", path)
 	}
-	pattern := regexp.MustCompile(`(?i){([a-z\.0-9_\s]*)=?([^{}]*)}`)
+	pattern := regexp.MustCompile(`(?i){([a-z.0-9_\s]*)=?([^{}]*)}`)
 	matches := pattern.FindAllStringSubmatch(path, -1)
 	res = make(map[string]*string, len(matches))
 	for _, m := range matches {
diff --git a/cmd/protoc-gen-go-http/version.go b/cmd/protoc-gen-go-http/version.go
index 065e7dd5..25b73c2f 100644
--- a/cmd/protoc-gen-go-http/version.go
+++ b/cmd/protoc-gen-go-http/version.go
@@ -1,4 +1,4 @@
 package main
 
 // release is the current protoc-gen-go-http version.
-const release = "v2.5.0"
+const release = "v2.5.1"
diff --git a/contrib/config/consul/config.go b/contrib/config/consul/config.go
index dca8a88e..0404801b 100644
--- a/contrib/config/consul/config.go
+++ b/contrib/config/consul/config.go
@@ -28,9 +28,9 @@ func WithContext(ctx context.Context) Option {
 
 // WithPath is config path
 func WithPath(p string) Option {
-	return Option(func(o *options) {
+	return func(o *options) {
 		o.path = p
-	})
+	}
 }
 
 type source struct {
diff --git a/contrib/config/etcd/README.md b/contrib/config/etcd/README.md
index d7668745..d69edd82 100644
--- a/contrib/config/etcd/README.md
+++ b/contrib/config/etcd/README.md
@@ -30,12 +30,16 @@ if err != nil {
 c := config.New(config.WithSource(source))
 defer c.Close()
 
+// load sources before get
+if err := c.Load(); err != nil {
+    log.Fatalln(err)
+}
+
 // acquire config value
 foo, err := c.Value("/app-config").String()
 if err != nil {
-    log.Println(err)
+    log.Fatalln(err)
 }
-println(foo)
 
+log.Println(foo)
 ```
-
diff --git a/contrib/encoding/msgpack/go.mod b/contrib/encoding/msgpack/go.mod
index 5d78c21b..5b6a1f7e 100644
--- a/contrib/encoding/msgpack/go.mod
+++ b/contrib/encoding/msgpack/go.mod
@@ -3,7 +3,7 @@ module github.com/go-kratos/kratos/contrib/encoding/msgpack/v2
 go 1.16
 
 require (
-	github.com/go-kratos/kratos/v2 v2.5.0
+	github.com/go-kratos/kratos/v2 v2.5.1
 	github.com/vmihailenco/msgpack/v5 v5.3.5
 )
 
diff --git a/contrib/log/aliyun/aliyun.go b/contrib/log/aliyun/aliyun.go
index a5e57ca6..351de69d 100644
--- a/contrib/log/aliyun/aliyun.go
+++ b/contrib/log/aliyun/aliyun.go
@@ -2,6 +2,7 @@ package aliyun
 
 import (
 	"encoding/json"
+	"fmt"
 	"strconv"
 	"time"
 
@@ -9,7 +10,7 @@ import (
 	"github.com/aliyun/aliyun-log-go-sdk/producer"
 	"google.golang.org/protobuf/proto"
 
-	log "github.com/go-kratos/kratos/v2/log"
+	"github.com/go-kratos/kratos/v2/log"
 )
 
 // Logger see more detail https://github.com/aliyun/aliyun-log-go-sdk
@@ -164,6 +165,8 @@ func toString(v interface{}) string {
 		key = strconv.FormatBool(v)
 	case []byte:
 		key = string(v)
+	case fmt.Stringer:
+		key = v.String()
 	default:
 		newValue, _ := json.Marshal(v)
 		key = string(newValue)
diff --git a/contrib/log/aliyun/go.mod b/contrib/log/aliyun/go.mod
index efe1b827..73edfb73 100644
--- a/contrib/log/aliyun/go.mod
+++ b/contrib/log/aliyun/go.mod
@@ -4,7 +4,7 @@ go 1.16
 
 require (
 	github.com/aliyun/aliyun-log-go-sdk v0.1.37
-	github.com/go-kratos/kratos/v2 v2.5.0
+	github.com/go-kratos/kratos/v2 v2.5.1
 	google.golang.org/protobuf v1.28.1
 )
 
diff --git a/contrib/log/fluent/go.mod b/contrib/log/fluent/go.mod
index b316425a..8b3a4efc 100644
--- a/contrib/log/fluent/go.mod
+++ b/contrib/log/fluent/go.mod
@@ -5,7 +5,7 @@ go 1.16
 require (
 	github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect
 	github.com/fluent/fluent-logger-golang v1.9.0
-	github.com/go-kratos/kratos/v2 v2.5.0
+	github.com/go-kratos/kratos/v2 v2.5.1
 	github.com/kr/pretty v0.3.0 // indirect
 	github.com/tinylib/msgp v1.1.6 // indirect
 )
diff --git a/contrib/log/logrus/go.mod b/contrib/log/logrus/go.mod
index c41e4ecc..5d923e1c 100644
--- a/contrib/log/logrus/go.mod
+++ b/contrib/log/logrus/go.mod
@@ -3,7 +3,7 @@ module github.com/go-kratos/kratos/contrib/log/logrus/v2
 go 1.16
 
 require (
-	github.com/go-kratos/kratos/v2 v2.5.0
+	github.com/go-kratos/kratos/v2 v2.5.1
 	github.com/sirupsen/logrus v1.8.1
 )
 
diff --git a/contrib/log/tencent/go.mod b/contrib/log/tencent/go.mod
index 2d3fa7dc..d5a758f0 100644
--- a/contrib/log/tencent/go.mod
+++ b/contrib/log/tencent/go.mod
@@ -3,7 +3,7 @@ module github.com/go-kratos/kratos/contrib/log/tencent/v2
 go 1.16
 
 require (
-	github.com/go-kratos/kratos/v2 v2.5.0
+	github.com/go-kratos/kratos/v2 v2.5.1
 	github.com/tencentcloud/tencentcloud-cls-sdk-go v1.0.2
 	google.golang.org/protobuf v1.28.0
 )
diff --git a/contrib/log/tencent/tencent.go b/contrib/log/tencent/tencent.go
index 40cc98cd..8becf788 100644
--- a/contrib/log/tencent/tencent.go
+++ b/contrib/log/tencent/tencent.go
@@ -9,7 +9,7 @@ import (
 	cls "github.com/tencentcloud/tencentcloud-cls-sdk-go"
 	"google.golang.org/protobuf/proto"
 
-	log "github.com/go-kratos/kratos/v2/log"
+	"github.com/go-kratos/kratos/v2/log"
 )
 
 type Logger interface {
@@ -115,7 +115,7 @@ func NewLogger(options ...Option) (Logger, error) {
 	}, nil
 }
 
-// toString any type to string
+// toString convert any type to string
 func toString(v interface{}) string {
 	var key string
 	if v == nil {
@@ -146,6 +146,12 @@ func toString(v interface{}) string {
 		key = strconv.FormatInt(v, 10)
 	case uint64:
 		key = strconv.FormatUint(v, 10)
+	case string:
+		key = v
+	case bool:
+		key = strconv.FormatBool(v)
+	case []byte:
+		key = string(v)
 	case fmt.Stringer:
 		key = v.String()
 	default:
diff --git a/contrib/log/zap/go.mod b/contrib/log/zap/go.mod
index 0a8e74f6..8f8d5a9a 100644
--- a/contrib/log/zap/go.mod
+++ b/contrib/log/zap/go.mod
@@ -3,7 +3,7 @@ module github.com/go-kratos/kratos/contrib/log/zap/v2
 go 1.16
 
 require (
-	github.com/go-kratos/kratos/v2 v2.5.0
+	github.com/go-kratos/kratos/v2 v2.5.1
 	go.uber.org/zap v1.23.0
 )
 
diff --git a/contrib/metrics/datadog/go.mod b/contrib/metrics/datadog/go.mod
index 206ccf4e..78f50039 100644
--- a/contrib/metrics/datadog/go.mod
+++ b/contrib/metrics/datadog/go.mod
@@ -5,7 +5,7 @@ go 1.16
 require (
 	github.com/DataDog/datadog-go v4.8.3+incompatible
 	github.com/Microsoft/go-winio v0.5.2 // indirect
-	github.com/go-kratos/kratos/v2 v2.5.0
+	github.com/go-kratos/kratos/v2 v2.5.1
 )
 
 replace github.com/go-kratos/kratos/v2 => ../../../
diff --git a/contrib/metrics/prometheus/go.mod b/contrib/metrics/prometheus/go.mod
index c6a1b5df..462f13f1 100644
--- a/contrib/metrics/prometheus/go.mod
+++ b/contrib/metrics/prometheus/go.mod
@@ -3,7 +3,7 @@ module github.com/go-kratos/kratos/contrib/metrics/prometheus/v2
 go 1.16
 
 require (
-	github.com/go-kratos/kratos/v2 v2.5.0
+	github.com/go-kratos/kratos/v2 v2.5.1
 	github.com/prometheus/client_golang v1.12.2
 	github.com/prometheus/common v0.37.0
 )
diff --git a/contrib/opensergo/go.mod b/contrib/opensergo/go.mod
index 0b920de4..1c96a271 100644
--- a/contrib/opensergo/go.mod
+++ b/contrib/opensergo/go.mod
@@ -3,7 +3,7 @@ module github.com/go-kratos/kratos/contrib/opensergo/v2
 go 1.17
 
 require (
-	github.com/go-kratos/kratos/v2 v2.5.0
+	github.com/go-kratos/kratos/v2 v2.5.1
 	github.com/opensergo/opensergo-go v0.0.0-20220331070310-e5b01fee4d1c
 	golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2
 	google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd
diff --git a/contrib/registry/consul/go.mod b/contrib/registry/consul/go.mod
index 5a0965b4..e78723c7 100644
--- a/contrib/registry/consul/go.mod
+++ b/contrib/registry/consul/go.mod
@@ -3,7 +3,7 @@ module github.com/go-kratos/kratos/contrib/registry/consul/v2
 go 1.16
 
 require (
-	github.com/go-kratos/kratos/v2 v2.5.0
+	github.com/go-kratos/kratos/v2 v2.5.1
 	github.com/hashicorp/consul/api v1.14.0
 )
 
diff --git a/contrib/registry/consul/registry.go b/contrib/registry/consul/registry.go
index d1c47d77..b2f2a3fe 100644
--- a/contrib/registry/consul/registry.go
+++ b/contrib/registry/consul/registry.go
@@ -7,14 +7,14 @@ import (
 	"sync/atomic"
 	"time"
 
-	"github.com/go-kratos/kratos/v2/registry"
-
 	"github.com/hashicorp/consul/api"
+
+	"github.com/go-kratos/kratos/v2/registry"
 )
 
 var (
-	_ registry.Registrar = &Registry{}
-	_ registry.Discovery = &Registry{}
+	_ registry.Registrar = (*Registry)(nil)
+	_ registry.Discovery = (*Registry)(nil)
 )
 
 // Option is consul registry option.
diff --git a/contrib/registry/discovery/go.mod b/contrib/registry/discovery/go.mod
index 407af47a..40b61cb0 100644
--- a/contrib/registry/discovery/go.mod
+++ b/contrib/registry/discovery/go.mod
@@ -3,7 +3,7 @@ module github.com/go-kratos/kratos/contrib/registry/discovery/v2
 go 1.16
 
 require (
-	github.com/go-kratos/kratos/v2 v2.5.0
+	github.com/go-kratos/kratos/v2 v2.5.1
 	github.com/go-resty/resty/v2 v2.7.0
 	github.com/pkg/errors v0.9.1
 )
diff --git a/contrib/registry/etcd/go.mod b/contrib/registry/etcd/go.mod
index 952e8a55..1976385e 100644
--- a/contrib/registry/etcd/go.mod
+++ b/contrib/registry/etcd/go.mod
@@ -3,7 +3,7 @@ module github.com/go-kratos/kratos/contrib/registry/etcd/v2
 go 1.16
 
 require (
-	github.com/go-kratos/kratos/v2 v2.5.0
+	github.com/go-kratos/kratos/v2 v2.5.1
 	go.etcd.io/etcd/client/v3 v3.5.4
 	google.golang.org/grpc v1.46.2
 )
diff --git a/contrib/registry/etcd/registry.go b/contrib/registry/etcd/registry.go
index 5592f62d..ca858c99 100644
--- a/contrib/registry/etcd/registry.go
+++ b/contrib/registry/etcd/registry.go
@@ -12,8 +12,8 @@ import (
 )
 
 var (
-	_ registry.Registrar = &Registry{}
-	_ registry.Discovery = &Registry{}
+	_ registry.Registrar = (*Registry)(nil)
+	_ registry.Discovery = (*Registry)(nil)
 )
 
 // Option is etcd registry option.
diff --git a/contrib/registry/eureka/client.go b/contrib/registry/eureka/client.go
index f2b4e96f..020a9a90 100644
--- a/contrib/registry/eureka/client.go
+++ b/contrib/registry/eureka/client.go
@@ -14,14 +14,14 @@ import (
 )
 
 const (
-	statusUp            = "UP"
-	statusDown          = "DOWN"
-	statusOutOfServeice = "OUT_OF_SERVICE"
-	heartbeatRetry      = 3
-	maxIdleConns        = 100
-	heartbeatTime       = 10
-	httpTimeout         = 3
-	refreshTime         = 30
+	statusUp           = "UP"
+	statusDown         = "DOWN"
+	statusOutOfService = "OUT_OF_SERVICE"
+	heartbeatRetry     = 3
+	maxIdleConns       = 100
+	heartbeatTime      = 10
+	httpTimeout        = 3
+	refreshTime        = 30
 )
 
 type Endpoint struct {
@@ -184,7 +184,7 @@ func (e *Client) FetchInstance(ctx context.Context, instanceID string) (m Instan
 }
 
 func (e *Client) Out(ctx context.Context, appID, instanceID string) error {
-	return e.do(ctx, "PUT", []string{"apps", appID, instanceID, fmt.Sprintf("status?value=%s", statusOutOfServeice)}, nil, nil)
+	return e.do(ctx, "PUT", []string{"apps", appID, instanceID, fmt.Sprintf("status?value=%s", statusOutOfService)}, nil, nil)
 }
 
 func (e *Client) Down(ctx context.Context, appID, instanceID string) error {
@@ -268,8 +268,8 @@ func (e *Client) Heartbeat(ep Endpoint) {
 }
 
 func (e *Client) cancelHeartbeat(appID string) {
-	defer e.lock.Unlock()
 	e.lock.Lock()
+	defer e.lock.Unlock()
 	if ch, ok := e.keepalive[appID]; ok {
 		ch <- struct{}{}
 	}
@@ -307,39 +307,50 @@ func (e *Client) buildAPI(currentTimes int, params ...string) string {
 	return strings.Join(params, "/")
 }
 
-func (e *Client) do(ctx context.Context, method string, params []string, input io.Reader, output interface{}) error {
-	for i := 0; i < e.maxRetry; i++ {
-		request, err := http.NewRequest(method, e.buildAPI(i, params...), input)
+func (e *Client) request(ctx context.Context, method string, params []string, input io.Reader, output interface{}, i int) (bool, error) {
+	request, err := http.NewRequestWithContext(ctx, method, e.buildAPI(i, params...), input)
+	if err != nil {
+		return false, err
+	}
+	request.Header.Add("User-Agent", "go-eureka-client")
+	request.Header.Add("Accept", "application/json;charset=UTF-8")
+	request.Header.Add("Content-Type", "application/json;charset=UTF-8")
+	resp, err := e.client.Do(request)
+	if err != nil {
+		return true, err
+	}
+	defer func() {
+		_, _ = io.Copy(io.Discard, resp.Body)
+		_ = resp.Body.Close()
+	}()
+
+	if output != nil && resp.StatusCode/100 == 2 {
+		data, err := io.ReadAll(resp.Body)
 		if err != nil {
-			return err
+			return false, err
 		}
-		request = request.WithContext(ctx)
-		request.Header.Add("User-Agent", "go-eureka-client")
-		request.Header.Add("Accept", "application/json;charset=UTF-8")
-		request.Header.Add("Content-Type", "application/json;charset=UTF-8")
-		resp, err := e.client.Do(request)
+		err = json.Unmarshal(data, output)
 		if err != nil {
-			continue
-		}
-		defer func() {
-			_, _ = io.Copy(io.Discard, resp.Body)
-			resp.Body.Close()
-		}()
-
-		if output != nil && resp.StatusCode/100 == 2 {
-			data, err := io.ReadAll(resp.Body)
-			if err != nil {
-				return err
-			}
-			if err = json.Unmarshal(data, output); err != nil {
-				return err
-			}
+			return false, err
 		}
+	}
 
-		if resp.StatusCode >= http.StatusBadRequest {
-			return fmt.Errorf("response Error %d", resp.StatusCode)
-		}
+	if resp.StatusCode >= http.StatusBadRequest {
+		return false, fmt.Errorf("response Error %d", resp.StatusCode)
+	}
 
+	return false, nil
+}
+
+func (e *Client) do(ctx context.Context, method string, params []string, input io.Reader, output interface{}) error {
+	for i := 0; i < e.maxRetry; i++ {
+		retry, err := e.request(ctx, method, params, input, output, i)
+		if retry {
+			continue
+		}
+		if err != nil {
+			return err
+		}
 		return nil
 	}
 	return fmt.Errorf("retry after %d times", e.maxRetry)
diff --git a/contrib/registry/eureka/register.go b/contrib/registry/eureka/register.go
index 9588df64..242ec910 100644
--- a/contrib/registry/eureka/register.go
+++ b/contrib/registry/eureka/register.go
@@ -11,8 +11,8 @@ import (
 )
 
 var (
-	_ registry.Registrar = &Registry{}
-	_ registry.Discovery = &Registry{}
+	_ registry.Registrar = (*Registry)(nil)
+	_ registry.Discovery = (*Registry)(nil)
 )
 
 type Option func(o *Registry)
diff --git a/contrib/registry/kubernetes/go.mod b/contrib/registry/kubernetes/go.mod
index cad07df0..3f20affb 100644
--- a/contrib/registry/kubernetes/go.mod
+++ b/contrib/registry/kubernetes/go.mod
@@ -3,7 +3,7 @@ module github.com/go-kratos/kratos/contrib/registry/kubernetes/v2
 go 1.16
 
 require (
-	github.com/go-kratos/kratos/v2 v2.5.0
+	github.com/go-kratos/kratos/v2 v2.5.1
 	github.com/json-iterator/go v1.1.12
 	k8s.io/api v0.24.3
 	k8s.io/apimachinery v0.24.3
diff --git a/contrib/registry/nacos/go.mod b/contrib/registry/nacos/go.mod
index bdfd037e..d6048dfc 100644
--- a/contrib/registry/nacos/go.mod
+++ b/contrib/registry/nacos/go.mod
@@ -3,7 +3,7 @@ module github.com/go-kratos/kratos/contrib/registry/nacos/v2
 go 1.16
 
 require (
-	github.com/go-kratos/kratos/v2 v2.5.0
+	github.com/go-kratos/kratos/v2 v2.5.1
 	github.com/nacos-group/nacos-sdk-go v1.0.9
 )
 
diff --git a/contrib/registry/polaris/go.mod b/contrib/registry/polaris/go.mod
index 2789a991..306fe927 100644
--- a/contrib/registry/polaris/go.mod
+++ b/contrib/registry/polaris/go.mod
@@ -3,7 +3,7 @@ module github.com/go-kratos/kratos/contrib/registry/polaris/v2
 go 1.16
 
 require (
-	github.com/go-kratos/kratos/v2 v2.5.0
+	github.com/go-kratos/kratos/v2 v2.5.1
 	github.com/polarismesh/polaris-go v1.1.0
 )
 
diff --git a/contrib/registry/servicecomb/registry.go b/contrib/registry/servicecomb/registry.go
index 7a532396..894cf343 100644
--- a/contrib/registry/servicecomb/registry.go
+++ b/contrib/registry/servicecomb/registry.go
@@ -9,10 +9,10 @@ import (
 	"github.com/go-chassis/cari/discovery"
 	"github.com/go-chassis/cari/pkg/errsvc"
 	"github.com/go-chassis/sc-client"
+	"github.com/gofrs/uuid"
 
 	"github.com/go-kratos/kratos/v2/log"
 	"github.com/go-kratos/kratos/v2/registry"
-	"github.com/gofrs/uuid"
 )
 
 func init() {
diff --git a/contrib/registry/servicecomb/watcher.go b/contrib/registry/servicecomb/watcher.go
index 2b2117e8..b1f4fccf 100644
--- a/contrib/registry/servicecomb/watcher.go
+++ b/contrib/registry/servicecomb/watcher.go
@@ -2,9 +2,9 @@ package servicecomb
 
 import (
 	"github.com/go-chassis/sc-client"
+	"golang.org/x/net/context"
 
 	"github.com/go-kratos/kratos/v2/registry"
-	"golang.org/x/net/context"
 )
 
 var _ registry.Watcher = (*Watcher)(nil)
diff --git a/contrib/registry/zookeeper/go.mod b/contrib/registry/zookeeper/go.mod
index a149d20a..09c35a1b 100644
--- a/contrib/registry/zookeeper/go.mod
+++ b/contrib/registry/zookeeper/go.mod
@@ -3,7 +3,7 @@ module github.com/go-kratos/kratos/contrib/registry/zookeeper/v2
 go 1.16
 
 require (
-	github.com/go-kratos/kratos/v2 v2.5.0
+	github.com/go-kratos/kratos/v2 v2.5.1
 	github.com/go-zookeeper/zk v1.0.3
 	golang.org/x/sync v0.0.0-20220513210516-0976fa681c29
 )
diff --git a/contrib/registry/zookeeper/register.go b/contrib/registry/zookeeper/register.go
index 771a5bbc..8f21745d 100644
--- a/contrib/registry/zookeeper/register.go
+++ b/contrib/registry/zookeeper/register.go
@@ -12,8 +12,8 @@ import (
 )
 
 var (
-	_ registry.Registrar = &Registry{}
-	_ registry.Discovery = &Registry{}
+	_ registry.Registrar = (*Registry)(nil)
+	_ registry.Discovery = (*Registry)(nil)
 )
 
 // Option is etcd registry option.
diff --git a/encoding/form/proto_decode.go b/encoding/form/proto_decode.go
index 47affee1..fcf46d46 100644
--- a/encoding/form/proto_decode.go
+++ b/encoding/form/proto_decode.go
@@ -99,6 +99,9 @@ func getDescriptorByFieldAndName(fields protoreflect.FieldDescriptors, fieldName
 }
 
 func populateField(fd protoreflect.FieldDescriptor, v protoreflect.Message, value string) error {
+	if value == "" {
+		return nil
+	}
 	val, err := parseField(fd, value)
 	if err != nil {
 		return fmt.Errorf("parsing field %q: %w", fd.FullName().Name(), err)
diff --git a/encoding/form/proto_encode.go b/encoding/form/proto_encode.go
index 28ebb0bc..94eac6ab 100644
--- a/encoding/form/proto_encode.go
+++ b/encoding/form/proto_encode.go
@@ -162,7 +162,7 @@ func encodeMessage(msgDescriptor protoreflect.MessageDescriptor, value protorefl
 	case "google.protobuf.DoubleValue", "google.protobuf.FloatValue", "google.protobuf.Int64Value", "google.protobuf.Int32Value",
 		"google.protobuf.UInt64Value", "google.protobuf.UInt32Value", "google.protobuf.BoolValue", "google.protobuf.StringValue":
 		fd := msgDescriptor.Fields()
-		v := value.Message().Get(fd.ByName(protoreflect.Name("value")))
+		v := value.Message().Get(fd.ByName("value"))
 		return fmt.Sprint(v.Interface()), nil
 	case fieldMaskFullName:
 		m, ok := value.Message().Interface().(*fieldmaskpb.FieldMask)
diff --git a/middleware/metadata/metadata.go b/middleware/metadata/metadata.go
index 5c29189f..da9e43a9 100644
--- a/middleware/metadata/metadata.go
+++ b/middleware/metadata/metadata.go
@@ -51,16 +51,19 @@ func Server(opts ...Option) middleware.Middleware {
 	}
 	return func(handler middleware.Handler) middleware.Handler {
 		return func(ctx context.Context, req interface{}) (reply interface{}, err error) {
-			if tr, ok := transport.FromServerContext(ctx); ok {
-				md := options.md.Clone()
-				header := tr.RequestHeader()
-				for _, k := range header.Keys() {
-					if options.hasPrefix(k) {
-						md.Set(k, header.Get(k))
-					}
+			tr, ok := transport.FromServerContext(ctx)
+			if !ok {
+				return handler(ctx, req)
+			}
+
+			md := options.md.Clone()
+			header := tr.RequestHeader()
+			for _, k := range header.Keys() {
+				if options.hasPrefix(k) {
+					md.Set(k, header.Get(k))
 				}
-				ctx = metadata.NewServerContext(ctx, md)
 			}
+			ctx = metadata.NewServerContext(ctx, md)
 			return handler(ctx, req)
 		}
 	}
@@ -76,25 +79,28 @@ func Client(opts ...Option) middleware.Middleware {
 	}
 	return func(handler middleware.Handler) middleware.Handler {
 		return func(ctx context.Context, req interface{}) (reply interface{}, err error) {
-			if tr, ok := transport.FromClientContext(ctx); ok {
-				header := tr.RequestHeader()
-				// x-md-local-
-				for k, v := range options.md {
+			tr, ok := transport.FromClientContext(ctx)
+			if !ok {
+				return handler(ctx, req)
+			}
+
+			header := tr.RequestHeader()
+			// x-md-local-
+			for k, v := range options.md {
+				header.Set(k, v)
+			}
+			if md, ok := metadata.FromClientContext(ctx); ok {
+				for k, v := range md {
 					header.Set(k, v)
 				}
-				if md, ok := metadata.FromClientContext(ctx); ok {
-					for k, v := range md {
+			}
+			// x-md-global-
+			if md, ok := metadata.FromServerContext(ctx); ok {
+				for k, v := range md {
+					if options.hasPrefix(k) {
 						header.Set(k, v)
 					}
 				}
-				// x-md-global-
-				if md, ok := metadata.FromServerContext(ctx); ok {
-					for k, v := range md {
-						if options.hasPrefix(k) {
-							header.Set(k, v)
-						}
-					}
-				}
 			}
 			return handler(ctx, req)
 		}
diff --git a/transport/grpc/balancer.go b/transport/grpc/balancer.go
index 948b736b..f42dc23b 100644
--- a/transport/grpc/balancer.go
+++ b/transport/grpc/balancer.go
@@ -1,13 +1,13 @@
 package grpc
 
 import (
-	"github.com/go-kratos/kratos/v2/registry"
-	"github.com/go-kratos/kratos/v2/selector"
-	"github.com/go-kratos/kratos/v2/transport"
-
 	"google.golang.org/grpc/balancer"
 	"google.golang.org/grpc/balancer/base"
 	"google.golang.org/grpc/metadata"
+
+	"github.com/go-kratos/kratos/v2/registry"
+	"github.com/go-kratos/kratos/v2/selector"
+	"github.com/go-kratos/kratos/v2/transport"
 )
 
 const (
@@ -15,8 +15,8 @@ const (
 )
 
 var (
-	_ base.PickerBuilder = &balancerBuilder{}
-	_ balancer.Picker    = &balancerPicker{}
+	_ base.PickerBuilder = (*balancerBuilder)(nil)
+	_ balancer.Picker    = (*balancerPicker)(nil)
 )
 
 func init() {
diff --git a/transport/http/binding/encode.go b/transport/http/binding/encode.go
index 9bcdbeef..72afd508 100644
--- a/transport/http/binding/encode.go
+++ b/transport/http/binding/encode.go
@@ -4,8 +4,9 @@ import (
 	"reflect"
 	"regexp"
 
-	"github.com/go-kratos/kratos/v2/encoding/form"
 	"google.golang.org/protobuf/proto"
+
+	"github.com/go-kratos/kratos/v2/encoding/form"
 )
 
 var reg = regexp.MustCompile(`{[\\.\w]+}`)
diff --git a/transport/http/codec.go b/transport/http/codec.go
index 20b1a8ec..da1b13e0 100644
--- a/transport/http/codec.go
+++ b/transport/http/codec.go
@@ -4,10 +4,14 @@ import (
 	"fmt"
 	"io"
 	"net/http"
+	"net/url"
 
 	"github.com/go-kratos/kratos/v2/encoding"
 	"github.com/go-kratos/kratos/v2/errors"
 	"github.com/go-kratos/kratos/v2/internal/httputil"
+	"github.com/go-kratos/kratos/v2/transport/http/binding"
+
+	"github.com/gorilla/mux"
 )
 
 // SupportPackageIsVersion1 These constants should not be referenced from any other code.
@@ -37,6 +41,21 @@ type EncodeResponseFunc func(http.ResponseWriter, *http.Request, interface{}) er
 // EncodeErrorFunc is encode error func.
 type EncodeErrorFunc func(http.ResponseWriter, *http.Request, error)
 
+// DefaultRequestVars decodes the request vars to object.
+func DefaultRequestVars(r *http.Request, v interface{}) error {
+	raws := mux.Vars(r)
+	vars := make(url.Values, len(raws))
+	for k, v := range raws {
+		vars[k] = []string{v}
+	}
+	return binding.BindQuery(vars, v)
+}
+
+// DefaultRequestQuery decodes the request vars to object.
+func DefaultRequestQuery(r *http.Request, v interface{}) error {
+	return binding.BindQuery(r.URL.Query(), v)
+}
+
 // DefaultRequestDecoder decodes the request body to object.
 func DefaultRequestDecoder(r *http.Request, v interface{}) error {
 	codec, ok := CodecForRequest(r, "Content-Type")
diff --git a/transport/http/context.go b/transport/http/context.go
index 347d7f2b..ab9a1d39 100644
--- a/transport/http/context.go
+++ b/transport/http/context.go
@@ -96,9 +96,9 @@ func (c *wrapper) Middleware(h middleware.Handler) middleware.Handler {
 	}
 	return middleware.Chain(c.router.srv.middleware.Match(c.req.URL.Path)...)(h)
 }
-func (c *wrapper) Bind(v interface{}) error      { return c.router.srv.dec(c.req, v) }
-func (c *wrapper) BindVars(v interface{}) error  { return binding.BindQuery(c.Vars(), v) }
-func (c *wrapper) BindQuery(v interface{}) error { return binding.BindQuery(c.Query(), v) }
+func (c *wrapper) Bind(v interface{}) error      { return c.router.srv.decBody(c.req, v) }
+func (c *wrapper) BindVars(v interface{}) error  { return c.router.srv.decVars(c.req, v) }
+func (c *wrapper) BindQuery(v interface{}) error { return c.router.srv.decQuery(c.req, v) }
 func (c *wrapper) BindForm(v interface{}) error  { return binding.BindForm(c.req, v) }
 func (c *wrapper) Returns(v interface{}, err error) error {
 	if err != nil {
diff --git a/transport/http/server.go b/transport/http/server.go
index db7d0a72..165bb99b 100644
--- a/transport/http/server.go
+++ b/transport/http/server.go
@@ -70,10 +70,24 @@ func Filter(filters ...FilterFunc) ServerOption {
 	}
 }
 
+// RequestVarsDecoder with request decoder.
+func RequestVarsDecoder(dec DecodeRequestFunc) ServerOption {
+	return func(o *Server) {
+		o.decVars = dec
+	}
+}
+
+// RequestQueryDecoder with request decoder.
+func RequestQueryDecoder(dec DecodeRequestFunc) ServerOption {
+	return func(o *Server) {
+		o.decQuery = dec
+	}
+}
+
 // RequestDecoder with request decoder.
 func RequestDecoder(dec DecodeRequestFunc) ServerOption {
 	return func(o *Server) {
-		o.dec = dec
+		o.decBody = dec
 	}
 }
 
@@ -114,6 +128,13 @@ func Listener(lis net.Listener) ServerOption {
 	}
 }
 
+// PathPrefix with mux's PathPrefix, router will replaced by a subrouter that start with prefix.
+func PathPrefix(prefix string) ServerOption {
+	return func(s *Server) {
+		s.router = s.router.PathPrefix(prefix).Subrouter()
+	}
+}
+
 // Server is an HTTP server wrapper.
 type Server struct {
 	*http.Server
@@ -126,7 +147,9 @@ type Server struct {
 	timeout     time.Duration
 	filters     []FilterFunc
 	middleware  matcher.Matcher
-	dec         DecodeRequestFunc
+	decVars     DecodeRequestFunc
+	decQuery    DecodeRequestFunc
+	decBody     DecodeRequestFunc
 	enc         EncodeResponseFunc
 	ene         EncodeErrorFunc
 	strictSlash bool
@@ -140,15 +163,18 @@ func NewServer(opts ...ServerOption) *Server {
 		address:     ":0",
 		timeout:     1 * time.Second,
 		middleware:  matcher.New(),
-		dec:         DefaultRequestDecoder,
+		decVars:     DefaultRequestVars,
+		decQuery:    DefaultRequestQuery,
+		decBody:     DefaultRequestDecoder,
 		enc:         DefaultResponseEncoder,
 		ene:         DefaultErrorEncoder,
 		strictSlash: true,
+		router:      mux.NewRouter(),
 	}
 	for _, o := range opts {
 		o(srv)
 	}
-	srv.router = mux.NewRouter().StrictSlash(srv.strictSlash)
+	srv.router.StrictSlash(srv.strictSlash)
 	srv.router.NotFoundHandler = http.DefaultServeMux
 	srv.router.MethodNotAllowedHandler = http.DefaultServeMux
 	srv.router.Use(srv.filter())
diff --git a/transport/http/transport.go b/transport/http/transport.go
index e7f53f6d..32f054ed 100644
--- a/transport/http/transport.go
+++ b/transport/http/transport.go
@@ -70,6 +70,16 @@ func SetOperation(ctx context.Context, op string) {
 	}
 }
 
+// RequestFromServerContext returns request from context.
+func RequestFromServerContext(ctx context.Context) (*http.Request, bool) {
+	if tr, ok := transport.FromServerContext(ctx); ok {
+		if tr, ok := tr.(*Transport); ok {
+			return tr.request, true
+		}
+	}
+	return nil, false
+}
+
 type headerCarrier http.Header
 
 // Get returns the value associated with the passed key.
diff --git a/version.go b/version.go
index d0afa603..2c8d6f46 100644
--- a/version.go
+++ b/version.go
@@ -1,4 +1,4 @@
 package kratos
 
 // Release is the current kratos version.
-const Release = "v2.5.0"
+const Release = "v2.5.1"


Test patch:
diff --git a/config/file/file_test.go b/config/file/file_test.go
index d3c55ad3..0eb2b24f 100644
--- a/config/file/file_test.go
+++ b/config/file/file_test.go
@@ -208,7 +208,7 @@ func TestConfig(t *testing.T) {
 func testConfig(t *testing.T, c config.Config) {
 	expected := map[string]interface{}{
 		"test.settings.int_key":      int64(1000),
-		"test.settings.float_key":    float64(1000.1),
+		"test.settings.float_key":    1000.1,
 		"test.settings.string_key":   "string_value",
 		"test.settings.duration_key": time.Duration(10000),
 		"test.server.addr":           "127.0.0.1",
diff --git a/config/value_test.go b/config/value_test.go
index 9f674538..68558bf8 100644
--- a/config/value_test.go
+++ b/config/value_test.go
@@ -70,7 +70,7 @@ func Test_atomicValue_Int(t *testing.T) {
 }
 
 func Test_atomicValue_Float(t *testing.T) {
-	vlist := []interface{}{"123123.1", float64(123123.1)}
+	vlist := []interface{}{"123123.1", 123123.1}
 	for _, x := range vlist {
 		v := atomicValue{}
 		v.Store(x)
@@ -78,7 +78,7 @@ func Test_atomicValue_Float(t *testing.T) {
 		if err != nil {
 			t.Fatal(`err is not nil`)
 		}
-		if b != float64(123123.1) {
+		if b != 123123.1 {
 			t.Fatal(`b is not equal to 123123.1`)
 		}
 	}
diff --git a/contrib/log/aliyun/aliyun_test.go b/contrib/log/aliyun/aliyun_test.go
index a5e1f080..e8e2624f 100644
--- a/contrib/log/aliyun/aliyun_test.go
+++ b/contrib/log/aliyun/aliyun_test.go
@@ -121,6 +121,7 @@ func TestToString(t *testing.T) {
 		{math.MaxInt64, "9223372036854775807"},
 		{uint64(math.MaxUint64), "18446744073709551615"},
 		{"abc", "abc"},
+		{false, "false"},
 		{[]byte("abc"), "abc"},
 	}
 	for _, test := range tests {
diff --git a/contrib/log/fluent/fluent_test.go b/contrib/log/fluent/fluent_test.go
index 5b7b3e17..41f7080d 100644
--- a/contrib/log/fluent/fluent_test.go
+++ b/contrib/log/fluent/fluent_test.go
@@ -11,18 +11,23 @@ import (
 )
 
 func TestMain(m *testing.M) {
+	listener := func(ln net.Listener) {
+		conn, err := ln.Accept()
+		if err != nil {
+			return
+		}
+		defer conn.Close()
+		_, err = io.ReadAll(conn)
+		if err != nil {
+			return
+		}
+	}
+
 	if ln, err := net.Listen("tcp", ":24224"); err == nil {
 		defer ln.Close()
 		go func() {
 			for {
-				conn, err := ln.Accept()
-				if err != nil {
-					return
-				}
-				defer conn.Close()
-				if _, err = io.ReadAll(conn); err != nil {
-					continue
-				}
+				listener(ln)
 			}
 		}()
 	}
diff --git a/contrib/registry/servicecomb/registry_test.go b/contrib/registry/servicecomb/registry_test.go
index 85afe306..4b4c2549 100644
--- a/contrib/registry/servicecomb/registry_test.go
+++ b/contrib/registry/servicecomb/registry_test.go
@@ -6,8 +6,9 @@ import (
 
 	pb "github.com/go-chassis/cari/discovery"
 	"github.com/go-chassis/sc-client"
-	"github.com/go-kratos/kratos/v2/registry"
 	"github.com/gofrs/uuid"
+
+	"github.com/go-kratos/kratos/v2/registry"
 )
 
 var r *Registry
diff --git a/encoding/xml/xml_test.go b/encoding/xml/xml_test.go
index 6c53c3dd..09e7d03b 100644
--- a/encoding/xml/xml_test.go
+++ b/encoding/xml/xml_test.go
@@ -25,7 +25,7 @@ func TestCodec_Marshal(t *testing.T) {
 		// Test value types
 		{Value: &Plain{true}, ExpectXML: `<Plain><V>true</V></Plain>`},
 		{Value: &Plain{false}, ExpectXML: `<Plain><V>false</V></Plain>`},
-		{Value: &Plain{int(42)}, ExpectXML: `<Plain><V>42</V></Plain>`},
+		{Value: &Plain{42}, ExpectXML: `<Plain><V>42</V></Plain>`},
 		{
 			Value: &NestedOrder{Field1: "C", Field2: "B", Field3: "A"},
 			ExpectXML: `<result>` +
diff --git a/middleware/auth/jwt/jwt_test.go b/middleware/auth/jwt/jwt_test.go
index 188744b0..711757bc 100644
--- a/middleware/auth/jwt/jwt_test.go
+++ b/middleware/auth/jwt/jwt_test.go
@@ -254,7 +254,7 @@ func TestServer(t *testing.T) {
 			}
 			if test.exceptErr == nil {
 				if testToken == nil {
-					t.Errorf("except testToken not nil, but got nil")
+					t.Fatal("except testToken not nil, but got nil")
 				}
 				_, ok := testToken.(jwt.MapClaims)
 				if !ok {
@@ -493,3 +493,26 @@ func TestClientMissKey(t *testing.T) {
 		}
 	})
 }
+
+func TestNewContextAndFromContext(t *testing.T) {
+	tests := []struct {
+		name   string
+		claims jwt.MapClaims
+	}{
+		{"val not nil", jwt.MapClaims{"name": "kratos"}},
+		{"val nil", nil},
+	}
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			ctx := NewContext(context.Background(), test.claims)
+
+			claims, ok := FromContext(ctx)
+			if !ok {
+				t.Fatal("ctx not found authKey{}")
+			}
+			if !reflect.DeepEqual(test.claims, claims) {
+				t.Errorf(`want: %s, got: %v`, test.claims, claims)
+			}
+		})
+	}
+}
diff --git a/middleware/logging/logging_test.go b/middleware/logging/logging_test.go
index 6ad1a02c..431d8901 100644
--- a/middleware/logging/logging_test.go
+++ b/middleware/logging/logging_test.go
@@ -111,7 +111,7 @@ func (d *dummyStringer) String() string {
 	return "my value"
 }
 
-func Test_extractArgs(t *testing.T) {
+func TestExtractArgs(t *testing.T) {
 	if extractArgs(&dummyStringer{field: ""}) != "my value" {
 		t.Errorf(`The stringified dummyStringer structure must be equal to "my value", %v given`, extractArgs(&dummyStringer{field: ""}))
 	}
@@ -120,3 +120,30 @@ func Test_extractArgs(t *testing.T) {
 		t.Errorf(`The stringified dummy structure must be equal to "&{field:value}", %v given`, extractArgs(&dummy{field: "value"}))
 	}
 }
+
+func TestExtractError(t *testing.T) {
+	tests := []struct {
+		name       string
+		err        error
+		wantLevel  log.Level
+		wantErrStr string
+	}{
+		{
+			"no error", nil, log.LevelInfo, "",
+		},
+		{
+			"error", errors.New("test error"), log.LevelError, "test error",
+		},
+	}
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			level, errStr := extractError(test.err)
+			if level != test.wantLevel {
+				t.Errorf("want: %d, got: %d", test.wantLevel, level)
+			}
+			if errStr != test.wantErrStr {
+				t.Errorf("want: %s, got: %s", test.wantErrStr, errStr)
+			}
+		})
+	}
+}
diff --git a/middleware/metadata/metadata_test.go b/middleware/metadata/metadata_test.go
index e46a94f5..b083f9c1 100644
--- a/middleware/metadata/metadata_test.go
+++ b/middleware/metadata/metadata_test.go
@@ -4,6 +4,7 @@ import (
 	"context"
 	"errors"
 	"net/http"
+	"reflect"
 	"testing"
 
 	"github.com/go-kratos/kratos/v2/metadata"
@@ -33,15 +34,18 @@ func (tr *testTransport) Operation() string               { return "" }
 func (tr *testTransport) RequestHeader() transport.Header { return tr.header }
 func (tr *testTransport) ReplyHeader() transport.Header   { return tr.header }
 
+var (
+	globalKey   = "x-md-global-key"
+	globalValue = "global-value"
+	localKey    = "x-md-local-key"
+	localValue  = "local-value"
+	customKey   = "x-md-local-custom"
+	customValue = "custom-value"
+	constKey    = "x-md-local-const"
+	constValue  = "x-md-local-const"
+)
+
 func TestSever(t *testing.T) {
-	var (
-		globalKey   = "x-md-global-key"
-		globalValue = "global-value"
-		localKey    = "x-md-local-key"
-		localValue  = "local-value"
-		constKey    = "x-md-local-const"
-		constValue  = "x-md-local-const"
-	)
 	hs := func(ctx context.Context, in interface{}) (interface{}, error) {
 		md, ok := metadata.FromServerContext(ctx)
 		if !ok {
@@ -75,16 +79,6 @@ func TestSever(t *testing.T) {
 }
 
 func TestClient(t *testing.T) {
-	var (
-		globalKey   = "x-md-global-key"
-		globalValue = "global-value"
-		localKey    = "x-md-local-key"
-		localValue  = "local-value"
-		customKey   = "x-md-local-custom"
-		customValue = "custom-value"
-		constKey    = "x-md-local-const"
-		constValue  = "x-md-local-const"
-	)
 	hs := func(ctx context.Context, in interface{}) (interface{}, error) {
 		tr, ok := transport.FromClientContext(ctx)
 		if !ok {
@@ -127,13 +121,52 @@ func TestClient(t *testing.T) {
 	}
 }
 
-func Test_WithPropagatedPrefix(t *testing.T) {
-	o := &options{
+func TestWithConstants(t *testing.T) {
+	md := metadata.Metadata{
+		constKey: constValue,
+	}
+	options := &options{
+		md: metadata.Metadata{
+			"override": "override",
+		},
+	}
+
+	WithConstants(md)(options)
+	if !reflect.DeepEqual(md, options.md) {
+		t.Errorf("want: %v, got: %v", md, options.md)
+	}
+}
+
+func TestOptions_WithPropagatedPrefix(t *testing.T) {
+	options := &options{
 		prefix: []string{"override"},
 	}
-	WithPropagatedPrefix("something", "another")(o)
+	prefixes := []string{"something", "another"}
 
-	if len(o.prefix) != 2 {
+	WithPropagatedPrefix(prefixes...)(options)
+	if !reflect.DeepEqual(prefixes, options.prefix) {
 		t.Error("The prefix must be overridden.")
 	}
 }
+
+func TestOptions_hasPrefix(t *testing.T) {
+	tests := []struct {
+		name    string
+		options *options
+		key     string
+		exists  bool
+	}{
+		{"exists key upper", &options{prefix: []string{"prefix"}}, "PREFIX_true", true},
+		{"exists key lower", &options{prefix: []string{"prefix"}}, "prefix_true", true},
+		{"not exists key upper", &options{prefix: []string{"prefix"}}, "false_PREFIX", false},
+		{"not exists key lower", &options{prefix: []string{"prefix"}}, "false_prefix", false},
+	}
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			exists := test.options.hasPrefix(test.key)
+			if test.exists != exists {
+				t.Errorf("key: '%sr', not exists prefixs: %v", test.key, test.options.prefix)
+			}
+		})
+	}
+}
diff --git a/transport/http/context_test.go b/transport/http/context_test.go
index b440e9df..daccccd9 100644
--- a/transport/http/context_test.go
+++ b/transport/http/context_test.go
@@ -12,9 +12,11 @@ import (
 	"time"
 )
 
+var testRouter = &Router{srv: NewServer()}
+
 func TestContextHeader(t *testing.T) {
 	w := wrapper{
-		router: nil,
+		router: testRouter,
 		req:    &http.Request{Header: map[string][]string{"name": {"kratos"}}},
 		res:    nil,
 		w:      responseWriter{},
@@ -27,7 +29,7 @@ func TestContextHeader(t *testing.T) {
 
 func TestContextForm(t *testing.T) {
 	w := wrapper{
-		router: nil,
+		router: testRouter,
 		req:    &http.Request{Header: map[string][]string{"name": {"kratos"}}, Method: "POST"},
 		res:    nil,
 		w:      responseWriter{},
@@ -38,7 +40,7 @@ func TestContextForm(t *testing.T) {
 	}
 
 	w = wrapper{
-		router: nil,
+		router: testRouter,
 		req:    &http.Request{Form: map[string][]string{"name": {"kratos"}}},
 		res:    nil,
 		w:      responseWriter{},
@@ -51,7 +53,7 @@ func TestContextForm(t *testing.T) {
 
 func TestContextQuery(t *testing.T) {
 	w := wrapper{
-		router: nil,
+		router: testRouter,
 		req:    &http.Request{URL: &url.URL{Scheme: "https", Host: "github.com", Path: "go-kratos/kratos", RawQuery: "page=1"}, Method: "POST"},
 		res:    nil,
 		w:      responseWriter{},
@@ -65,7 +67,7 @@ func TestContextQuery(t *testing.T) {
 func TestContextRequest(t *testing.T) {
 	req := &http.Request{Method: "POST"}
 	w := wrapper{
-		router: nil,
+		router: testRouter,
 		req:    req,
 		res:    nil,
 		w:      responseWriter{},
@@ -100,7 +102,7 @@ func TestContextResponse(t *testing.T) {
 
 func TestContextBindQuery(t *testing.T) {
 	w := wrapper{
-		router: nil,
+		router: testRouter,
 		req:    &http.Request{URL: &url.URL{Scheme: "https", Host: "go-kratos-dev", RawQuery: "page=2"}},
 		res:    nil,
 		w:      responseWriter{},
@@ -120,7 +122,7 @@ func TestContextBindQuery(t *testing.T) {
 
 func TestContextBindForm(t *testing.T) {
 	w := wrapper{
-		router: nil,
+		router: testRouter,
 		req:    &http.Request{URL: &url.URL{Scheme: "https", Host: "go-kratos-dev"}, Form: map[string][]string{"page": {"2"}}},
 		res:    nil,
 		w:      responseWriter{},
@@ -141,7 +143,7 @@ func TestContextBindForm(t *testing.T) {
 func TestContextResponseReturn(t *testing.T) {
 	writer := httptest.NewRecorder()
 	w := wrapper{
-		router: nil,
+		router: testRouter,
 		req:    nil,
 		res:    writer,
 		w:      responseWriter{},
@@ -174,7 +176,7 @@ func TestContextCtx(t *testing.T) {
 	req := &http.Request{Method: "POST"}
 	req = req.WithContext(ctx)
 	w := wrapper{
-		router: &Router{srv: &Server{enc: DefaultResponseEncoder}},
+		router: testRouter,
 		req:    req,
 		res:    nil,
 		w:      responseWriter{},
diff --git a/transport/http/server_test.go b/transport/http/server_test.go
index 0d4be88b..ff9b3918 100644
--- a/transport/http/server_test.go
+++ b/transport/http/server_test.go
@@ -316,8 +316,8 @@ func TestRequestDecoder(t *testing.T) {
 	o := &Server{}
 	v := func(*http.Request, interface{}) error { return nil }
 	RequestDecoder(v)(o)
-	if o.dec == nil {
-		t.Errorf("expected nil got %v", o.dec)
+	if o.decBody == nil {
+		t.Errorf("expected nil got %v", o.decBody)
 	}
 }