Repository: go-kratos/kratos
Pull request #1029 (2021-06-11 07:25:55)

transport/http: add http route
grpc:
operation = fullMethod

http:
operation = /users/{name}
path = /users/123
method = GET


Patch:
diff --git a/api/metadata/metadata_http.pb.go b/api/metadata/metadata_http.pb.go
index e19b1592..1005cd50 100644
--- a/api/metadata/metadata_http.pb.go
+++ b/api/metadata/metadata_http.pb.go
@@ -1,4 +1,6 @@
 // Code generated by protoc-gen-go-http. DO NOT EDIT.
+// versions:
+// protoc-gen-go-http v2.0.0-rc3
 
 package metadata
 
@@ -6,126 +8,102 @@ import (
 	context "context"
 	middleware "github.com/go-kratos/kratos/v2/middleware"
 	transport "github.com/go-kratos/kratos/v2/transport"
-	http1 "github.com/go-kratos/kratos/v2/transport/http"
+	http "github.com/go-kratos/kratos/v2/transport/http"
 	binding "github.com/go-kratos/kratos/v2/transport/http/binding"
-	mux "github.com/gorilla/mux"
-	http "net/http"
 )
 
 // This is a compile-time assertion to ensure that this generated file
 // is compatible with the kratos package it is being compiled against.
-var _ = new(http.Request)
 var _ = new(context.Context)
 var _ = new(middleware.Middleware)
 var _ = new(transport.Transporter)
 var _ = binding.BindVars
-var _ = mux.NewRouter
 
-const _ = http1.SupportPackageIsVersion1
+const _ = http.SupportPackageIsVersion1
 
-type MetadataHandler interface {
+type MetadataHTTPServer interface {
 	GetServiceDesc(context.Context, *GetServiceDescRequest) (*GetServiceDescReply, error)
-
 	ListServices(context.Context, *ListServicesRequest) (*ListServicesReply, error)
 }
 
-func NewMetadataHandler(srv MetadataHandler, opts ...http1.HandleOption) http.Handler {
-	h := http1.DefaultHandleOptions()
-	for _, o := range opts {
-		o(&h)
-	}
-	r := mux.NewRouter()
+func RegisterMetadataHTTPServer(s *http.Server, srv MetadataHTTPServer) {
+	r := s.Route("/")
+	r.GET("/services", _Metadata_ListServices0_HTTP_Handler(srv))
+	r.GET("/services/{name}", _Metadata_GetServiceDesc0_HTTP_Handler(srv))
+}
 
-	r.HandleFunc("/services", func(w http.ResponseWriter, r *http.Request) {
+func _Metadata_ListServices0_HTTP_Handler(srv MetadataHTTPServer) func(ctx http.Context) error {
+	return func(ctx http.Context) error {
 		var in ListServicesRequest
-		if err := h.Decode(r, &in); err != nil {
-			h.Error(w, r, err)
-			return
+		if err := ctx.Bind(&in); err != nil {
+			return err
 		}
-
-		next := func(ctx context.Context, req interface{}) (interface{}, error) {
+		transport.SetOperation(ctx, "/kratos.api.Metadata/ListServices")
+		h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) {
 			return srv.ListServices(ctx, req.(*ListServicesRequest))
-		}
-		if h.Middleware != nil {
-			next = h.Middleware(next)
-		}
-		ctx := r.Context()
-		transport.SetMethod(ctx, "/kratos.api.Metadata/ListServices")
-		out, err := next(ctx, &in)
+		})
+		out, err := h(ctx, &in)
 		if err != nil {
-			h.Error(w, r, err)
-			return
+			return err
 		}
 		reply := out.(*ListServicesReply)
-		if err := h.Encode(w, r, reply); err != nil {
-			h.Error(w, r, err)
-		}
-	}).Methods("GET")
+		return ctx.Result(200, reply)
+	}
+}
 
-	r.HandleFunc("/services/{name}", func(w http.ResponseWriter, r *http.Request) {
+func _Metadata_GetServiceDesc0_HTTP_Handler(srv MetadataHTTPServer) func(ctx http.Context) error {
+	return func(ctx http.Context) error {
 		var in GetServiceDescRequest
-		if err := h.Decode(r, &in); err != nil {
-			h.Error(w, r, err)
-			return
+		if err := ctx.Bind(&in); err != nil {
+			return err
 		}
-
-		if err := binding.BindVars(mux.Vars(r), &in); err != nil {
-			h.Error(w, r, err)
-			return
+		if err := binding.BindVars(ctx.Vars(), &in); err != nil {
+			return err
 		}
-
-		next := func(ctx context.Context, req interface{}) (interface{}, error) {
+		transport.SetOperation(ctx, "/kratos.api.Metadata/GetServiceDesc")
+		h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) {
 			return srv.GetServiceDesc(ctx, req.(*GetServiceDescRequest))
-		}
-		if h.Middleware != nil {
-			next = h.Middleware(next)
-		}
-		ctx := r.Context()
-		transport.SetMethod(ctx, "/kratos.api.Metadata/GetServiceDesc")
-		out, err := next(ctx, &in)
+		})
+		out, err := h(ctx, &in)
 		if err != nil {
-			h.Error(w, r, err)
-			return
+			return err
 		}
 		reply := out.(*GetServiceDescReply)
-		if err := h.Encode(w, r, reply); err != nil {
-			h.Error(w, r, err)
-		}
-	}).Methods("GET")
-
-	return r
+		return ctx.Result(200, reply)
+	}
 }
 
 type MetadataHTTPClient interface {
-	GetServiceDesc(ctx context.Context, req *GetServiceDescRequest, opts ...http1.CallOption) (rsp *GetServiceDescReply, err error)
-
-	ListServices(ctx context.Context, req *ListServicesRequest, opts ...http1.CallOption) (rsp *ListServicesReply, err error)
+	GetServiceDesc(ctx context.Context, req *GetServiceDescRequest, opts ...http.CallOption) (rsp *GetServiceDescReply, err error)
+	ListServices(ctx context.Context, req *ListServicesRequest, opts ...http.CallOption) (rsp *ListServicesReply, err error)
 }
 
 type MetadataHTTPClientImpl struct {
-	cc *http1.Client
+	cc *http.Client
 }
 
-func NewMetadataHTTPClient(client *http1.Client) MetadataHTTPClient {
+func NewMetadataHTTPClient(client *http.Client) MetadataHTTPClient {
 	return &MetadataHTTPClientImpl{client}
 }
 
-func (c *MetadataHTTPClientImpl) GetServiceDesc(ctx context.Context, in *GetServiceDescRequest, opts ...http1.CallOption) (*GetServiceDescReply, error) {
+func (c *MetadataHTTPClientImpl) GetServiceDesc(ctx context.Context, in *GetServiceDescRequest, opts ...http.CallOption) (*GetServiceDescReply, error) {
 	var out GetServiceDescReply
-	path := binding.EncodePath("GET", "/services/{name}", in)
-	opts = append(opts, http1.Method("/kratos.api.Metadata/GetServiceDesc"))
-
+	path := binding.EncodeVars("/services/{name}", in, true)
+	opts = append(opts, http.Operation("/kratos.api.Metadata/GetServiceDesc"))
 	err := c.cc.Invoke(ctx, "GET", path, nil, &out, opts...)
-
+	if err != nil {
+		return nil, err
+	}
 	return &out, err
 }
 
-func (c *MetadataHTTPClientImpl) ListServices(ctx context.Context, in *ListServicesRequest, opts ...http1.CallOption) (*ListServicesReply, error) {
+func (c *MetadataHTTPClientImpl) ListServices(ctx context.Context, in *ListServicesRequest, opts ...http.CallOption) (*ListServicesReply, error) {
 	var out ListServicesReply
-	path := binding.EncodePath("GET", "/services", in)
-	opts = append(opts, http1.Method("/kratos.api.Metadata/ListServices"))
-
+	path := binding.EncodeVars("/services", in, true)
+	opts = append(opts, http.Operation("/kratos.api.Metadata/ListServices"))
 	err := c.cc.Invoke(ctx, "GET", path, nil, &out, opts...)
-
+	if err != nil {
+		return nil, err
+	}
 	return &out, err
 }
diff --git a/cmd/protoc-gen-go-errors/errors.go b/cmd/protoc-gen-go-errors/errors.go
index 03c7a2dd..3380a852 100644
--- a/cmd/protoc-gen-go-errors/errors.go
+++ b/cmd/protoc-gen-go-errors/errors.go
@@ -15,7 +15,13 @@ const (
 
 // generateFile generates a _http.pb.go file containing kratos errors definitions.
 func generateFile(gen *protogen.Plugin, file *protogen.File) *protogen.GeneratedFile {
-	if len(file.Enums) == 0 {
+	var hasCode bool
+	for _, enum := range file.Enums {
+		if code := defaultErrorCode(enum); code > 0 {
+			hasCode = true
+		}
+	}
+	if len(file.Enums) == 0 || !hasCode {
 		return nil
 	}
 	filename := file.GeneratedFilenamePrefix + "_errors.pb.go"
@@ -44,36 +50,37 @@ func generateFileContent(gen *protogen.Plugin, file *protogen.File, g *protogen.
 	}
 }
 
-func genErrorsReason(gen *protogen.Plugin, file *protogen.File, g *protogen.GeneratedFile, enum *protogen.Enum) {
+func defaultErrorCode(enum *protogen.Enum) int {
 	defaultCode := proto.GetExtension(enum.Desc.Options(), errors.E_DefaultCode)
-	code := 0
-	if ok := defaultCode.(int32); ok != 0 && ok <= 600 && ok >= 200 {
-		code = int(ok)
-	} else {
-		return
+	if code, ok := defaultCode.(int32); ok && code > 0 {
+		return int(code)
 	}
+	return 0
+}
+
+func genErrorsReason(gen *protogen.Plugin, file *protogen.File, g *protogen.GeneratedFile, enum *protogen.Enum) {
 	var ew errorWrapper
+	defCode := defaultErrorCode(enum)
 	for _, v := range enum.Values {
-		eCode := proto.GetExtension(v.Desc.Options(), errors.E_Code)
-		enumCode := int(eCode.(int32))
-		if enumCode == 0 {
-			enumCode = code
+		code := int(proto.GetExtension(v.Desc.Options(), errors.E_Code).(int32))
+		if code == 0 {
+			code = defCode
 		}
-		if enumCode > 600 || enumCode < 200 {
-			return
+		if code > 600 || code < 200 {
+			panic("httpstatus code must be greater than or equal to 200 and less than 600")
 		}
 		err := &errorInfo{
 			Name:       string(enum.Desc.Name()),
 			Value:      string(v.Desc.Name()),
-			CamelValue: Case2Camel(string(v.Desc.Name())),
-			HttpCode:   enumCode,
+			CamelValue: case2Camel(string(v.Desc.Name())),
+			HttpCode:   code,
 		}
 		ew.Errors = append(ew.Errors, err)
 	}
 	g.P(ew.execute())
 }
 
-func Case2Camel(name string) string {
+func case2Camel(name string) string {
 	if !strings.Contains(name, "_") {
 		return name
 	}
diff --git a/cmd/protoc-gen-go-http/http.go b/cmd/protoc-gen-go-http/http.go
index 97148677..3cdca5a1 100644
--- a/cmd/protoc-gen-go-http/http.go
+++ b/cmd/protoc-gen-go-http/http.go
@@ -1,6 +1,7 @@
 package main
 
 import (
+	"fmt"
 	"strings"
 
 	"google.golang.org/genproto/googleapis/api/annotations"
@@ -11,8 +12,6 @@ import (
 
 const (
 	contextPackage       = protogen.GoImportPath("context")
-	httpPackage          = protogen.GoImportPath("net/http")
-	muxPackage           = protogen.GoImportPath("github.com/gorilla/mux")
 	middlewarePackage    = protogen.GoImportPath("github.com/go-kratos/kratos/v2/middleware")
 	transportHTTPPackage = protogen.GoImportPath("github.com/go-kratos/kratos/v2/transport/http")
 	bindingPackage       = protogen.GoImportPath("github.com/go-kratos/kratos/v2/transport/http/binding")
@@ -29,6 +28,8 @@ func generateFile(gen *protogen.Plugin, file *protogen.File) *protogen.Generated
 	filename := file.GeneratedFilenamePrefix + "_http.pb.go"
 	g := gen.NewGeneratedFile(filename, file.GoImportPath)
 	g.P("// Code generated by protoc-gen-go-http. DO NOT EDIT.")
+	g.P("// versions:")
+	g.P(fmt.Sprintf("// protoc-gen-go-http %s", Version))
 	g.P()
 	g.P("package ", file.GoPackageName)
 	g.P()
@@ -43,12 +44,10 @@ func generateFileContent(gen *protogen.Plugin, file *protogen.File, g *protogen.
 	}
 	g.P("// This is a compile-time assertion to ensure that this generated file")
 	g.P("// is compatible with the kratos package it is being compiled against.")
-	g.P("var _ = new(", httpPackage.Ident("Request"), ")")
 	g.P("var _ = new(", contextPackage.Ident("Context"), ")")
 	g.P("var _ = new(", middlewarePackage.Ident("Middleware"), ")")
 	g.P("var _ = new(", transportPackage.Ident("Transporter"), ")")
 	g.P("var _ = ", bindingPackage.Ident("BindVars"))
-	g.P("var _ = ", muxPackage.Ident("NewRouter"))
 	g.P("const _ = ", transportHTTPPackage.Ident("SupportPackageIsVersion1"))
 	g.P()
 
@@ -104,11 +103,13 @@ func buildHTTPRule(g *protogen.GeneratedFile, m *protogen.Method, rule *annotati
 		method       string
 		body         string
 		responseBody string
+		isQuery      bool
 	)
 	switch pattern := rule.Pattern.(type) {
 	case *annotations.HttpRule_Get:
 		path = pattern.Get
 		method = "GET"
+		isQuery = true
 	case *annotations.HttpRule_Put:
 		path = pattern.Put
 		method = "PUT"
@@ -142,6 +143,7 @@ func buildHTTPRule(g *protogen.GeneratedFile, m *protogen.Method, rule *annotati
 	} else if responseBody != "" {
 		md.ResponseBody = "." + camelCaseVars(responseBody)
 	}
+	md.IsQuery = isQuery
 	return md
 }
 
diff --git a/cmd/protoc-gen-go-http/template.go b/cmd/protoc-gen-go-http/template.go
index 37245363..6b781d15 100644
--- a/cmd/protoc-gen-go-http/template.go
+++ b/cmd/protoc-gen-go-http/template.go
@@ -9,77 +9,72 @@ import (
 var httpTemplate = `
 {{$svrType := .ServiceType}}
 {{$svrName := .ServiceName}}
-type {{.ServiceType}}Handler interface {
-{{range .MethodSets}}
+type {{.ServiceType}}HTTPServer interface {
+{{- range .MethodSets}}
 	{{.Name}}(context.Context, *{{.Request}}) (*{{.Reply}}, error)
-{{end}}
+{{- end}}
 }
 
-func New{{.ServiceType}}Handler(srv {{.ServiceType}}Handler, opts ...http1.HandleOption) http.Handler {
-	h := http1.DefaultHandleOptions()
-	for _, o := range opts {
-		o(&h)
-	}
-	r := mux.NewRouter()
-	{{range .Methods}}
-	r.HandleFunc("{{.Path}}", func(w http.ResponseWriter, r *http.Request) {
+func Register{{.ServiceType}}HTTPServer(s *http.Server, srv {{.ServiceType}}HTTPServer) {
+	r := s.Route("/")
+	{{- range .Methods}}
+	r.{{.Method}}("{{.Path}}", _{{$svrType}}_{{.Name}}{{.Num}}_HTTP_Handler(srv))
+	{{- end}}
+}
+
+{{range .Methods}}
+func _{{$svrType}}_{{.Name}}{{.Num}}_HTTP_Handler(srv {{$svrType}}HTTPServer) func(ctx http.Context) error {
+	return func(ctx http.Context) error {
 		var in {{.Request}}
-		if err := h.Decode(r, &in{{.Body}}); err != nil {
-			h.Error(w, r, err)
-			return
+		if err := ctx.Bind(&in{{.Body}}); err != nil {
+			return err
 		}
-		{{if ne (len .Vars) 0}}
-		if err := binding.BindVars(mux.Vars(r), &in); err != nil {
-			h.Error(w, r, err)
-			return
+		{{- if ne (len .Vars) 0}}
+		if err := binding.BindVars(ctx.Vars(), &in); err != nil {
+			return err
 		}
-		{{end}}
-		next := func(ctx context.Context, req interface{}) (interface{}, error) {
+		{{- end}}
+		transport.SetOperation(ctx,"/{{$svrName}}/{{.Name}}")
+		h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) {
 			return srv.{{.Name}}(ctx, req.(*{{.Request}}))
-		}
-		if h.Middleware != nil {
-			next = h.Middleware(next)
-		}
-		ctx := r.Context()
-		transport.SetMethod(ctx,"/{{$svrName}}/{{.Name}}")
-		out, err := next(ctx, &in)
+		})
+		out, err := h(ctx, &in)
 		if err != nil {
-			h.Error(w, r, err)
-			return
+			return err
 		}
 		reply := out.(*{{.Reply}})
-		if err := h.Encode(w, r, reply{{.ResponseBody}}); err != nil {
-			h.Error(w, r, err)
-		}
-	}).Methods("{{.Method}}")
-	{{end}}
-	return r
+		return ctx.Result(200, reply{{.ResponseBody}})
+	}
 }
+{{end}}
 
 type {{.ServiceType}}HTTPClient interface {
-{{range .MethodSets}}
-	{{.Name}}(ctx context.Context, req *{{.Request}}, opts ...http1.CallOption) (rsp *{{.Reply}}, err error) 
-{{end}}
+{{- range .MethodSets}}
+	{{.Name}}(ctx context.Context, req *{{.Request}}, opts ...http.CallOption) (rsp *{{.Reply}}, err error) 
+{{- end}}
 }
 	
 type {{.ServiceType}}HTTPClientImpl struct{
-	cc *http1.Client
+	cc *http.Client
 }
 	
-func New{{.ServiceType}}HTTPClient (client *http1.Client) {{.ServiceType}}HTTPClient {
+func New{{.ServiceType}}HTTPClient (client *http.Client) {{.ServiceType}}HTTPClient {
 	return &{{.ServiceType}}HTTPClientImpl{client}
 }
 
 {{range .MethodSets}}
-func (c *{{$svrType}}HTTPClientImpl) {{.Name}}(ctx context.Context, in *{{.Request}}, opts ...http1.CallOption) (*{{.Reply}}, error) {
+func (c *{{$svrType}}HTTPClientImpl) {{.Name}}(ctx context.Context, in *{{.Request}}, opts ...http.CallOption) (*{{.Reply}}, error) {
 	var out {{.Reply}}
-	path := binding.EncodePath("{{.Method}}", "{{.Path}}", in)
-	opts = append(opts, http1.Method("/{{$svrName}}/{{.Name}}"))
-	{{if .HasBody }}
+	path := binding.EncodeVars("{{.Path}}", in, {{.IsQuery}})
+	opts = append(opts, http.Operation("/{{$svrName}}/{{.Name}}"))
+	{{if .HasBody -}}
 	err := c.cc.Invoke(ctx, "{{.Method}}", path, in{{.Body}}, &out{{.ResponseBody}}, opts...)
-	{{else}} 
+	{{else -}} 
 	err := c.cc.Invoke(ctx, "{{.Method}}", path, nil, &out{{.ResponseBody}}, opts...)
-	{{end}}
+	{{end -}}
+	if err != nil {
+		return nil, err
+	}
 	return &out, err
 }
 {{end}}
@@ -106,6 +101,7 @@ type methodDesc struct {
 	HasBody      bool
 	Body         string
 	ResponseBody string
+	IsQuery      bool
 }
 
 func (s *serviceDesc) execute() string {
@@ -121,5 +117,5 @@ func (s *serviceDesc) execute() string {
 	if err := tmpl.Execute(buf, s); err != nil {
 		panic(err)
 	}
-	return string(buf.Bytes())
+	return strings.Trim(string(buf.Bytes()), "\r\n")
 }
diff --git a/cmd/protoc-gen-go-http/version.go b/cmd/protoc-gen-go-http/version.go
new file mode 100644
index 00000000..71321b5e
--- /dev/null
+++ b/cmd/protoc-gen-go-http/version.go
@@ -0,0 +1,6 @@
+package main
+
+const (
+	// Version is protoc-gen-go-http version
+	Version = "v2.0.0-rc3"
+)
diff --git a/examples/blog/api/blog/v1/blog_http.pb.go b/examples/blog/api/blog/v1/blog_http.pb.go
index 4a0ceff2..93e2c771 100644
--- a/examples/blog/api/blog/v1/blog_http.pb.go
+++ b/examples/blog/api/blog/v1/blog_http.pb.go
@@ -1,4 +1,6 @@
 // Code generated by protoc-gen-go-http. DO NOT EDIT.
+// versions:
+// protoc-gen-go-http v2.0.0-rc3
 
 package v1
 
@@ -6,256 +8,207 @@ import (
 	context "context"
 	middleware "github.com/go-kratos/kratos/v2/middleware"
 	transport "github.com/go-kratos/kratos/v2/transport"
-	http1 "github.com/go-kratos/kratos/v2/transport/http"
+	http "github.com/go-kratos/kratos/v2/transport/http"
 	binding "github.com/go-kratos/kratos/v2/transport/http/binding"
-	mux "github.com/gorilla/mux"
-	http "net/http"
 )
 
 // This is a compile-time assertion to ensure that this generated file
 // is compatible with the kratos package it is being compiled against.
-var _ = new(http.Request)
 var _ = new(context.Context)
 var _ = new(middleware.Middleware)
 var _ = new(transport.Transporter)
 var _ = binding.BindVars
-var _ = mux.NewRouter
 
-const _ = http1.SupportPackageIsVersion1
+const _ = http.SupportPackageIsVersion1
 
-type BlogServiceHandler interface {
+type BlogServiceHTTPServer interface {
 	CreateArticle(context.Context, *CreateArticleRequest) (*CreateArticleReply, error)
-
 	DeleteArticle(context.Context, *DeleteArticleRequest) (*DeleteArticleReply, error)
-
 	GetArticle(context.Context, *GetArticleRequest) (*GetArticleReply, error)
-
 	ListArticle(context.Context, *ListArticleRequest) (*ListArticleReply, error)
-
 	UpdateArticle(context.Context, *UpdateArticleRequest) (*UpdateArticleReply, error)
 }
 
-func NewBlogServiceHandler(srv BlogServiceHandler, opts ...http1.HandleOption) http.Handler {
-	h := http1.DefaultHandleOptions()
-	for _, o := range opts {
-		o(&h)
-	}
-	r := mux.NewRouter()
+func RegisterBlogServiceHTTPServer(s *http.Server, srv BlogServiceHTTPServer) {
+	r := s.Route("/")
+	r.POST("/v1/article/", _BlogService_CreateArticle0_HTTP_Handler(srv))
+	r.PUT("/v1/article/{id}", _BlogService_UpdateArticle0_HTTP_Handler(srv))
+	r.DELETE("/v1/article/{id}", _BlogService_DeleteArticle0_HTTP_Handler(srv))
+	r.GET("/v1/article/{id}", _BlogService_GetArticle0_HTTP_Handler(srv))
+	r.GET("/v1/article/", _BlogService_ListArticle0_HTTP_Handler(srv))
+}
 
-	r.HandleFunc("/v1/article/", func(w http.ResponseWriter, r *http.Request) {
+func _BlogService_CreateArticle0_HTTP_Handler(srv BlogServiceHTTPServer) func(ctx http.Context) error {
+	return func(ctx http.Context) error {
 		var in CreateArticleRequest
-		if err := h.Decode(r, &in); err != nil {
-			h.Error(w, r, err)
-			return
+		if err := ctx.Bind(&in); err != nil {
+			return err
 		}
-
-		next := func(ctx context.Context, req interface{}) (interface{}, error) {
+		transport.SetOperation(ctx, "/blog.api.v1.BlogService/CreateArticle")
+		h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) {
 			return srv.CreateArticle(ctx, req.(*CreateArticleRequest))
-		}
-		if h.Middleware != nil {
-			next = h.Middleware(next)
-		}
-		ctx := r.Context()
-		transport.SetMethod(ctx, "/blog.api.v1.BlogService/CreateArticle")
-		out, err := next(ctx, &in)
+		})
+		out, err := h(ctx, &in)
 		if err != nil {
-			h.Error(w, r, err)
-			return
+			return err
 		}
 		reply := out.(*CreateArticleReply)
-		if err := h.Encode(w, r, reply); err != nil {
-			h.Error(w, r, err)
-		}
-	}).Methods("POST")
+		return ctx.Result(200, reply)
+	}
+}
 
-	r.HandleFunc("/v1/article/{id}", func(w http.ResponseWriter, r *http.Request) {
+func _BlogService_UpdateArticle0_HTTP_Handler(srv BlogServiceHTTPServer) func(ctx http.Context) error {
+	return func(ctx http.Context) error {
 		var in UpdateArticleRequest
-		if err := h.Decode(r, &in); err != nil {
-			h.Error(w, r, err)
-			return
+		if err := ctx.Bind(&in); err != nil {
+			return err
 		}
-
-		if err := binding.BindVars(mux.Vars(r), &in); err != nil {
-			h.Error(w, r, err)
-			return
+		if err := binding.BindVars(ctx.Vars(), &in); err != nil {
+			return err
 		}
-
-		next := func(ctx context.Context, req interface{}) (interface{}, error) {
+		transport.SetOperation(ctx, "/blog.api.v1.BlogService/UpdateArticle")
+		h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) {
 			return srv.UpdateArticle(ctx, req.(*UpdateArticleRequest))
-		}
-		if h.Middleware != nil {
-			next = h.Middleware(next)
-		}
-		ctx := r.Context()
-		transport.SetMethod(ctx, "/blog.api.v1.BlogService/UpdateArticle")
-		out, err := next(ctx, &in)
+		})
+		out, err := h(ctx, &in)
 		if err != nil {
-			h.Error(w, r, err)
-			return
+			return err
 		}
 		reply := out.(*UpdateArticleReply)
-		if err := h.Encode(w, r, reply); err != nil {
-			h.Error(w, r, err)
-		}
-	}).Methods("PUT")
+		return ctx.Result(200, reply)
+	}
+}
 
-	r.HandleFunc("/v1/article/{id}", func(w http.ResponseWriter, r *http.Request) {
+func _BlogService_DeleteArticle0_HTTP_Handler(srv BlogServiceHTTPServer) func(ctx http.Context) error {
+	return func(ctx http.Context) error {
 		var in DeleteArticleRequest
-		if err := h.Decode(r, &in); err != nil {
-			h.Error(w, r, err)
-			return
+		if err := ctx.Bind(&in); err != nil {
+			return err
 		}
-
-		if err := binding.BindVars(mux.Vars(r), &in); err != nil {
-			h.Error(w, r, err)
-			return
+		if err := binding.BindVars(ctx.Vars(), &in); err != nil {
+			return err
 		}
-
-		next := func(ctx context.Context, req interface{}) (interface{}, error) {
+		transport.SetOperation(ctx, "/blog.api.v1.BlogService/DeleteArticle")
+		h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) {
 			return srv.DeleteArticle(ctx, req.(*DeleteArticleRequest))
-		}
-		if h.Middleware != nil {
-			next = h.Middleware(next)
-		}
-		ctx := r.Context()
-		transport.SetMethod(ctx, "/blog.api.v1.BlogService/DeleteArticle")
-		out, err := next(ctx, &in)
+		})
+		out, err := h(ctx, &in)
 		if err != nil {
-			h.Error(w, r, err)
-			return
+			return err
 		}
 		reply := out.(*DeleteArticleReply)
-		if err := h.Encode(w, r, reply); err != nil {
-			h.Error(w, r, err)
-		}
-	}).Methods("DELETE")
+		return ctx.Result(200, reply)
+	}
+}
 
-	r.HandleFunc("/v1/article/{id}", func(w http.ResponseWriter, r *http.Request) {
+func _BlogService_GetArticle0_HTTP_Handler(srv BlogServiceHTTPServer) func(ctx http.Context) error {
+	return func(ctx http.Context) error {
 		var in GetArticleRequest
-		if err := h.Decode(r, &in); err != nil {
-			h.Error(w, r, err)
-			return
+		if err := ctx.Bind(&in); err != nil {
+			return err
 		}
-
-		if err := binding.BindVars(mux.Vars(r), &in); err != nil {
-			h.Error(w, r, err)
-			return
+		if err := binding.BindVars(ctx.Vars(), &in); err != nil {
+			return err
 		}
-
-		next := func(ctx context.Context, req interface{}) (interface{}, error) {
+		transport.SetOperation(ctx, "/blog.api.v1.BlogService/GetArticle")
+		h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) {
 			return srv.GetArticle(ctx, req.(*GetArticleRequest))
-		}
-		if h.Middleware != nil {
-			next = h.Middleware(next)
-		}
-		ctx := r.Context()
-		transport.SetMethod(ctx, "/blog.api.v1.BlogService/GetArticle")
-		out, err := next(ctx, &in)
+		})
+		out, err := h(ctx, &in)
 		if err != nil {
-			h.Error(w, r, err)
-			return
+			return err
 		}
 		reply := out.(*GetArticleReply)
-		if err := h.Encode(w, r, reply); err != nil {
-			h.Error(w, r, err)
-		}
-	}).Methods("GET")
+		return ctx.Result(200, reply)
+	}
+}
 
-	r.HandleFunc("/v1/article/", func(w http.ResponseWriter, r *http.Request) {
+func _BlogService_ListArticle0_HTTP_Handler(srv BlogServiceHTTPServer) func(ctx http.Context) error {
+	return func(ctx http.Context) error {
 		var in ListArticleRequest
-		if err := h.Decode(r, &in); err != nil {
-			h.Error(w, r, err)
-			return
+		if err := ctx.Bind(&in); err != nil {
+			return err
 		}
-
-		next := func(ctx context.Context, req interface{}) (interface{}, error) {
+		transport.SetOperation(ctx, "/blog.api.v1.BlogService/ListArticle")
+		h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) {
 			return srv.ListArticle(ctx, req.(*ListArticleRequest))
-		}
-		if h.Middleware != nil {
-			next = h.Middleware(next)
-		}
-		ctx := r.Context()
-		transport.SetMethod(ctx, "/blog.api.v1.BlogService/ListArticle")
-		out, err := next(ctx, &in)
+		})
+		out, err := h(ctx, &in)
 		if err != nil {
-			h.Error(w, r, err)
-			return
+			return err
 		}
 		reply := out.(*ListArticleReply)
-		if err := h.Encode(w, r, reply); err != nil {
-			h.Error(w, r, err)
-		}
-	}).Methods("GET")
-
-	return r
+		return ctx.Result(200, reply)
+	}
 }
 
 type BlogServiceHTTPClient interface {
-	CreateArticle(ctx context.Context, req *CreateArticleRequest, opts ...http1.CallOption) (rsp *CreateArticleReply, err error)
-
-	DeleteArticle(ctx context.Context, req *DeleteArticleRequest, opts ...http1.CallOption) (rsp *DeleteArticleReply, err error)
-
-	GetArticle(ctx context.Context, req *GetArticleRequest, opts ...http1.CallOption) (rsp *GetArticleReply, err error)
-
-	ListArticle(ctx context.Context, req *ListArticleRequest, opts ...http1.CallOption) (rsp *ListArticleReply, err error)
-
-	UpdateArticle(ctx context.Context, req *UpdateArticleRequest, opts ...http1.CallOption) (rsp *UpdateArticleReply, err error)
+	CreateArticle(ctx context.Context, req *CreateArticleRequest, opts ...http.CallOption) (rsp *CreateArticleReply, err error)
+	DeleteArticle(ctx context.Context, req *DeleteArticleRequest, opts ...http.CallOption) (rsp *DeleteArticleReply, err error)
+	GetArticle(ctx context.Context, req *GetArticleRequest, opts ...http.CallOption) (rsp *GetArticleReply, err error)
+	ListArticle(ctx context.Context, req *ListArticleRequest, opts ...http.CallOption) (rsp *ListArticleReply, err error)
+	UpdateArticle(ctx context.Context, req *UpdateArticleRequest, opts ...http.CallOption) (rsp *UpdateArticleReply, err error)
 }
 
 type BlogServiceHTTPClientImpl struct {
-	cc *http1.Client
+	cc *http.Client
 }
 
-func NewBlogServiceHTTPClient(client *http1.Client) BlogServiceHTTPClient {
+func NewBlogServiceHTTPClient(client *http.Client) BlogServiceHTTPClient {
 	return &BlogServiceHTTPClientImpl{client}
 }
 
-func (c *BlogServiceHTTPClientImpl) CreateArticle(ctx context.Context, in *CreateArticleRequest, opts ...http1.CallOption) (*CreateArticleReply, error) {
+func (c *BlogServiceHTTPClientImpl) CreateArticle(ctx context.Context, in *CreateArticleRequest, opts ...http.CallOption) (*CreateArticleReply, error) {
 	var out CreateArticleReply
-	path := binding.EncodePath("POST", "/v1/article/", in)
-	opts = append(opts, http1.Method("/blog.api.v1.BlogService/CreateArticle"))
-
+	path := binding.EncodeVars("/v1/article/", in, false)
+	opts = append(opts, http.Operation("/blog.api.v1.BlogService/CreateArticle"))
 	err := c.cc.Invoke(ctx, "POST", path, in, &out, opts...)
-
+	if err != nil {
+		return nil, err
+	}
 	return &out, err
 }
 
-func (c *BlogServiceHTTPClientImpl) DeleteArticle(ctx context.Context, in *DeleteArticleRequest, opts ...http1.CallOption) (*DeleteArticleReply, error) {
+func (c *BlogServiceHTTPClientImpl) DeleteArticle(ctx context.Context, in *DeleteArticleRequest, opts ...http.CallOption) (*DeleteArticleReply, error) {
 	var out DeleteArticleReply
-	path := binding.EncodePath("DELETE", "/v1/article/{id}", in)
-	opts = append(opts, http1.Method("/blog.api.v1.BlogService/DeleteArticle"))
-
+	path := binding.EncodeVars("/v1/article/{id}", in, false)
+	opts = append(opts, http.Operation("/blog.api.v1.BlogService/DeleteArticle"))
 	err := c.cc.Invoke(ctx, "DELETE", path, nil, &out, opts...)
-
+	if err != nil {
+		return nil, err
+	}
 	return &out, err
 }
 
-func (c *BlogServiceHTTPClientImpl) GetArticle(ctx context.Context, in *GetArticleRequest, opts ...http1.CallOption) (*GetArticleReply, error) {
+func (c *BlogServiceHTTPClientImpl) GetArticle(ctx context.Context, in *GetArticleRequest, opts ...http.CallOption) (*GetArticleReply, error) {
 	var out GetArticleReply
-	path := binding.EncodePath("GET", "/v1/article/{id}", in)
-	opts = append(opts, http1.Method("/blog.api.v1.BlogService/GetArticle"))
-
+	path := binding.EncodeVars("/v1/article/{id}", in, true)
+	opts = append(opts, http.Operation("/blog.api.v1.BlogService/GetArticle"))
 	err := c.cc.Invoke(ctx, "GET", path, nil, &out, opts...)
-
+	if err != nil {
+		return nil, err
+	}
 	return &out, err
 }
 
-func (c *BlogServiceHTTPClientImpl) ListArticle(ctx context.Context, in *ListArticleRequest, opts ...http1.CallOption) (*ListArticleReply, error) {
+func (c *BlogServiceHTTPClientImpl) ListArticle(ctx context.Context, in *ListArticleRequest, opts ...http.CallOption) (*ListArticleReply, error) {
 	var out ListArticleReply
-	path := binding.EncodePath("GET", "/v1/article/", in)
-	opts = append(opts, http1.Method("/blog.api.v1.BlogService/ListArticle"))
-
+	path := binding.EncodeVars("/v1/article/", in, true)
+	opts = append(opts, http.Operation("/blog.api.v1.BlogService/ListArticle"))
 	err := c.cc.Invoke(ctx, "GET", path, nil, &out, opts...)
-
+	if err != nil {
+		return nil, err
+	}
 	return &out, err
 }
 
-func (c *BlogServiceHTTPClientImpl) UpdateArticle(ctx context.Context, in *UpdateArticleRequest, opts ...http1.CallOption) (*UpdateArticleReply, error) {
+func (c *BlogServiceHTTPClientImpl) UpdateArticle(ctx context.Context, in *UpdateArticleRequest, opts ...http.CallOption) (*UpdateArticleReply, error) {
 	var out UpdateArticleReply
-	path := binding.EncodePath("PUT", "/v1/article/{id}", in)
-	opts = append(opts, http1.Method("/blog.api.v1.BlogService/UpdateArticle"))
-
+	path := binding.EncodeVars("/v1/article/{id}", in, false)
+	opts = append(opts, http.Operation("/blog.api.v1.BlogService/UpdateArticle"))
 	err := c.cc.Invoke(ctx, "PUT", path, in, &out, opts...)
-
+	if err != nil {
+		return nil, err
+	}
 	return &out, err
 }
diff --git a/examples/blog/api/blog/v1/error_reason.pb.go b/examples/blog/api/blog/v1/error_reason.pb.go
deleted file mode 100644
index 708113e7..00000000
--- a/examples/blog/api/blog/v1/error_reason.pb.go
+++ /dev/null
@@ -1,134 +0,0 @@
-// Code generated by protoc-gen-go. DO NOT EDIT.
-// versions:
-// 	protoc-gen-go v1.26.0
-// 	protoc        v3.15.7
-// source: blog/api/blog/v1/error_reason.proto
-
-package v1
-
-import (
-	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
-	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
-	reflect "reflect"
-	sync "sync"
-)
-
-const (
-	// Verify that this generated code is sufficiently up-to-date.
-	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
-	// Verify that runtime/protoimpl is sufficiently up-to-date.
-	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
-)
-
-type ErrorReason int32
-
-const (
-	ErrorReason_TITLE_MISSING  ErrorReason = 0
-	ErrorReason_CONTENTMISSING ErrorReason = 1
-)
-
-// Enum value maps for ErrorReason.
-var (
-	ErrorReason_name = map[int32]string{
-		0: "TITLE_MISSING",
-		1: "CONTENTMISSING",
-	}
-	ErrorReason_value = map[string]int32{
-		"TITLE_MISSING":  0,
-		"CONTENTMISSING": 1,
-	}
-)
-
-func (x ErrorReason) Enum() *ErrorReason {
-	p := new(ErrorReason)
-	*p = x
-	return p
-}
-
-func (x ErrorReason) String() string {
-	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
-}
-
-func (ErrorReason) Descriptor() protoreflect.EnumDescriptor {
-	return file_blog_api_blog_v1_error_reason_proto_enumTypes[0].Descriptor()
-}
-
-func (ErrorReason) Type() protoreflect.EnumType {
-	return &file_blog_api_blog_v1_error_reason_proto_enumTypes[0]
-}
-
-func (x ErrorReason) Number() protoreflect.EnumNumber {
-	return protoreflect.EnumNumber(x)
-}
-
-// Deprecated: Use ErrorReason.Descriptor instead.
-func (ErrorReason) EnumDescriptor() ([]byte, []int) {
-	return file_blog_api_blog_v1_error_reason_proto_rawDescGZIP(), []int{0}
-}
-
-var File_blog_api_blog_v1_error_reason_proto protoreflect.FileDescriptor
-
-var file_blog_api_blog_v1_error_reason_proto_rawDesc = []byte{
-	0x0a, 0x23, 0x62, 0x6c, 0x6f, 0x67, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x62, 0x6c, 0x6f, 0x67, 0x2f,
-	0x76, 0x31, 0x2f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x2e,
-	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0b, 0x61, 0x70, 0x69, 0x2e, 0x62, 0x6c, 0x6f, 0x67, 0x2e,
-	0x76, 0x31, 0x2a, 0x34, 0x0a, 0x0b, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x61, 0x73, 0x6f,
-	0x6e, 0x12, 0x11, 0x0a, 0x0d, 0x54, 0x49, 0x54, 0x4c, 0x45, 0x5f, 0x4d, 0x49, 0x53, 0x53, 0x49,
-	0x4e, 0x47, 0x10, 0x00, 0x12, 0x12, 0x0a, 0x0e, 0x43, 0x4f, 0x4e, 0x54, 0x45, 0x4e, 0x54, 0x4d,
-	0x49, 0x53, 0x53, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x42, 0x57, 0x0a, 0x0e, 0x62, 0x6c, 0x6f, 0x67,
-	0x2e, 0x76, 0x31, 0x2e, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x50, 0x01, 0x5a, 0x33, 0x67, 0x69,
-	0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x2d, 0x6b, 0x72, 0x61, 0x74,
-	0x6f, 0x73, 0x2f, 0x6b, 0x72, 0x61, 0x74, 0x6f, 0x73, 0x2f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c,
-	0x65, 0x73, 0x2f, 0x62, 0x6c, 0x6f, 0x67, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x3b, 0x76,
-	0x31, 0xa2, 0x02, 0x0d, 0x41, 0x50, 0x49, 0x42, 0x6c, 0x6f, 0x67, 0x45, 0x72, 0x72, 0x6f, 0x72,
-	0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
-}
-
-var (
-	file_blog_api_blog_v1_error_reason_proto_rawDescOnce sync.Once
-	file_blog_api_blog_v1_error_reason_proto_rawDescData = file_blog_api_blog_v1_error_reason_proto_rawDesc
-)
-
-func file_blog_api_blog_v1_error_reason_proto_rawDescGZIP() []byte {
-	file_blog_api_blog_v1_error_reason_proto_rawDescOnce.Do(func() {
-		file_blog_api_blog_v1_error_reason_proto_rawDescData = protoimpl.X.CompressGZIP(file_blog_api_blog_v1_error_reason_proto_rawDescData)
-	})
-	return file_blog_api_blog_v1_error_reason_proto_rawDescData
-}
-
-var file_blog_api_blog_v1_error_reason_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
-var file_blog_api_blog_v1_error_reason_proto_goTypes = []interface{}{
-	(ErrorReason)(0), // 0: api.blog.v1.ErrorReason
-}
-var file_blog_api_blog_v1_error_reason_proto_depIdxs = []int32{
-	0, // [0:0] is the sub-list for method output_type
-	0, // [0:0] is the sub-list for method input_type
-	0, // [0:0] is the sub-list for extension type_name
-	0, // [0:0] is the sub-list for extension extendee
-	0, // [0:0] is the sub-list for field type_name
-}
-
-func init() { file_blog_api_blog_v1_error_reason_proto_init() }
-func file_blog_api_blog_v1_error_reason_proto_init() {
-	if File_blog_api_blog_v1_error_reason_proto != nil {
-		return
-	}
-	type x struct{}
-	out := protoimpl.TypeBuilder{
-		File: protoimpl.DescBuilder{
-			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
-			RawDescriptor: file_blog_api_blog_v1_error_reason_proto_rawDesc,
-			NumEnums:      1,
-			NumMessages:   0,
-			NumExtensions: 0,
-			NumServices:   0,
-		},
-		GoTypes:           file_blog_api_blog_v1_error_reason_proto_goTypes,
-		DependencyIndexes: file_blog_api_blog_v1_error_reason_proto_depIdxs,
-		EnumInfos:         file_blog_api_blog_v1_error_reason_proto_enumTypes,
-	}.Build()
-	File_blog_api_blog_v1_error_reason_proto = out.File
-	file_blog_api_blog_v1_error_reason_proto_rawDesc = nil
-	file_blog_api_blog_v1_error_reason_proto_goTypes = nil
-	file_blog_api_blog_v1_error_reason_proto_depIdxs = nil
-}
diff --git a/examples/blog/api/blog/v1/error_reason.proto b/examples/blog/api/blog/v1/error_reason.proto
deleted file mode 100644
index 0ce01b00..00000000
--- a/examples/blog/api/blog/v1/error_reason.proto
+++ /dev/null
@@ -1,14 +0,0 @@
-syntax = "proto3";
-
-package api.blog.v1;
-
-// 多语言特定包名，用于源代码引用
-option go_package = "github.com/go-kratos/kratos/examples/blog/api/v1;v1";
-option java_multiple_files = true;
-option java_package = "blog.v1.errors";
-option objc_class_prefix = "APIBlogErrors";
-
-enum ErrorReason {
-    TITLE_MISSING = 0;
-    CONTENTMISSING = 1;
-}
diff --git a/examples/blog/cmd/blog/main.go b/examples/blog/cmd/blog/main.go
index 7a383835..51e352ad 100644
--- a/examples/blog/cmd/blog/main.go
+++ b/examples/blog/cmd/blog/main.go
@@ -6,7 +6,7 @@ import (
 	"os"
 	"time"
 
-	"github.com/go-kratos/kratos/examples/blog/api/blog/v1"
+	v1 "github.com/go-kratos/kratos/examples/blog/api/blog/v1"
 	"github.com/go-kratos/kratos/examples/blog/internal/conf"
 	"github.com/go-kratos/kratos/v2"
 	"github.com/go-kratos/kratos/v2/config"
@@ -19,7 +19,6 @@ import (
 	"go.opentelemetry.io/otel/sdk/resource"
 	tracesdk "go.opentelemetry.io/otel/sdk/trace"
 	"go.opentelemetry.io/otel/semconv"
-	"gopkg.in/yaml.v2"
 )
 
 // go build -ldflags "-X main.Version=x.y.z"
@@ -76,9 +75,6 @@ func main() {
 		config.WithSource(
 			file.NewSource(flagconf),
 		),
-		config.WithDecoder(func(kv *config.KeyValue, v map[string]interface{}) error {
-			return yaml.Unmarshal(kv.Value, v)
-		}),
 	)
 	if err := config.Load(); err != nil {
 		panic(err)
diff --git a/examples/blog/cmd/blog/wire_gen.go b/examples/blog/cmd/blog/wire_gen.go
index a5a1bb8e..c5b8eada 100644
--- a/examples/blog/cmd/blog/wire_gen.go
+++ b/examples/blog/cmd/blog/wire_gen.go
@@ -27,8 +27,8 @@ func initApp(confServer *conf.Server, confData *conf.Data, traceTracerProvider t
 	articleRepo := data.NewArticleRepo(dataData, logger)
 	articleUsecase := biz.NewArticleUsecase(articleRepo, logger)
 	blogService := service.NewBlogService(articleUsecase, logger)
-	httpServer := server.NewHTTPServer(confServer, traceTracerProvider, blogService)
-	grpcServer := server.NewGRPCServer(confServer, traceTracerProvider, blogService)
+	httpServer := server.NewHTTPServer(confServer, logger, traceTracerProvider, blogService)
+	grpcServer := server.NewGRPCServer(confServer, logger, traceTracerProvider, blogService)
 	app := newApp(logger, httpServer, grpcServer)
 	return app, func() {
 		cleanup()
diff --git a/examples/blog/internal/server/grpc.go b/examples/blog/internal/server/grpc.go
index fbee3659..d8f8d73a 100644
--- a/examples/blog/internal/server/grpc.go
+++ b/examples/blog/internal/server/grpc.go
@@ -14,12 +14,12 @@ import (
 )
 
 // NewGRPCServer new a gRPC server.
-func NewGRPCServer(c *conf.Server, tracer trace.TracerProvider, blog *service.BlogService) *grpc.Server {
+func NewGRPCServer(c *conf.Server, logger log.Logger, tracer trace.TracerProvider, blog *service.BlogService) *grpc.Server {
 	var opts = []grpc.ServerOption{
 		grpc.Middleware(
 			recovery.Recovery(),
 			tracing.Server(tracing.WithTracerProvider(tracer)),
-			logging.Server(log.DefaultLogger),
+			logging.Server(logger),
 			validate.Validator(),
 		),
 	}
diff --git a/examples/blog/internal/server/http.go b/examples/blog/internal/server/http.go
index 59cb4a6d..a394fc1e 100644
--- a/examples/blog/internal/server/http.go
+++ b/examples/blog/internal/server/http.go
@@ -14,8 +14,15 @@ import (
 )
 
 // NewHTTPServer new a HTTP server.
-func NewHTTPServer(c *conf.Server, tracer trace.TracerProvider, blog *service.BlogService) *http.Server {
-	var opts []http.ServerOption
+func NewHTTPServer(c *conf.Server, logger log.Logger, tracer trace.TracerProvider, blog *service.BlogService) *http.Server {
+	var opts = []http.ServerOption{
+		http.Middleware(
+			recovery.Recovery(),
+			tracing.Server(tracing.WithTracerProvider(tracer)),
+			logging.Server(logger),
+			validate.Validator(),
+		),
+	}
 	if c.Http.Network != "" {
 		opts = append(opts, http.Network(c.Http.Network))
 	}
@@ -25,13 +32,7 @@ func NewHTTPServer(c *conf.Server, tracer trace.TracerProvider, blog *service.Bl
 	if c.Http.Timeout != nil {
 		opts = append(opts, http.Timeout(c.Http.Timeout.AsDuration()))
 	}
-	m := http.Middleware(
-		recovery.Recovery(),
-		tracing.Server(tracing.WithTracerProvider(tracer)),
-		logging.Server(log.DefaultLogger),
-		validate.Validator(),
-	)
 	srv := http.NewServer(opts...)
-	srv.HandlePrefix("/", v1.NewBlogServiceHandler(blog, m))
+	v1.RegisterBlogServiceHTTPServer(srv, blog)
 	return srv
 }
diff --git a/examples/helloworld/helloworld/helloworld_http.pb.go b/examples/helloworld/helloworld/helloworld_http.pb.go
index f358a62a..bd368ad6 100644
--- a/examples/helloworld/helloworld/helloworld_http.pb.go
+++ b/examples/helloworld/helloworld/helloworld_http.pb.go
@@ -1,4 +1,6 @@
 // Code generated by protoc-gen-go-http. DO NOT EDIT.
+// versions:
+// protoc-gen-go-http v2.0.0-rc3
 
 package helloworld
 
@@ -6,86 +8,69 @@ import (
 	context "context"
 	middleware "github.com/go-kratos/kratos/v2/middleware"
 	transport "github.com/go-kratos/kratos/v2/transport"
-	http1 "github.com/go-kratos/kratos/v2/transport/http"
+	http "github.com/go-kratos/kratos/v2/transport/http"
 	binding "github.com/go-kratos/kratos/v2/transport/http/binding"
-	mux "github.com/gorilla/mux"
-	http "net/http"
 )
 
 // This is a compile-time assertion to ensure that this generated file
 // is compatible with the kratos package it is being compiled against.
-var _ = new(http.Request)
 var _ = new(context.Context)
 var _ = new(middleware.Middleware)
 var _ = new(transport.Transporter)
 var _ = binding.BindVars
-var _ = mux.NewRouter
 
-const _ = http1.SupportPackageIsVersion1
+const _ = http.SupportPackageIsVersion1
 
-type GreeterHandler interface {
+type GreeterHTTPServer interface {
 	SayHello(context.Context, *HelloRequest) (*HelloReply, error)
 }
 
-func NewGreeterHandler(srv GreeterHandler, opts ...http1.HandleOption) http.Handler {
-	h := http1.DefaultHandleOptions()
-	for _, o := range opts {
-		o(&h)
-	}
-	r := mux.NewRouter()
+func RegisterGreeterHTTPServer(s *http.Server, srv GreeterHTTPServer) {
+	r := s.Route("/")
+	r.GET("/helloworld/{name}", _Greeter_SayHello0_HTTP_Handler(srv))
+}
 
-	r.HandleFunc("/helloworld/{name}", func(w http.ResponseWriter, r *http.Request) {
+func _Greeter_SayHello0_HTTP_Handler(srv GreeterHTTPServer) func(ctx http.Context) error {
+	return func(ctx http.Context) error {
 		var in HelloRequest
-		if err := h.Decode(r, &in); err != nil {
-			h.Error(w, r, err)
-			return
+		if err := ctx.Bind(&in); err != nil {
+			return err
 		}
-
-		if err := binding.BindVars(mux.Vars(r), &in); err != nil {
-			h.Error(w, r, err)
-			return
+		if err := binding.BindVars(ctx.Vars(), &in); err != nil {
+			return err
 		}
-
-		next := func(ctx context.Context, req interface{}) (interface{}, error) {
+		transport.SetOperation(ctx, "/helloworld.Greeter/SayHello")
+		h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) {
 			return srv.SayHello(ctx, req.(*HelloRequest))
-		}
-		if h.Middleware != nil {
-			next = h.Middleware(next)
-		}
-		ctx := r.Context()
-		transport.SetMethod(ctx, "/helloworld.Greeter/SayHello")
-		out, err := next(ctx, &in)
+		})
+		out, err := h(ctx, &in)
 		if err != nil {
-			h.Error(w, r, err)
-			return
+			return err
 		}
 		reply := out.(*HelloReply)
-		if err := h.Encode(w, r, reply); err != nil {
-			h.Error(w, r, err)
-		}
-	}).Methods("GET")
-
-	return r
+		return ctx.Result(200, reply)
+	}
 }
 
 type GreeterHTTPClient interface {
-	SayHello(ctx context.Context, req *HelloRequest, opts ...http1.CallOption) (rsp *HelloReply, err error)
+	SayHello(ctx context.Context, req *HelloRequest, opts ...http.CallOption) (rsp *HelloReply, err error)
 }
 
 type GreeterHTTPClientImpl struct {
-	cc *http1.Client
+	cc *http.Client
 }
 
-func NewGreeterHTTPClient(client *http1.Client) GreeterHTTPClient {
+func NewGreeterHTTPClient(client *http.Client) GreeterHTTPClient {
 	return &GreeterHTTPClientImpl{client}
 }
 
-func (c *GreeterHTTPClientImpl) SayHello(ctx context.Context, in *HelloRequest, opts ...http1.CallOption) (*HelloReply, error) {
+func (c *GreeterHTTPClientImpl) SayHello(ctx context.Context, in *HelloRequest, opts ...http.CallOption) (*HelloReply, error) {
 	var out HelloReply
-	path := binding.EncodePath("GET", "/helloworld/{name}", in)
-	opts = append(opts, http1.Method("/helloworld.Greeter/SayHello"))
-
+	path := binding.EncodeVars("/helloworld/{name}", in, true)
+	opts = append(opts, http.Operation("/helloworld.Greeter/SayHello"))
 	err := c.cc.Invoke(ctx, "GET", path, nil, &out, opts...)
-
+	if err != nil {
+		return nil, err
+	}
 	return &out, err
 }
diff --git a/examples/helloworld/server/main.go b/examples/helloworld/server/main.go
index a196553d..fb51effa 100644
--- a/examples/helloworld/server/main.go
+++ b/examples/helloworld/server/main.go
@@ -42,25 +42,23 @@ func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloRe
 func main() {
 	logger := log.NewStdLogger(os.Stdout)
 
-	log := log.NewHelper(logger)
-
+	s := &server{}
 	grpcSrv := grpc.NewServer(
 		grpc.Address(":9000"),
 		grpc.Middleware(
 			recovery.Recovery(),
 			logging.Server(logger),
-		))
-
-	s := &server{}
-	pb.RegisterGreeterServer(grpcSrv, s)
-
-	httpSrv := http.NewServer(http.Address(":8000"))
-	httpSrv.HandlePrefix("/", pb.NewGreeterHandler(s,
+		),
+	)
+	httpSrv := http.NewServer(
+		http.Address(":8000"),
 		http.Middleware(
 			recovery.Recovery(),
 			logging.Server(logger),
-		)),
+		),
 	)
+	pb.RegisterGreeterServer(grpcSrv, s)
+	pb.RegisterGreeterHTTPServer(httpSrv, s)
 
 	app := kratos.New(
 		kratos.Name(Name),
@@ -71,6 +69,6 @@ func main() {
 	)
 
 	if err := app.Run(); err != nil {
-		log.Error(err)
+		panic(err)
 	}
 }
diff --git a/examples/http/handler/main.go b/examples/http/handler/main.go
deleted file mode 100644
index 53913bec..00000000
--- a/examples/http/handler/main.go
+++ /dev/null
@@ -1,52 +0,0 @@
-package main
-
-import (
-	"context"
-	"log"
-
-	"github.com/go-kratos/kratos/v2"
-	"github.com/go-kratos/kratos/v2/transport/http"
-	"github.com/gorilla/mux"
-)
-
-// User is a user model.
-type User struct {
-	ID   string `json:"id"`
-	Name string `json:"name"`
-}
-
-// UserService is a user service.
-type UserService struct{ users map[string]*User }
-
-// Get get a user from memory.
-func (u *UserService) Get(ctx context.Context, user *User) (*User, error) {
-	return u.users[user.ID], nil
-}
-
-// Add add a user to memory.
-func (u *UserService) Add(ctx context.Context, user *User) (*User, error) {
-	u.users[user.ID] = user
-	return user, nil
-}
-
-func main() {
-	us := &UserService{
-		users: make(map[string]*User),
-	}
-	router := mux.NewRouter()
-	router.Handle("/users/add", http.NewHandler(us.Add)).Methods("POST")
-	router.Handle("/users/detail", http.NewHandler(us.Get)).Methods("GET")
-
-	httpSrv := http.NewServer(http.Address(":8000"))
-	httpSrv.HandlePrefix("/", router)
-
-	app := kratos.New(
-		kratos.Name("handler"),
-		kratos.Server(
-			httpSrv,
-		),
-	)
-	if err := app.Run(); err != nil {
-		log.Println(err)
-	}
-}
diff --git a/examples/http/middlewares/handlers.go b/examples/http/middlewares/handlers.go
new file mode 100644
index 00000000..0575be1c
--- /dev/null
+++ b/examples/http/middlewares/handlers.go
@@ -0,0 +1,28 @@
+package main
+
+import (
+	"context"
+
+	"github.com/go-kratos/kratos/examples/helloworld/helloworld"
+	"github.com/go-kratos/kratos/v2/transport"
+	"github.com/go-kratos/kratos/v2/transport/http"
+	"github.com/go-kratos/kratos/v2/transport/http/binding"
+)
+
+func sayHelloHandler(ctx http.Context) error {
+	var in helloworld.HelloRequest
+	if err := ctx.Bind(&in); err != nil {
+		return err
+	}
+
+	// binding /hello/{name} to in.Name
+	if err := binding.BindVars(ctx.Vars(), &in); err != nil {
+		return err
+	}
+
+	transport.SetOperation(ctx, "/helloworld.Greeter/SayHello")
+	h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) {
+		return &helloworld.HelloReply{Message: "test:" + req.(*helloworld.HelloRequest).Name}, nil
+	})
+	return ctx.Returns(h(ctx, &in))
+}
diff --git a/examples/http/middlewares/main.go b/examples/http/middlewares/main.go
new file mode 100644
index 00000000..4f2418ad
--- /dev/null
+++ b/examples/http/middlewares/main.go
@@ -0,0 +1,40 @@
+package main
+
+import (
+	"github.com/go-kratos/kratos/examples/helloworld/helloworld"
+	"github.com/go-kratos/kratos/v2"
+	"github.com/go-kratos/kratos/v2/transport/http"
+)
+
+// this example shows how to add middlewares,
+// execution order is globalFilter(http) --> routeFilter(http) --> pathFilter(http) --> serviceFilter(service)
+func main() {
+	s := &server{}
+	httpSrv := http.NewServer(
+		http.Address(":8000"),
+		http.Middleware(
+			// add service filter
+			serviceMiddleware,
+			serviceMiddleware2,
+		),
+		// add global filter
+		http.Filter(globalFilter, globalFilter2),
+	)
+	// register http hanlder to http server
+	helloworld.RegisterGreeterHTTPServer(httpSrv, s)
+
+	// add route filter
+	r := httpSrv.Route("/", routeFilter, routeFilter2)
+	// add path filter to custom route
+	r.GET("/hello/{name}", sayHelloHandler, pathFilter, pathFilter2)
+
+	app := kratos.New(
+		kratos.Name("helloworld"),
+		kratos.Server(
+			httpSrv,
+		),
+	)
+	if err := app.Run(); err != nil {
+		panic(err)
+	}
+}
diff --git a/examples/http/middlewares/middlewares.go b/examples/http/middlewares/middlewares.go
new file mode 100644
index 00000000..515f7b43
--- /dev/null
+++ b/examples/http/middlewares/middlewares.go
@@ -0,0 +1,75 @@
+package main
+
+import (
+	"context"
+	"fmt"
+	"net/http"
+
+	"github.com/go-kratos/kratos/v2/middleware"
+)
+
+func globalFilter(next http.Handler) http.Handler {
+	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		fmt.Println("global filter in")
+		next.ServeHTTP(w, r)
+		fmt.Println("global filter out")
+	})
+}
+
+func globalFilter2(next http.Handler) http.Handler {
+	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		fmt.Println("global filter 2 in")
+		next.ServeHTTP(w, r)
+		fmt.Println("global filter 2 out")
+	})
+}
+
+func routeFilter(next http.Handler) http.Handler {
+	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		fmt.Println("route filter in")
+		next.ServeHTTP(w, r)
+		fmt.Println("route filter out")
+	})
+}
+
+func routeFilter2(next http.Handler) http.Handler {
+	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		fmt.Println("route filter 2 in")
+		next.ServeHTTP(w, r)
+		fmt.Println("route filter 2 out")
+	})
+}
+
+func pathFilter(next http.Handler) http.Handler {
+	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		fmt.Println("path filter in")
+		next.ServeHTTP(w, r)
+		fmt.Println("path filter out")
+	})
+}
+
+func pathFilter2(next http.Handler) http.Handler {
+	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		fmt.Println("path filter 2 in")
+		next.ServeHTTP(w, r)
+		fmt.Println("path filter 2 out")
+	})
+}
+
+func serviceMiddleware(handler middleware.Handler) middleware.Handler {
+	return func(ctx context.Context, req interface{}) (reply interface{}, err error) {
+		fmt.Println("service middleware in")
+		reply, err = handler(ctx, req)
+		fmt.Println("service middleware out")
+		return
+	}
+}
+
+func serviceMiddleware2(handler middleware.Handler) middleware.Handler {
+	return func(ctx context.Context, req interface{}) (reply interface{}, err error) {
+		fmt.Println("service middleware 2 in")
+		reply, err = handler(ctx, req)
+		fmt.Println("service middleware 2 out")
+		return
+	}
+}
diff --git a/examples/http/middlewares/services.go b/examples/http/middlewares/services.go
new file mode 100644
index 00000000..4af9e688
--- /dev/null
+++ b/examples/http/middlewares/services.go
@@ -0,0 +1,25 @@
+package main
+
+import (
+	"context"
+	"fmt"
+
+	"github.com/go-kratos/kratos/examples/helloworld/helloworld"
+	"github.com/go-kratos/kratos/v2/errors"
+)
+
+// server is used to implement helloworld.GreeterServer.
+type server struct {
+	helloworld.UnimplementedGreeterServer
+}
+
+// SayHello implements helloworld.GreeterServer
+func (s *server) SayHello(ctx context.Context, in *helloworld.HelloRequest) (*helloworld.HelloReply, error) {
+	if in.Name == "error" {
+		return nil, errors.BadRequest("custom_error", fmt.Sprintf("invalid argument %s", in.Name))
+	}
+	if in.Name == "panic" {
+		panic("grpc panic")
+	}
+	return &helloworld.HelloReply{Message: fmt.Sprintf("Hello %+v", in.Name)}, nil
+}
diff --git a/examples/http/upload/main.go b/examples/http/upload/main.go
index b8960ace..621d4fda 100644
--- a/examples/http/upload/main.go
+++ b/examples/http/upload/main.go
@@ -1,42 +1,37 @@
 package main
 
 import (
-	"fmt"
 	"io"
-	"log"
-	"net/http"
 	"os"
 
 	"github.com/go-kratos/kratos/v2"
-	transhttp "github.com/go-kratos/kratos/v2/transport/http"
-	"github.com/gorilla/mux"
+	"github.com/go-kratos/kratos/v2/transport/http"
 )
 
-func uploadFile(w http.ResponseWriter, r *http.Request) {
-	fileName := r.FormValue("name")
-	file, handler, err := r.FormFile("file")
+func uploadFile(ctx http.Context) error {
+	req := ctx.Request()
+
+	fileName := req.FormValue("name")
+	file, handler, err := req.FormFile("file")
 	if err != nil {
-		w.WriteHeader(http.StatusInternalServerError)
-		return
+		return err
 	}
 	defer file.Close()
 
 	f, err := os.OpenFile(handler.Filename, os.O_WRONLY|os.O_CREATE, 0666)
 	if err != nil {
-		w.WriteHeader(http.StatusInternalServerError)
-		return
+		return err
 	}
 	defer f.Close()
 	_, _ = io.Copy(f, file)
-	fmt.Fprint(w, "File "+fileName+" Uploaded successfully")
+
+	return ctx.String(200, "File "+fileName+" Uploaded successfully")
 }
 
 func main() {
-	router := mux.NewRouter()
-	router.HandleFunc("/upload", uploadFile).Methods("POST")
-
-	httpSrv := transhttp.NewServer(transhttp.Address(":8000"))
-	httpSrv.HandlePrefix("/", router)
+	httpSrv := http.NewServer(http.Address(":8000"))
+	route := httpSrv.Route("/")
+	route.POST("/upload", uploadFile)
 
 	app := kratos.New(
 		kratos.Name("upload"),
@@ -45,6 +40,6 @@ func main() {
 		),
 	)
 	if err := app.Run(); err != nil {
-		log.Println(err)
+		panic(err)
 	}
 }
diff --git a/examples/metadata/server/main.go b/examples/metadata/server/main.go
index c5601b55..e10ab544 100644
--- a/examples/metadata/server/main.go
+++ b/examples/metadata/server/main.go
@@ -39,25 +39,23 @@ func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloRe
 
 func main() {
 	logger := log.NewStdLogger(os.Stdout)
-	log := log.NewHelper(logger)
 
+	s := &server{}
 	grpcSrv := grpc.NewServer(
 		grpc.Address(":9000"),
 		grpc.Middleware(
 			recovery.Recovery(),
 			logging.Server(logger),
 		))
-
-	s := &server{}
-	pb.RegisterGreeterServer(grpcSrv, s)
-
-	httpSrv := http.NewServer(http.Address(":8000"))
-	httpSrv.HandlePrefix("/", pb.NewGreeterHandler(s,
+	httpSrv := http.NewServer(
+		http.Address(":8000"),
 		http.Middleware(
 			recovery.Recovery(),
 			logging.Server(logger),
-		)),
+		),
 	)
+	pb.RegisterGreeterServer(grpcSrv, s)
+	pb.RegisterGreeterHTTPServer(httpSrv, s)
 
 	app := kratos.New(
 		kratos.Name(Name),
@@ -68,6 +66,6 @@ func main() {
 	)
 
 	if err := app.Run(); err != nil {
-		log.Error(err)
+		panic(err)
 	}
 }
diff --git a/examples/proto/echo_service_http.pb.go b/examples/proto/echo_service_http.pb.go
deleted file mode 100644
index ea1dfec0..00000000
--- a/examples/proto/echo_service_http.pb.go
+++ /dev/null
@@ -1,380 +0,0 @@
-// Code generated by protoc-gen-go-http. DO NOT EDIT.
-
-package testproto
-
-import (
-	context "context"
-	middleware "github.com/go-kratos/kratos/v2/middleware"
-	transport "github.com/go-kratos/kratos/v2/transport"
-	http1 "github.com/go-kratos/kratos/v2/transport/http"
-	binding "github.com/go-kratos/kratos/v2/transport/http/binding"
-	mux "github.com/gorilla/mux"
-	http "net/http"
-)
-
-// This is a compile-time assertion to ensure that this generated file
-// is compatible with the kratos package it is being compiled against.
-var _ = new(http.Request)
-var _ = new(context.Context)
-var _ = new(middleware.Middleware)
-var _ = new(transport.Transporter)
-var _ = binding.BindVars
-var _ = mux.NewRouter
-
-const _ = http1.SupportPackageIsVersion1
-
-type EchoServiceHandler interface {
-	Echo(context.Context, *SimpleMessage) (*SimpleMessage, error)
-
-	EchoBody(context.Context, *SimpleMessage) (*SimpleMessage, error)
-
-	EchoDelete(context.Context, *SimpleMessage) (*SimpleMessage, error)
-
-	EchoPatch(context.Context, *DynamicMessageUpdate) (*DynamicMessageUpdate, error)
-
-	EchoResponseBody(context.Context, *DynamicMessageUpdate) (*DynamicMessageUpdate, error)
-}
-
-func NewEchoServiceHandler(srv EchoServiceHandler, opts ...http1.HandleOption) http.Handler {
-	h := http1.DefaultHandleOptions()
-	for _, o := range opts {
-		o(&h)
-	}
-	r := mux.NewRouter()
-
-	r.HandleFunc("/v1/example/echo/{id}/{num}", func(w http.ResponseWriter, r *http.Request) {
-		var in SimpleMessage
-		if err := h.Decode(r, &in); err != nil {
-			h.Error(w, r, err)
-			return
-		}
-
-		if err := binding.BindVars(mux.Vars(r), &in); err != nil {
-			h.Error(w, r, err)
-			return
-		}
-
-		next := func(ctx context.Context, req interface{}) (interface{}, error) {
-			return srv.Echo(ctx, req.(*SimpleMessage))
-		}
-		if h.Middleware != nil {
-			next = h.Middleware(next)
-		}
-		ctx := r.Context()
-		transport.SetMethod(ctx, "/testproto.EchoService/Echo")
-		out, err := next(ctx, &in)
-		if err != nil {
-			h.Error(w, r, err)
-			return
-		}
-		reply := out.(*SimpleMessage)
-		if err := h.Encode(w, r, reply); err != nil {
-			h.Error(w, r, err)
-		}
-	}).Methods("GET")
-
-	r.HandleFunc("/v1/example/echo/{id}/{num}/{lang}", func(w http.ResponseWriter, r *http.Request) {
-		var in SimpleMessage
-		if err := h.Decode(r, &in); err != nil {
-			h.Error(w, r, err)
-			return
-		}
-
-		if err := binding.BindVars(mux.Vars(r), &in); err != nil {
-			h.Error(w, r, err)
-			return
-		}
-
-		next := func(ctx context.Context, req interface{}) (interface{}, error) {
-			return srv.Echo(ctx, req.(*SimpleMessage))
-		}
-		if h.Middleware != nil {
-			next = h.Middleware(next)
-		}
-		ctx := r.Context()
-		transport.SetMethod(ctx, "/testproto.EchoService/Echo")
-		out, err := next(ctx, &in)
-		if err != nil {
-			h.Error(w, r, err)
-			return
-		}
-		reply := out.(*SimpleMessage)
-		if err := h.Encode(w, r, reply); err != nil {
-			h.Error(w, r, err)
-		}
-	}).Methods("GET")
-
-	r.HandleFunc("/v1/example/echo1/{id}/{line_num}/{status.note}", func(w http.ResponseWriter, r *http.Request) {
-		var in SimpleMessage
-		if err := h.Decode(r, &in); err != nil {
-			h.Error(w, r, err)
-			return
-		}
-
-		if err := binding.BindVars(mux.Vars(r), &in); err != nil {
-			h.Error(w, r, err)
-			return
-		}
-
-		next := func(ctx context.Context, req interface{}) (interface{}, error) {
-			return srv.Echo(ctx, req.(*SimpleMessage))
-		}
-		if h.Middleware != nil {
-			next = h.Middleware(next)
-		}
-		ctx := r.Context()
-		transport.SetMethod(ctx, "/testproto.EchoService/Echo")
-		out, err := next(ctx, &in)
-		if err != nil {
-			h.Error(w, r, err)
-			return
-		}
-		reply := out.(*SimpleMessage)
-		if err := h.Encode(w, r, reply); err != nil {
-			h.Error(w, r, err)
-		}
-	}).Methods("GET")
-
-	r.HandleFunc("/v1/example/echo2/{no.note}", func(w http.ResponseWriter, r *http.Request) {
-		var in SimpleMessage
-		if err := h.Decode(r, &in); err != nil {
-			h.Error(w, r, err)
-			return
-		}
-
-		if err := binding.BindVars(mux.Vars(r), &in); err != nil {
-			h.Error(w, r, err)
-			return
-		}
-
-		next := func(ctx context.Context, req interface{}) (interface{}, error) {
-			return srv.Echo(ctx, req.(*SimpleMessage))
-		}
-		if h.Middleware != nil {
-			next = h.Middleware(next)
-		}
-		ctx := r.Context()
-		transport.SetMethod(ctx, "/testproto.EchoService/Echo")
-		out, err := next(ctx, &in)
-		if err != nil {
-			h.Error(w, r, err)
-			return
-		}
-		reply := out.(*SimpleMessage)
-		if err := h.Encode(w, r, reply); err != nil {
-			h.Error(w, r, err)
-		}
-	}).Methods("GET")
-
-	r.HandleFunc("/v1/example/echo/{id}", func(w http.ResponseWriter, r *http.Request) {
-		var in SimpleMessage
-		if err := h.Decode(r, &in); err != nil {
-			h.Error(w, r, err)
-			return
-		}
-
-		if err := binding.BindVars(mux.Vars(r), &in); err != nil {
-			h.Error(w, r, err)
-			return
-		}
-
-		next := func(ctx context.Context, req interface{}) (interface{}, error) {
-			return srv.Echo(ctx, req.(*SimpleMessage))
-		}
-		if h.Middleware != nil {
-			next = h.Middleware(next)
-		}
-		ctx := r.Context()
-		transport.SetMethod(ctx, "/testproto.EchoService/Echo")
-		out, err := next(ctx, &in)
-		if err != nil {
-			h.Error(w, r, err)
-			return
-		}
-		reply := out.(*SimpleMessage)
-		if err := h.Encode(w, r, reply); err != nil {
-			h.Error(w, r, err)
-		}
-	}).Methods("POST")
-
-	r.HandleFunc("/v1/example/echo_body", func(w http.ResponseWriter, r *http.Request) {
-		var in SimpleMessage
-		if err := h.Decode(r, &in); err != nil {
-			h.Error(w, r, err)
-			return
-		}
-
-		next := func(ctx context.Context, req interface{}) (interface{}, error) {
-			return srv.EchoBody(ctx, req.(*SimpleMessage))
-		}
-		if h.Middleware != nil {
-			next = h.Middleware(next)
-		}
-		ctx := r.Context()
-		transport.SetMethod(ctx, "/testproto.EchoService/EchoBody")
-		out, err := next(ctx, &in)
-		if err != nil {
-			h.Error(w, r, err)
-			return
-		}
-		reply := out.(*SimpleMessage)
-		if err := h.Encode(w, r, reply); err != nil {
-			h.Error(w, r, err)
-		}
-	}).Methods("POST")
-
-	r.HandleFunc("/v1/example/echo_response_body", func(w http.ResponseWriter, r *http.Request) {
-		var in DynamicMessageUpdate
-		if err := h.Decode(r, &in); err != nil {
-			h.Error(w, r, err)
-			return
-		}
-
-		next := func(ctx context.Context, req interface{}) (interface{}, error) {
-			return srv.EchoResponseBody(ctx, req.(*DynamicMessageUpdate))
-		}
-		if h.Middleware != nil {
-			next = h.Middleware(next)
-		}
-		ctx := r.Context()
-		transport.SetMethod(ctx, "/testproto.EchoService/EchoResponseBody")
-		out, err := next(ctx, &in)
-		if err != nil {
-			h.Error(w, r, err)
-			return
-		}
-		reply := out.(*DynamicMessageUpdate)
-		if err := h.Encode(w, r, reply.Body); err != nil {
-			h.Error(w, r, err)
-		}
-	}).Methods("POST")
-
-	r.HandleFunc("/v1/example/echo_delete/{id}/{num}", func(w http.ResponseWriter, r *http.Request) {
-		var in SimpleMessage
-		if err := h.Decode(r, &in); err != nil {
-			h.Error(w, r, err)
-			return
-		}
-
-		if err := binding.BindVars(mux.Vars(r), &in); err != nil {
-			h.Error(w, r, err)
-			return
-		}
-
-		next := func(ctx context.Context, req interface{}) (interface{}, error) {
-			return srv.EchoDelete(ctx, req.(*SimpleMessage))
-		}
-		if h.Middleware != nil {
-			next = h.Middleware(next)
-		}
-		ctx := r.Context()
-		transport.SetMethod(ctx, "/testproto.EchoService/EchoDelete")
-		out, err := next(ctx, &in)
-		if err != nil {
-			h.Error(w, r, err)
-			return
-		}
-		reply := out.(*SimpleMessage)
-		if err := h.Encode(w, r, reply); err != nil {
-			h.Error(w, r, err)
-		}
-	}).Methods("DELETE")
-
-	r.HandleFunc("/v1/example/echo_patch", func(w http.ResponseWriter, r *http.Request) {
-		var in DynamicMessageUpdate
-		if err := h.Decode(r, &in.Body); err != nil {
-			h.Error(w, r, err)
-			return
-		}
-
-		next := func(ctx context.Context, req interface{}) (interface{}, error) {
-			return srv.EchoPatch(ctx, req.(*DynamicMessageUpdate))
-		}
-		if h.Middleware != nil {
-			next = h.Middleware(next)
-		}
-		ctx := r.Context()
-		transport.SetMethod(ctx, "/testproto.EchoService/EchoPatch")
-		out, err := next(ctx, &in)
-		if err != nil {
-			h.Error(w, r, err)
-			return
-		}
-		reply := out.(*DynamicMessageUpdate)
-		if err := h.Encode(w, r, reply); err != nil {
-			h.Error(w, r, err)
-		}
-	}).Methods("PATCH")
-
-	return r
-}
-
-type EchoServiceHTTPClient interface {
-	Echo(ctx context.Context, req *SimpleMessage, opts ...http1.CallOption) (rsp *SimpleMessage, err error)
-
-	EchoBody(ctx context.Context, req *SimpleMessage, opts ...http1.CallOption) (rsp *SimpleMessage, err error)
-
-	EchoDelete(ctx context.Context, req *SimpleMessage, opts ...http1.CallOption) (rsp *SimpleMessage, err error)
-
-	EchoPatch(ctx context.Context, req *DynamicMessageUpdate, opts ...http1.CallOption) (rsp *DynamicMessageUpdate, err error)
-
-	EchoResponseBody(ctx context.Context, req *DynamicMessageUpdate, opts ...http1.CallOption) (rsp *DynamicMessageUpdate, err error)
-}
-
-type EchoServiceHTTPClientImpl struct {
-	cc *http1.Client
-}
-
-func NewEchoServiceHTTPClient(client *http1.Client) EchoServiceHTTPClient {
-	return &EchoServiceHTTPClientImpl{client}
-}
-
-func (c *EchoServiceHTTPClientImpl) Echo(ctx context.Context, in *SimpleMessage, opts ...http1.CallOption) (*SimpleMessage, error) {
-	var out SimpleMessage
-	path := binding.EncodePath("POST", "/v1/example/echo/{id}", in)
-	opts = append(opts, http1.Method("/testproto.EchoService/Echo"))
-
-	err := c.cc.Invoke(ctx, "POST", path, nil, &out, opts...)
-
-	return &out, err
-}
-
-func (c *EchoServiceHTTPClientImpl) EchoBody(ctx context.Context, in *SimpleMessage, opts ...http1.CallOption) (*SimpleMessage, error) {
-	var out SimpleMessage
-	path := binding.EncodePath("POST", "/v1/example/echo_body", in)
-	opts = append(opts, http1.Method("/testproto.EchoService/EchoBody"))
-
-	err := c.cc.Invoke(ctx, "POST", path, in, &out, opts...)
-
-	return &out, err
-}
-
-func (c *EchoServiceHTTPClientImpl) EchoDelete(ctx context.Context, in *SimpleMessage, opts ...http1.CallOption) (*SimpleMessage, error) {
-	var out SimpleMessage
-	path := binding.EncodePath("DELETE", "/v1/example/echo_delete/{id}/{num}", in)
-	opts = append(opts, http1.Method("/testproto.EchoService/EchoDelete"))
-
-	err := c.cc.Invoke(ctx, "DELETE", path, nil, &out, opts...)
-
-	return &out, err
-}
-
-func (c *EchoServiceHTTPClientImpl) EchoPatch(ctx context.Context, in *DynamicMessageUpdate, opts ...http1.CallOption) (*DynamicMessageUpdate, error) {
-	var out DynamicMessageUpdate
-	path := binding.EncodePath("PATCH", "/v1/example/echo_patch", in)
-	opts = append(opts, http1.Method("/testproto.EchoService/EchoPatch"))
-
-	err := c.cc.Invoke(ctx, "PATCH", path, in.Body, &out, opts...)
-
-	return &out, err
-}
-
-func (c *EchoServiceHTTPClientImpl) EchoResponseBody(ctx context.Context, in *DynamicMessageUpdate, opts ...http1.CallOption) (*DynamicMessageUpdate, error) {
-	var out DynamicMessageUpdate
-	path := binding.EncodePath("POST", "/v1/example/echo_response_body", in)
-	opts = append(opts, http1.Method("/testproto.EchoService/EchoResponseBody"))
-
-	err := c.cc.Invoke(ctx, "POST", path, in, &out.Body, opts...)
-
-	return &out, err
-}
diff --git a/examples/proto/stream.pb.go b/examples/proto/stream.pb.go
deleted file mode 100644
index 658d821e..00000000
--- a/examples/proto/stream.pb.go
+++ /dev/null
@@ -1,86 +0,0 @@
-// Code generated by protoc-gen-go. DO NOT EDIT.
-// versions:
-// 	protoc-gen-go v1.26.0
-// 	protoc        v3.15.7
-// source: proto/stream.proto
-
-package testproto
-
-import (
-	_ "google.golang.org/genproto/googleapis/api/annotations"
-	httpbody "google.golang.org/genproto/googleapis/api/httpbody"
-	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
-	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
-	emptypb "google.golang.org/protobuf/types/known/emptypb"
-	reflect "reflect"
-)
-
-const (
-	// Verify that this generated code is sufficiently up-to-date.
-	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
-	// Verify that runtime/protoimpl is sufficiently up-to-date.
-	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
-)
-
-var File_proto_stream_proto protoreflect.FileDescriptor
-
-var file_proto_stream_proto_rawDesc = []byte{
-	0x0a, 0x12, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x70,
-	0x72, 0x6f, 0x74, 0x6f, 0x12, 0x09, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a,
-	0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x61, 0x6e, 0x6e, 0x6f,
-	0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x19, 0x67,
-	0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x68, 0x74, 0x74, 0x70, 0x62, 0x6f,
-	0x64, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
-	0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e,
-	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x69, 0x0a, 0x0d, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x53,
-	0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x58, 0x0a, 0x08, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f,
-	0x61, 0x64, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,
-	0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x14, 0x2e, 0x67, 0x6f, 0x6f,
-	0x67, 0x6c, 0x65, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x48, 0x74, 0x74, 0x70, 0x42, 0x6f, 0x64, 0x79,
-	0x22, 0x1c, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x16, 0x12, 0x14, 0x2f, 0x76, 0x31, 0x2f, 0x65, 0x78,
-	0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2f, 0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x30, 0x01,
-	0x42, 0x51, 0x5a, 0x4f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67,
-	0x6f, 0x2d, 0x6b, 0x72, 0x61, 0x74, 0x6f, 0x73, 0x2f, 0x6b, 0x72, 0x61, 0x74, 0x6f, 0x73, 0x2f,
-	0x63, 0x6d, 0x64, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x2d, 0x67, 0x65, 0x6e, 0x2d, 0x67,
-	0x6f, 0x2d, 0x68, 0x74, 0x74, 0x70, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f,
-	0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x3b, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72,
-	0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
-}
-
-var file_proto_stream_proto_goTypes = []interface{}{
-	(*emptypb.Empty)(nil),     // 0: google.protobuf.Empty
-	(*httpbody.HttpBody)(nil), // 1: google.api.HttpBody
-}
-var file_proto_stream_proto_depIdxs = []int32{
-	0, // 0: testproto.StreamService.Download:input_type -> google.protobuf.Empty
-	1, // 1: testproto.StreamService.Download:output_type -> google.api.HttpBody
-	1, // [1:2] is the sub-list for method output_type
-	0, // [0:1] is the sub-list for method input_type
-	0, // [0:0] is the sub-list for extension type_name
-	0, // [0:0] is the sub-list for extension extendee
-	0, // [0:0] is the sub-list for field type_name
-}
-
-func init() { file_proto_stream_proto_init() }
-func file_proto_stream_proto_init() {
-	if File_proto_stream_proto != nil {
-		return
-	}
-	type x struct{}
-	out := protoimpl.TypeBuilder{
-		File: protoimpl.DescBuilder{
-			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
-			RawDescriptor: file_proto_stream_proto_rawDesc,
-			NumEnums:      0,
-			NumMessages:   0,
-			NumExtensions: 0,
-			NumServices:   1,
-		},
-		GoTypes:           file_proto_stream_proto_goTypes,
-		DependencyIndexes: file_proto_stream_proto_depIdxs,
-	}.Build()
-	File_proto_stream_proto = out.File
-	file_proto_stream_proto_rawDesc = nil
-	file_proto_stream_proto_goTypes = nil
-	file_proto_stream_proto_depIdxs = nil
-}
diff --git a/examples/registry/consul/server/main.go b/examples/registry/consul/server/main.go
index 133f3b51..318b9c34 100644
--- a/examples/registry/consul/server/main.go
+++ b/examples/registry/consul/server/main.go
@@ -28,12 +28,14 @@ func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloRe
 
 func main() {
 	logger := log.NewStdLogger(os.Stdout)
-	log := log.NewHelper(logger)
+
 	consulClient, err := api.NewClient(api.DefaultConfig())
 	if err != nil {
 		panic(err)
 	}
+	r := registry.New(consulClient)
 
+	s := &server{}
 	grpcSrv := grpc.NewServer(
 		grpc.Address(":9000"),
 		grpc.Middleware(
@@ -41,17 +43,15 @@ func main() {
 			logging.Server(logger),
 		),
 	)
-	s := &server{}
-	pb.RegisterGreeterServer(grpcSrv, s)
-
-	httpSrv := http.NewServer(http.Address(":8000"))
-	httpSrv.HandlePrefix("/", pb.NewGreeterHandler(s,
+	httpSrv := http.NewServer(
+		http.Address(":8000"),
 		http.Middleware(
 			recovery.Recovery(),
-		)),
+		),
 	)
+	pb.RegisterGreeterServer(grpcSrv, s)
+	pb.RegisterGreeterHTTPServer(httpSrv, s)
 
-	r := registry.New(consulClient)
 	app := kratos.New(
 		kratos.Name("helloworld"),
 		kratos.Server(
@@ -62,6 +62,6 @@ func main() {
 	)
 
 	if err := app.Run(); err != nil {
-		log.Errorf("app run failed:%v", err)
+		panic(err)
 	}
 }
diff --git a/examples/traces/api/message/message_http.pb.go b/examples/traces/api/message/message_http.pb.go
index affc5d46..90aeeb22 100644
--- a/examples/traces/api/message/message_http.pb.go
+++ b/examples/traces/api/message/message_http.pb.go
@@ -1,4 +1,6 @@
 // Code generated by protoc-gen-go-http. DO NOT EDIT.
+// versions:
+// protoc-gen-go-http v2.0.0-rc3
 
 package v1
 
@@ -6,86 +8,69 @@ import (
 	context "context"
 	middleware "github.com/go-kratos/kratos/v2/middleware"
 	transport "github.com/go-kratos/kratos/v2/transport"
-	http1 "github.com/go-kratos/kratos/v2/transport/http"
+	http "github.com/go-kratos/kratos/v2/transport/http"
 	binding "github.com/go-kratos/kratos/v2/transport/http/binding"
-	mux "github.com/gorilla/mux"
-	http "net/http"
 )
 
 // This is a compile-time assertion to ensure that this generated file
 // is compatible with the kratos package it is being compiled against.
-var _ = new(http.Request)
 var _ = new(context.Context)
 var _ = new(middleware.Middleware)
 var _ = new(transport.Transporter)
 var _ = binding.BindVars
-var _ = mux.NewRouter
 
-const _ = http1.SupportPackageIsVersion1
+const _ = http.SupportPackageIsVersion1
 
-type MessageServiceHandler interface {
+type MessageServiceHTTPServer interface {
 	GetUserMessage(context.Context, *GetUserMessageRequest) (*GetUserMessageReply, error)
 }
 
-func NewMessageServiceHandler(srv MessageServiceHandler, opts ...http1.HandleOption) http.Handler {
-	h := http1.DefaultHandleOptions()
-	for _, o := range opts {
-		o(&h)
-	}
-	r := mux.NewRouter()
+func RegisterMessageServiceHTTPServer(s *http.Server, srv MessageServiceHTTPServer) {
+	r := s.Route("/")
+	r.GET("/v1/message/user/{id}/{count}", _MessageService_GetUserMessage0_HTTP_Handler(srv))
+}
 
-	r.HandleFunc("/v1/message/user/{id}/{count}", func(w http.ResponseWriter, r *http.Request) {
+func _MessageService_GetUserMessage0_HTTP_Handler(srv MessageServiceHTTPServer) func(ctx http.Context) error {
+	return func(ctx http.Context) error {
 		var in GetUserMessageRequest
-		if err := h.Decode(r, &in); err != nil {
-			h.Error(w, r, err)
-			return
+		if err := ctx.Bind(&in); err != nil {
+			return err
 		}
-
-		if err := binding.BindVars(mux.Vars(r), &in); err != nil {
-			h.Error(w, r, err)
-			return
+		if err := binding.BindVars(ctx.Vars(), &in); err != nil {
+			return err
 		}
-
-		next := func(ctx context.Context, req interface{}) (interface{}, error) {
+		transport.SetOperation(ctx, "/api.message.v1.MessageService/GetUserMessage")
+		h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) {
 			return srv.GetUserMessage(ctx, req.(*GetUserMessageRequest))
-		}
-		if h.Middleware != nil {
-			next = h.Middleware(next)
-		}
-		ctx := r.Context()
-		transport.SetMethod(ctx, "/api.message.v1.MessageService/GetUserMessage")
-		out, err := next(ctx, &in)
+		})
+		out, err := h(ctx, &in)
 		if err != nil {
-			h.Error(w, r, err)
-			return
+			return err
 		}
 		reply := out.(*GetUserMessageReply)
-		if err := h.Encode(w, r, reply); err != nil {
-			h.Error(w, r, err)
-		}
-	}).Methods("GET")
-
-	return r
+		return ctx.Result(200, reply)
+	}
 }
 
 type MessageServiceHTTPClient interface {
-	GetUserMessage(ctx context.Context, req *GetUserMessageRequest, opts ...http1.CallOption) (rsp *GetUserMessageReply, err error)
+	GetUserMessage(ctx context.Context, req *GetUserMessageRequest, opts ...http.CallOption) (rsp *GetUserMessageReply, err error)
 }
 
 type MessageServiceHTTPClientImpl struct {
-	cc *http1.Client
+	cc *http.Client
 }
 
-func NewMessageServiceHTTPClient(client *http1.Client) MessageServiceHTTPClient {
+func NewMessageServiceHTTPClient(client *http.Client) MessageServiceHTTPClient {
 	return &MessageServiceHTTPClientImpl{client}
 }
 
-func (c *MessageServiceHTTPClientImpl) GetUserMessage(ctx context.Context, in *GetUserMessageRequest, opts ...http1.CallOption) (*GetUserMessageReply, error) {
+func (c *MessageServiceHTTPClientImpl) GetUserMessage(ctx context.Context, in *GetUserMessageRequest, opts ...http.CallOption) (*GetUserMessageReply, error) {
 	var out GetUserMessageReply
-	path := binding.EncodePath("GET", "/v1/message/user/{id}/{count}", in)
-	opts = append(opts, http1.Method("/api.message.v1.MessageService/GetUserMessage"))
-
+	path := binding.EncodeVars("/v1/message/user/{id}/{count}", in, true)
+	opts = append(opts, http.Operation("/api.message.v1.MessageService/GetUserMessage"))
 	err := c.cc.Invoke(ctx, "GET", path, in, &out, opts...)
-
+	if err != nil {
+		return nil, err
+	}
 	return &out, err
 }
diff --git a/examples/traces/api/user/user_http.pb.go b/examples/traces/api/user/user_http.pb.go
index 5d385cd0..200076fe 100644
--- a/examples/traces/api/user/user_http.pb.go
+++ b/examples/traces/api/user/user_http.pb.go
@@ -1,4 +1,6 @@
 // Code generated by protoc-gen-go-http. DO NOT EDIT.
+// versions:
+// protoc-gen-go-http v2.0.0-rc3
 
 package v1
 
@@ -6,86 +8,69 @@ import (
 	context "context"
 	middleware "github.com/go-kratos/kratos/v2/middleware"
 	transport "github.com/go-kratos/kratos/v2/transport"
-	http1 "github.com/go-kratos/kratos/v2/transport/http"
+	http "github.com/go-kratos/kratos/v2/transport/http"
 	binding "github.com/go-kratos/kratos/v2/transport/http/binding"
-	mux "github.com/gorilla/mux"
-	http "net/http"
 )
 
 // This is a compile-time assertion to ensure that this generated file
 // is compatible with the kratos package it is being compiled against.
-var _ = new(http.Request)
 var _ = new(context.Context)
 var _ = new(middleware.Middleware)
 var _ = new(transport.Transporter)
 var _ = binding.BindVars
-var _ = mux.NewRouter
 
-const _ = http1.SupportPackageIsVersion1
+const _ = http.SupportPackageIsVersion1
 
-type UserHandler interface {
+type UserHTTPServer interface {
 	GetMyMessages(context.Context, *GetMyMessagesRequest) (*GetMyMessagesReply, error)
 }
 
-func NewUserHandler(srv UserHandler, opts ...http1.HandleOption) http.Handler {
-	h := http1.DefaultHandleOptions()
-	for _, o := range opts {
-		o(&h)
-	}
-	r := mux.NewRouter()
+func RegisterUserHTTPServer(s *http.Server, srv UserHTTPServer) {
+	r := s.Route("/")
+	r.GET("/v1/user/get/message/{count}", _User_GetMyMessages0_HTTP_Handler(srv))
+}
 
-	r.HandleFunc("/v1/user/get/message/{count}", func(w http.ResponseWriter, r *http.Request) {
+func _User_GetMyMessages0_HTTP_Handler(srv UserHTTPServer) func(ctx http.Context) error {
+	return func(ctx http.Context) error {
 		var in GetMyMessagesRequest
-		if err := h.Decode(r, &in); err != nil {
-			h.Error(w, r, err)
-			return
+		if err := ctx.Bind(&in); err != nil {
+			return err
 		}
-
-		if err := binding.BindVars(mux.Vars(r), &in); err != nil {
-			h.Error(w, r, err)
-			return
+		if err := binding.BindVars(ctx.Vars(), &in); err != nil {
+			return err
 		}
-
-		next := func(ctx context.Context, req interface{}) (interface{}, error) {
+		transport.SetOperation(ctx, "/api.user.v1.User/GetMyMessages")
+		h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) {
 			return srv.GetMyMessages(ctx, req.(*GetMyMessagesRequest))
-		}
-		if h.Middleware != nil {
-			next = h.Middleware(next)
-		}
-		ctx := r.Context()
-		transport.SetMethod(ctx, "/api.user.v1.User/GetMyMessages")
-		out, err := next(ctx, &in)
+		})
+		out, err := h(ctx, &in)
 		if err != nil {
-			h.Error(w, r, err)
-			return
+			return err
 		}
 		reply := out.(*GetMyMessagesReply)
-		if err := h.Encode(w, r, reply); err != nil {
-			h.Error(w, r, err)
-		}
-	}).Methods("GET")
-
-	return r
+		return ctx.Result(200, reply)
+	}
 }
 
 type UserHTTPClient interface {
-	GetMyMessages(ctx context.Context, req *GetMyMessagesRequest, opts ...http1.CallOption) (rsp *GetMyMessagesReply, err error)
+	GetMyMessages(ctx context.Context, req *GetMyMessagesRequest, opts ...http.CallOption) (rsp *GetMyMessagesReply, err error)
 }
 
 type UserHTTPClientImpl struct {
-	cc *http1.Client
+	cc *http.Client
 }
 
-func NewUserHTTPClient(client *http1.Client) UserHTTPClient {
+func NewUserHTTPClient(client *http.Client) UserHTTPClient {
 	return &UserHTTPClientImpl{client}
 }
 
-func (c *UserHTTPClientImpl) GetMyMessages(ctx context.Context, in *GetMyMessagesRequest, opts ...http1.CallOption) (*GetMyMessagesReply, error) {
+func (c *UserHTTPClientImpl) GetMyMessages(ctx context.Context, in *GetMyMessagesRequest, opts ...http.CallOption) (*GetMyMessagesReply, error) {
 	var out GetMyMessagesReply
-	path := binding.EncodePath("GET", "/v1/user/get/message/{count}", in)
-	opts = append(opts, http1.Method("/api.user.v1.User/GetMyMessages"))
-
+	path := binding.EncodeVars("/v1/user/get/message/{count}", in, true)
+	opts = append(opts, http.Operation("/api.user.v1.User/GetMyMessages"))
 	err := c.cc.Invoke(ctx, "GET", path, in, &out, opts...)
-
+	if err != nil {
+		return nil, err
+	}
 	return &out, err
 }
diff --git a/examples/traces/app/user/main.go b/examples/traces/app/user/main.go
index d0837634..875028ea 100644
--- a/examples/traces/app/user/main.go
+++ b/examples/traces/app/user/main.go
@@ -9,7 +9,6 @@ import (
 	pb "github.com/go-kratos/kratos/examples/traces/api/user"
 	"github.com/go-kratos/kratos/v2"
 	"github.com/go-kratos/kratos/v2/log"
-	"github.com/go-kratos/kratos/v2/middleware"
 	"github.com/go-kratos/kratos/v2/middleware/logging"
 	"github.com/go-kratos/kratos/v2/middleware/recovery"
 	"github.com/go-kratos/kratos/v2/middleware/tracing"
@@ -63,14 +62,15 @@ func (s *server) GetMyMessages(ctx context.Context, in *pb.GetMyMessagesRequest)
 	// create grpc conn
 	conn, err := grpc.DialInsecure(ctx,
 		grpc.WithEndpoint("127.0.0.1:9000"),
-		grpc.WithMiddleware(middleware.Chain(
+		grpc.WithMiddleware(
+			recovery.Recovery(),
 			tracing.Client(
 				tracing.WithTracerProvider(s.tracer),
 				tracing.WithPropagators(
 					propagation.NewCompositeTextMapPropagator(propagation.Baggage{}, propagation.TraceContext{}),
 				),
 			),
-			recovery.Recovery())),
+		),
 		grpc.WithTimeout(2*time.Second),
 	)
 	if err != nil {
@@ -100,24 +100,22 @@ func main() {
 		log.Error(err)
 	}
 
-	s := &server{tracer: tp}
-
-	httpSrv := http.NewServer(http.Address(":8000"))
-	httpSrv.HandlePrefix("/", pb.NewUserHandler(s,
+	httpSrv := http.NewServer(
+		http.Address(":8000"),
 		http.Middleware(
-			middleware.Chain(
-				recovery.Recovery(),
-				// Configuring tracing middleware
-				tracing.Server(
-					tracing.WithTracerProvider(tp),
-					tracing.WithPropagators(
-						propagation.NewCompositeTextMapPropagator(propagation.Baggage{}, propagation.TraceContext{}),
-					),
+			recovery.Recovery(),
+			// Configuring tracing middleware
+			tracing.Server(
+				tracing.WithTracerProvider(tp),
+				tracing.WithPropagators(
+					propagation.NewCompositeTextMapPropagator(propagation.Baggage{}, propagation.TraceContext{}),
 				),
-				logging.Server(logger),
 			),
-		)),
+			logging.Server(logger),
+		),
 	)
+	s := &server{tracer: tp}
+	pb.RegisterUserHTTPServer(httpSrv, s)
 
 	app := kratos.New(
 		kratos.Name(Name),
diff --git a/examples/proto/echo_service.pb.go b/internal/testproto/echo_service.pb.go
similarity index 54%
rename from examples/proto/echo_service.pb.go
rename to internal/testproto/echo_service.pb.go
index 669731b2..5ff2d3c8 100644
--- a/examples/proto/echo_service.pb.go
+++ b/internal/testproto/echo_service.pb.go
@@ -2,7 +2,7 @@
 // versions:
 // 	protoc-gen-go v1.26.0
 // 	protoc        v3.15.7
-// source: proto/echo_service.proto
+// source: echo_service.proto
 
 package testproto
 
@@ -68,11 +68,11 @@ func (x Corpus) String() string {
 }
 
 func (Corpus) Descriptor() protoreflect.EnumDescriptor {
-	return file_proto_echo_service_proto_enumTypes[0].Descriptor()
+	return file_echo_service_proto_enumTypes[0].Descriptor()
 }
 
 func (Corpus) Type() protoreflect.EnumType {
-	return &file_proto_echo_service_proto_enumTypes[0]
+	return &file_echo_service_proto_enumTypes[0]
 }
 
 func (x Corpus) Number() protoreflect.EnumNumber {
@@ -81,7 +81,7 @@ func (x Corpus) Number() protoreflect.EnumNumber {
 
 // Deprecated: Use Corpus.Descriptor instead.
 func (Corpus) EnumDescriptor() ([]byte, []int) {
-	return file_proto_echo_service_proto_rawDescGZIP(), []int{0}
+	return file_echo_service_proto_rawDescGZIP(), []int{0}
 }
 
 // Embedded represents a message embedded in SimpleMessage.
@@ -99,7 +99,7 @@ type Embedded struct {
 func (x *Embedded) Reset() {
 	*x = Embedded{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_proto_echo_service_proto_msgTypes[0]
+		mi := &file_echo_service_proto_msgTypes[0]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -112,7 +112,7 @@ func (x *Embedded) String() string {
 func (*Embedded) ProtoMessage() {}
 
 func (x *Embedded) ProtoReflect() protoreflect.Message {
-	mi := &file_proto_echo_service_proto_msgTypes[0]
+	mi := &file_echo_service_proto_msgTypes[0]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -125,7 +125,7 @@ func (x *Embedded) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use Embedded.ProtoReflect.Descriptor instead.
 func (*Embedded) Descriptor() ([]byte, []int) {
-	return file_proto_echo_service_proto_rawDescGZIP(), []int{0}
+	return file_echo_service_proto_rawDescGZIP(), []int{0}
 }
 
 func (m *Embedded) GetMark() isEmbedded_Mark {
@@ -189,7 +189,7 @@ type SimpleMessage struct {
 func (x *SimpleMessage) Reset() {
 	*x = SimpleMessage{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_proto_echo_service_proto_msgTypes[1]
+		mi := &file_echo_service_proto_msgTypes[1]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -202,7 +202,7 @@ func (x *SimpleMessage) String() string {
 func (*SimpleMessage) ProtoMessage() {}
 
 func (x *SimpleMessage) ProtoReflect() protoreflect.Message {
-	mi := &file_proto_echo_service_proto_msgTypes[1]
+	mi := &file_echo_service_proto_msgTypes[1]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -215,7 +215,7 @@ func (x *SimpleMessage) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use SimpleMessage.ProtoReflect.Descriptor instead.
 func (*SimpleMessage) Descriptor() ([]byte, []int) {
-	return file_proto_echo_service_proto_rawDescGZIP(), []int{1}
+	return file_echo_service_proto_rawDescGZIP(), []int{1}
 }
 
 func (x *SimpleMessage) GetId() string {
@@ -334,7 +334,7 @@ type DynamicMessage struct {
 func (x *DynamicMessage) Reset() {
 	*x = DynamicMessage{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_proto_echo_service_proto_msgTypes[2]
+		mi := &file_echo_service_proto_msgTypes[2]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -347,7 +347,7 @@ func (x *DynamicMessage) String() string {
 func (*DynamicMessage) ProtoMessage() {}
 
 func (x *DynamicMessage) ProtoReflect() protoreflect.Message {
-	mi := &file_proto_echo_service_proto_msgTypes[2]
+	mi := &file_echo_service_proto_msgTypes[2]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -360,7 +360,7 @@ func (x *DynamicMessage) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use DynamicMessage.ProtoReflect.Descriptor instead.
 func (*DynamicMessage) Descriptor() ([]byte, []int) {
-	return file_proto_echo_service_proto_rawDescGZIP(), []int{2}
+	return file_echo_service_proto_rawDescGZIP(), []int{2}
 }
 
 func (x *DynamicMessage) GetStructField() *structpb.Struct {
@@ -389,7 +389,7 @@ type DynamicMessageUpdate struct {
 func (x *DynamicMessageUpdate) Reset() {
 	*x = DynamicMessageUpdate{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_proto_echo_service_proto_msgTypes[3]
+		mi := &file_echo_service_proto_msgTypes[3]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -402,7 +402,7 @@ func (x *DynamicMessageUpdate) String() string {
 func (*DynamicMessageUpdate) ProtoMessage() {}
 
 func (x *DynamicMessageUpdate) ProtoReflect() protoreflect.Message {
-	mi := &file_proto_echo_service_proto_msgTypes[3]
+	mi := &file_echo_service_proto_msgTypes[3]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -415,7 +415,7 @@ func (x *DynamicMessageUpdate) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use DynamicMessageUpdate.ProtoReflect.Descriptor instead.
 func (*DynamicMessageUpdate) Descriptor() ([]byte, []int) {
-	return file_proto_echo_service_proto_rawDescGZIP(), []int{3}
+	return file_echo_service_proto_rawDescGZIP(), []int{3}
 }
 
 func (x *DynamicMessageUpdate) GetBody() *DynamicMessage {
@@ -432,129 +432,129 @@ func (x *DynamicMessageUpdate) GetUpdateMask() *fieldmaskpb.FieldMask {
 	return nil
 }
 
-var File_proto_echo_service_proto protoreflect.FileDescriptor
-
-var file_proto_echo_service_proto_rawDesc = []byte{
-	0x0a, 0x18, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x65, 0x63, 0x68, 0x6f, 0x5f, 0x73, 0x65, 0x72,
-	0x76, 0x69, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x09, 0x74, 0x65, 0x73, 0x74,
-	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x61, 0x70,
-	0x69, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72,
-	0x6f, 0x74, 0x6f, 0x1a, 0x20, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74,
-	0x6f, 0x62, 0x75, 0x66, 0x2f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x6d, 0x61, 0x73, 0x6b, 0x2e,
-	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72,
-	0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x2e, 0x70, 0x72,
-	0x6f, 0x74, 0x6f, 0x22, 0x46, 0x0a, 0x08, 0x45, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x65, 0x64, 0x12,
-	0x1c, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28,
-	0x03, 0x48, 0x00, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x12, 0x14, 0x0a,
-	0x04, 0x6e, 0x6f, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x04, 0x6e,
-	0x6f, 0x74, 0x65, 0x42, 0x06, 0x0a, 0x04, 0x6d, 0x61, 0x72, 0x6b, 0x22, 0x84, 0x02, 0x0a, 0x0d,
-	0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x0e, 0x0a,
-	0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x10, 0x0a,
-	0x03, 0x6e, 0x75, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x6e, 0x75, 0x6d, 0x12,
-	0x1b, 0x0a, 0x08, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x6e, 0x75, 0x6d, 0x18, 0x03, 0x20, 0x01, 0x28,
-	0x03, 0x48, 0x00, 0x52, 0x07, 0x6c, 0x69, 0x6e, 0x65, 0x4e, 0x75, 0x6d, 0x12, 0x14, 0x0a, 0x04,
-	0x6c, 0x61, 0x6e, 0x67, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x04, 0x6c, 0x61,
-	0x6e, 0x67, 0x12, 0x2b, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x05, 0x20, 0x01,
-	0x28, 0x0b, 0x32, 0x13, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x45,
-	0x6d, 0x62, 0x65, 0x64, 0x64, 0x65, 0x64, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12,
-	0x10, 0x0a, 0x02, 0x65, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x48, 0x01, 0x52, 0x02, 0x65,
-	0x6e, 0x12, 0x25, 0x0a, 0x02, 0x6e, 0x6f, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e,
-	0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x45, 0x6d, 0x62, 0x65, 0x64, 0x64,
-	0x65, 0x64, 0x48, 0x01, 0x52, 0x02, 0x6e, 0x6f, 0x12, 0x29, 0x0a, 0x06, 0x63, 0x6f, 0x72, 0x70,
-	0x75, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x11, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70,
-	0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x43, 0x6f, 0x72, 0x70, 0x75, 0x73, 0x52, 0x06, 0x63, 0x6f, 0x72,
-	0x70, 0x75, 0x73, 0x42, 0x06, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x42, 0x05, 0x0a, 0x03, 0x65,
-	0x78, 0x74, 0x22, 0x85, 0x01, 0x0a, 0x0e, 0x44, 0x79, 0x6e, 0x61, 0x6d, 0x69, 0x63, 0x4d, 0x65,
-	0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x3a, 0x0a, 0x0c, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x5f,
-	0x66, 0x69, 0x65, 0x6c, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f,
-	0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74,
-	0x72, 0x75, 0x63, 0x74, 0x52, 0x0b, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x46, 0x69, 0x65, 0x6c,
-	0x64, 0x12, 0x37, 0x0a, 0x0b, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x66, 0x69, 0x65, 0x6c, 0x64,
-	0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
-	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0a,
-	0x76, 0x61, 0x6c, 0x75, 0x65, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x22, 0x82, 0x01, 0x0a, 0x14, 0x44,
-	0x79, 0x6e, 0x61, 0x6d, 0x69, 0x63, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x55, 0x70, 0x64,
-	0x61, 0x74, 0x65, 0x12, 0x2d, 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28,
-	0x0b, 0x32, 0x19, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x44, 0x79,
-	0x6e, 0x61, 0x6d, 0x69, 0x63, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x04, 0x62, 0x6f,
-	0x64, 0x79, 0x12, 0x3b, 0x0a, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x6d, 0x61, 0x73,
-	0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
-	0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4d,
-	0x61, 0x73, 0x6b, 0x52, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x73, 0x6b, 0x2a,
-	0x5a, 0x0a, 0x06, 0x43, 0x6f, 0x72, 0x70, 0x75, 0x73, 0x12, 0x0d, 0x0a, 0x09, 0x55, 0x4e, 0x49,
-	0x56, 0x45, 0x52, 0x53, 0x41, 0x4c, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x57, 0x45, 0x42, 0x10,
-	0x01, 0x12, 0x0a, 0x0a, 0x06, 0x49, 0x4d, 0x41, 0x47, 0x45, 0x53, 0x10, 0x02, 0x12, 0x09, 0x0a,
-	0x05, 0x4c, 0x4f, 0x43, 0x41, 0x4c, 0x10, 0x03, 0x12, 0x08, 0x0a, 0x04, 0x4e, 0x45, 0x57, 0x53,
-	0x10, 0x04, 0x12, 0x0c, 0x0a, 0x08, 0x50, 0x52, 0x4f, 0x44, 0x55, 0x43, 0x54, 0x53, 0x10, 0x05,
-	0x12, 0x09, 0x0a, 0x05, 0x56, 0x49, 0x44, 0x45, 0x4f, 0x10, 0x06, 0x32, 0xcf, 0x05, 0x0a, 0x0b,
-	0x45, 0x63, 0x68, 0x6f, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0xf2, 0x01, 0x0a, 0x04,
-	0x45, 0x63, 0x68, 0x6f, 0x12, 0x18, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-	0x2e, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x18,
+var File_echo_service_proto protoreflect.FileDescriptor
+
+var file_echo_service_proto_rawDesc = []byte{
+	0x0a, 0x12, 0x65, 0x63, 0x68, 0x6f, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70,
+	0x72, 0x6f, 0x74, 0x6f, 0x12, 0x09, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a,
+	0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x61, 0x6e, 0x6e, 0x6f,
+	0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x20, 0x67,
+	0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x66,
+	0x69, 0x65, 0x6c, 0x64, 0x5f, 0x6d, 0x61, 0x73, 0x6b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a,
+	0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
+	0x2f, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x46, 0x0a,
+	0x08, 0x45, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x65, 0x64, 0x12, 0x1c, 0x0a, 0x08, 0x70, 0x72, 0x6f,
+	0x67, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x48, 0x00, 0x52, 0x08, 0x70,
+	0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x12, 0x14, 0x0a, 0x04, 0x6e, 0x6f, 0x74, 0x65, 0x18,
+	0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x04, 0x6e, 0x6f, 0x74, 0x65, 0x42, 0x06, 0x0a,
+	0x04, 0x6d, 0x61, 0x72, 0x6b, 0x22, 0x84, 0x02, 0x0a, 0x0d, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65,
+	0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20,
+	0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x6e, 0x75, 0x6d, 0x18, 0x02,
+	0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x6e, 0x75, 0x6d, 0x12, 0x1b, 0x0a, 0x08, 0x6c, 0x69, 0x6e,
+	0x65, 0x5f, 0x6e, 0x75, 0x6d, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x48, 0x00, 0x52, 0x07, 0x6c,
+	0x69, 0x6e, 0x65, 0x4e, 0x75, 0x6d, 0x12, 0x14, 0x0a, 0x04, 0x6c, 0x61, 0x6e, 0x67, 0x18, 0x04,
+	0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x04, 0x6c, 0x61, 0x6e, 0x67, 0x12, 0x2b, 0x0a, 0x06,
+	0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x74,
+	0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x45, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x65,
+	0x64, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x10, 0x0a, 0x02, 0x65, 0x6e, 0x18,
+	0x06, 0x20, 0x01, 0x28, 0x03, 0x48, 0x01, 0x52, 0x02, 0x65, 0x6e, 0x12, 0x25, 0x0a, 0x02, 0x6e,
+	0x6f, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72,
+	0x6f, 0x74, 0x6f, 0x2e, 0x45, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x65, 0x64, 0x48, 0x01, 0x52, 0x02,
+	0x6e, 0x6f, 0x12, 0x29, 0x0a, 0x06, 0x63, 0x6f, 0x72, 0x70, 0x75, 0x73, 0x18, 0x08, 0x20, 0x01,
+	0x28, 0x0e, 0x32, 0x11, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x43,
+	0x6f, 0x72, 0x70, 0x75, 0x73, 0x52, 0x06, 0x63, 0x6f, 0x72, 0x70, 0x75, 0x73, 0x42, 0x06, 0x0a,
+	0x04, 0x63, 0x6f, 0x64, 0x65, 0x42, 0x05, 0x0a, 0x03, 0x65, 0x78, 0x74, 0x22, 0x85, 0x01, 0x0a,
+	0x0e, 0x44, 0x79, 0x6e, 0x61, 0x6d, 0x69, 0x63, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12,
+	0x3a, 0x0a, 0x0c, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x5f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x18,
+	0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,
+	0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, 0x0b,
+	0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x12, 0x37, 0x0a, 0x0b, 0x76,
+	0x61, 0x6c, 0x75, 0x65, 0x5f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b,
+	0x32, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
+	0x75, 0x66, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0a, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x46,
+	0x69, 0x65, 0x6c, 0x64, 0x22, 0x82, 0x01, 0x0a, 0x14, 0x44, 0x79, 0x6e, 0x61, 0x6d, 0x69, 0x63,
+	0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x2d, 0x0a,
+	0x04, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x74, 0x65,
+	0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x44, 0x79, 0x6e, 0x61, 0x6d, 0x69, 0x63, 0x4d,
+	0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x12, 0x3b, 0x0a, 0x0b,
+	0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x6d, 0x61, 0x73, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28,
+	0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+	0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4d, 0x61, 0x73, 0x6b, 0x52, 0x0a, 0x75,
+	0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x73, 0x6b, 0x2a, 0x5a, 0x0a, 0x06, 0x43, 0x6f, 0x72,
+	0x70, 0x75, 0x73, 0x12, 0x0d, 0x0a, 0x09, 0x55, 0x4e, 0x49, 0x56, 0x45, 0x52, 0x53, 0x41, 0x4c,
+	0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x57, 0x45, 0x42, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x49,
+	0x4d, 0x41, 0x47, 0x45, 0x53, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x4c, 0x4f, 0x43, 0x41, 0x4c,
+	0x10, 0x03, 0x12, 0x08, 0x0a, 0x04, 0x4e, 0x45, 0x57, 0x53, 0x10, 0x04, 0x12, 0x0c, 0x0a, 0x08,
+	0x50, 0x52, 0x4f, 0x44, 0x55, 0x43, 0x54, 0x53, 0x10, 0x05, 0x12, 0x09, 0x0a, 0x05, 0x56, 0x49,
+	0x44, 0x45, 0x4f, 0x10, 0x06, 0x32, 0xcf, 0x05, 0x0a, 0x0b, 0x45, 0x63, 0x68, 0x6f, 0x53, 0x65,
+	0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0xf2, 0x01, 0x0a, 0x04, 0x45, 0x63, 0x68, 0x6f, 0x12, 0x18,
 	0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x69, 0x6d, 0x70, 0x6c,
-	0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0xb5, 0x01, 0x82, 0xd3, 0xe4, 0x93, 0x02,
-	0xae, 0x01, 0x22, 0x15, 0x2f, 0x76, 0x31, 0x2f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2f,
-	0x65, 0x63, 0x68, 0x6f, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x5a, 0x1d, 0x12, 0x1b, 0x2f, 0x76, 0x31,
-	0x2f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2f, 0x65, 0x63, 0x68, 0x6f, 0x2f, 0x7b, 0x69,
-	0x64, 0x7d, 0x2f, 0x7b, 0x6e, 0x75, 0x6d, 0x7d, 0x5a, 0x24, 0x12, 0x22, 0x2f, 0x76, 0x31, 0x2f,
-	0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2f, 0x65, 0x63, 0x68, 0x6f, 0x2f, 0x7b, 0x69, 0x64,
-	0x7d, 0x2f, 0x7b, 0x6e, 0x75, 0x6d, 0x7d, 0x2f, 0x7b, 0x6c, 0x61, 0x6e, 0x67, 0x7d, 0x5a, 0x31,
-	0x12, 0x2f, 0x2f, 0x76, 0x31, 0x2f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2f, 0x65, 0x63,
-	0x68, 0x6f, 0x31, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x2f, 0x7b, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x6e,
-	0x75, 0x6d, 0x7d, 0x2f, 0x7b, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x6e, 0x6f, 0x74, 0x65,
-	0x7d, 0x5a, 0x1d, 0x12, 0x1b, 0x2f, 0x76, 0x31, 0x2f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65,
-	0x2f, 0x65, 0x63, 0x68, 0x6f, 0x32, 0x2f, 0x7b, 0x6e, 0x6f, 0x2e, 0x6e, 0x6f, 0x74, 0x65, 0x7d,
-	0x12, 0x60, 0x0a, 0x08, 0x45, 0x63, 0x68, 0x6f, 0x42, 0x6f, 0x64, 0x79, 0x12, 0x18, 0x2e, 0x74,
-	0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x4d,
-	0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x18, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f,
-	0x74, 0x6f, 0x2e, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
-	0x22, 0x20, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x22, 0x15, 0x2f, 0x76, 0x31, 0x2f, 0x65, 0x78,
-	0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2f, 0x65, 0x63, 0x68, 0x6f, 0x5f, 0x62, 0x6f, 0x64, 0x79, 0x3a,
-	0x01, 0x2a, 0x12, 0x85, 0x01, 0x0a, 0x10, 0x45, 0x63, 0x68, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f,
-	0x6e, 0x73, 0x65, 0x42, 0x6f, 0x64, 0x79, 0x12, 0x1f, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72,
-	0x6f, 0x74, 0x6f, 0x2e, 0x44, 0x79, 0x6e, 0x61, 0x6d, 0x69, 0x63, 0x4d, 0x65, 0x73, 0x73, 0x61,
-	0x67, 0x65, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x1a, 0x1f, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70,
-	0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x44, 0x79, 0x6e, 0x61, 0x6d, 0x69, 0x63, 0x4d, 0x65, 0x73, 0x73,
-	0x61, 0x67, 0x65, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x22, 0x2f, 0x82, 0xd3, 0xe4, 0x93, 0x02,
-	0x29, 0x22, 0x1e, 0x2f, 0x76, 0x31, 0x2f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2f, 0x65,
-	0x63, 0x68, 0x6f, 0x5f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x5f, 0x62, 0x6f, 0x64,
-	0x79, 0x3a, 0x01, 0x2a, 0x62, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x12, 0x6c, 0x0a, 0x0a, 0x45, 0x63,
-	0x68, 0x6f, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x18, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70,
+	0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x18, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70,
 	0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61,
-	0x67, 0x65, 0x1a, 0x18, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53,
-	0x69, 0x6d, 0x70, 0x6c, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x2a, 0x82, 0xd3,
-	0xe4, 0x93, 0x02, 0x24, 0x2a, 0x22, 0x2f, 0x76, 0x31, 0x2f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c,
-	0x65, 0x2f, 0x65, 0x63, 0x68, 0x6f, 0x5f, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x2f, 0x7b, 0x69,
-	0x64, 0x7d, 0x2f, 0x7b, 0x6e, 0x75, 0x6d, 0x7d, 0x12, 0x73, 0x0a, 0x09, 0x45, 0x63, 0x68, 0x6f,
-	0x50, 0x61, 0x74, 0x63, 0x68, 0x12, 0x1f, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74,
-	0x6f, 0x2e, 0x44, 0x79, 0x6e, 0x61, 0x6d, 0x69, 0x63, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
-	0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x1a, 0x1f, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f,
-	0x74, 0x6f, 0x2e, 0x44, 0x79, 0x6e, 0x61, 0x6d, 0x69, 0x63, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67,
-	0x65, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x22, 0x24, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1e, 0x32,
-	0x16, 0x2f, 0x76, 0x31, 0x2f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2f, 0x65, 0x63, 0x68,
-	0x6f, 0x5f, 0x70, 0x61, 0x74, 0x63, 0x68, 0x3a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x42, 0x51, 0x5a,
-	0x4f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x2d, 0x6b,
-	0x72, 0x61, 0x74, 0x6f, 0x73, 0x2f, 0x6b, 0x72, 0x61, 0x74, 0x6f, 0x73, 0x2f, 0x63, 0x6d, 0x64,
-	0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x2d, 0x67, 0x65, 0x6e, 0x2d, 0x67, 0x6f, 0x2d, 0x68,
-	0x74, 0x74, 0x70, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x74, 0x65, 0x73,
-	0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x3b, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-	0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+	0x67, 0x65, 0x22, 0xb5, 0x01, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0xae, 0x01, 0x22, 0x15, 0x2f, 0x76,
+	0x31, 0x2f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2f, 0x65, 0x63, 0x68, 0x6f, 0x2f, 0x7b,
+	0x69, 0x64, 0x7d, 0x5a, 0x1d, 0x12, 0x1b, 0x2f, 0x76, 0x31, 0x2f, 0x65, 0x78, 0x61, 0x6d, 0x70,
+	0x6c, 0x65, 0x2f, 0x65, 0x63, 0x68, 0x6f, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x2f, 0x7b, 0x6e, 0x75,
+	0x6d, 0x7d, 0x5a, 0x24, 0x12, 0x22, 0x2f, 0x76, 0x31, 0x2f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c,
+	0x65, 0x2f, 0x65, 0x63, 0x68, 0x6f, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x2f, 0x7b, 0x6e, 0x75, 0x6d,
+	0x7d, 0x2f, 0x7b, 0x6c, 0x61, 0x6e, 0x67, 0x7d, 0x5a, 0x31, 0x12, 0x2f, 0x2f, 0x76, 0x31, 0x2f,
+	0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2f, 0x65, 0x63, 0x68, 0x6f, 0x31, 0x2f, 0x7b, 0x69,
+	0x64, 0x7d, 0x2f, 0x7b, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x6e, 0x75, 0x6d, 0x7d, 0x2f, 0x7b, 0x73,
+	0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x6e, 0x6f, 0x74, 0x65, 0x7d, 0x5a, 0x1d, 0x12, 0x1b, 0x2f,
+	0x76, 0x31, 0x2f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2f, 0x65, 0x63, 0x68, 0x6f, 0x32,
+	0x2f, 0x7b, 0x6e, 0x6f, 0x2e, 0x6e, 0x6f, 0x74, 0x65, 0x7d, 0x12, 0x60, 0x0a, 0x08, 0x45, 0x63,
+	0x68, 0x6f, 0x42, 0x6f, 0x64, 0x79, 0x12, 0x18, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f,
+	0x74, 0x6f, 0x2e, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
+	0x1a, 0x18, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x69, 0x6d,
+	0x70, 0x6c, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x20, 0x82, 0xd3, 0xe4, 0x93,
+	0x02, 0x1a, 0x22, 0x15, 0x2f, 0x76, 0x31, 0x2f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2f,
+	0x65, 0x63, 0x68, 0x6f, 0x5f, 0x62, 0x6f, 0x64, 0x79, 0x3a, 0x01, 0x2a, 0x12, 0x85, 0x01, 0x0a,
+	0x10, 0x45, 0x63, 0x68, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x6f, 0x64,
+	0x79, 0x12, 0x1f, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x44, 0x79,
+	0x6e, 0x61, 0x6d, 0x69, 0x63, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x55, 0x70, 0x64, 0x61,
+	0x74, 0x65, 0x1a, 0x1f, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x44,
+	0x79, 0x6e, 0x61, 0x6d, 0x69, 0x63, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x55, 0x70, 0x64,
+	0x61, 0x74, 0x65, 0x22, 0x2f, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x29, 0x22, 0x1e, 0x2f, 0x76, 0x31,
+	0x2f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2f, 0x65, 0x63, 0x68, 0x6f, 0x5f, 0x72, 0x65,
+	0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x5f, 0x62, 0x6f, 0x64, 0x79, 0x3a, 0x01, 0x2a, 0x62, 0x04,
+	0x62, 0x6f, 0x64, 0x79, 0x12, 0x6c, 0x0a, 0x0a, 0x45, 0x63, 0x68, 0x6f, 0x44, 0x65, 0x6c, 0x65,
+	0x74, 0x65, 0x12, 0x18, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53,
+	0x69, 0x6d, 0x70, 0x6c, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x18, 0x2e, 0x74,
+	0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x4d,
+	0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x2a, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x24, 0x2a, 0x22,
+	0x2f, 0x76, 0x31, 0x2f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2f, 0x65, 0x63, 0x68, 0x6f,
+	0x5f, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x2f, 0x7b, 0x6e, 0x75,
+	0x6d, 0x7d, 0x12, 0x73, 0x0a, 0x09, 0x45, 0x63, 0x68, 0x6f, 0x50, 0x61, 0x74, 0x63, 0x68, 0x12,
+	0x1f, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x44, 0x79, 0x6e, 0x61,
+	0x6d, 0x69, 0x63, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65,
+	0x1a, 0x1f, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x44, 0x79, 0x6e,
+	0x61, 0x6d, 0x69, 0x63, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x55, 0x70, 0x64, 0x61, 0x74,
+	0x65, 0x22, 0x24, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1e, 0x32, 0x16, 0x2f, 0x76, 0x31, 0x2f, 0x65,
+	0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2f, 0x65, 0x63, 0x68, 0x6f, 0x5f, 0x70, 0x61, 0x74, 0x63,
+	0x68, 0x3a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x42, 0x51, 0x5a, 0x4f, 0x67, 0x69, 0x74, 0x68, 0x75,
+	0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x2d, 0x6b, 0x72, 0x61, 0x74, 0x6f, 0x73, 0x2f,
+	0x6b, 0x72, 0x61, 0x74, 0x6f, 0x73, 0x2f, 0x63, 0x6d, 0x64, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+	0x63, 0x2d, 0x67, 0x65, 0x6e, 0x2d, 0x67, 0x6f, 0x2d, 0x68, 0x74, 0x74, 0x70, 0x2f, 0x69, 0x6e,
+	0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+	0x3b, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
+	0x6f, 0x33,
 }
 
 var (
-	file_proto_echo_service_proto_rawDescOnce sync.Once
-	file_proto_echo_service_proto_rawDescData = file_proto_echo_service_proto_rawDesc
+	file_echo_service_proto_rawDescOnce sync.Once
+	file_echo_service_proto_rawDescData = file_echo_service_proto_rawDesc
 )
 
-func file_proto_echo_service_proto_rawDescGZIP() []byte {
-	file_proto_echo_service_proto_rawDescOnce.Do(func() {
-		file_proto_echo_service_proto_rawDescData = protoimpl.X.CompressGZIP(file_proto_echo_service_proto_rawDescData)
+func file_echo_service_proto_rawDescGZIP() []byte {
+	file_echo_service_proto_rawDescOnce.Do(func() {
+		file_echo_service_proto_rawDescData = protoimpl.X.CompressGZIP(file_echo_service_proto_rawDescData)
 	})
-	return file_proto_echo_service_proto_rawDescData
+	return file_echo_service_proto_rawDescData
 }
 
-var file_proto_echo_service_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
-var file_proto_echo_service_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
-var file_proto_echo_service_proto_goTypes = []interface{}{
+var file_echo_service_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
+var file_echo_service_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
+var file_echo_service_proto_goTypes = []interface{}{
 	(Corpus)(0),                   // 0: testproto.Corpus
 	(*Embedded)(nil),              // 1: testproto.Embedded
 	(*SimpleMessage)(nil),         // 2: testproto.SimpleMessage
@@ -564,7 +564,7 @@ var file_proto_echo_service_proto_goTypes = []interface{}{
 	(*structpb.Value)(nil),        // 6: google.protobuf.Value
 	(*fieldmaskpb.FieldMask)(nil), // 7: google.protobuf.FieldMask
 }
-var file_proto_echo_service_proto_depIdxs = []int32{
+var file_echo_service_proto_depIdxs = []int32{
 	1,  // 0: testproto.SimpleMessage.status:type_name -> testproto.Embedded
 	1,  // 1: testproto.SimpleMessage.no:type_name -> testproto.Embedded
 	0,  // 2: testproto.SimpleMessage.corpus:type_name -> testproto.Corpus
@@ -589,13 +589,13 @@ var file_proto_echo_service_proto_depIdxs = []int32{
 	0,  // [0:7] is the sub-list for field type_name
 }
 
-func init() { file_proto_echo_service_proto_init() }
-func file_proto_echo_service_proto_init() {
-	if File_proto_echo_service_proto != nil {
+func init() { file_echo_service_proto_init() }
+func file_echo_service_proto_init() {
+	if File_echo_service_proto != nil {
 		return
 	}
 	if !protoimpl.UnsafeEnabled {
-		file_proto_echo_service_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+		file_echo_service_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
 			switch v := v.(*Embedded); i {
 			case 0:
 				return &v.state
@@ -607,7 +607,7 @@ func file_proto_echo_service_proto_init() {
 				return nil
 			}
 		}
-		file_proto_echo_service_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+		file_echo_service_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
 			switch v := v.(*SimpleMessage); i {
 			case 0:
 				return &v.state
@@ -619,7 +619,7 @@ func file_proto_echo_service_proto_init() {
 				return nil
 			}
 		}
-		file_proto_echo_service_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
+		file_echo_service_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
 			switch v := v.(*DynamicMessage); i {
 			case 0:
 				return &v.state
@@ -631,7 +631,7 @@ func file_proto_echo_service_proto_init() {
 				return nil
 			}
 		}
-		file_proto_echo_service_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
+		file_echo_service_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
 			switch v := v.(*DynamicMessageUpdate); i {
 			case 0:
 				return &v.state
@@ -644,11 +644,11 @@ func file_proto_echo_service_proto_init() {
 			}
 		}
 	}
-	file_proto_echo_service_proto_msgTypes[0].OneofWrappers = []interface{}{
+	file_echo_service_proto_msgTypes[0].OneofWrappers = []interface{}{
 		(*Embedded_Progress)(nil),
 		(*Embedded_Note)(nil),
 	}
-	file_proto_echo_service_proto_msgTypes[1].OneofWrappers = []interface{}{
+	file_echo_service_proto_msgTypes[1].OneofWrappers = []interface{}{
 		(*SimpleMessage_LineNum)(nil),
 		(*SimpleMessage_Lang)(nil),
 		(*SimpleMessage_En)(nil),
@@ -658,19 +658,19 @@ func file_proto_echo_service_proto_init() {
 	out := protoimpl.TypeBuilder{
 		File: protoimpl.DescBuilder{
 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
-			RawDescriptor: file_proto_echo_service_proto_rawDesc,
+			RawDescriptor: file_echo_service_proto_rawDesc,
 			NumEnums:      1,
 			NumMessages:   4,
 			NumExtensions: 0,
 			NumServices:   1,
 		},
-		GoTypes:           file_proto_echo_service_proto_goTypes,
-		DependencyIndexes: file_proto_echo_service_proto_depIdxs,
-		EnumInfos:         file_proto_echo_service_proto_enumTypes,
-		MessageInfos:      file_proto_echo_service_proto_msgTypes,
+		GoTypes:           file_echo_service_proto_goTypes,
+		DependencyIndexes: file_echo_service_proto_depIdxs,
+		EnumInfos:         file_echo_service_proto_enumTypes,
+		MessageInfos:      file_echo_service_proto_msgTypes,
 	}.Build()
-	File_proto_echo_service_proto = out.File
-	file_proto_echo_service_proto_rawDesc = nil
-	file_proto_echo_service_proto_goTypes = nil
-	file_proto_echo_service_proto_depIdxs = nil
+	File_echo_service_proto = out.File
+	file_echo_service_proto_rawDesc = nil
+	file_echo_service_proto_goTypes = nil
+	file_echo_service_proto_depIdxs = nil
 }
diff --git a/examples/proto/echo_service.proto b/internal/testproto/echo_service.proto
similarity index 100%
rename from examples/proto/echo_service.proto
rename to internal/testproto/echo_service.proto
diff --git a/examples/proto/echo_service_grpc.pb.go b/internal/testproto/echo_service_grpc.pb.go
similarity index 99%
rename from examples/proto/echo_service_grpc.pb.go
rename to internal/testproto/echo_service_grpc.pb.go
index 6174691b..23374bdc 100644
--- a/examples/proto/echo_service_grpc.pb.go
+++ b/internal/testproto/echo_service_grpc.pb.go
@@ -257,5 +257,5 @@ var EchoService_ServiceDesc = grpc.ServiceDesc{
 		},
 	},
 	Streams:  []grpc.StreamDesc{},
-	Metadata: "proto/echo_service.proto",
+	Metadata: "echo_service.proto",
 }
diff --git a/examples/proto/stream.proto b/internal/testproto/stream.proto
similarity index 100%
rename from examples/proto/stream.proto
rename to internal/testproto/stream.proto
diff --git a/examples/proto/stream_grpc.pb.go b/internal/testproto/stream_grpc.pb.go
similarity index 99%
rename from examples/proto/stream_grpc.pb.go
rename to internal/testproto/stream_grpc.pb.go
index 5f36aacd..9dfe96f8 100644
--- a/examples/proto/stream_grpc.pb.go
+++ b/internal/testproto/stream_grpc.pb.go
@@ -126,5 +126,5 @@ var StreamService_ServiceDesc = grpc.ServiceDesc{
 			ServerStreams: true,
 		},
 	},
-	Metadata: "proto/stream.proto",
+	Metadata: "stream.proto",
 }
diff --git a/middleware/logging/logging.go b/middleware/logging/logging.go
index 65623c47..af4e6a8c 100644
--- a/middleware/logging/logging.go
+++ b/middleware/logging/logging.go
@@ -16,15 +16,15 @@ func Server(logger log.Logger) middleware.Middleware {
 	return func(handler middleware.Handler) middleware.Handler {
 		return func(ctx context.Context, req interface{}) (reply interface{}, err error) {
 			var (
-				code   int32
-				reason string
-				kind   string
-				method string
+				code      int32
+				reason    string
+				kind      string
+				operation string
 			)
 			startTime := time.Now()
 			if info, ok := transport.FromServerContext(ctx); ok {
 				kind = info.Kind()
-				method = info.Method()
+				operation = info.Operation()
 			}
 			reply, err = handler(ctx, req)
 			if se := errors.FromError(err); se != nil {
@@ -35,7 +35,7 @@ func Server(logger log.Logger) middleware.Middleware {
 			log.WithContext(ctx, logger).Log(level,
 				"kind", "server",
 				"component", kind,
-				"method", method,
+				"operation", operation,
 				"args", extractArgs(req),
 				"code", code,
 				"reason", reason,
@@ -52,15 +52,15 @@ func Client(logger log.Logger) middleware.Middleware {
 	return func(handler middleware.Handler) middleware.Handler {
 		return func(ctx context.Context, req interface{}) (reply interface{}, err error) {
 			var (
-				code   int32
-				reason string
-				kind   string
-				method string
+				code      int32
+				reason    string
+				kind      string
+				operation string
 			)
 			startTime := time.Now()
 			if info, ok := transport.FromClientContext(ctx); ok {
 				kind = info.Kind()
-				method = info.Method()
+				operation = info.Operation()
 			}
 			reply, err = handler(ctx, req)
 			if se := errors.FromError(err); se != nil {
@@ -71,7 +71,7 @@ func Client(logger log.Logger) middleware.Middleware {
 			log.WithContext(ctx, logger).Log(level,
 				"kind", "client",
 				"component", kind,
-				"method", method,
+				"operation", operation,
 				"args", extractArgs(req),
 				"code", code,
 				"reason", reason,
diff --git a/middleware/metrics/metrics.go b/middleware/metrics/metrics.go
index d7b08e51..780b9110 100644
--- a/middleware/metrics/metrics.go
+++ b/middleware/metrics/metrics.go
@@ -29,9 +29,9 @@ func WithSeconds(c metrics.Observer) Option {
 }
 
 type options struct {
-	// counter: <client/server>_requests_code_total{kind, method, code, reason}
+	// counter: <client/server>_requests_code_total{kind, operation, code, reason}
 	requests metrics.Counter
-	// histogram: <client/server>_requests_seconds_bucket{kind, method}
+	// histogram: <client/server>_requests_seconds_bucket{kind, operation}
 	seconds metrics.Observer
 }
 
@@ -44,15 +44,15 @@ func Server(opts ...Option) middleware.Middleware {
 	return func(handler middleware.Handler) middleware.Handler {
 		return func(ctx context.Context, req interface{}) (interface{}, error) {
 			var (
-				code   int
-				reason string
-				kind   string
-				method string
+				code      int
+				reason    string
+				kind      string
+				operation string
 			)
 			startTime := time.Now()
 			if info, ok := transport.FromServerContext(ctx); ok {
 				kind = info.Kind()
-				method = info.Method()
+				operation = info.Operation()
 			}
 			reply, err := handler(ctx, req)
 			if se := errors.FromError(err); se != nil {
@@ -60,10 +60,10 @@ func Server(opts ...Option) middleware.Middleware {
 				reason = se.Reason
 			}
 			if options.requests != nil {
-				options.requests.With(kind, method, strconv.Itoa(code), reason).Inc()
+				options.requests.With(kind, operation, strconv.Itoa(code), reason).Inc()
 			}
 			if options.seconds != nil {
-				options.seconds.With(kind, method).Observe(time.Since(startTime).Seconds())
+				options.seconds.With(kind, operation).Observe(time.Since(startTime).Seconds())
 			}
 			return reply, err
 		}
@@ -79,15 +79,15 @@ func Client(opts ...Option) middleware.Middleware {
 	return func(handler middleware.Handler) middleware.Handler {
 		return func(ctx context.Context, req interface{}) (interface{}, error) {
 			var (
-				code   int
-				reason string
-				kind   string
-				method string
+				code      int
+				reason    string
+				kind      string
+				operation string
 			)
 			startTime := time.Now()
 			if info, ok := transport.FromClientContext(ctx); ok {
 				kind = info.Kind()
-				method = info.Method()
+				operation = info.Operation()
 			}
 			reply, err := handler(ctx, req)
 			if se := errors.FromError(err); se != nil {
@@ -95,10 +95,10 @@ func Client(opts ...Option) middleware.Middleware {
 				reason = se.Reason
 			}
 			if options.requests != nil {
-				options.requests.With(kind, method, strconv.Itoa(code), reason).Inc()
+				options.requests.With(kind, operation, strconv.Itoa(code), reason).Inc()
 			}
 			if options.seconds != nil {
-				options.seconds.With(kind, method).Observe(time.Since(startTime).Seconds())
+				options.seconds.With(kind, operation).Observe(time.Since(startTime).Seconds())
 			}
 			return reply, err
 		}
diff --git a/middleware/tracing/tracing.go b/middleware/tracing/tracing.go
index ed16b7e9..2b3f385d 100644
--- a/middleware/tracing/tracing.go
+++ b/middleware/tracing/tracing.go
@@ -38,7 +38,7 @@ func Server(opts ...Option) middleware.Middleware {
 		return func(ctx context.Context, req interface{}) (reply interface{}, err error) {
 			if tr, ok := transport.FromServerContext(ctx); ok {
 				var span trace.Span
-				ctx, span = tracer.Start(ctx, tr.Kind(), tr.Method(), tr.Metadata())
+				ctx, span = tracer.Start(ctx, tr.Kind(), tr.Operation(), tr.Metadata())
 				defer func() { tracer.End(ctx, span, err) }()
 			}
 			return handler(ctx, req)
@@ -53,7 +53,7 @@ func Client(opts ...Option) middleware.Middleware {
 		return func(ctx context.Context, req interface{}) (reply interface{}, err error) {
 			if tr, ok := transport.FromClientContext(ctx); ok {
 				var span trace.Span
-				ctx, span = tracer.Start(ctx, tr.Kind(), tr.Method(), tr.Metadata())
+				ctx, span = tracer.Start(ctx, tr.Kind(), tr.Operation(), tr.Metadata())
 				defer func() { tracer.End(ctx, span, err) }()
 			}
 			return handler(ctx, req)
diff --git a/transport/grpc/client.go b/transport/grpc/client.go
index 18af330b..55d6e7f2 100644
--- a/transport/grpc/client.go
+++ b/transport/grpc/client.go
@@ -38,7 +38,7 @@ func WithTimeout(timeout time.Duration) ClientOption {
 // WithMiddleware with client middleware.
 func WithMiddleware(m ...middleware.Middleware) ClientOption {
 	return func(o *clientOptions) {
-		o.middleware = middleware.Chain(m...)
+		o.middleware = m
 	}
 }
 
@@ -67,8 +67,8 @@ func WithOptions(opts ...grpc.DialOption) ClientOption {
 type clientOptions struct {
 	endpoint   string
 	timeout    time.Duration
-	middleware middleware.Middleware
 	discovery  registry.Discovery
+	middleware []middleware.Middleware
 	ints       []grpc.UnaryClientInterceptor
 	grpcOpts   []grpc.DialOption
 }
@@ -112,12 +112,12 @@ func dial(ctx context.Context, insecure bool, opts ...ClientOption) (*grpc.Clien
 	return grpc.DialContext(ctx, options.endpoint, grpcOpts...)
 }
 
-func unaryClientInterceptor(m middleware.Middleware, timeout time.Duration) grpc.UnaryClientInterceptor {
+func unaryClientInterceptor(ms []middleware.Middleware, timeout time.Duration) grpc.UnaryClientInterceptor {
 	return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
 		ctx = transport.NewClientContext(ctx, &Transport{
-			endpoint: cc.Target(),
-			method:   method,
-			metadata: metadata.Metadata{},
+			endpoint:  cc.Target(),
+			operation: method,
+			metadata:  metadata.Metadata{},
 		})
 		if timeout > 0 {
 			var cancel context.CancelFunc
@@ -130,8 +130,8 @@ func unaryClientInterceptor(m middleware.Middleware, timeout time.Duration) grpc
 			}
 			return reply, invoker(ctx, method, req, reply, cc, opts...)
 		}
-		if m != nil {
-			h = m(h)
+		if len(ms) > 0 {
+			h = middleware.Chain(ms...)(h)
 		}
 		_, err := h(ctx, req)
 		return err
diff --git a/transport/grpc/server.go b/transport/grpc/server.go
index 74cfe97b..e6c6bf3a 100644
--- a/transport/grpc/server.go
+++ b/transport/grpc/server.go
@@ -59,7 +59,7 @@ func Logger(logger log.Logger) ServerOption {
 // Middleware with server middleware.
 func Middleware(m ...middleware.Middleware) ServerOption {
 	return func(s *Server) {
-		s.middleware = middleware.Chain(m...)
+		s.middleware = m
 	}
 }
 
@@ -89,7 +89,7 @@ type Server struct {
 	endpoint   *url.URL
 	timeout    time.Duration
 	log        *log.Helper
-	middleware middleware.Middleware
+	middleware []middleware.Middleware
 	ints       []grpc.UnaryServerInterceptor
 	grpcOpts   []grpc.ServerOption
 	health     *health.Server
@@ -179,9 +179,9 @@ func (s *Server) unaryServerInterceptor() grpc.UnaryServerInterceptor {
 		defer cancel()
 		md, _ := grpcmd.FromIncomingContext(ctx)
 		ctx = transport.NewServerContext(ctx, &Transport{
-			endpoint: s.endpoint.String(),
-			method:   info.FullMethod,
-			metadata: metadata.New(md),
+			endpoint:  s.endpoint.String(),
+			operation: info.FullMethod,
+			metadata:  metadata.New(md),
 		})
 		if s.timeout > 0 {
 			var cancel context.CancelFunc
@@ -191,8 +191,8 @@ func (s *Server) unaryServerInterceptor() grpc.UnaryServerInterceptor {
 		h := func(ctx context.Context, req interface{}) (interface{}, error) {
 			return handler(ctx, req)
 		}
-		if s.middleware != nil {
-			h = s.middleware(h)
+		if len(s.middleware) > 0 {
+			h = middleware.Chain(s.middleware...)(h)
 		}
 		return h(ctx, req)
 	}
diff --git a/transport/grpc/transport.go b/transport/grpc/transport.go
index 91bc3eed..89bed23b 100644
--- a/transport/grpc/transport.go
+++ b/transport/grpc/transport.go
@@ -11,9 +11,9 @@ var (
 
 // Transport is a gRPC transport.
 type Transport struct {
-	endpoint string
-	method   string
-	metadata metadata.Metadata
+	endpoint  string
+	operation string
+	metadata  metadata.Metadata
 }
 
 // Kind returns the transport kind.
@@ -26,14 +26,14 @@ func (tr *Transport) Endpoint() string {
 	return tr.endpoint
 }
 
-// Method returns the transport method.
-func (tr *Transport) Method() string {
-	return tr.method
+// Operation returns the transport operation.
+func (tr *Transport) Operation() string {
+	return tr.operation
 }
 
-// SetMethod sets the transport method.
-func (tr *Transport) SetMethod(method string) {
-	tr.method = method
+// SetOperation sets the transport operation.
+func (tr *Transport) SetOperation(operation string) {
+	tr.operation = operation
 }
 
 // Metadata returns the transport metadata.
@@ -43,10 +43,6 @@ func (tr *Transport) Metadata() metadata.Metadata {
 
 // WithMetadata with a metadata into transport md.
 func (tr *Transport) WithMetadata(md metadata.Metadata) {
-	if tr.metadata == nil {
-		tr.metadata = md
-		return
-	}
 	for k, v := range md {
 		tr.metadata.Set(k, v)
 	}
diff --git a/transport/http/binding/bind.go b/transport/http/binding/bind.go
index b8f8f4c4..b16148da 100644
--- a/transport/http/binding/bind.go
+++ b/transport/http/binding/bind.go
@@ -18,11 +18,7 @@ func BindForm(req *http.Request, target interface{}) error {
 }
 
 // BindVars bind map parameters to target.
-func BindVars(vars map[string]string, target interface{}) error {
-	values := make(map[string][]string, len(vars))
-	for k, v := range vars {
-		values[k] = []string{v}
-	}
+func BindVars(values map[string][]string, target interface{}) error {
 	if msg, ok := target.(proto.Message); ok {
 		return mapProto(msg, values)
 	}
diff --git a/transport/http/binding/encode.go b/transport/http/binding/encode.go
index 5aa86b36..bec790a8 100644
--- a/transport/http/binding/encode.go
+++ b/transport/http/binding/encode.go
@@ -18,8 +18,8 @@ import (
 	"google.golang.org/protobuf/types/known/wrapperspb"
 )
 
-// EncodePath binds proto message to url path
-func EncodePath(method string, pathPattern string, msg proto.Message) string {
+// EncodeVars binds proto message to url path
+func EncodeVars(pathPattern string, msg proto.Message, isQuery bool) string {
 	if msg == nil || (reflect.ValueOf(msg).Kind() == reflect.Ptr && reflect.ValueOf(msg).IsNil()) {
 		return pathPattern
 	}
@@ -40,7 +40,7 @@ func EncodePath(method string, pathPattern string, msg proto.Message) string {
 		}
 		return in
 	})
-	if method == "GET" {
+	if isQuery {
 		u, err := encodeQuery(msg)
 		if err == nil && len(u) > 0 {
 			for key := range pathParams {
diff --git a/transport/http/calloption.go b/transport/http/calloption.go
index 840ada85..b2f6b043 100644
--- a/transport/http/calloption.go
+++ b/transport/http/calloption.go
@@ -16,7 +16,7 @@ type CallOption interface {
 
 type callInfo struct {
 	contentType string
-	method      string
+	operation   string
 	metatada    metadata.Metadata
 }
 
@@ -49,24 +49,24 @@ func (o ContentTypeCallOption) before(c *callInfo) error {
 func defaultCallInfo(path string) callInfo {
 	return callInfo{
 		contentType: "application/json",
-		method:      path,
+		operation:   path,
 		metatada:    metadata.Metadata{},
 	}
 }
 
-// Method is serviceMethod call option
-func Method(method string) CallOption {
-	return MethodCallOption{Method: method}
+// Operation is serviceMethod call option
+func Operation(operation string) CallOption {
+	return OperationCallOption{Operation: operation}
 }
 
-// MethodCallOption is set ServiceMethod for client call
-type MethodCallOption struct {
+// OperationCallOption is set ServiceMethod for client call
+type OperationCallOption struct {
 	EmptyCallOption
-	Method string
+	Operation string
 }
 
-func (o MethodCallOption) before(c *callInfo) error {
-	c.method = o.Method
+func (o OperationCallOption) before(c *callInfo) error {
+	c.operation = o.Operation
 	return nil
 }
 
diff --git a/transport/http/client.go b/transport/http/client.go
index 4f5cf6b3..436859d5 100644
--- a/transport/http/client.go
+++ b/transport/http/client.go
@@ -44,7 +44,7 @@ type clientOptions struct {
 	transport    http.RoundTripper
 	balancer     balancer.Balancer
 	discovery    registry.Discovery
-	middleware   middleware.Middleware
+	middleware   []middleware.Middleware
 }
 
 // WithTransport with client transport.
@@ -71,7 +71,7 @@ func WithUserAgent(ua string) ClientOption {
 // WithMiddleware with client middleware.
 func WithMiddleware(m ...middleware.Middleware) ClientOption {
 	return func(o *clientOptions) {
-		o.middleware = middleware.Chain(m...)
+		o.middleware = m
 	}
 }
 
@@ -201,9 +201,11 @@ func (client *Client) Invoke(ctx context.Context, method, path string, args inte
 		c.metatada = metadata.Metadata{}
 	}
 	ctx = transport.NewClientContext(ctx, &Transport{
-		endpoint: client.opts.endpoint,
-		metadata: c.metatada,
-		method:   c.method,
+		endpoint:  client.opts.endpoint,
+		metadata:  c.metatada,
+		path:      path,
+		method:    method,
+		operation: c.operation,
 	})
 	return client.invoke(ctx, req, args, reply, c)
 }
@@ -246,8 +248,8 @@ func (client *Client) invoke(ctx context.Context, req *http.Request, args interf
 		}
 		return reply, nil
 	}
-	if client.opts.middleware != nil {
-		h = client.opts.middleware(h)
+	if len(client.opts.middleware) > 0 {
+		h = middleware.Chain(client.opts.middleware...)(h)
 	}
 	_, err := h(ctx, args)
 	return err
diff --git a/transport/http/codec.go b/transport/http/codec.go
new file mode 100644
index 00000000..744a7c97
--- /dev/null
+++ b/transport/http/codec.go
@@ -0,0 +1,89 @@
+package http
+
+import (
+	"io/ioutil"
+	"net/http"
+
+	"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"
+)
+
+// SupportPackageIsVersion1 These constants should not be referenced from any other code.
+const SupportPackageIsVersion1 = true
+
+// DecodeRequestFunc is decode request func.
+type DecodeRequestFunc func(*http.Request, interface{}) error
+
+// EncodeResponseFunc is encode response func.
+type EncodeResponseFunc func(http.ResponseWriter, *http.Request, interface{}) error
+
+// EncodeErrorFunc is encode error func.
+type EncodeErrorFunc func(http.ResponseWriter, *http.Request, error)
+
+// DefaultRequestDecoder decodes the request body to object.
+func DefaultRequestDecoder(r *http.Request, v interface{}) error {
+	codec, ok := CodecForRequest(r, "Content-Type")
+	if ok {
+		data, err := ioutil.ReadAll(r.Body)
+		if err != nil {
+			return errors.BadRequest("CODEC", err.Error())
+		}
+		if err := codec.Unmarshal(data, v); err != nil {
+			return errors.BadRequest("CODEC", err.Error())
+		}
+	} else {
+		if err := binding.BindForm(r, v); err != nil {
+			return errors.BadRequest("CODEC", err.Error())
+		}
+	}
+	return nil
+}
+
+// DefaultResponseEncoder encodes the object to the HTTP response.
+func DefaultResponseEncoder(w http.ResponseWriter, r *http.Request, v interface{}) error {
+	codec, _ := CodecForRequest(r, "Accept")
+	data, err := codec.Marshal(v)
+	if err != nil {
+		return err
+	}
+	w.Header().Set("Content-Type", httputil.ContentType(codec.Name()))
+	if sc, ok := v.(interface {
+		StatusCode() int
+	}); ok {
+		w.WriteHeader(sc.StatusCode())
+	}
+	_, _ = w.Write(data)
+	return nil
+}
+
+// DefaultErrorEncoder encodes the error to the HTTP response.
+func DefaultErrorEncoder(w http.ResponseWriter, r *http.Request, se error) {
+	codec, _ := CodecForRequest(r, "Accept")
+	body, err := codec.Marshal(se)
+	if err != nil {
+		w.WriteHeader(http.StatusInternalServerError)
+		return
+	}
+	w.Header().Set("Content-Type", httputil.ContentType(codec.Name()))
+	if sc, ok := se.(interface {
+		StatusCode() int
+	}); ok {
+		w.WriteHeader(sc.StatusCode())
+	} else {
+		w.WriteHeader(http.StatusInternalServerError)
+	}
+	w.Write(body)
+}
+
+// CodecForRequest get encoding.Codec via http.Request
+func CodecForRequest(r *http.Request, name string) (encoding.Codec, bool) {
+	for _, accept := range r.Header[name] {
+		codec := encoding.GetCodec(httputil.ContentSubtype(accept))
+		if codec != nil {
+			return codec, true
+		}
+	}
+	return encoding.GetCodec("json"), false
+}
diff --git a/transport/http/context.go b/transport/http/context.go
new file mode 100644
index 00000000..6f74960a
--- /dev/null
+++ b/transport/http/context.go
@@ -0,0 +1,142 @@
+package http
+
+import (
+	"context"
+	"encoding/json"
+	"encoding/xml"
+	"io"
+	"net/http"
+	"net/url"
+	"time"
+
+	"github.com/go-kratos/kratos/v2/middleware"
+	"github.com/gorilla/mux"
+)
+
+var _ Context = (*wrapper)(nil)
+
+// HandlerFunc defines a function to serve HTTP requests.
+type HandlerFunc func(Context) error
+
+// Context is an HTTP Context.
+type Context interface {
+	context.Context
+	Vars() url.Values
+	Form() url.Values
+	Header() http.Header
+	Request() *http.Request
+	Response() http.ResponseWriter
+	Middleware(middleware.Handler) middleware.Handler
+	Bind(interface{}) error
+	Returns(interface{}, error) error
+	Result(int, interface{}) error
+	JSON(int, interface{}) error
+	XML(int, interface{}) error
+	String(int, string) error
+	Blob(int, string, []byte) error
+	Stream(int, string, io.Reader) error
+	Reset(http.ResponseWriter, *http.Request)
+}
+
+type wrapper struct {
+	route *Route
+	req   *http.Request
+	res   http.ResponseWriter
+}
+
+func (c *wrapper) Header() http.Header {
+	return c.req.Header
+}
+
+func (c *wrapper) Vars() url.Values {
+	raws := mux.Vars(c.req)
+	vars := make(url.Values, len(raws))
+	for k, v := range raws {
+		vars[k] = []string{v}
+	}
+	return vars
+}
+func (c *wrapper) Form() url.Values {
+	if err := c.req.ParseForm(); err != nil {
+		return url.Values{}
+	}
+	return c.req.Form
+}
+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.route.srv.ms...)(h)
+}
+func (c *wrapper) Bind(v interface{}) error { return c.route.srv.dec(c.req, v) }
+func (c *wrapper) Returns(v interface{}, err error) error {
+	if err != nil {
+		return err
+	}
+	if err := c.route.srv.enc(c.res, c.req, v); err != nil {
+		return err
+	}
+	return nil
+}
+func (c *wrapper) Result(code int, v interface{}) error {
+	c.res.WriteHeader(code)
+	if err := c.route.srv.enc(c.res, c.req, v); err != nil {
+		return err
+	}
+	return nil
+}
+func (c *wrapper) JSON(code int, v interface{}) error {
+	c.res.WriteHeader(code)
+	c.res.Header().Set("Content-Type", "application/json")
+	return json.NewEncoder(c.res).Encode(v)
+}
+func (c *wrapper) XML(code int, v interface{}) error {
+	c.res.WriteHeader(code)
+	c.res.Header().Set("Content-Type", "application/xml")
+	return xml.NewEncoder(c.res).Encode(v)
+}
+func (c *wrapper) String(code int, text string) error {
+	c.res.WriteHeader(code)
+	c.res.Header().Set("Content-Type", "text/plain")
+	c.res.Write([]byte(text))
+	return nil
+}
+func (c *wrapper) Blob(code int, contentType string, data []byte) error {
+	c.res.WriteHeader(code)
+	c.res.Header().Set("Content-Type", contentType)
+	c.res.Write(data)
+	return nil
+}
+func (c *wrapper) Stream(code int, contentType string, rd io.Reader) error {
+	c.res.WriteHeader(code)
+	c.res.Header().Set("Content-Type", contentType)
+	_, err := io.Copy(c.res, rd)
+	return err
+}
+func (c *wrapper) Reset(res http.ResponseWriter, req *http.Request) {
+	c.res = res
+	c.req = req
+}
+func (c *wrapper) Deadline() (time.Time, bool) {
+	if c.req == nil {
+		return time.Time{}, false
+	}
+	return c.req.Context().Deadline()
+}
+func (c *wrapper) Done() <-chan struct{} {
+	if c.req == nil {
+		return nil
+	}
+	return c.req.Context().Done()
+}
+func (c *wrapper) Err() error {
+	if c.req == nil {
+		return context.Canceled
+	}
+	return c.req.Context().Err()
+}
+func (c *wrapper) Value(key interface{}) interface{} {
+	if c.req == nil {
+		return nil
+	}
+	return c.req.Context().Value(key)
+}
diff --git a/transport/http/handle.go b/transport/http/handle.go
deleted file mode 100644
index 1034a251..00000000
--- a/transport/http/handle.go
+++ /dev/null
@@ -1,215 +0,0 @@
-package http
-
-import (
-	"context"
-	"fmt"
-	"io/ioutil"
-	"net/http"
-	"reflect"
-
-	"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/middleware"
-	"github.com/go-kratos/kratos/v2/transport/http/binding"
-)
-
-// SupportPackageIsVersion1 These constants should not be referenced from any other code.
-const SupportPackageIsVersion1 = true
-
-// DecodeRequestFunc is decode request func.
-type DecodeRequestFunc func(*http.Request, interface{}) error
-
-// EncodeResponseFunc is encode response func.
-type EncodeResponseFunc func(http.ResponseWriter, *http.Request, interface{}) error
-
-// EncodeErrorFunc is encode error func.
-type EncodeErrorFunc func(http.ResponseWriter, *http.Request, error)
-
-// HandleOption is handle option.
-type HandleOption func(*HandleOptions)
-
-// HandleOptions is handle options.
-// Deprecated: use Handler instead.
-type HandleOptions struct {
-	Decode     DecodeRequestFunc
-	Encode     EncodeResponseFunc
-	Error      EncodeErrorFunc
-	Middleware middleware.Middleware
-}
-
-// DefaultHandleOptions returns a default handle options.
-// Deprecated: use NewHandler instead.
-func DefaultHandleOptions() HandleOptions {
-	return HandleOptions{
-		Decode: DefaultRequestDecoder,
-		Encode: DefaultResponseEncoder,
-		Error:  DefaultErrorEncoder,
-	}
-}
-
-// RequestDecoder with request decoder.
-func RequestDecoder(dec DecodeRequestFunc) HandleOption {
-	return func(o *HandleOptions) {
-		o.Decode = dec
-	}
-}
-
-// ResponseEncoder with response encoder.
-func ResponseEncoder(en EncodeResponseFunc) HandleOption {
-	return func(o *HandleOptions) {
-		o.Encode = en
-	}
-}
-
-// ErrorEncoder with error encoder.
-func ErrorEncoder(en EncodeErrorFunc) HandleOption {
-	return func(o *HandleOptions) {
-		o.Error = en
-	}
-}
-
-// Middleware with middleware option.
-func Middleware(m ...middleware.Middleware) HandleOption {
-	return func(o *HandleOptions) {
-		o.Middleware = middleware.Chain(m...)
-	}
-}
-
-// Handler is handle options.
-type Handler struct {
-	method reflect.Value
-	in     reflect.Type
-	out    reflect.Type
-	opts   HandleOptions
-}
-
-// NewHandler new an HTTP handler.
-func NewHandler(handler interface{}, opts ...HandleOption) http.Handler {
-	if err := validateHandler(handler); err != nil {
-		panic(err)
-	}
-	typ := reflect.TypeOf(handler)
-	h := &Handler{
-		method: reflect.ValueOf(handler),
-		in:     typ.In(1).Elem(),
-		out:    typ.Out(0).Elem(),
-		opts:   DefaultHandleOptions(),
-	}
-	for _, o := range opts {
-		o(&h.opts)
-	}
-	return h
-}
-
-func (h *Handler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
-	in := reflect.New(h.in).Interface()
-	if err := h.opts.Decode(req, in); err != nil {
-		h.opts.Error(w, req, err)
-		return
-	}
-	invoke := func(ctx context.Context, in interface{}) (interface{}, error) {
-		ret := h.method.Call([]reflect.Value{
-			reflect.ValueOf(ctx),
-			reflect.ValueOf(in),
-		})
-		if ret[1].IsNil() {
-			return ret[0].Interface(), nil
-		}
-		return nil, ret[1].Interface().(error)
-	}
-	if h.opts.Middleware != nil {
-		invoke = h.opts.Middleware(invoke)
-	}
-	out, err := invoke(req.Context(), in)
-	if err != nil {
-		h.opts.Error(w, req, err)
-		return
-	}
-	if err := h.opts.Encode(w, req, out); err != nil {
-		h.opts.Error(w, req, err)
-	}
-}
-
-func validateHandler(handler interface{}) error {
-	typ := reflect.TypeOf(handler)
-	if typ.NumIn() != 2 || typ.NumOut() != 2 {
-		return fmt.Errorf("invalid types, in: %d out: %d", typ.NumIn(), typ.NumOut())
-	}
-	if typ.In(1).Kind() != reflect.Ptr || typ.Out(0).Kind() != reflect.Ptr {
-		return fmt.Errorf("invalid types is not a pointer")
-	}
-	if !typ.In(0).Implements(reflect.TypeOf((*context.Context)(nil)).Elem()) {
-		return fmt.Errorf("input does not implement the context")
-	}
-	if !typ.Out(1).Implements(reflect.TypeOf((*error)(nil)).Elem()) {
-		return fmt.Errorf("input does not implement the error")
-	}
-	return nil
-}
-
-// DefaultRequestDecoder decodes the request body to object.
-func DefaultRequestDecoder(r *http.Request, v interface{}) error {
-	codec, ok := CodecForRequest(r, "Content-Type")
-	if ok {
-		data, err := ioutil.ReadAll(r.Body)
-		if err != nil {
-			return errors.BadRequest("CODEC", err.Error())
-		}
-		if err := codec.Unmarshal(data, v); err != nil {
-			return errors.BadRequest("CODEC", err.Error())
-		}
-	} else {
-		if err := binding.BindForm(r, v); err != nil {
-			return errors.BadRequest("CODEC", err.Error())
-		}
-	}
-	return nil
-}
-
-// DefaultResponseEncoder encodes the object to the HTTP response.
-func DefaultResponseEncoder(w http.ResponseWriter, r *http.Request, v interface{}) error {
-	codec, _ := CodecForRequest(r, "Accept")
-	data, err := codec.Marshal(v)
-	if err != nil {
-		return err
-	}
-	w.Header().Set("Content-Type", httputil.ContentType(codec.Name()))
-	if sc, ok := v.(interface {
-		StatusCode() int
-	}); ok {
-		w.WriteHeader(sc.StatusCode())
-	}
-	_, _ = w.Write(data)
-	return nil
-}
-
-// DefaultErrorEncoder encodes the error to the HTTP response.
-func DefaultErrorEncoder(w http.ResponseWriter, r *http.Request, se error) {
-	codec, _ := CodecForRequest(r, "Accept")
-	body, err := codec.Marshal(se)
-	if err != nil {
-		w.WriteHeader(http.StatusInternalServerError)
-		return
-	}
-	w.Header().Set("Content-Type", httputil.ContentType(codec.Name()))
-	if sc, ok := se.(interface {
-		StatusCode() int
-	}); ok {
-		w.WriteHeader(sc.StatusCode())
-	} else {
-		w.WriteHeader(http.StatusInternalServerError)
-	}
-	w.Write(body)
-}
-
-// CodecForRequest get encoding.Codec via http.Request
-func CodecForRequest(r *http.Request, name string) (encoding.Codec, bool) {
-	for _, accept := range r.Header[name] {
-		codec := encoding.GetCodec(httputil.ContentSubtype(accept))
-		if codec != nil {
-			return codec, true
-		}
-	}
-	return encoding.GetCodec("json"), false
-}
diff --git a/transport/http/route.go b/transport/http/route.go
new file mode 100644
index 00000000..aae84fea
--- /dev/null
+++ b/transport/http/route.go
@@ -0,0 +1,101 @@
+package http
+
+import (
+	"net/http"
+	"path"
+	"sync"
+)
+
+// FilterFunc is a function which receives an http.Handler and returns another http.Handler.
+type FilterFunc func(http.Handler) http.Handler
+
+// FilterChain returns a FilterFunc that specifies the chained handler for HTTP Router.
+func FilterChain(filters ...FilterFunc) FilterFunc {
+	return func(next http.Handler) http.Handler {
+		for i := len(filters) - 1; i >= 0; i-- {
+			next = filters[i](next)
+		}
+		return next
+	}
+}
+
+// Route is an HTTP route.
+type Route struct {
+	prefix  string
+	pool    sync.Pool
+	srv     *Server
+	filters []FilterFunc
+}
+
+func newRoute(prefix string, srv *Server, filters ...FilterFunc) *Route {
+	r := &Route{
+		prefix:  prefix,
+		srv:     srv,
+		filters: filters,
+	}
+	r.pool.New = func() interface{} {
+		return &wrapper{route: r}
+	}
+	return r
+}
+
+// Handle registers a new route with a matcher for the URL path and method.
+func (r *Route) Handle(method, relativePath string, h HandlerFunc, filters ...FilterFunc) {
+	next := http.Handler(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
+		ctx := r.pool.Get().(Context)
+		ctx.Reset(res, req)
+		if err := h(ctx); err != nil {
+			r.srv.ene(res, req, err)
+		}
+		ctx.Reset(nil, nil)
+		r.pool.Put(ctx)
+	}))
+	next = FilterChain(filters...)(next)
+	next = FilterChain(r.filters...)(next)
+	r.srv.router.Handle(path.Join(r.prefix, relativePath), next).Methods(method)
+}
+
+// GET registers a new GET route for a path with matching handler in the router.
+func (r *Route) GET(path string, h HandlerFunc, m ...FilterFunc) {
+	r.Handle(http.MethodGet, path, h, m...)
+}
+
+// HEAD registers a new HEAD route for a path with matching handler in the router.
+func (r *Route) HEAD(path string, h HandlerFunc, m ...FilterFunc) {
+	r.Handle(http.MethodHead, path, h, m...)
+}
+
+// POST registers a new POST route for a path with matching handler in the router.
+func (r *Route) POST(path string, h HandlerFunc, m ...FilterFunc) {
+	r.Handle(http.MethodPost, path, h, m...)
+}
+
+// PUT registers a new PUT route for a path with matching handler in the router.
+func (r *Route) PUT(path string, h HandlerFunc, m ...FilterFunc) {
+	r.Handle(http.MethodPut, path, h, m...)
+}
+
+// PATCH registers a new PATCH route for a path with matching handler in the router.
+func (r *Route) PATCH(path string, h HandlerFunc, m ...FilterFunc) {
+	r.Handle(http.MethodPatch, path, h, m...)
+}
+
+// DELETE registers a new DELETE route for a path with matching handler in the router.
+func (r *Route) DELETE(path string, h HandlerFunc, m ...FilterFunc) {
+	r.Handle(http.MethodDelete, path, h, m...)
+}
+
+// CONNECT registers a new CONNECT route for a path with matching handler in the router.
+func (r *Route) CONNECT(path string, h HandlerFunc, m ...FilterFunc) {
+	r.Handle(http.MethodConnect, path, h, m...)
+}
+
+// OPTIONS registers a new OPTIONS route for a path with matching handler in the router.
+func (r *Route) OPTIONS(path string, h HandlerFunc, m ...FilterFunc) {
+	r.Handle(http.MethodOptions, path, h, m...)
+}
+
+// TRACE registers a new TRACE route for a path with matching handler in the router.
+func (r *Route) TRACE(path string, h HandlerFunc, m ...FilterFunc) {
+	r.Handle(http.MethodTrace, path, h, m...)
+}
diff --git a/transport/http/server.go b/transport/http/server.go
index fbe21d2c..12f7664c 100644
--- a/transport/http/server.go
+++ b/transport/http/server.go
@@ -13,6 +13,7 @@ import (
 	"github.com/go-kratos/kratos/v2/internal/host"
 	"github.com/go-kratos/kratos/v2/log"
 	"github.com/go-kratos/kratos/v2/metadata"
+	"github.com/go-kratos/kratos/v2/middleware"
 	"github.com/go-kratos/kratos/v2/transport"
 
 	"github.com/gorilla/mux"
@@ -52,17 +53,57 @@ func Logger(logger log.Logger) ServerOption {
 	}
 }
 
+// Middleware with service middleware option.
+func Middleware(m ...middleware.Middleware) ServerOption {
+	return func(o *Server) {
+		o.ms = m
+	}
+}
+
+// Filter with HTTP middleware option.
+func Filter(filters ...FilterFunc) ServerOption {
+	return func(o *Server) {
+		o.filters = filters
+	}
+}
+
+// RequestDecoder with request decoder.
+func RequestDecoder(dec DecodeRequestFunc) ServerOption {
+	return func(o *Server) {
+		o.dec = dec
+	}
+}
+
+// ResponseEncoder with response encoder.
+func ResponseEncoder(en EncodeResponseFunc) ServerOption {
+	return func(o *Server) {
+		o.enc = en
+	}
+}
+
+// ErrorEncoder with error encoder.
+func ErrorEncoder(en EncodeErrorFunc) ServerOption {
+	return func(o *Server) {
+		o.ene = en
+	}
+}
+
 // Server is an HTTP server wrapper.
 type Server struct {
 	*http.Server
 	ctx      context.Context
 	lis      net.Listener
 	once     sync.Once
+	endpoint *url.URL
 	err      error
 	network  string
 	address  string
-	endpoint *url.URL
 	timeout  time.Duration
+	filters  []FilterFunc
+	ms       []middleware.Middleware
+	dec      DecodeRequestFunc
+	enc      EncodeResponseFunc
+	ene      EncodeErrorFunc
 	router   *mux.Router
 	log      *log.Helper
 }
@@ -73,16 +114,25 @@ func NewServer(opts ...ServerOption) *Server {
 		network: "tcp",
 		address: ":0",
 		timeout: 1 * time.Second,
+		dec:     DefaultRequestDecoder,
+		enc:     DefaultResponseEncoder,
+		ene:     DefaultErrorEncoder,
 		log:     log.NewHelper(log.DefaultLogger),
 	}
 	for _, o := range opts {
 		o(srv)
 	}
-	srv.router = mux.NewRouter()
 	srv.Server = &http.Server{Handler: srv}
+	srv.router = mux.NewRouter()
+	srv.router.Use(srv.filter())
 	return srv
 }
 
+// Route registers an HTTP route.
+func (s *Server) Route(prefix string, filters ...FilterFunc) *Route {
+	return newRoute(prefix, s, filters...)
+}
+
 // Handle registers a new route with a matcher for the URL path.
 func (s *Server) Handle(path string, h http.Handler) {
 	s.router.Handle(path, h)
@@ -100,18 +150,35 @@ func (s *Server) HandleFunc(path string, h http.HandlerFunc) {
 
 // ServeHTTP should write reply headers and data to the ResponseWriter and then return.
 func (s *Server) ServeHTTP(res http.ResponseWriter, req *http.Request) {
-	ctx, cancel := ic.Merge(req.Context(), s.ctx)
-	defer cancel()
-	ctx = transport.NewServerContext(ctx, &Transport{
-		endpoint: s.endpoint.String(),
-		method:   req.RequestURI,
-		metadata: metadata.New(req.Header),
-	})
-	if s.timeout > 0 {
-		ctx, cancel = context.WithTimeout(ctx, s.timeout)
-		defer cancel()
+	s.router.ServeHTTP(res, req)
+}
+
+func (s *Server) filter() mux.MiddlewareFunc {
+	return func(next http.Handler) http.Handler {
+		next = FilterChain(s.filters...)(next)
+		return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+			ctx, cancel := ic.Merge(req.Context(), s.ctx)
+			defer cancel()
+			if s.timeout > 0 {
+				ctx, cancel = context.WithTimeout(ctx, s.timeout)
+				defer cancel()
+			}
+			tr := &Transport{
+				endpoint:  s.endpoint.String(),
+				path:      req.RequestURI,
+				method:    req.Method,
+				operation: req.RequestURI,
+				metadata:  metadata.New(req.Header),
+			}
+			if r := mux.CurrentRoute(req); r != nil {
+				if path, err := r.GetPathTemplate(); err == nil {
+					tr.operation = path
+				}
+			}
+			ctx = transport.NewServerContext(ctx, tr)
+			next.ServeHTTP(w, req.WithContext(ctx))
+		})
 	}
-	s.router.ServeHTTP(res, req.WithContext(ctx))
 }
 
 // Endpoint return a real address to registry endpoint.
diff --git a/transport/http/transport.go b/transport/http/transport.go
index e2e3f46d..8a9eb9bb 100644
--- a/transport/http/transport.go
+++ b/transport/http/transport.go
@@ -1,6 +1,8 @@
 package http
 
 import (
+	"context"
+
 	"github.com/go-kratos/kratos/v2/metadata"
 	"github.com/go-kratos/kratos/v2/transport"
 )
@@ -11,9 +13,11 @@ var (
 
 // Transport is an HTTP transport.
 type Transport struct {
-	endpoint string
-	method   string
-	metadata metadata.Metadata
+	endpoint  string
+	path      string
+	method    string
+	operation string
+	metadata  metadata.Metadata
 }
 
 // Kind returns the transport kind.
@@ -26,14 +30,14 @@ func (tr *Transport) Endpoint() string {
 	return tr.endpoint
 }
 
-// Method returns the transport method.
-func (tr *Transport) Method() string {
-	return tr.method
+// Operation returns the transport operation.
+func (tr *Transport) Operation() string {
+	return tr.operation
 }
 
-// SetMethod sets the transport method.
-func (tr *Transport) SetMethod(method string) {
-	tr.method = method
+// SetOperation sets the transport operation.
+func (tr *Transport) SetOperation(operation string) {
+	tr.operation = operation
 }
 
 // Metadata returns the transport metadata.
@@ -43,11 +47,27 @@ func (tr *Transport) Metadata() metadata.Metadata {
 
 // WithMetadata with a metadata into transport md.
 func (tr *Transport) WithMetadata(md metadata.Metadata) {
-	if tr.metadata == nil {
-		tr.metadata = md
-		return
-	}
 	for k, v := range md {
 		tr.metadata.Set(k, v)
 	}
 }
+
+// Path returns the Transport path from server context.
+func Path(ctx context.Context) string {
+	if tr, ok := transport.FromServerContext(ctx); ok {
+		if tr, ok := tr.(*Transport); ok {
+			return tr.path
+		}
+	}
+	return ""
+}
+
+// Method returns the Transport method from server context.
+func Method(ctx context.Context) string {
+	if tr, ok := transport.FromServerContext(ctx); ok {
+		if tr, ok := tr.(*Transport); ok {
+			return tr.method
+		}
+	}
+	return ""
+}
diff --git a/transport/transport.go b/transport/transport.go
index 3e2995a9..3d3ac113 100644
--- a/transport/transport.go
+++ b/transport/transport.go
@@ -28,8 +28,8 @@ type Transporter interface {
 	Kind() string
 	Endpoint() string
 
-	Method() string
-	SetMethod(string)
+	Operation() string
+	SetOperation(string)
 
 	Metadata() metadata.Metadata
 	// WithMetadata merge new metadata into transport,
@@ -62,19 +62,19 @@ func FromClientContext(ctx context.Context) (tr Transporter, ok bool) {
 	return
 }
 
-// Method returns the Transport method from server context.
-func Method(ctx context.Context) string {
+// SetOperation set operation into context transport.
+func SetOperation(ctx context.Context, method string) {
 	if tr, ok := FromServerContext(ctx); ok {
-		return tr.Method()
+		tr.SetOperation(method)
 	}
-	return ""
 }
 
-// SetMethod set serviceMethod into context transport.
-func SetMethod(ctx context.Context, method string) {
+// Operation returns the Transport operation from server context.
+func Operation(ctx context.Context) string {
 	if tr, ok := FromServerContext(ctx); ok {
-		tr.SetMethod(method)
+		return tr.Operation()
 	}
+	return ""
 }
 
 // Metadata returns incoming metadata from server transport.
@@ -84,10 +84,3 @@ func Metadata(ctx context.Context) metadata.Metadata {
 	}
 	return metadata.Metadata{}
 }
-
-// SetMetadata sets outgoing metadata into client transport.
-func SetMetadata(ctx context.Context, md metadata.Metadata) {
-	if tr, ok := FromClientContext(ctx); ok {
-		tr.WithMetadata(md)
-	}
-}


Test patch:
diff --git a/encoding/json/json_test.go b/encoding/json/json_test.go
new file mode 100644
index 00000000..06d80ee2
--- /dev/null
+++ b/encoding/json/json_test.go
@@ -0,0 +1,80 @@
+package json
+
+import (
+	"bytes"
+	"strings"
+	"testing"
+)
+
+type testEmbed struct {
+	Level1a int `json:"a"`
+	Level1b int `json:"b"`
+	Level1c int `json:"c"`
+}
+
+type testMessage struct {
+	Field1 string     `json:"a"`
+	Field2 string     `json:"b"`
+	Field3 string     `json:"c"`
+	Embed  *testEmbed `json:"embed,omitempty"`
+}
+
+func TestJSON_Marshal(t *testing.T) {
+	tests := []struct {
+		input  interface{}
+		expect string
+	}{
+		{
+			input:  &testMessage{},
+			expect: `{"a":"","b":"","c":""}`,
+		},
+		{
+			input:  &testMessage{Field1: "a", Field2: "b", Field3: "c"},
+			expect: `{"a":"a","b":"b","c":"c"}`,
+		},
+	}
+	for _, v := range tests {
+		data, err := (codec{}).Marshal(v.input)
+		if err != nil {
+			t.Errorf("marshal(%#v): %s", v.input, err)
+		}
+		if got, want := string(data), v.expect; got != want {
+			if strings.Contains(want, "\n") {
+				t.Errorf("marshal(%#v):\nHAVE:\n%s\nWANT:\n%s", v.input, got, want)
+			} else {
+				t.Errorf("marshal(%#v):\nhave %#q\nwant %#q", v.input, got, want)
+			}
+		}
+	}
+}
+
+func TestJSON_Unmarshal(t *testing.T) {
+	p := &testMessage{}
+	tests := []struct {
+		input  string
+		expect interface{}
+	}{
+		{
+			input:  `{"a":"","b":"","c":""}`,
+			expect: &testMessage{},
+		},
+		{
+			input:  `{"a":"a","b":"b","c":"c"}`,
+			expect: &p,
+		},
+	}
+	for _, v := range tests {
+		want := []byte(v.input)
+		err := (codec{}).Unmarshal(want, &v.expect)
+		if err != nil {
+			t.Errorf("marshal(%#v): %s", v.input, err)
+		}
+		got, err := codec{}.Marshal(v.expect)
+		if err != nil {
+			t.Errorf("marshal(%#v): %s", v.input, err)
+		}
+		if !bytes.Equal(got, want) {
+			t.Errorf("marshal(%#v):\nhave %#q\nwant %#q", v.input, got, want)
+		}
+	}
+}
diff --git a/internal/testproto/echo_service_http.pb.go b/internal/testproto/echo_service_http.pb.go
new file mode 100644
index 00000000..d4162362
--- /dev/null
+++ b/internal/testproto/echo_service_http.pb.go
@@ -0,0 +1,303 @@
+// Code generated by protoc-gen-go-http. DO NOT EDIT.
+// versions:
+// protoc-gen-go-http v2.0.0-rc3
+
+package testproto
+
+import (
+	context "context"
+	middleware "github.com/go-kratos/kratos/v2/middleware"
+	transport "github.com/go-kratos/kratos/v2/transport"
+	http "github.com/go-kratos/kratos/v2/transport/http"
+	binding "github.com/go-kratos/kratos/v2/transport/http/binding"
+)
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the kratos package it is being compiled against.
+var _ = new(context.Context)
+var _ = new(middleware.Middleware)
+var _ = new(transport.Transporter)
+var _ = binding.BindVars
+
+const _ = http.SupportPackageIsVersion1
+
+type EchoServiceHTTPServer interface {
+	Echo(context.Context, *SimpleMessage) (*SimpleMessage, error)
+	EchoBody(context.Context, *SimpleMessage) (*SimpleMessage, error)
+	EchoDelete(context.Context, *SimpleMessage) (*SimpleMessage, error)
+	EchoPatch(context.Context, *DynamicMessageUpdate) (*DynamicMessageUpdate, error)
+	EchoResponseBody(context.Context, *DynamicMessageUpdate) (*DynamicMessageUpdate, error)
+}
+
+func RegisterEchoServiceHTTPServer(s *http.Server, srv EchoServiceHTTPServer) {
+	r := s.Route("/")
+	r.GET("/v1/example/echo/{id}/{num}", _EchoService_Echo0_HTTP_Handler(srv))
+	r.GET("/v1/example/echo/{id}/{num}/{lang}", _EchoService_Echo1_HTTP_Handler(srv))
+	r.GET("/v1/example/echo1/{id}/{line_num}/{status.note}", _EchoService_Echo2_HTTP_Handler(srv))
+	r.GET("/v1/example/echo2/{no.note}", _EchoService_Echo3_HTTP_Handler(srv))
+	r.POST("/v1/example/echo/{id}", _EchoService_Echo4_HTTP_Handler(srv))
+	r.POST("/v1/example/echo_body", _EchoService_EchoBody0_HTTP_Handler(srv))
+	r.POST("/v1/example/echo_response_body", _EchoService_EchoResponseBody0_HTTP_Handler(srv))
+	r.DELETE("/v1/example/echo_delete/{id}/{num}", _EchoService_EchoDelete0_HTTP_Handler(srv))
+	r.PATCH("/v1/example/echo_patch", _EchoService_EchoPatch0_HTTP_Handler(srv))
+}
+
+func _EchoService_Echo0_HTTP_Handler(srv EchoServiceHTTPServer) func(ctx http.Context) error {
+	return func(ctx http.Context) error {
+		var in SimpleMessage
+		if err := ctx.Bind(&in); err != nil {
+			return err
+		}
+		if err := binding.BindVars(ctx.Vars(), &in); err != nil {
+			return err
+		}
+		transport.SetOperation(ctx, "/testproto.EchoService/Echo")
+		h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) {
+			return srv.Echo(ctx, req.(*SimpleMessage))
+		})
+		out, err := h(ctx, &in)
+		if err != nil {
+			return err
+		}
+		reply := out.(*SimpleMessage)
+		return ctx.Result(200, reply)
+	}
+}
+
+func _EchoService_Echo1_HTTP_Handler(srv EchoServiceHTTPServer) func(ctx http.Context) error {
+	return func(ctx http.Context) error {
+		var in SimpleMessage
+		if err := ctx.Bind(&in); err != nil {
+			return err
+		}
+		if err := binding.BindVars(ctx.Vars(), &in); err != nil {
+			return err
+		}
+		transport.SetOperation(ctx, "/testproto.EchoService/Echo")
+		h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) {
+			return srv.Echo(ctx, req.(*SimpleMessage))
+		})
+		out, err := h(ctx, &in)
+		if err != nil {
+			return err
+		}
+		reply := out.(*SimpleMessage)
+		return ctx.Result(200, reply)
+	}
+}
+
+func _EchoService_Echo2_HTTP_Handler(srv EchoServiceHTTPServer) func(ctx http.Context) error {
+	return func(ctx http.Context) error {
+		var in SimpleMessage
+		if err := ctx.Bind(&in); err != nil {
+			return err
+		}
+		if err := binding.BindVars(ctx.Vars(), &in); err != nil {
+			return err
+		}
+		transport.SetOperation(ctx, "/testproto.EchoService/Echo")
+		h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) {
+			return srv.Echo(ctx, req.(*SimpleMessage))
+		})
+		out, err := h(ctx, &in)
+		if err != nil {
+			return err
+		}
+		reply := out.(*SimpleMessage)
+		return ctx.Result(200, reply)
+	}
+}
+
+func _EchoService_Echo3_HTTP_Handler(srv EchoServiceHTTPServer) func(ctx http.Context) error {
+	return func(ctx http.Context) error {
+		var in SimpleMessage
+		if err := ctx.Bind(&in); err != nil {
+			return err
+		}
+		if err := binding.BindVars(ctx.Vars(), &in); err != nil {
+			return err
+		}
+		transport.SetOperation(ctx, "/testproto.EchoService/Echo")
+		h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) {
+			return srv.Echo(ctx, req.(*SimpleMessage))
+		})
+		out, err := h(ctx, &in)
+		if err != nil {
+			return err
+		}
+		reply := out.(*SimpleMessage)
+		return ctx.Result(200, reply)
+	}
+}
+
+func _EchoService_Echo4_HTTP_Handler(srv EchoServiceHTTPServer) func(ctx http.Context) error {
+	return func(ctx http.Context) error {
+		var in SimpleMessage
+		if err := ctx.Bind(&in); err != nil {
+			return err
+		}
+		if err := binding.BindVars(ctx.Vars(), &in); err != nil {
+			return err
+		}
+		transport.SetOperation(ctx, "/testproto.EchoService/Echo")
+		h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) {
+			return srv.Echo(ctx, req.(*SimpleMessage))
+		})
+		out, err := h(ctx, &in)
+		if err != nil {
+			return err
+		}
+		reply := out.(*SimpleMessage)
+		return ctx.Result(200, reply)
+	}
+}
+
+func _EchoService_EchoBody0_HTTP_Handler(srv EchoServiceHTTPServer) func(ctx http.Context) error {
+	return func(ctx http.Context) error {
+		var in SimpleMessage
+		if err := ctx.Bind(&in); err != nil {
+			return err
+		}
+		transport.SetOperation(ctx, "/testproto.EchoService/EchoBody")
+		h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) {
+			return srv.EchoBody(ctx, req.(*SimpleMessage))
+		})
+		out, err := h(ctx, &in)
+		if err != nil {
+			return err
+		}
+		reply := out.(*SimpleMessage)
+		return ctx.Result(200, reply)
+	}
+}
+
+func _EchoService_EchoResponseBody0_HTTP_Handler(srv EchoServiceHTTPServer) func(ctx http.Context) error {
+	return func(ctx http.Context) error {
+		var in DynamicMessageUpdate
+		if err := ctx.Bind(&in); err != nil {
+			return err
+		}
+		transport.SetOperation(ctx, "/testproto.EchoService/EchoResponseBody")
+		h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) {
+			return srv.EchoResponseBody(ctx, req.(*DynamicMessageUpdate))
+		})
+		out, err := h(ctx, &in)
+		if err != nil {
+			return err
+		}
+		reply := out.(*DynamicMessageUpdate)
+		return ctx.Result(200, reply.Body)
+	}
+}
+
+func _EchoService_EchoDelete0_HTTP_Handler(srv EchoServiceHTTPServer) func(ctx http.Context) error {
+	return func(ctx http.Context) error {
+		var in SimpleMessage
+		if err := ctx.Bind(&in); err != nil {
+			return err
+		}
+		if err := binding.BindVars(ctx.Vars(), &in); err != nil {
+			return err
+		}
+		transport.SetOperation(ctx, "/testproto.EchoService/EchoDelete")
+		h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) {
+			return srv.EchoDelete(ctx, req.(*SimpleMessage))
+		})
+		out, err := h(ctx, &in)
+		if err != nil {
+			return err
+		}
+		reply := out.(*SimpleMessage)
+		return ctx.Result(200, reply)
+	}
+}
+
+func _EchoService_EchoPatch0_HTTP_Handler(srv EchoServiceHTTPServer) func(ctx http.Context) error {
+	return func(ctx http.Context) error {
+		var in DynamicMessageUpdate
+		if err := ctx.Bind(&in.Body); err != nil {
+			return err
+		}
+		transport.SetOperation(ctx, "/testproto.EchoService/EchoPatch")
+		h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) {
+			return srv.EchoPatch(ctx, req.(*DynamicMessageUpdate))
+		})
+		out, err := h(ctx, &in)
+		if err != nil {
+			return err
+		}
+		reply := out.(*DynamicMessageUpdate)
+		return ctx.Result(200, reply)
+	}
+}
+
+type EchoServiceHTTPClient interface {
+	Echo(ctx context.Context, req *SimpleMessage, opts ...http.CallOption) (rsp *SimpleMessage, err error)
+	EchoBody(ctx context.Context, req *SimpleMessage, opts ...http.CallOption) (rsp *SimpleMessage, err error)
+	EchoDelete(ctx context.Context, req *SimpleMessage, opts ...http.CallOption) (rsp *SimpleMessage, err error)
+	EchoPatch(ctx context.Context, req *DynamicMessageUpdate, opts ...http.CallOption) (rsp *DynamicMessageUpdate, err error)
+	EchoResponseBody(ctx context.Context, req *DynamicMessageUpdate, opts ...http.CallOption) (rsp *DynamicMessageUpdate, err error)
+}
+
+type EchoServiceHTTPClientImpl struct {
+	cc *http.Client
+}
+
+func NewEchoServiceHTTPClient(client *http.Client) EchoServiceHTTPClient {
+	return &EchoServiceHTTPClientImpl{client}
+}
+
+func (c *EchoServiceHTTPClientImpl) Echo(ctx context.Context, in *SimpleMessage, opts ...http.CallOption) (*SimpleMessage, error) {
+	var out SimpleMessage
+	path := binding.EncodeVars("/v1/example/echo/{id}", in, false)
+	opts = append(opts, http.Operation("/testproto.EchoService/Echo"))
+	err := c.cc.Invoke(ctx, "POST", path, nil, &out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return &out, err
+}
+
+func (c *EchoServiceHTTPClientImpl) EchoBody(ctx context.Context, in *SimpleMessage, opts ...http.CallOption) (*SimpleMessage, error) {
+	var out SimpleMessage
+	path := binding.EncodeVars("/v1/example/echo_body", in, false)
+	opts = append(opts, http.Operation("/testproto.EchoService/EchoBody"))
+	err := c.cc.Invoke(ctx, "POST", path, in, &out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return &out, err
+}
+
+func (c *EchoServiceHTTPClientImpl) EchoDelete(ctx context.Context, in *SimpleMessage, opts ...http.CallOption) (*SimpleMessage, error) {
+	var out SimpleMessage
+	path := binding.EncodeVars("/v1/example/echo_delete/{id}/{num}", in, false)
+	opts = append(opts, http.Operation("/testproto.EchoService/EchoDelete"))
+	err := c.cc.Invoke(ctx, "DELETE", path, nil, &out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return &out, err
+}
+
+func (c *EchoServiceHTTPClientImpl) EchoPatch(ctx context.Context, in *DynamicMessageUpdate, opts ...http.CallOption) (*DynamicMessageUpdate, error) {
+	var out DynamicMessageUpdate
+	path := binding.EncodeVars("/v1/example/echo_patch", in, false)
+	opts = append(opts, http.Operation("/testproto.EchoService/EchoPatch"))
+	err := c.cc.Invoke(ctx, "PATCH", path, in.Body, &out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return &out, err
+}
+
+func (c *EchoServiceHTTPClientImpl) EchoResponseBody(ctx context.Context, in *DynamicMessageUpdate, opts ...http.CallOption) (*DynamicMessageUpdate, error) {
+	var out DynamicMessageUpdate
+	path := binding.EncodeVars("/v1/example/echo_response_body", in, false)
+	opts = append(opts, http.Operation("/testproto.EchoService/EchoResponseBody"))
+	err := c.cc.Invoke(ctx, "POST", path, in, &out.Body, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return &out, err
+}
diff --git a/examples/proto/echo_service_test.go b/internal/testproto/echo_service_test.go
similarity index 62%
rename from examples/proto/echo_service_test.go
rename to internal/testproto/echo_service_test.go
index 8b392551..3621cfc3 100644
--- a/examples/proto/echo_service_test.go
+++ b/internal/testproto/echo_service_test.go
@@ -2,21 +2,32 @@ package testproto
 
 import (
 	context "context"
+	"errors"
 	"fmt"
-	"net"
-	http "net/http"
 	"testing"
 	"time"
 
 	"github.com/go-kratos/kratos/v2/encoding"
-	tr "github.com/go-kratos/kratos/v2/transport/http"
+	"github.com/go-kratos/kratos/v2/metadata"
+	"github.com/go-kratos/kratos/v2/transport"
+	"github.com/go-kratos/kratos/v2/transport/grpc"
+	"github.com/go-kratos/kratos/v2/transport/http"
+
 	_struct "github.com/golang/protobuf/ptypes/struct"
+	grpcmd "google.golang.org/grpc/metadata"
 )
 
+var md = metadata.Metadata{"test_key": "test_value"}
+
 type echoService struct {
+	UnimplementedEchoServiceServer
 }
 
 func (s *echoService) Echo(ctx context.Context, m *SimpleMessage) (*SimpleMessage, error) {
+	md := transport.Metadata(ctx)
+	if v := md.Get("test_key"); v != "test_value" {
+		return nil, errors.New("md not match" + v)
+	}
 	return m, nil
 }
 
@@ -42,7 +53,7 @@ type echoClient struct {
 
 // post: /v1/example/echo/{id}
 func (c *echoClient) Echo(ctx context.Context, in *SimpleMessage) (out *SimpleMessage, err error) {
-	return c.client.Echo(ctx, in)
+	return c.client.Echo(ctx, in, http.Metadata(md))
 }
 
 // post: /v1/example/echo_body
@@ -65,25 +76,47 @@ func (c *echoClient) EchoResponseBody(ctx context.Context, in *DynamicMessageUpd
 	return c.client.EchoResponseBody(ctx, in)
 }
 
-func TestEchoService(t *testing.T) {
-	s := &echoService{}
-	h := NewEchoServiceHandler(s)
-	srv := &http.Server{Addr: ":0", Handler: h}
-	lis, err := net.Listen("tcp", srv.Addr)
+func TestJSON(t *testing.T) {
+	in := &SimpleMessage{Id: "test_id", Num: 100}
+	out := &SimpleMessage{}
+	codec := encoding.GetCodec("json")
+	data, err := codec.Marshal(in)
 	if err != nil {
 		t.Fatal(err)
 	}
-	addr := lis.Addr().(*net.TCPAddr)
-	time.AfterFunc(time.Second, func() {
-		defer srv.Shutdown(context.Background())
-		testEchoClient(t, fmt.Sprintf("127.0.0.1:%d", addr.Port))
-	})
-	if err := srv.Serve(lis); err != nil && err != http.ErrServerClosed {
+	if err := codec.Unmarshal(data, out); err != nil {
+		t.Fatal(err)
+	}
+	// body
+	in2 := &DynamicMessageUpdate{Body: &DynamicMessage{
+		ValueField: &_struct.Value{Kind: &_struct.Value_StringValue{StringValue: "test"}},
+	}}
+	out2 := &DynamicMessageUpdate{}
+	data, err = codec.Marshal(&in2.Body)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if err := codec.Unmarshal(data, &out2.Body); err != nil {
 		t.Fatal(err)
 	}
 }
 
-func testEchoClient(t *testing.T, addr string) {
+func TestEchoHTTPServer(t *testing.T) {
+	echo := &echoService{}
+	ctx := context.Background()
+	srv := http.NewServer(http.Address(":2333"))
+	RegisterEchoServiceHTTPServer(srv, echo)
+	go func() {
+		if err := srv.Start(ctx); err != nil {
+			panic(err)
+		}
+	}()
+	time.Sleep(time.Second)
+	testEchoHTTPClient(t, fmt.Sprintf("127.0.0.1:2333"))
+	srv.Stop(ctx)
+}
+
+func testEchoHTTPClient(t *testing.T, addr string) {
 	var (
 		err error
 		in  = &SimpleMessage{Id: "test_id", Num: 100}
@@ -94,7 +127,7 @@ func testEchoClient(t *testing.T, addr string) {
 			t.Errorf("[%s] expected %v got %v", name, in, out)
 		}
 	}
-	cc, _ := tr.NewClient(context.Background(), tr.WithEndpoint(addr))
+	cc, _ := http.NewClient(context.Background(), http.WithEndpoint(addr))
 
 	cli := &echoClient{client: NewEchoServiceHTTPClient(cc)}
 
@@ -137,15 +170,37 @@ func testEchoClient(t *testing.T, addr string) {
 	fmt.Println("echo test success!")
 }
 
-func TestJSON(t *testing.T) {
-	in := &SimpleMessage{Id: "test_id", Num: 100}
-	out := &SimpleMessage{}
-	codec := encoding.GetCodec("json")
-	data, err := codec.Marshal(in)
+func TestEchoGRPCServer(t *testing.T) {
+	echo := &echoService{}
+	ctx := context.Background()
+	srv := grpc.NewServer(grpc.Address(":2233"))
+	RegisterEchoServiceServer(srv, echo)
+	go func() {
+		if err := srv.Start(ctx); err != nil {
+			panic(err)
+		}
+	}()
+	time.Sleep(time.Second)
+	testEchoGRPCClient(t, fmt.Sprintf("127.0.0.1:2233"))
+	srv.Stop(ctx)
+}
+
+func testEchoGRPCClient(t *testing.T, addr string) {
+	ctx := context.Background()
+	cc, err := grpc.DialInsecure(ctx, grpc.WithEndpoint(addr))
 	if err != nil {
 		t.Fatal(err)
 	}
-	if err := codec.Unmarshal(data, out); err != nil {
+	var (
+		in  = &SimpleMessage{Id: "test_id", Num: 100}
+		out = &SimpleMessage{}
+	)
+	client := NewEchoServiceClient(cc)
+	ctx = grpcmd.NewOutgoingContext(ctx, grpcmd.New(md))
+	if out, err = client.Echo(ctx, in); err != nil {
 		t.Fatal(err)
 	}
+	if in.Id != out.Id || in.Num != out.Num {
+		t.Errorf("expected %v got %v", in, out)
+	}
 }
diff --git a/internal/testproto/generate.go b/internal/testproto/generate.go
new file mode 100644
index 00000000..c0c47a75
--- /dev/null
+++ b/internal/testproto/generate.go
@@ -0,0 +1,3 @@
+package testproto
+
+//go:generate protoc --proto_path=. --proto_path=../../third_party --go_out=paths=source_relative:. --go-grpc_out=paths=source_relative:. --go-http_out=paths=source_relative:. echo_service.proto stream.proto
diff --git a/internal/testproto/stream.pb.go b/internal/testproto/stream.pb.go
new file mode 100644
index 00000000..7036cf0b
--- /dev/null
+++ b/internal/testproto/stream.pb.go
@@ -0,0 +1,86 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// 	protoc-gen-go v1.26.0
+// 	protoc        v3.15.7
+// source: stream.proto
+
+package testproto
+
+import (
+	_ "google.golang.org/genproto/googleapis/api/annotations"
+	httpbody "google.golang.org/genproto/googleapis/api/httpbody"
+	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+	emptypb "google.golang.org/protobuf/types/known/emptypb"
+	reflect "reflect"
+)
+
+const (
+	// Verify that this generated code is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+	// Verify that runtime/protoimpl is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+var File_stream_proto protoreflect.FileDescriptor
+
+var file_stream_proto_rawDesc = []byte{
+	0x0a, 0x0c, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x09,
+	0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
+	0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e,
+	0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x19, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f,
+	0x61, 0x70, 0x69, 0x2f, 0x68, 0x74, 0x74, 0x70, 0x62, 0x6f, 0x64, 0x79, 0x2e, 0x70, 0x72, 0x6f,
+	0x74, 0x6f, 0x1a, 0x1b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+	0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32,
+	0x69, 0x0a, 0x0d, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,
+	0x12, 0x58, 0x0a, 0x08, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x16, 0x2e, 0x67,
+	0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45,
+	0x6d, 0x70, 0x74, 0x79, 0x1a, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x61, 0x70,
+	0x69, 0x2e, 0x48, 0x74, 0x74, 0x70, 0x42, 0x6f, 0x64, 0x79, 0x22, 0x1c, 0x82, 0xd3, 0xe4, 0x93,
+	0x02, 0x16, 0x12, 0x14, 0x2f, 0x76, 0x31, 0x2f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2f,
+	0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x30, 0x01, 0x42, 0x51, 0x5a, 0x4f, 0x67, 0x69,
+	0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x2d, 0x6b, 0x72, 0x61, 0x74,
+	0x6f, 0x73, 0x2f, 0x6b, 0x72, 0x61, 0x74, 0x6f, 0x73, 0x2f, 0x63, 0x6d, 0x64, 0x2f, 0x70, 0x72,
+	0x6f, 0x74, 0x6f, 0x63, 0x2d, 0x67, 0x65, 0x6e, 0x2d, 0x67, 0x6f, 0x2d, 0x68, 0x74, 0x74, 0x70,
+	0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72,
+	0x6f, 0x74, 0x6f, 0x3b, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70,
+	0x72, 0x6f, 0x74, 0x6f, 0x33,
+}
+
+var file_stream_proto_goTypes = []interface{}{
+	(*emptypb.Empty)(nil),     // 0: google.protobuf.Empty
+	(*httpbody.HttpBody)(nil), // 1: google.api.HttpBody
+}
+var file_stream_proto_depIdxs = []int32{
+	0, // 0: testproto.StreamService.Download:input_type -> google.protobuf.Empty
+	1, // 1: testproto.StreamService.Download:output_type -> google.api.HttpBody
+	1, // [1:2] is the sub-list for method output_type
+	0, // [0:1] is the sub-list for method input_type
+	0, // [0:0] is the sub-list for extension type_name
+	0, // [0:0] is the sub-list for extension extendee
+	0, // [0:0] is the sub-list for field type_name
+}
+
+func init() { file_stream_proto_init() }
+func file_stream_proto_init() {
+	if File_stream_proto != nil {
+		return
+	}
+	type x struct{}
+	out := protoimpl.TypeBuilder{
+		File: protoimpl.DescBuilder{
+			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+			RawDescriptor: file_stream_proto_rawDesc,
+			NumEnums:      0,
+			NumMessages:   0,
+			NumExtensions: 0,
+			NumServices:   1,
+		},
+		GoTypes:           file_stream_proto_goTypes,
+		DependencyIndexes: file_stream_proto_depIdxs,
+	}.Build()
+	File_stream_proto = out.File
+	file_stream_proto_rawDesc = nil
+	file_stream_proto_goTypes = nil
+	file_stream_proto_depIdxs = nil
+}
diff --git a/middleware/logging/logging_test.go b/middleware/logging/logging_test.go
index 335677e8..104c6570 100644
--- a/middleware/logging/logging_test.go
+++ b/middleware/logging/logging_test.go
@@ -17,9 +17,9 @@ var (
 )
 
 type Transport struct {
-	kind     string
-	endpoint string
-	method   string
+	kind      string
+	endpoint  string
+	operation string
 }
 
 func (tr *Transport) Kind() string {
@@ -30,12 +30,12 @@ func (tr *Transport) Endpoint() string {
 	return tr.endpoint
 }
 
-func (tr *Transport) Method() string {
-	return tr.method
+func (tr *Transport) Operation() string {
+	return tr.operation
 }
 
-func (tr *Transport) SetMethod(method string) {
-	tr.method = method
+func (tr *Transport) SetOperation(operation string) {
+	tr.operation = operation
 }
 
 func (tr *Transport) Metadata() metadata.Metadata {
@@ -61,21 +61,21 @@ func TestHTTP(t *testing.T) {
 			Server,
 			err,
 			func() context.Context {
-				return transport.NewServerContext(context.Background(), &Transport{kind: "http", endpoint: "endpoint", method: "/package.service/method"})
+				return transport.NewServerContext(context.Background(), &Transport{kind: "http", endpoint: "endpoint", operation: "/package.service/method"})
 			}(),
 		},
 		{"http-server@succ",
 			Server,
 			nil,
 			func() context.Context {
-				return transport.NewServerContext(context.Background(), &Transport{kind: "http", endpoint: "endpoint", method: "/package.service/method"})
+				return transport.NewServerContext(context.Background(), &Transport{kind: "http", endpoint: "endpoint", operation: "/package.service/method"})
 			}(),
 		},
 		{"http-client@succ",
 			Client,
 			nil,
 			func() context.Context {
-				return transport.NewClientContext(context.Background(), &Transport{kind: "http", endpoint: "endpoint", method: "/package.service/method"})
+				return transport.NewClientContext(context.Background(), &Transport{kind: "http", endpoint: "endpoint", operation: "/package.service/method"})
 
 			}(),
 		},
@@ -83,7 +83,7 @@ func TestHTTP(t *testing.T) {
 			Client,
 			err,
 			func() context.Context {
-				return transport.NewClientContext(context.Background(), &Transport{kind: "http", endpoint: "endpoint", method: "/package.service/method"})
+				return transport.NewClientContext(context.Background(), &Transport{kind: "http", endpoint: "endpoint", operation: "/package.service/method"})
 			}(),
 		},
 	}
diff --git a/transport/http/binding/proto_test.go b/transport/http/binding/proto_test.go
index 6a828b23..6018dda7 100644
--- a/transport/http/binding/proto_test.go
+++ b/transport/http/binding/proto_test.go
@@ -6,25 +6,25 @@ import (
 )
 
 func TestProtoPath(t *testing.T) {
-	url := EncodePath("POST", "http://helloworld.Greeter/helloworld/{name}/sub/{sub.name}", &HelloRequest{Name: "test", Sub: &Sub{Name: "2233!!!"}})
+	url := EncodeVars("http://helloworld.Greeter/helloworld/{name}/sub/{sub.name}", &HelloRequest{Name: "test", Sub: &Sub{Name: "2233!!!"}}, false)
 	fmt.Println(url)
 	if url != `http://helloworld.Greeter/helloworld/test/sub/2233!!!` {
 		t.Fatalf("proto path not expected!actual: %s ", url)
 	}
 
-	url = EncodePath("POST", "http://helloworld.Greeter/helloworld/sub", &HelloRequest{Name: "test", Sub: &Sub{Name: "2233!!!"}})
+	url = EncodeVars("http://helloworld.Greeter/helloworld/sub", &HelloRequest{Name: "test", Sub: &Sub{Name: "2233!!!"}}, false)
 	fmt.Println(url)
 	if url != `http://helloworld.Greeter/helloworld/sub` {
 		t.Fatalf("proto path not expected!actual: %s ", url)
 	}
 
-	url = EncodePath("POST", "http://helloworld.Greeter/helloworld/{name}/sub/{sub.name}", &HelloRequest{Name: "test"})
+	url = EncodeVars("http://helloworld.Greeter/helloworld/{name}/sub/{sub.name}", &HelloRequest{Name: "test"}, false)
 	fmt.Println(url)
 	if url != `http://helloworld.Greeter/helloworld/test/sub/` {
 		t.Fatalf("proto path not expected!actual: %s ", url)
 	}
 
-	url = EncodePath("POST", "http://helloworld.Greeter/helloworld/{name}/sub/{sub.name33}", &HelloRequest{Name: "test"})
+	url = EncodeVars("http://helloworld.Greeter/helloworld/{name}/sub/{sub.name33}", &HelloRequest{Name: "test"}, false)
 	fmt.Println(url)
 	if url != `http://helloworld.Greeter/helloworld/test/sub/{sub.name33}` {
 		t.Fatalf("proto path not expected!actual: %s ", url)
diff --git a/transport/http/handle_test.go b/transport/http/handle_test.go
deleted file mode 100644
index 0165c97d..00000000
--- a/transport/http/handle_test.go
+++ /dev/null
@@ -1,24 +0,0 @@
-package http
-
-import (
-	"context"
-	"testing"
-)
-
-type HelloRequest struct {
-	Name string `json:"name"`
-}
-type HelloReply struct {
-	Message string `json:"message"`
-}
-type GreeterService struct {
-}
-
-func (s *GreeterService) SayHello(ctx context.Context, req *HelloRequest) (*HelloReply, error) {
-	return &HelloReply{Message: "hello " + req.Name}, nil
-}
-
-func TestHandler(t *testing.T) {
-	s := &GreeterService{}
-	_ = NewHandler(s.SayHello)
-}
diff --git a/transport/http/route_test.go b/transport/http/route_test.go
new file mode 100644
index 00000000..a32b28a6
--- /dev/null
+++ b/transport/http/route_test.go
@@ -0,0 +1,135 @@
+package http
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"log"
+	"net/http"
+	"strings"
+	"testing"
+	"time"
+
+	"github.com/go-kratos/kratos/v2/internal/host"
+)
+
+type User struct {
+	Name string `json:"name"`
+}
+
+func authFilter(next http.Handler) http.Handler {
+	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		// Do stuff here
+		log.Println("auth:", r.Method, r.RequestURI)
+		// Call the next handler, which can be another middleware in the chain, or the final handler.
+		next.ServeHTTP(w, r)
+	})
+}
+func loggingFilter(next http.Handler) http.Handler {
+	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		// Do stuff here
+		log.Println("logging:", r.Method, r.RequestURI)
+		// Call the next handler, which can be another middleware in the chain, or the final handler.
+		next.ServeHTTP(w, r)
+	})
+}
+
+func TestRoute(t *testing.T) {
+	ctx := context.Background()
+	srv := NewServer(
+		Filter(loggingFilter),
+	)
+	route := srv.Route("/v1")
+	route.GET("/users/{name}", func(ctx Context) error {
+		u := new(User)
+		u.Name = ctx.Vars().Get("name")
+		return ctx.Result(200, u)
+	}, authFilter)
+	route.POST("/users", func(ctx Context) error {
+		u := new(User)
+		if err := ctx.Bind(u); err != nil {
+			return err
+		}
+		return ctx.Result(201, u)
+	})
+	route.PUT("/users", func(ctx Context) error {
+		u := new(User)
+		if err := ctx.Bind(u); err != nil {
+			return err
+		}
+		h := ctx.Middleware(func(ctx context.Context, in interface{}) (interface{}, error) {
+			return u, nil
+		})
+		return ctx.Returns(h(ctx, u))
+	})
+
+	if e, err := srv.Endpoint(); err != nil || e == nil {
+		t.Fatal(e, err)
+	}
+	go func() {
+		if err := srv.Start(ctx); err != nil {
+			panic(err)
+		}
+	}()
+	time.Sleep(time.Second)
+	testRoute(t, srv)
+	srv.Stop(ctx)
+}
+
+func testRoute(t *testing.T, srv *Server) {
+	port, ok := host.Port(srv.lis)
+	if !ok {
+		t.Fatalf("extract port error: %v", srv.lis)
+	}
+	base := fmt.Sprintf("http://127.0.0.1:%d/v1", port)
+	// GET
+	resp, err := http.Get(base + "/users/foo")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer resp.Body.Close()
+	if resp.StatusCode != 200 {
+		t.Fatalf("code: %d", resp.StatusCode)
+	}
+	u := new(User)
+	if err := json.NewDecoder(resp.Body).Decode(u); err != nil {
+		t.Fatal(err)
+	}
+	if u.Name != "foo" {
+		t.Fatalf("got %s want foo", u.Name)
+	}
+	// POST
+	resp, err = http.Post(base+"/users", "application/json", strings.NewReader(`{"name":"bar"}`))
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer resp.Body.Close()
+	if resp.StatusCode != 201 {
+		t.Fatalf("code: %d", resp.StatusCode)
+	}
+	u = new(User)
+	if err = json.NewDecoder(resp.Body).Decode(u); err != nil {
+		t.Fatal(err)
+	}
+	if u.Name != "bar" {
+		t.Fatalf("got %s want bar", u.Name)
+	}
+	// PUT
+	req, _ := http.NewRequest("PUT", base+"/users", strings.NewReader(`{"name":"bar"}`))
+	req.Header.Set("Content-Type", "application/json")
+	resp, err = http.DefaultClient.Do(req)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer resp.Body.Close()
+	if resp.StatusCode != 200 {
+		t.Fatalf("code: %d", resp.StatusCode)
+	}
+	u = new(User)
+	if err = json.NewDecoder(resp.Body).Decode(u); err != nil {
+		t.Fatal(err)
+	}
+	if u.Name != "bar" {
+		t.Fatalf("got %s want bar", u.Name)
+	}
+}