Repository: go-kratos/kratos
Pull request #2772 (2023-03-29 17:41:10)

refactor: metadata supports key corresponding to multiple values
Adding Add/Values func for transport and metadata layers, fixed tests a… #2771 

codecov-commenter: ## [Codecov](https://codecov.io/gh/go-kratos/kratos/pull/2772?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos) Report
> Merging [#2772](https://codecov.io/gh/go-kratos/kratos/pull/2772?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos) (98807a0) into [main](https://codecov.io/gh/go-kratos/kratos/commit/f03f5f89881ffa038265987a5830aeadbcfa7a87?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos) (f03f5f8) will **decrease** coverage by `0.17%`.
> The diff coverage is `67.56%`.

> :exclamation: Current head 98807a0 differs from pull request most recent head dcc58b3. Consider uploading reports for the commit dcc58b3 to get more accurate results

:mega: This organization is not using Codecov’s [GitHub App Integration](https://github.com/apps/codecov). We recommend you install it so Codecov can continue to function properly for your repositories. [Learn more](https://about.codecov.io/blog/codecov-is-updating-its-github-integration/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos)

```diff
@@            Coverage Diff             @@
##             main    #2772      +/-   ##
==========================================
- Coverage   84.75%   84.59%   -0.17%     
==========================================
  Files          88       88              
  Lines        3891     3915      +24     
==========================================
+ Hits         3298     3312      +14     
- Misses        419      428       +9     
- Partials      174      175       +1     
```


| [Impacted Files](https://codecov.io/gh/go-kratos/kratos/pull/2772?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos) | Coverage Δ | |
|---|---|---|
| [transport/grpc/transport.go](https://codecov.io/gh/go-kratos/kratos/pull/2772?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos#diff-dHJhbnNwb3J0L2dycGMvdHJhbnNwb3J0Lmdv) | `85.71% <0.00%> (-14.29%)` | :arrow_down: |
| [transport/http/transport.go](https://codecov.io/gh/go-kratos/kratos/pull/2772?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos#diff-dHJhbnNwb3J0L2h0dHAvdHJhbnNwb3J0Lmdv) | `75.00% <0.00%> (-9.38%)` | :arrow_down: |
| [transport/transport.go](https://codecov.io/gh/go-kratos/kratos/pull/2772?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos#diff-dHJhbnNwb3J0L3RyYW5zcG9ydC5nbw==) | `100.00% <ø> (ø)` | |
| [metadata/metadata.go](https://codecov.io/gh/go-kratos/kratos/pull/2772?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos#diff-bWV0YWRhdGEvbWV0YWRhdGEuZ28=) | `92.98% <77.77%> (-7.02%)` | :arrow_down: |
| [middleware/metadata/metadata.go](https://codecov.io/gh/go-kratos/kratos/pull/2772?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos#diff-bWlkZGxld2FyZS9tZXRhZGF0YS9tZXRhZGF0YS5nbw==) | `92.59% <100.00%> (+0.59%)` | :arrow_up: |

... and [1 file with indirect coverage changes](https://codecov.io/gh/go-kratos/kratos/pull/2772/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos)

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/contrib/config/consul/go.mod b/contrib/config/consul/go.mod
index 2425c79d..a767999a 100644
--- a/contrib/config/consul/go.mod
+++ b/contrib/config/consul/go.mod
@@ -6,7 +6,7 @@ require (
 	github.com/armon/go-metrics v0.3.10 // indirect
 	github.com/go-kratos/kratos/v2 v2.4.0
 	github.com/google/btree v1.0.0 // indirect
-	github.com/hashicorp/consul/api v1.19.1
+	github.com/hashicorp/consul/api v1.20.0
 	github.com/hashicorp/go-hclog v0.14.1 // indirect
 	github.com/hashicorp/go-immutable-radix v1.3.0 // indirect
 	github.com/hashicorp/go-msgpack v0.5.5 // indirect
diff --git a/contrib/config/consul/go.sum b/contrib/config/consul/go.sum
index 9e5549a2..1453cc50 100644
--- a/contrib/config/consul/go.sum
+++ b/contrib/config/consul/go.sum
@@ -91,8 +91,8 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
 github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
 github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
-github.com/hashicorp/consul/api v1.19.1 h1:GLeK1WD4VIRvt4wRhQKHFudztEkRb8pDs+uRiJgNwes=
-github.com/hashicorp/consul/api v1.19.1/go.mod h1:jAt316eYgWGNLJtxkMQrcqRpuDE/kFJdqkEFwRXFv8U=
+github.com/hashicorp/consul/api v1.20.0 h1:9IHTjNVSZ7MIwjlW3N3a7iGiykCMDpxZu8jsxFJh0yc=
+github.com/hashicorp/consul/api v1.20.0/go.mod h1:nR64eD44KQ59Of/ECwt2vUmIK2DKsDzAwTmwmLl8Wpo=
 github.com/hashicorp/consul/sdk v0.13.1 h1:EygWVWWMczTzXGpO93awkHFzfUka6hLYJ0qhETd+6lY=
 github.com/hashicorp/consul/sdk v0.13.1/go.mod h1:SW/mM4LbKfqmMvcFu8v+eiQQ7oitXEFeiBe9StxERb0=
 github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
diff --git a/contrib/log/aliyun/go.mod b/contrib/log/aliyun/go.mod
index f75a8148..1fcdfa34 100644
--- a/contrib/log/aliyun/go.mod
+++ b/contrib/log/aliyun/go.mod
@@ -5,7 +5,7 @@ go 1.16
 require (
 	github.com/aliyun/aliyun-log-go-sdk v0.1.43
 	github.com/go-kratos/kratos/v2 v2.6.1
-	google.golang.org/protobuf v1.28.1
+	google.golang.org/protobuf v1.30.0
 )
 
 replace (
diff --git a/contrib/log/aliyun/go.sum b/contrib/log/aliyun/go.sum
index b60a8d88..cd68d59f 100644
--- a/contrib/log/aliyun/go.sum
+++ b/contrib/log/aliyun/go.sum
@@ -481,8 +481,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0
 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
 google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
 google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
-google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
-google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
+google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
 gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/log/helper.go b/log/helper.go
index b01c947a..cc0cb8c9 100644
--- a/log/helper.go
+++ b/log/helper.go
@@ -14,8 +14,10 @@ type Option func(*Helper)
 
 // Helper is a logger helper.
 type Helper struct {
-	logger Logger
-	msgKey string
+	logger  Logger
+	msgKey  string
+	sprint  func(...interface{}) string
+	sprintf func(format string, a ...interface{}) string
 }
 
 // WithMessageKey with message key.
@@ -25,11 +27,27 @@ func WithMessageKey(k string) Option {
 	}
 }
 
+// WithSprint with sprint
+func WithSprint(sprint func(...interface{}) string) Option {
+	return func(opts *Helper) {
+		opts.sprint = sprint
+	}
+}
+
+// WithSprintf with sprintf
+func WithSprintf(sprintf func(format string, a ...interface{}) string) Option {
+	return func(opts *Helper) {
+		opts.sprintf = sprintf
+	}
+}
+
 // NewHelper new a logger helper.
 func NewHelper(logger Logger, opts ...Option) *Helper {
 	options := &Helper{
-		msgKey: DefaultMessageKey, // default message key
-		logger: logger,
+		msgKey:  DefaultMessageKey, // default message key
+		logger:  logger,
+		sprint:  fmt.Sprint,
+		sprintf: fmt.Sprintf,
 	}
 	for _, o := range opts {
 		o(options)
@@ -41,8 +59,10 @@ func NewHelper(logger Logger, opts ...Option) *Helper {
 // to ctx. The provided ctx must be non-nil.
 func (h *Helper) WithContext(ctx context.Context) *Helper {
 	return &Helper{
-		msgKey: h.msgKey,
-		logger: WithContext(ctx, h.logger),
+		msgKey:  h.msgKey,
+		logger:  WithContext(ctx, h.logger),
+		sprint:  h.sprint,
+		sprintf: h.sprintf,
 	}
 }
 
@@ -53,12 +73,12 @@ func (h *Helper) Log(level Level, keyvals ...interface{}) {
 
 // Debug logs a message at debug level.
 func (h *Helper) Debug(a ...interface{}) {
-	_ = h.logger.Log(LevelDebug, h.msgKey, fmt.Sprint(a...))
+	_ = h.logger.Log(LevelDebug, h.msgKey, h.sprint(a...))
 }
 
 // Debugf logs a message at debug level.
 func (h *Helper) Debugf(format string, a ...interface{}) {
-	_ = h.logger.Log(LevelDebug, h.msgKey, fmt.Sprintf(format, a...))
+	_ = h.logger.Log(LevelDebug, h.msgKey, h.sprintf(format, a...))
 }
 
 // Debugw logs a message at debug level.
@@ -68,12 +88,12 @@ func (h *Helper) Debugw(keyvals ...interface{}) {
 
 // Info logs a message at info level.
 func (h *Helper) Info(a ...interface{}) {
-	_ = h.logger.Log(LevelInfo, h.msgKey, fmt.Sprint(a...))
+	_ = h.logger.Log(LevelInfo, h.msgKey, h.sprint(a...))
 }
 
 // Infof logs a message at info level.
 func (h *Helper) Infof(format string, a ...interface{}) {
-	_ = h.logger.Log(LevelInfo, h.msgKey, fmt.Sprintf(format, a...))
+	_ = h.logger.Log(LevelInfo, h.msgKey, h.sprintf(format, a...))
 }
 
 // Infow logs a message at info level.
@@ -83,12 +103,12 @@ func (h *Helper) Infow(keyvals ...interface{}) {
 
 // Warn logs a message at warn level.
 func (h *Helper) Warn(a ...interface{}) {
-	_ = h.logger.Log(LevelWarn, h.msgKey, fmt.Sprint(a...))
+	_ = h.logger.Log(LevelWarn, h.msgKey, h.sprint(a...))
 }
 
 // Warnf logs a message at warnf level.
 func (h *Helper) Warnf(format string, a ...interface{}) {
-	_ = h.logger.Log(LevelWarn, h.msgKey, fmt.Sprintf(format, a...))
+	_ = h.logger.Log(LevelWarn, h.msgKey, h.sprintf(format, a...))
 }
 
 // Warnw logs a message at warnf level.
@@ -98,12 +118,12 @@ func (h *Helper) Warnw(keyvals ...interface{}) {
 
 // Error logs a message at error level.
 func (h *Helper) Error(a ...interface{}) {
-	_ = h.logger.Log(LevelError, h.msgKey, fmt.Sprint(a...))
+	_ = h.logger.Log(LevelError, h.msgKey, h.sprint(a...))
 }
 
 // Errorf logs a message at error level.
 func (h *Helper) Errorf(format string, a ...interface{}) {
-	_ = h.logger.Log(LevelError, h.msgKey, fmt.Sprintf(format, a...))
+	_ = h.logger.Log(LevelError, h.msgKey, h.sprintf(format, a...))
 }
 
 // Errorw logs a message at error level.
@@ -113,13 +133,13 @@ func (h *Helper) Errorw(keyvals ...interface{}) {
 
 // Fatal logs a message at fatal level.
 func (h *Helper) Fatal(a ...interface{}) {
-	_ = h.logger.Log(LevelFatal, h.msgKey, fmt.Sprint(a...))
+	_ = h.logger.Log(LevelFatal, h.msgKey, h.sprint(a...))
 	os.Exit(1)
 }
 
 // Fatalf logs a message at fatal level.
 func (h *Helper) Fatalf(format string, a ...interface{}) {
-	_ = h.logger.Log(LevelFatal, h.msgKey, fmt.Sprintf(format, a...))
+	_ = h.logger.Log(LevelFatal, h.msgKey, h.sprintf(format, a...))
 	os.Exit(1)
 }
 
diff --git a/metadata/metadata.go b/metadata/metadata.go
index 3dabdbae..8b072146 100644
--- a/metadata/metadata.go
+++ b/metadata/metadata.go
@@ -9,22 +9,37 @@ import (
 // Metadata is our way of representing request headers internally.
 // They're used at the RPC level and translate back and forth
 // from Transport headers.
-type Metadata map[string]string
+type Metadata map[string][]string
 
 // New creates an MD from a given key-values map.
-func New(mds ...map[string]string) Metadata {
+func New(mds ...map[string][]string) Metadata {
 	md := Metadata{}
 	for _, m := range mds {
-		for k, v := range m {
-			md.Set(k, v)
+		for k, vList := range m {
+			for _, v := range vList {
+				md.Add(k, v)
+			}
 		}
 	}
 	return md
 }
 
+// Add adds the key, value pair to the header.
+func (m Metadata) Add(key, value string) {
+	if len(key) == 0 {
+		return
+	}
+
+	m[strings.ToLower(key)] = append(m[strings.ToLower(key)], value)
+}
+
 // Get returns the value associated with the passed key.
 func (m Metadata) Get(key string) string {
-	return m[strings.ToLower(key)]
+	v := m[strings.ToLower(key)]
+	if len(v) == 0 {
+		return ""
+	}
+	return v[0]
 }
 
 // Set stores the key-value pair.
@@ -32,11 +47,11 @@ func (m Metadata) Set(key string, value string) {
 	if key == "" || value == "" {
 		return
 	}
-	m[strings.ToLower(key)] = value
+	m[strings.ToLower(key)] = []string{value}
 }
 
 // Range iterate over element in metadata.
-func (m Metadata) Range(f func(k, v string) bool) {
+func (m Metadata) Range(f func(k string, v []string) bool) {
 	for k, v := range m {
 		if !f(k, v) {
 			break
@@ -44,6 +59,11 @@ func (m Metadata) Range(f func(k, v string) bool) {
 	}
 }
 
+// Values returns a slice of values associated with the passed key.
+func (m Metadata) Values(key string) []string {
+	return m[strings.ToLower(key)]
+}
+
 // Clone returns a deep copy of Metadata
 func (m Metadata) Clone() Metadata {
 	md := make(Metadata, len(m))
diff --git a/middleware/metadata/metadata.go b/middleware/metadata/metadata.go
index da9e43a9..30cb8df9 100644
--- a/middleware/metadata/metadata.go
+++ b/middleware/metadata/metadata.go
@@ -60,7 +60,9 @@ func Server(opts ...Option) middleware.Middleware {
 			header := tr.RequestHeader()
 			for _, k := range header.Keys() {
 				if options.hasPrefix(k) {
-					md.Set(k, header.Get(k))
+					for _, v := range header.Values(k) {
+						md.Add(k, v)
+					}
 				}
 			}
 			ctx = metadata.NewServerContext(ctx, md)
@@ -86,19 +88,25 @@ func Client(opts ...Option) middleware.Middleware {
 
 			header := tr.RequestHeader()
 			// x-md-local-
-			for k, v := range options.md {
-				header.Set(k, v)
+			for k, vList := range options.md {
+				for _, v := range vList {
+					header.Add(k, v)
+				}
 			}
 			if md, ok := metadata.FromClientContext(ctx); ok {
-				for k, v := range md {
-					header.Set(k, v)
+				for k, vList := range md {
+					for _, v := range vList {
+						header.Add(k, v)
+					}
 				}
 			}
 			// x-md-global-
 			if md, ok := metadata.FromServerContext(ctx); ok {
-				for k, v := range md {
+				for k, vList := range md {
 					if options.hasPrefix(k) {
-						header.Set(k, v)
+						for _, v := range vList {
+							header.Add(k, v)
+						}
 					}
 				}
 			}
diff --git a/transport/grpc/transport.go b/transport/grpc/transport.go
index 50ca5c91..56e21a86 100644
--- a/transport/grpc/transport.go
+++ b/transport/grpc/transport.go
@@ -64,6 +64,11 @@ func (mc headerCarrier) Set(key string, value string) {
 	metadata.MD(mc).Set(key, value)
 }
 
+// Add append value to key-values pair.
+func (mc headerCarrier) Add(key string, value string) {
+	metadata.MD(mc).Append(key, value)
+}
+
 // Keys lists the keys stored in this carrier.
 func (mc headerCarrier) Keys() []string {
 	keys := make([]string, 0, len(mc))
@@ -72,3 +77,8 @@ func (mc headerCarrier) Keys() []string {
 	}
 	return keys
 }
+
+// Values returns a slice of values associated with the passed key.
+func (mc headerCarrier) Values(key string) []string {
+	return metadata.MD(mc).Get(key)
+}
diff --git a/transport/http/transport.go b/transport/http/transport.go
index 32f054ed..686baed7 100644
--- a/transport/http/transport.go
+++ b/transport/http/transport.go
@@ -92,6 +92,11 @@ func (hc headerCarrier) Set(key string, value string) {
 	http.Header(hc).Set(key, value)
 }
 
+// Add append value to key-values pair.
+func (hc headerCarrier) Add(key string, value string) {
+	http.Header(hc).Add(key, value)
+}
+
 // Keys lists the keys stored in this carrier.
 func (hc headerCarrier) Keys() []string {
 	keys := make([]string, 0, len(hc))
@@ -100,3 +105,8 @@ func (hc headerCarrier) Keys() []string {
 	}
 	return keys
 }
+
+// Values returns a slice of values associated with the passed key.
+func (hc headerCarrier) Values(key string) []string {
+	return http.Header(hc).Values(key)
+}
diff --git a/transport/transport.go b/transport/transport.go
index 42510df3..c1a5396f 100644
--- a/transport/transport.go
+++ b/transport/transport.go
@@ -27,7 +27,9 @@ type Endpointer interface {
 type Header interface {
 	Get(key string) string
 	Set(key string, value string)
+	Add(key string, value string)
 	Keys() []string
+	Values(key string) []string
 }
 
 // Transporter is transport context value interface.


Test patch:
diff --git a/metadata/metadata_test.go b/metadata/metadata_test.go
index 8653bbe5..9aa1c620 100644
--- a/metadata/metadata_test.go
+++ b/metadata/metadata_test.go
@@ -8,7 +8,7 @@ import (
 
 func TestNew(t *testing.T) {
 	type args struct {
-		mds []map[string]string
+		mds []map[string][]string
 	}
 	tests := []struct {
 		name string
@@ -17,13 +17,13 @@ func TestNew(t *testing.T) {
 	}{
 		{
 			name: "hello",
-			args: args{[]map[string]string{{"hello": "kratos"}, {"hello2": "go-kratos"}}},
-			want: Metadata{"hello": "kratos", "hello2": "go-kratos"},
+			args: args{[]map[string][]string{{"hello": {"kratos"}}, {"hello2": {"go-kratos"}}}},
+			want: Metadata{"hello": {"kratos"}, "hello2": {"go-kratos"}},
 		},
 		{
 			name: "hi",
-			args: args{[]map[string]string{{"hi": "kratos"}, {"hi2": "go-kratos"}}},
-			want: Metadata{"hi": "kratos", "hi2": "go-kratos"},
+			args: args{[]map[string][]string{{"hi": {"kratos"}}, {"hi2": {"go-kratos"}}}},
+			want: Metadata{"hi": {"kratos"}, "hi2": {"go-kratos"}},
 		},
 	}
 	for _, tt := range tests {
@@ -47,13 +47,13 @@ func TestMetadata_Get(t *testing.T) {
 	}{
 		{
 			name: "kratos",
-			m:    Metadata{"kratos": "value", "env": "dev"},
+			m:    Metadata{"kratos": {"value"}, "env": {"dev"}},
 			args: args{key: "kratos"},
 			want: "value",
 		},
 		{
 			name: "env",
-			m:    Metadata{"kratos": "value", "env": "dev"},
+			m:    Metadata{"kratos": {"value"}, "env": {"dev"}},
 			args: args{key: "env"},
 			want: "dev",
 		},
@@ -67,6 +67,38 @@ func TestMetadata_Get(t *testing.T) {
 	}
 }
 
+func TestMetadata_Values(t *testing.T) {
+	type args struct {
+		key string
+	}
+	tests := []struct {
+		name string
+		m    Metadata
+		args args
+		want []string
+	}{
+		{
+			name: "kratos",
+			m:    Metadata{"kratos": {"value", "value2"}, "env": {"dev"}},
+			args: args{key: "kratos"},
+			want: []string{"value", "value2"},
+		},
+		{
+			name: "env",
+			m:    Metadata{"kratos": {"value", "value2"}, "env": {"dev"}},
+			args: args{key: "env"},
+			want: []string{"dev"},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := tt.m.Values(tt.args.key); !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("Get() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
 func TestMetadata_Set(t *testing.T) {
 	type args struct {
 		key   string
@@ -82,13 +114,13 @@ func TestMetadata_Set(t *testing.T) {
 			name: "kratos",
 			m:    Metadata{},
 			args: args{key: "hello", value: "kratos"},
-			want: Metadata{"hello": "kratos"},
+			want: Metadata{"hello": {"kratos"}},
 		},
 		{
 			name: "env",
-			m:    Metadata{"hello": "kratos"},
+			m:    Metadata{"hello": {"kratos"}},
 			args: args{key: "env", value: "pro"},
-			want: Metadata{"hello": "kratos", "env": "pro"},
+			want: Metadata{"hello": {"kratos"}, "env": {"pro"}},
 		},
 		{
 			name: "empty",
@@ -107,6 +139,46 @@ func TestMetadata_Set(t *testing.T) {
 	}
 }
 
+func TestMetadata_Add(t *testing.T) {
+	type args struct {
+		key   string
+		value string
+	}
+	tests := []struct {
+		name string
+		m    Metadata
+		args args
+		want Metadata
+	}{
+		{
+			name: "kratos",
+			m:    Metadata{},
+			args: args{key: "hello", value: "kratos"},
+			want: Metadata{"hello": {"kratos"}},
+		},
+		{
+			name: "env",
+			m:    Metadata{"hello": {"kratos"}},
+			args: args{key: "hello", value: "again"},
+			want: Metadata{"hello": {"kratos", "again"}},
+		},
+		{
+			name: "empty",
+			m:    Metadata{},
+			args: args{key: "", value: ""},
+			want: Metadata{},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			tt.m.Add(tt.args.key, tt.args.value)
+			if !reflect.DeepEqual(tt.m, tt.want) {
+				t.Errorf("Set() = %v, want %v", tt.m, tt.want)
+			}
+		})
+	}
+}
+
 func TestClientContext(t *testing.T) {
 	type args struct {
 		ctx context.Context
@@ -118,11 +190,11 @@ func TestClientContext(t *testing.T) {
 	}{
 		{
 			name: "kratos",
-			args: args{context.Background(), Metadata{"hello": "kratos", "kratos": "https://go-kratos.dev"}},
+			args: args{context.Background(), Metadata{"hello": {"kratos"}, "kratos": {"https://go-kratos.dev"}}},
 		},
 		{
 			name: "hello",
-			args: args{context.Background(), Metadata{"hello": "kratos", "hello2": "https://go-kratos.dev"}},
+			args: args{context.Background(), Metadata{"hello": {"kratos"}, "hello2": {"https://go-kratos.dev"}}},
 		},
 	}
 	for _, tt := range tests {
@@ -151,11 +223,11 @@ func TestServerContext(t *testing.T) {
 	}{
 		{
 			name: "kratos",
-			args: args{context.Background(), Metadata{"hello": "kratos", "kratos": "https://go-kratos.dev"}},
+			args: args{context.Background(), Metadata{"hello": {"kratos"}, "kratos": {"https://go-kratos.dev"}}},
 		},
 		{
 			name: "hello",
-			args: args{context.Background(), Metadata{"hello": "kratos", "hello2": "https://go-kratos.dev"}},
+			args: args{context.Background(), Metadata{"hello": {"kratos"}, "hello2": {"https://go-kratos.dev"}}},
 		},
 	}
 	for _, tt := range tests {
@@ -186,12 +258,12 @@ func TestAppendToClientContext(t *testing.T) {
 		{
 			name: "kratos",
 			args: args{Metadata{}, []string{"hello", "kratos", "env", "dev"}},
-			want: Metadata{"hello": "kratos", "env": "dev"},
+			want: Metadata{"hello": {"kratos"}, "env": {"dev"}},
 		},
 		{
 			name: "hello",
-			args: args{Metadata{"hi": "https://go-kratos.dev/"}, []string{"hello", "kratos", "env", "dev"}},
-			want: Metadata{"hello": "kratos", "env": "dev", "hi": "https://go-kratos.dev/"},
+			args: args{Metadata{"hi": {"https://go-kratos.dev/"}}, []string{"hello", "kratos", "env", "dev"}},
+			want: Metadata{"hello": {"kratos"}, "env": {"dev"}, "hi": {"https://go-kratos.dev/"}},
 		},
 	}
 	for _, tt := range tests {
@@ -240,13 +312,13 @@ func TestMergeToClientContext(t *testing.T) {
 	}{
 		{
 			name: "kratos",
-			args: args{Metadata{}, Metadata{"hello": "kratos", "env": "dev"}},
-			want: Metadata{"hello": "kratos", "env": "dev"},
+			args: args{Metadata{}, Metadata{"hello": {"kratos"}, "env": {"dev"}}},
+			want: Metadata{"hello": {"kratos"}, "env": {"dev"}},
 		},
 		{
 			name: "hello",
-			args: args{Metadata{"hi": "https://go-kratos.dev/"}, Metadata{"hello": "kratos", "env": "dev"}},
-			want: Metadata{"hello": "kratos", "env": "dev", "hi": "https://go-kratos.dev/"},
+			args: args{Metadata{"hi": {"https://go-kratos.dev/"}}, Metadata{"hello": {"kratos"}, "env": {"dev"}}},
+			want: Metadata{"hello": {"kratos"}, "env": {"dev"}, "hi": {"https://go-kratos.dev/"}},
 		},
 	}
 	for _, tt := range tests {
@@ -265,19 +337,19 @@ func TestMergeToClientContext(t *testing.T) {
 }
 
 func TestMetadata_Range(t *testing.T) {
-	md := Metadata{"kratos": "kratos", "https://go-kratos.dev/": "https://go-kratos.dev/", "go-kratos": "go-kratos"}
+	md := Metadata{"kratos": {"kratos"}, "https://go-kratos.dev/": {"https://go-kratos.dev/"}, "go-kratos": {"go-kratos"}}
 	tmp := Metadata{}
-	md.Range(func(k, v string) bool {
+	md.Range(func(k string, v []string) bool {
 		if k == "https://go-kratos.dev/" || k == "kratos" {
 			tmp[k] = v
 		}
 		return true
 	})
-	if !reflect.DeepEqual(tmp, Metadata{"https://go-kratos.dev/": "https://go-kratos.dev/", "kratos": "kratos"}) {
-		t.Errorf("metadata = %v, want %v", tmp, Metadata{"https://go-kratos.dev/": "https://go-kratos.dev/", "kratos": "kratos"})
+	if !reflect.DeepEqual(tmp, Metadata{"https://go-kratos.dev/": {"https://go-kratos.dev/"}, "kratos": {"kratos"}}) {
+		t.Errorf("metadata = %v, want %v", tmp, Metadata{"https://go-kratos.dev/": {"https://go-kratos.dev/"}, "kratos": {"kratos"}})
 	}
 	tmp = Metadata{}
-	md.Range(func(k, v string) bool {
+	md.Range(func(k string, v []string) bool {
 		return false
 	})
 	if !reflect.DeepEqual(tmp, Metadata{}) {
@@ -293,13 +365,13 @@ func TestMetadata_Clone(t *testing.T) {
 	}{
 		{
 			name: "kratos",
-			m:    Metadata{"kratos": "kratos", "https://go-kratos.dev/": "https://go-kratos.dev/", "go-kratos": "go-kratos"},
-			want: Metadata{"kratos": "kratos", "https://go-kratos.dev/": "https://go-kratos.dev/", "go-kratos": "go-kratos"},
+			m:    Metadata{"kratos": {"kratos"}, "https://go-kratos.dev/": {"https://go-kratos.dev/"}, "go-kratos": {"go-kratos"}},
+			want: Metadata{"kratos": {"kratos"}, "https://go-kratos.dev/": {"https://go-kratos.dev/"}, "go-kratos": {"go-kratos"}},
 		},
 		{
 			name: "go",
-			m:    Metadata{"language": "golang"},
-			want: Metadata{"language": "golang"},
+			m:    Metadata{"language": {"golang"}},
+			want: Metadata{"language": {"golang"}},
 		},
 	}
 	for _, tt := range tests {
@@ -308,7 +380,7 @@ func TestMetadata_Clone(t *testing.T) {
 			if !reflect.DeepEqual(got, tt.want) {
 				t.Errorf("Clone() = %v, want %v", got, tt.want)
 			}
-			got["kratos"] = "go"
+			got["kratos"] = []string{"go"}
 			if reflect.DeepEqual(got, tt.want) {
 				t.Errorf("want got != want got %v want %v", got, tt.want)
 			}
diff --git a/middleware/auth/jwt/jwt_test.go b/middleware/auth/jwt/jwt_test.go
index 711757bc..dc2cc9e0 100644
--- a/middleware/auth/jwt/jwt_test.go
+++ b/middleware/auth/jwt/jwt_test.go
@@ -24,6 +24,8 @@ func (hc headerCarrier) Get(key string) string { return http.Header(hc).Get(key)
 
 func (hc headerCarrier) Set(key string, value string) { http.Header(hc).Set(key, value) }
 
+func (hc headerCarrier) Add(key string, value string) { http.Header(hc).Add(key, value) }
+
 // Keys lists the keys stored in this carrier.
 func (hc headerCarrier) Keys() []string {
 	keys := make([]string, 0, len(hc))
@@ -33,6 +35,11 @@ func (hc headerCarrier) Keys() []string {
 	return keys
 }
 
+// Values returns a slice value associated with the passed key.
+func (hc headerCarrier) Values(key string) []string {
+	return http.Header(hc).Values(key)
+}
+
 func newTokenHeader(headerKey string, token string) *headerCarrier {
 	header := &headerCarrier{}
 	header.Set(headerKey, token)
diff --git a/middleware/metadata/metadata_test.go b/middleware/metadata/metadata_test.go
index b083f9c1..9e9436d7 100644
--- a/middleware/metadata/metadata_test.go
+++ b/middleware/metadata/metadata_test.go
@@ -17,6 +17,8 @@ func (hc headerCarrier) Get(key string) string { return http.Header(hc).Get(key)
 
 func (hc headerCarrier) Set(key string, value string) { http.Header(hc).Set(key, value) }
 
+func (hc headerCarrier) Add(key string, value string) { http.Header(hc).Add(key, value) }
+
 // Keys lists the keys stored in this carrier.
 func (hc headerCarrier) Keys() []string {
 	keys := make([]string, 0, len(hc))
@@ -26,6 +28,11 @@ func (hc headerCarrier) Keys() []string {
 	return keys
 }
 
+// Values returns a slice value associated with the passed key.
+func (hc headerCarrier) Values(key string) []string {
+	return http.Header(hc).Values(key)
+}
+
 type testTransport struct{ header headerCarrier }
 
 func (tr *testTransport) Kind() transport.Kind            { return transport.KindHTTP }
@@ -123,11 +130,11 @@ func TestClient(t *testing.T) {
 
 func TestWithConstants(t *testing.T) {
 	md := metadata.Metadata{
-		constKey: constValue,
+		constKey: {constValue},
 	}
 	options := &options{
 		md: metadata.Metadata{
-			"override": "override",
+			"override": {"override"},
 		},
 	}
 
diff --git a/middleware/selector/selector_test.go b/middleware/selector/selector_test.go
index f12943e4..0835ef84 100644
--- a/middleware/selector/selector_test.go
+++ b/middleware/selector/selector_test.go
@@ -40,15 +40,23 @@ func (tr *Transport) ReplyHeader() transport.Header {
 }
 
 type mockHeader struct {
-	m map[string]string
+	m map[string][]string
 }
 
 func (m *mockHeader) Get(key string) string {
-	return m.m[key]
+	vals := m.m[key]
+	if len(vals) > 0 {
+		return vals[0]
+	}
+	return ""
 }
 
 func (m *mockHeader) Set(key, value string) {
-	m.m[key] = value
+	m.m[key] = []string{value}
+}
+
+func (m *mockHeader) Add(key, value string) {
+	m.m[key] = append(m.m[key], value)
 }
 
 func (m *mockHeader) Keys() []string {
@@ -59,6 +67,10 @@ func (m *mockHeader) Keys() []string {
 	return keys
 }
 
+func (m *mockHeader) Values(key string) []string {
+	return m.m[key]
+}
+
 func TestMatch(t *testing.T) {
 	tests := []struct {
 		name string
@@ -185,28 +197,28 @@ func TestHeaderFunc(t *testing.T) {
 			name: "/hello.Update/world",
 			ctx: transport.NewServerContext(context.Background(), &Transport{
 				operation: "/hello.Update/world",
-				headers:   &mockHeader{map[string]string{"X-Test": "test"}},
+				headers:   &mockHeader{map[string][]string{"X-Test": {"test"}}},
 			}),
 		},
 		{
 			name: "/hi.Create/world",
 			ctx: transport.NewServerContext(context.Background(), &Transport{
 				operation: "/hi.Create/world",
-				headers:   &mockHeader{map[string]string{"X-Test": "test2", "go-kratos": "kratos"}},
+				headers:   &mockHeader{map[string][]string{"X-Test": {"test2"}, "go-kratos": {"kratos"}}},
 			}),
 		},
 		{
 			name: "/test.Name/1234",
 			ctx: transport.NewServerContext(context.Background(), &Transport{
 				operation: "/test.Name/1234",
-				headers:   &mockHeader{map[string]string{"X-Test": "test3"}},
+				headers:   &mockHeader{map[string][]string{"X-Test": {"test3"}}},
 			}),
 		},
 		{
 			name: "/go-kratos.dev/kratos",
 			ctx: transport.NewServerContext(context.Background(), &Transport{
 				operation: "/go-kratos.dev/kratos",
-				headers:   &mockHeader{map[string]string{"X-Test": "test"}},
+				headers:   &mockHeader{map[string][]string{"X-Test": {"test"}}},
 			}),
 		},
 	}
diff --git a/middleware/tracing/tracing_test.go b/middleware/tracing/tracing_test.go
index b95771ab..72740baf 100644
--- a/middleware/tracing/tracing_test.go
+++ b/middleware/tracing/tracing_test.go
@@ -29,6 +29,11 @@ func (hc headerCarrier) Set(key string, value string) {
 	http.Header(hc).Set(key, value)
 }
 
+// Add value to the key-value pair.
+func (hc headerCarrier) Add(key string, value string) {
+	http.Header(hc).Add(key, value)
+}
+
 // Keys lists the keys stored in this carrier.
 func (hc headerCarrier) Keys() []string {
 	keys := make([]string, 0, len(hc))
@@ -38,6 +43,11 @@ func (hc headerCarrier) Keys() []string {
 	return keys
 }
 
+// Values returns a slice value associated with the passed key.
+func (hc headerCarrier) Values(key string) []string {
+	return http.Header(hc).Values(key)
+}
+
 type mockTransport struct {
 	kind      transport.Kind
 	endpoint  string