Repository: go-kratos/kratos
Pull request #2239 (2022-07-26 15:40:33)

feat(middleware): add selector matcher
```go
// Use uses a service middleware with selector.
// selector:
//   - '/*'
//   - '/helloworld.v1.Greeter/*'
//   - '/helloworld.v1.Greeter/SayHello'

srv := http.NewServer()
srv.Use("/v1/users/*", Auth)
```

codecov-commenter: # [Codecov](https://codecov.io/gh/go-kratos/kratos/pull/2239?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos) Report
> Merging [#2239](https://codecov.io/gh/go-kratos/kratos/pull/2239?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos) (7303939) into [main](https://codecov.io/gh/go-kratos/kratos/commit/14cfd65b62751fd07006a0d43b40c03f9ca516d1?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos) (14cfd65) will **increase** coverage by `0.00%`.
> The diff coverage is `85.00%`.

> :exclamation: Current head 7303939 differs from pull request most recent head 266f764. Consider uploading reports for the commit 266f764 to get more accurate results

```diff
@@           Coverage Diff           @@
##             main    #2239   +/-   ##
=======================================
  Coverage   85.73%   85.74%           
=======================================
  Files          86       87    +1     
  Lines        3758     3787   +29     
=======================================
+ Hits         3222     3247   +25     
- Misses        369      375    +6     
+ Partials      167      165    -2     
```


| [Impacted Files](https://codecov.io/gh/go-kratos/kratos/pull/2239?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos) | Coverage Δ | |
|---|---|---|
| [transport/http/server.go](https://codecov.io/gh/go-kratos/kratos/pull/2239/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos#diff-dHJhbnNwb3J0L2h0dHAvc2VydmVyLmdv) | `78.72% <40.00%> (-3.17%)` | :arrow_down: |
| [transport/grpc/server.go](https://codecov.io/gh/go-kratos/kratos/pull/2239/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos#diff-dHJhbnNwb3J0L2dycGMvc2VydmVyLmdv) | `85.86% <77.77%> (-1.78%)` | :arrow_down: |
| [internal/matcher/middleware.go](https://codecov.io/gh/go-kratos/kratos/pull/2239/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos#diff-aW50ZXJuYWwvbWF0Y2hlci9taWRkbGV3YXJlLmdv) | `95.65% <95.65%> (ø)` | |
| [transport/grpc/interceptor.go](https://codecov.io/gh/go-kratos/kratos/pull/2239/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos#diff-dHJhbnNwb3J0L2dycGMvaW50ZXJjZXB0b3IuZ28=) | `100.00% <100.00%> (ø)` | |
| [transport/http/context.go](https://codecov.io/gh/go-kratos/kratos/pull/2239/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos#diff-dHJhbnNwb3J0L2h0dHAvY29udGV4dC5nbw==) | `94.04% <100.00%> (ø)` | |
| [config/file/file.go](https://codecov.io/gh/go-kratos/kratos/pull/2239/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos#diff-Y29uZmlnL2ZpbGUvZmlsZS5nbw==) | `66.66% <0.00%> (+4.76%)` | :arrow_up: |
| [config/file/watcher.go](https://codecov.io/gh/go-kratos/kratos/pull/2239/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos#diff-Y29uZmlnL2ZpbGUvd2F0Y2hlci5nbw==) | `65.62% <0.00%> (+6.25%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/go-kratos/kratos/pull/2239?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/go-kratos/kratos/pull/2239?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos). Last update [14cfd65...266f764](https://codecov.io/gh/go-kratos/kratos/pull/2239?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos).


Patch:
diff --git a/cmd/kratos/internal/proto/server/server.go b/cmd/kratos/internal/proto/server/server.go
index dcc19790..3b8a8196 100644
--- a/cmd/kratos/internal/proto/server/server.go
+++ b/cmd/kratos/internal/proto/server/server.go
@@ -9,6 +9,8 @@ import (
 
 	"github.com/emicklei/proto"
 	"github.com/spf13/cobra"
+	"golang.org/x/text/cases"
+	"golang.org/x/text/language"
 )
 
 // CmdServer the service command.
@@ -54,7 +56,7 @@ func run(cmd *cobra.Command, args []string) {
 		proto.WithService(func(s *proto.Service) {
 			cs := &Service{
 				Package: pkg,
-				Service: s.Name,
+				Service: serviceName(s.Name),
 			}
 			for _, e := range s.Elements {
 				r, ok := e.(*proto.RPC)
@@ -62,7 +64,7 @@ func run(cmd *cobra.Command, args []string) {
 					continue
 				}
 				cs.Methods = append(cs.Methods, &Method{
-					Service: s.Name, Name: ucFirst(r.Name), Request: r.RequestType,
+					Service: serviceName(s.Name), Name: serviceName(r.Name), Request: r.RequestType,
 					Reply: r.ReturnsType, Type: getMethodType(r.StreamsRequest, r.StreamsReturns),
 				})
 			}
@@ -103,10 +105,12 @@ func getMethodType(streamsRequest, streamsReturns bool) MethodType {
 	return unaryType
 }
 
-func ucFirst(str string) string {
-	if str == "" {
-		return ""
-	}
+func serviceName(name string) string {
+	return toUpperCamelCase(strings.Split(name, ".")[0])
+}
 
-	return strings.ToUpper(str[:1]) + str[1:]
+func toUpperCamelCase(s string) string {
+	s = strings.ReplaceAll(s, "_", " ")
+	s = cases.Title(language.Und, cases.NoLower).String(s)
+	return strings.ReplaceAll(s, " ", "")
 }
diff --git a/cmd/kratos/internal/run/run.go b/cmd/kratos/internal/run/run.go
index a84d45ba..ac229bab 100644
--- a/cmd/kratos/internal/run/run.go
+++ b/cmd/kratos/internal/run/run.go
@@ -23,8 +23,9 @@ var CmdRun = &cobra.Command{
 // Run run project.
 func Run(cmd *cobra.Command, args []string) {
 	var dir string
-	if len(args) > 0 {
-		dir = args[0]
+	cmdArgs, programArgs := splitArgs(cmd, args)
+	if len(cmdArgs) > 0 {
+		dir = cmdArgs[0]
 	}
 	base, err := os.Getwd()
 	if err != nil {
@@ -63,7 +64,7 @@ func Run(cmd *cobra.Command, args []string) {
 			dir = cmdPath[dir]
 		}
 	}
-	fd := exec.Command("go", "run", ".")
+	fd := exec.Command("go", append([]string{"run", "."}, programArgs...)...)
 	fd.Stdout = os.Stdout
 	fd.Stderr = os.Stderr
 	fd.Dir = dir
@@ -73,6 +74,14 @@ func Run(cmd *cobra.Command, args []string) {
 	}
 }
 
+func splitArgs(cmd *cobra.Command, args []string) (cmdArgs, programArgs []string) {
+	dashAt := cmd.ArgsLenAtDash()
+	if dashAt >= 0 {
+		return args[:dashAt], args[dashAt:]
+	}
+	return args, []string{}
+}
+
 func findCMD(base string) (map[string]string, error) {
 	wd, err := os.Getwd()
 	if err != nil {
diff --git a/contrib/config/consul/go.mod b/contrib/config/consul/go.mod
index 278452ab..a251affb 100644
--- a/contrib/config/consul/go.mod
+++ b/contrib/config/consul/go.mod
@@ -4,7 +4,7 @@ go 1.15
 
 require (
 	github.com/go-kratos/kratos/v2 v2.4.0
-	github.com/hashicorp/consul/api v1.13.0
+	github.com/hashicorp/consul/api v1.13.1
 )
 
 replace github.com/go-kratos/kratos/v2 => ../../../
diff --git a/contrib/config/consul/go.sum b/contrib/config/consul/go.sum
index 758fdcb1..5816b336 100644
--- a/contrib/config/consul/go.sum
+++ b/contrib/config/consul/go.sum
@@ -70,10 +70,10 @@ 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.13.0 h1:2hnLQ0GjQvw7f3O61jMO8gbasZviZTrt9R8WzgiirHc=
-github.com/hashicorp/consul/api v1.13.0/go.mod h1:ZlVrynguJKcYr54zGaDbaL3fOvKC9m72FhPvA8T35KQ=
-github.com/hashicorp/consul/sdk v0.8.0 h1:OJtKBtEjboEZvG6AOUdh4Z1Zbyu0WcxQ0qatRrZHTVU=
-github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms=
+github.com/hashicorp/consul/api v1.13.1 h1:r5cPdVFUy+pFF7nt+0ArLD9hm+E39OewJkvNdjKXcL4=
+github.com/hashicorp/consul/api v1.13.1/go.mod h1:+1VcOos0TVdQFqXxphG4zmGcwQB4KVGkp1maPqnkDpE=
+github.com/hashicorp/consul/sdk v0.10.0 h1:rGLEh2AWK4K0KCMvqWAz2EYxQqgciIfMagWZ0nVe5MI=
+github.com/hashicorp/consul/sdk v0.10.0/go.mod h1:yPkX5Q6CsxTFMjQQDJwzeNmUUF5NUGGbrDsv9wTb8cw=
 github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
 github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
 github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM=
@@ -126,8 +126,6 @@ github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJys
 github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
 github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
 github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
-github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0=
-github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
 github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
 github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
 github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
@@ -200,7 +198,6 @@ golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
diff --git a/contrib/config/kubernetes/go.mod b/contrib/config/kubernetes/go.mod
index aa900a17..0ba216b7 100644
--- a/contrib/config/kubernetes/go.mod
+++ b/contrib/config/kubernetes/go.mod
@@ -4,8 +4,8 @@ go 1.16
 
 require (
 	github.com/go-kratos/kratos/v2 v2.4.0
-	k8s.io/api v0.24.2
-	k8s.io/apimachinery v0.24.2
+	k8s.io/api v0.24.3
+	k8s.io/apimachinery v0.24.3
 	k8s.io/client-go v0.24.2
 )
 
diff --git a/contrib/config/kubernetes/go.sum b/contrib/config/kubernetes/go.sum
index 138314b9..6b48ebd1 100644
--- a/contrib/config/kubernetes/go.sum
+++ b/contrib/config/kubernetes/go.sum
@@ -652,10 +652,12 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
 honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
 honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
-k8s.io/api v0.24.2 h1:g518dPU/L7VRLxWfcadQn2OnsiGWVOadTLpdnqgY2OI=
 k8s.io/api v0.24.2/go.mod h1:AHqbSkTm6YrQ0ObxjO3Pmp/ubFF/KuM7jU+3khoBsOg=
-k8s.io/apimachinery v0.24.2 h1:5QlH9SL2C8KMcrNJPor+LbXVTaZRReml7svPEh4OKDM=
+k8s.io/api v0.24.3 h1:tt55QEmKd6L2k5DP6G/ZzdMQKvG5ro4H4teClqm0sTY=
+k8s.io/api v0.24.3/go.mod h1:elGR/XSZrS7z7cSZPzVWaycpJuGIw57j9b95/1PdJNI=
 k8s.io/apimachinery v0.24.2/go.mod h1:82Bi4sCzVBdpYjyI4jY6aHX+YCUchUIrZrXKedjd2UM=
+k8s.io/apimachinery v0.24.3 h1:hrFiNSA2cBZqllakVYyH/VyEh4B581bQRmqATJSeQTg=
+k8s.io/apimachinery v0.24.3/go.mod h1:82Bi4sCzVBdpYjyI4jY6aHX+YCUchUIrZrXKedjd2UM=
 k8s.io/client-go v0.24.2 h1:CoXFSf8if+bLEbinDqN9ePIDGzcLtqhfd6jpfnwGOFA=
 k8s.io/client-go v0.24.2/go.mod h1:zg4Xaoo+umDsfCWr4fCnmLEtQXyCNXCvJuSsglNcV30=
 k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E=
diff --git a/contrib/config/polaris/config.go b/contrib/config/polaris/config.go
index f5d52d18..c3c39ece 100644
--- a/contrib/config/polaris/config.go
+++ b/contrib/config/polaris/config.go
@@ -43,11 +43,11 @@ func WithFileName(fileName string) Option {
 }
 
 type source struct {
-	client  *polaris.ConfigAPI
+	client  polaris.ConfigAPI
 	options *options
 }
 
-func New(client *polaris.ConfigAPI, opts ...Option) (config.Source, error) {
+func New(client polaris.ConfigAPI, opts ...Option) (config.Source, error) {
 	options := &options{
 		namespace: "default",
 		fileGroup: "",
@@ -74,12 +74,7 @@ func New(client *polaris.ConfigAPI, opts ...Option) (config.Source, error) {
 
 // Load return the config values
 func (s *source) Load() ([]*config.KeyValue, error) {
-	configAPI, err := polaris.NewConfigAPI()
-	if err != nil {
-		return nil, err
-	}
-
-	configFile, err := configAPI.GetConfigFile(s.options.namespace, s.options.fileGroup, s.options.fileName)
+	configFile, err := s.client.GetConfigFile(s.options.namespace, s.options.fileGroup, s.options.fileName)
 	if err != nil {
 		fmt.Println("fail to get config.", err)
 		return nil, err
diff --git a/contrib/registry/consul/go.mod b/contrib/registry/consul/go.mod
index 33cf1afd..749ca4ee 100644
--- a/contrib/registry/consul/go.mod
+++ b/contrib/registry/consul/go.mod
@@ -4,7 +4,7 @@ go 1.16
 
 require (
 	github.com/go-kratos/kratos/v2 v2.4.0
-	github.com/hashicorp/consul/api v1.13.0
+	github.com/hashicorp/consul/api v1.13.1
 )
 
 replace github.com/go-kratos/kratos/v2 => ../../../
diff --git a/contrib/registry/consul/go.sum b/contrib/registry/consul/go.sum
index b2e006cb..24890656 100644
--- a/contrib/registry/consul/go.sum
+++ b/contrib/registry/consul/go.sum
@@ -70,10 +70,10 @@ 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.13.0 h1:2hnLQ0GjQvw7f3O61jMO8gbasZviZTrt9R8WzgiirHc=
-github.com/hashicorp/consul/api v1.13.0/go.mod h1:ZlVrynguJKcYr54zGaDbaL3fOvKC9m72FhPvA8T35KQ=
-github.com/hashicorp/consul/sdk v0.8.0 h1:OJtKBtEjboEZvG6AOUdh4Z1Zbyu0WcxQ0qatRrZHTVU=
-github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms=
+github.com/hashicorp/consul/api v1.13.1 h1:r5cPdVFUy+pFF7nt+0ArLD9hm+E39OewJkvNdjKXcL4=
+github.com/hashicorp/consul/api v1.13.1/go.mod h1:+1VcOos0TVdQFqXxphG4zmGcwQB4KVGkp1maPqnkDpE=
+github.com/hashicorp/consul/sdk v0.10.0 h1:rGLEh2AWK4K0KCMvqWAz2EYxQqgciIfMagWZ0nVe5MI=
+github.com/hashicorp/consul/sdk v0.10.0/go.mod h1:yPkX5Q6CsxTFMjQQDJwzeNmUUF5NUGGbrDsv9wTb8cw=
 github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
 github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
 github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM=
@@ -123,8 +123,6 @@ github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJys
 github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
 github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
 github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
-github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0=
-github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
 github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
 github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
 github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
@@ -196,7 +194,6 @@ golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
diff --git a/contrib/registry/kubernetes/go.mod b/contrib/registry/kubernetes/go.mod
index 96a1fa66..cd33ac26 100644
--- a/contrib/registry/kubernetes/go.mod
+++ b/contrib/registry/kubernetes/go.mod
@@ -5,8 +5,8 @@ go 1.16
 require (
 	github.com/go-kratos/kratos/v2 v2.4.0
 	github.com/json-iterator/go v1.1.12
-	k8s.io/api v0.24.2
-	k8s.io/apimachinery v0.24.2
+	k8s.io/api v0.24.3
+	k8s.io/apimachinery v0.24.3
 	k8s.io/client-go v0.24.2
 )
 
diff --git a/contrib/registry/kubernetes/go.sum b/contrib/registry/kubernetes/go.sum
index 368dd5d5..4296b6fe 100644
--- a/contrib/registry/kubernetes/go.sum
+++ b/contrib/registry/kubernetes/go.sum
@@ -653,10 +653,12 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
 honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
 honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
-k8s.io/api v0.24.2 h1:g518dPU/L7VRLxWfcadQn2OnsiGWVOadTLpdnqgY2OI=
 k8s.io/api v0.24.2/go.mod h1:AHqbSkTm6YrQ0ObxjO3Pmp/ubFF/KuM7jU+3khoBsOg=
-k8s.io/apimachinery v0.24.2 h1:5QlH9SL2C8KMcrNJPor+LbXVTaZRReml7svPEh4OKDM=
+k8s.io/api v0.24.3 h1:tt55QEmKd6L2k5DP6G/ZzdMQKvG5ro4H4teClqm0sTY=
+k8s.io/api v0.24.3/go.mod h1:elGR/XSZrS7z7cSZPzVWaycpJuGIw57j9b95/1PdJNI=
 k8s.io/apimachinery v0.24.2/go.mod h1:82Bi4sCzVBdpYjyI4jY6aHX+YCUchUIrZrXKedjd2UM=
+k8s.io/apimachinery v0.24.3 h1:hrFiNSA2cBZqllakVYyH/VyEh4B581bQRmqATJSeQTg=
+k8s.io/apimachinery v0.24.3/go.mod h1:82Bi4sCzVBdpYjyI4jY6aHX+YCUchUIrZrXKedjd2UM=
 k8s.io/client-go v0.24.2 h1:CoXFSf8if+bLEbinDqN9ePIDGzcLtqhfd6jpfnwGOFA=
 k8s.io/client-go v0.24.2/go.mod h1:zg4Xaoo+umDsfCWr4fCnmLEtQXyCNXCvJuSsglNcV30=
 k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E=
diff --git a/contrib/registry/zookeeper/go.mod b/contrib/registry/zookeeper/go.mod
index 33d5697f..e33c1d93 100644
--- a/contrib/registry/zookeeper/go.mod
+++ b/contrib/registry/zookeeper/go.mod
@@ -4,7 +4,7 @@ go 1.16
 
 require (
 	github.com/go-kratos/kratos/v2 v2.4.0
-	github.com/go-zookeeper/zk v1.0.2
+	github.com/go-zookeeper/zk v1.0.3
 	golang.org/x/sync v0.0.0-20220513210516-0976fa681c29
 )
 
diff --git a/contrib/registry/zookeeper/go.sum b/contrib/registry/zookeeper/go.sum
index c2c80c1a..f5cce467 100644
--- a/contrib/registry/zookeeper/go.sum
+++ b/contrib/registry/zookeeper/go.sum
@@ -28,8 +28,8 @@ github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre
 github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
 github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
 github.com/go-playground/form/v4 v4.2.0/go.mod h1:q1a2BY+AQUUzhl6xA/6hBetay6dEIhMHjgvJiGo6K7U=
-github.com/go-zookeeper/zk v1.0.2 h1:4mx0EYENAdX/B/rbunjlt5+4RTA/a9SMHBRuSKdGxPM=
-github.com/go-zookeeper/zk v1.0.2/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw=
+github.com/go-zookeeper/zk v1.0.3 h1:7M2kwOsc//9VeeFiPtf+uSJlVpU66x9Ba5+8XK7/TDg=
+github.com/go-zookeeper/zk v1.0.3/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw=
 github.com/golang-jwt/jwt/v4 v4.4.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
 github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
diff --git a/contrib/registry/zookeeper/register.go b/contrib/registry/zookeeper/register.go
index 2aedc198..a9de43c8 100644
--- a/contrib/registry/zookeeper/register.go
+++ b/contrib/registry/zookeeper/register.go
@@ -3,6 +3,7 @@ package zookeeper
 import (
 	"context"
 	"path"
+	"time"
 
 	"github.com/go-zookeeper/zk"
 	"golang.org/x/sync/singleflight"
@@ -77,6 +78,7 @@ func (r *Registry) Register(ctx context.Context, service *registry.ServiceInstan
 	if err = r.ensureName(servicePath, data, zk.FlagEphemeral); err != nil {
 		return err
 	}
+	go r.reRegister(servicePath, data)
 	return nil
 }
 
@@ -150,3 +152,21 @@ func (r *Registry) ensureName(path string, data []byte, flags int32) error {
 	}
 	return nil
 }
+
+// reRegister re-register data node info when bad connection recovered
+func (r *Registry) reRegister(path string, data []byte) {
+	sessionID := r.conn.SessionID()
+	ticker := time.NewTicker(time.Second)
+	defer ticker.Stop()
+	for range ticker.C {
+		cur := r.conn.SessionID()
+		// sessionID changed
+		if cur > 0 && sessionID != cur {
+			// re-ensureName
+			if err := r.ensureName(path, data, zk.FlagEphemeral); err != nil {
+				return
+			}
+			sessionID = cur
+		}
+	}
+}
diff --git a/internal/matcher/middleware.go b/internal/matcher/middleware.go
new file mode 100644
index 00000000..8d568182
--- /dev/null
+++ b/internal/matcher/middleware.go
@@ -0,0 +1,62 @@
+package matcher
+
+import (
+	"sort"
+	"strings"
+
+	"github.com/go-kratos/kratos/v2/middleware"
+)
+
+// Matcher is a middleware matcher.
+type Matcher interface {
+	Use(ms ...middleware.Middleware)
+	Add(selector string, ms ...middleware.Middleware)
+	Match(operation string) []middleware.Middleware
+}
+
+// New new a middleware matcher.
+func New() Matcher {
+	return &matcher{
+		matchs: make(map[string][]middleware.Middleware),
+	}
+}
+
+type matcher struct {
+	prefix   []string
+	defaults []middleware.Middleware
+	matchs   map[string][]middleware.Middleware
+}
+
+func (m *matcher) Use(ms ...middleware.Middleware) {
+	m.defaults = ms
+}
+
+func (m *matcher) Add(selector string, ms ...middleware.Middleware) {
+	if strings.HasSuffix(selector, "*") {
+		selector = strings.TrimSuffix(selector, "*")
+		m.prefix = append(m.prefix, selector)
+		// sort the prefix:
+		//  - /foo/bar
+		//  - /foo
+		sort.Slice(m.prefix, func(i, j int) bool {
+			return m.prefix[i] > m.prefix[j]
+		})
+	}
+	m.matchs[selector] = ms
+}
+
+func (m *matcher) Match(operation string) []middleware.Middleware {
+	ms := make([]middleware.Middleware, 0, len(m.defaults))
+	if len(m.defaults) > 0 {
+		ms = append(ms, m.defaults...)
+	}
+	if next, ok := m.matchs[operation]; ok {
+		return append(ms, next...)
+	}
+	for _, prefix := range m.prefix {
+		if strings.HasPrefix(operation, prefix) {
+			return append(ms, m.matchs[prefix]...)
+		}
+	}
+	return ms
+}
diff --git a/transport/grpc/codec.go b/transport/grpc/codec.go
index 1b2c8289..19670bba 100644
--- a/transport/grpc/codec.go
+++ b/transport/grpc/codec.go
@@ -3,6 +3,7 @@ package grpc
 import (
 	"fmt"
 
+	enc "github.com/go-kratos/kratos/v2/encoding"
 	"github.com/go-kratos/kratos/v2/encoding/json"
 	"google.golang.org/grpc/encoding"
 	"google.golang.org/protobuf/proto"
@@ -20,7 +21,7 @@ func (codec) Marshal(v interface{}) ([]byte, error) {
 	if !ok {
 		return nil, fmt.Errorf("failed to marshal, message is %T, want proto.Message", v)
 	}
-	return json.MarshalOptions.Marshal(vv)
+	return enc.GetCodec(json.Name).Marshal(vv)
 }
 
 func (codec) Unmarshal(data []byte, v interface{}) error {
@@ -28,9 +29,9 @@ func (codec) Unmarshal(data []byte, v interface{}) error {
 	if !ok {
 		return fmt.Errorf("failed to unmarshal, message is %T, want proto.Message", v)
 	}
-	return json.UnmarshalOptions.Unmarshal(data, vv)
+	return enc.GetCodec(json.Name).Unmarshal(data, vv)
 }
 
 func (codec) Name() string {
-	return "json"
+	return json.Name
 }
diff --git a/transport/grpc/interceptor.go b/transport/grpc/interceptor.go
index 26daedfb..ccd83ea6 100644
--- a/transport/grpc/interceptor.go
+++ b/transport/grpc/interceptor.go
@@ -33,8 +33,8 @@ func (s *Server) unaryServerInterceptor() grpc.UnaryServerInterceptor {
 		h := func(ctx context.Context, req interface{}) (interface{}, error) {
 			return handler(ctx, req)
 		}
-		if len(s.middleware) > 0 {
-			h = middleware.Chain(s.middleware...)(h)
+		if next := s.middleware.Match(tr.Operation()); len(next) > 0 {
+			h = middleware.Chain(next...)(h)
 		}
 		reply, err := h(ctx, req)
 		if len(replyHeader) > 0 {
diff --git a/transport/grpc/server.go b/transport/grpc/server.go
index 2b5bde74..b66ba932 100644
--- a/transport/grpc/server.go
+++ b/transport/grpc/server.go
@@ -8,6 +8,7 @@ import (
 	"time"
 
 	"github.com/go-kratos/kratos/v2/internal/endpoint"
+	"github.com/go-kratos/kratos/v2/internal/matcher"
 
 	apimd "github.com/go-kratos/kratos/v2/api/metadata"
 
@@ -62,7 +63,7 @@ func Logger(logger log.Logger) ServerOption {
 // Middleware with server middleware.
 func Middleware(m ...middleware.Middleware) ServerOption {
 	return func(s *Server) {
-		s.middleware = m
+		s.middleware.Use(m...)
 	}
 }
 
@@ -112,7 +113,7 @@ type Server struct {
 	address    string
 	endpoint   *url.URL
 	timeout    time.Duration
-	middleware []middleware.Middleware
+	middleware matcher.Matcher
 	unaryInts  []grpc.UnaryServerInterceptor
 	streamInts []grpc.StreamServerInterceptor
 	grpcOpts   []grpc.ServerOption
@@ -123,11 +124,12 @@ type Server struct {
 // NewServer creates a gRPC server by options.
 func NewServer(opts ...ServerOption) *Server {
 	srv := &Server{
-		baseCtx: context.Background(),
-		network: "tcp",
-		address: ":0",
-		timeout: 1 * time.Second,
-		health:  health.NewServer(),
+		baseCtx:    context.Background(),
+		network:    "tcp",
+		address:    ":0",
+		timeout:    1 * time.Second,
+		health:     health.NewServer(),
+		middleware: matcher.New(),
 	}
 	for _, o := range opts {
 		o(srv)
@@ -163,6 +165,15 @@ func NewServer(opts ...ServerOption) *Server {
 	return srv
 }
 
+// Use uses a service middleware with selector.
+// selector:
+//   - '/*'
+//   - '/helloworld.v1.Greeter/*'
+//   - '/helloworld.v1.Greeter/SayHello'
+func (s *Server) Use(selector string, m ...middleware.Middleware) {
+	s.middleware.Add(selector, m...)
+}
+
 // Endpoint return a real address to registry endpoint.
 // examples:
 //   grpc://127.0.0.1:9000?isSecure=false
diff --git a/transport/http/context.go b/transport/http/context.go
index bc54134f..347d93ff 100644
--- a/transport/http/context.go
+++ b/transport/http/context.go
@@ -10,6 +10,7 @@ import (
 	"time"
 
 	"github.com/go-kratos/kratos/v2/middleware"
+	"github.com/go-kratos/kratos/v2/transport"
 	"github.com/go-kratos/kratos/v2/transport/http/binding"
 	"github.com/gorilla/mux"
 )
@@ -89,7 +90,10 @@ func (c *wrapper) Query() url.Values {
 func (c *wrapper) Request() *http.Request        { return c.req }
 func (c *wrapper) Response() http.ResponseWriter { return c.res }
 func (c *wrapper) Middleware(h middleware.Handler) middleware.Handler {
-	return middleware.Chain(c.router.srv.ms...)(h)
+	if tr, ok := transport.FromServerContext(c.req.Context()); ok {
+		return middleware.Chain(c.router.srv.middleware.Match(tr.Operation())...)(h)
+	}
+	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) }
diff --git a/transport/http/server.go b/transport/http/server.go
index 9bb9ffea..4767e79b 100644
--- a/transport/http/server.go
+++ b/transport/http/server.go
@@ -10,6 +10,7 @@ import (
 	"time"
 
 	"github.com/go-kratos/kratos/v2/internal/endpoint"
+	"github.com/go-kratos/kratos/v2/internal/matcher"
 
 	"github.com/go-kratos/kratos/v2/internal/host"
 	"github.com/go-kratos/kratos/v2/log"
@@ -58,7 +59,7 @@ func Logger(logger log.Logger) ServerOption {
 // Middleware with service middleware option.
 func Middleware(m ...middleware.Middleware) ServerOption {
 	return func(o *Server) {
-		o.ms = m
+		o.middleware.Use(m...)
 	}
 }
 
@@ -124,7 +125,7 @@ type Server struct {
 	address     string
 	timeout     time.Duration
 	filters     []FilterFunc
-	ms          []middleware.Middleware
+	middleware  matcher.Matcher
 	dec         DecodeRequestFunc
 	enc         EncodeResponseFunc
 	ene         EncodeErrorFunc
@@ -138,6 +139,7 @@ func NewServer(opts ...ServerOption) *Server {
 		network:     "tcp",
 		address:     ":0",
 		timeout:     1 * time.Second,
+		middleware:  matcher.New(),
 		dec:         DefaultRequestDecoder,
 		enc:         DefaultResponseEncoder,
 		ene:         DefaultErrorEncoder,
@@ -157,6 +159,15 @@ func NewServer(opts ...ServerOption) *Server {
 	return srv
 }
 
+// Use uses a service middleware with selector.
+// selector:
+//   - '/*'
+//   - '/helloworld.v1.Greeter/*'
+//   - '/helloworld.v1.Greeter/SayHello'
+func (s *Server) Use(selector string, m ...middleware.Middleware) {
+	s.middleware.Add(selector, m...)
+}
+
 // WalkRoute walks the router and all its sub-routers, calling walkFn for each route in the tree.
 func (s *Server) WalkRoute(fn WalkRouteFunc) error {
 	return s.router.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error {
@@ -229,10 +240,10 @@ func (s *Server) filter() mux.MiddlewareFunc {
 
 			tr := &Transport{
 				operation:    pathTemplate,
+				pathTemplate: pathTemplate,
 				reqHeader:    headerCarrier(req.Header),
 				replyHeader:  headerCarrier(w.Header()),
 				request:      req,
-				pathTemplate: pathTemplate,
 			}
 			if s.endpoint != nil {
 				tr.endpoint = s.endpoint.String()


Test patch:
diff --git a/cmd/kratos/internal/proto/server/server_test.go b/cmd/kratos/internal/proto/server/server_test.go
index a2cbeee6..fa90f692 100644
--- a/cmd/kratos/internal/proto/server/server_test.go
+++ b/cmd/kratos/internal/proto/server/server_test.go
@@ -2,7 +2,7 @@ package server
 
 import "testing"
 
-func Test_ucFirst(t *testing.T) {
+func Test_serviceName(t *testing.T) {
 	type args struct {
 		str string
 	}
@@ -12,40 +12,50 @@ func Test_ucFirst(t *testing.T) {
 		want string
 	}{
 		{
-			name: "ucFirst on lowercase words",
+			name: "serviceName on lowercase words",
 			args: args{str: "helloworld"},
 			want: "Helloworld",
 		},
 		{
-			name: "ucFirst on uppercase words",
+			name: "serviceName on uppercase words",
 			args: args{str: "HELLOWORLD"},
 			want: "HELLOWORLD",
 		},
 		{
-			name: "ucFirst on lowercase words with spaces",
+			name: "serviceName on lowercase words with spaces",
 			args: args{str: "hello world"},
-			want: "Hello world",
+			want: "HelloWorld",
 		},
 		{
-			name: "ucFirst on uppercase words with spaces",
+			name: "serviceName on uppercase words with spaces",
 			args: args{str: "HELLO WORLD"},
-			want: "HELLO WORLD",
+			want: "HELLOWORLD",
 		},
 		{
-			name: "ucFirst on Lower Camel Case words",
+			name: "serviceName on Lower Camel Case words",
 			args: args{str: "helloWorld"},
 			want: "HelloWorld",
 		},
 		{
-			name: "ucFirst on Upper Camel Case words",
+			name: "serviceName on Lower Camel Case words",
+			args: args{str: "helloWorld"},
+			want: "HelloWorld",
+		},
+		{
+			name: "serviceName on Upper Camel Case words",
 			args: args{str: "HelloWorld"},
 			want: "HelloWorld",
 		},
+		{
+			name: "serviceName on Upper Camel Case words",
+			args: args{str: "hello_world"},
+			want: "HelloWorld",
+		},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			if got := ucFirst(tt.args.str); got != tt.want {
-				t.Errorf("ucFirst() = %v, want %v", got, tt.want)
+			if got := serviceName(tt.args.str); got != tt.want {
+				t.Errorf("serviceName() = %v, want %v", got, tt.want)
 			}
 		})
 	}
diff --git a/contrib/config/polaris/config_test.go b/contrib/config/polaris/config_test.go
index 6dfaeb3f..fc864526 100644
--- a/contrib/config/polaris/config_test.go
+++ b/contrib/config/polaris/config_test.go
@@ -213,7 +213,7 @@ func TestConfig(t *testing.T) {
 	if err != nil {
 		t.Fatal(err)
 	}
-	config, err := New(&configAPI, WithNamespace(namespace), WithFileGroup(fileGroup), WithFileName(name))
+	config, err := New(configAPI, WithNamespace(namespace), WithFileGroup(fileGroup), WithFileName(name))
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -285,7 +285,7 @@ func TestExtToFormat(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	config, err := New(&configAPI, WithNamespace(namespace), WithFileGroup(fileGroup), WithFileName(name))
+	config, err := New(configAPI, WithNamespace(namespace), WithFileGroup(fileGroup), WithFileName(name))
 	if err != nil {
 		t.Fatal(err)
 	}
diff --git a/internal/matcher/middleware_test.go b/internal/matcher/middleware_test.go
new file mode 100644
index 00000000..9c948aeb
--- /dev/null
+++ b/internal/matcher/middleware_test.go
@@ -0,0 +1,62 @@
+package matcher
+
+import (
+	"context"
+	"testing"
+
+	"github.com/go-kratos/kratos/v2/middleware"
+)
+
+func logging(module string) middleware.Middleware {
+	return func(handler middleware.Handler) middleware.Handler {
+		return func(ctx context.Context, req interface{}) (reply interface{}, err error) {
+			return module, nil
+		}
+	}
+}
+
+func equal(ms []middleware.Middleware, modules ...string) bool {
+	if len(ms) == 0 {
+		return false
+	}
+	for i, m := range ms {
+		x, _ := m(nil)(nil, nil)
+		if x != modules[i] {
+			return false
+		}
+	}
+	return true
+}
+
+func TestMatcher(t *testing.T) {
+	m := New()
+	m.Use(logging("logging"))
+	m.Add("*", logging("*"))
+	m.Add("/foo/*", logging("foo/*"))
+	m.Add("/foo/bar/*", logging("foo/bar/*"))
+	m.Add("/foo/bar", logging("foo/bar"))
+
+	if ms := m.Match("/"); len(ms) != 2 {
+		t.Fatal("not equal")
+	} else if !equal(ms, "logging", "*") {
+		t.Fatal("not equal")
+	}
+
+	if ms := m.Match("/foo/xxx"); len(ms) != 2 {
+		t.Fatal("not equal")
+	} else if !equal(ms, "logging", "foo/*") {
+		t.Fatal("not equal")
+	}
+
+	if ms := m.Match("/foo/bar"); len(ms) != 2 {
+		t.Fatal("not equal")
+	} else if !equal(ms, "logging", "foo/bar") {
+		t.Fatal("not equal")
+	}
+
+	if ms := m.Match("/foo/bar/x"); len(ms) != 2 {
+		t.Fatal("not equal")
+	} else if !equal(ms, "logging", "foo/bar/*") {
+		t.Fatal("not equal")
+	}
+}
diff --git a/transport/grpc/server_test.go b/transport/grpc/server_test.go
index 368c3295..fd1ae675 100644
--- a/transport/grpc/server_test.go
+++ b/transport/grpc/server_test.go
@@ -12,6 +12,7 @@ import (
 	"time"
 
 	"github.com/go-kratos/kratos/v2/errors"
+	"github.com/go-kratos/kratos/v2/internal/matcher"
 	pb "github.com/go-kratos/kratos/v2/internal/testdata/helloworld"
 	"github.com/go-kratos/kratos/v2/middleware"
 	"github.com/go-kratos/kratos/v2/transport"
@@ -198,17 +199,6 @@ func TestTimeout(t *testing.T) {
 	}
 }
 
-func TestMiddleware(t *testing.T) {
-	o := &Server{}
-	v := []middleware.Middleware{
-		func(middleware.Handler) middleware.Handler { return nil },
-	}
-	Middleware(v...)(o)
-	if !reflect.DeepEqual(v, o.middleware) {
-		t.Errorf("expect %v, got %v", v, o.middleware)
-	}
-}
-
 func TestTLSConfig(t *testing.T) {
 	o := &Server{}
 	v := &tls.Config{}
@@ -273,9 +263,10 @@ func TestServer_unaryServerInterceptor(t *testing.T) {
 	srv := &Server{
 		baseCtx:    context.Background(),
 		endpoint:   u,
-		middleware: []middleware.Middleware{EmptyMiddleware()},
 		timeout:    time.Duration(10),
+		middleware: matcher.New(),
 	}
+	srv.middleware.Use(EmptyMiddleware())
 	req := &struct{}{}
 	rv, err := srv.unaryServerInterceptor()(context.TODO(), req, &grpc.UnaryServerInfo{}, func(ctx context.Context, req interface{}) (i interface{}, e error) {
 		return &testResp{Data: "hi"}, nil
diff --git a/transport/http/server_test.go b/transport/http/server_test.go
index a6f05080..05bc8399 100644
--- a/transport/http/server_test.go
+++ b/transport/http/server_test.go
@@ -14,7 +14,6 @@ import (
 	"time"
 
 	"github.com/go-kratos/kratos/v2/errors"
-	"github.com/go-kratos/kratos/v2/middleware"
 
 	"github.com/go-kratos/kratos/v2/internal/host"
 )
@@ -313,17 +312,6 @@ func TestLogger(t *testing.T) {
 	// todo
 }
 
-func TestMiddleware(t *testing.T) {
-	o := &Server{}
-	v := []middleware.Middleware{
-		func(middleware.Handler) middleware.Handler { return nil },
-	}
-	Middleware(v...)(o)
-	if !reflect.DeepEqual(v, o.ms) {
-		t.Errorf("expected %v got %v", v, o.ms)
-	}
-}
-
 func TestRequestDecoder(t *testing.T) {
 	o := &Server{}
 	v := func(*http.Request, interface{}) error { return nil }