Repository: go-kratos/kratos
Pull request #728 (2021-02-27 05:30:54)

set transport.kind to be strong type
There are many reasons why strong type should be used here, but only one reason that is closest to the user is described here:

The user obtains the protocol information through `transport.FromContext`, but the definition of `Transport` is as follows:
```
type Transport struct {
	Kind string
}
```

If users don’t look at the source code, they don’t know how kratos assigns a value to `Kind`. It might be：
```
grpc, gRPC, GRPC, http, HTTP
```

This makes the `Kind` field difficult to use.

Signed-off-by: storyicon <storyicon@foxmail.com>


Patch:
diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml
index 6f6cfd50..f7ff358f 100644
--- a/.github/workflows/go.yml
+++ b/.github/workflows/go.yml
@@ -23,3 +23,21 @@ jobs:
 
     - name: Test
       run: go test -v ./...
+
+    - name: Kratos
+      run: |
+        cd cmd/kratos
+        go build ./...
+        go test ./...
+
+    - name: HTTP
+      run: |
+        cd cmd/protoc-gen-go-http
+        go build ./...
+        go test ./...
+
+    - name: Errors
+      run: |
+        cd cmd/protoc-gen-go-errors
+        go build ./...
+        go test ./...
diff --git a/README.md b/README.md
index 83be5251..c76c2154 100644
--- a/README.md
+++ b/README.md
@@ -17,30 +17,30 @@ Kratos is a microservice-oriented governance framework implements by golang, whi
 
 ## Goals
 
-Kratos boosts your productivity. With the integration of excellent resources and further support, you can get rid of most issues you might encounter in the field of distributed systems and software engineering, so that you can focus on business delivery. For each developer, Kratos is an ideal one for learning as well. You can navigate the diverse knowledge of microservice and accumulate engineering experience.
-
+Kratos boosts your productivity. With the integration of excellent resources and further support, programmers can get rid of most issues might encounter in the field of distributed systems and software engineering such that they are allowed to focus on the release of businesses only. Additionally, for each programmer, Kratos is also an ideal one learning warehouse for many aspects of microservices to enrich their experiences and skills.
 ### Principles
 
-* **Simple**: Appropriate design, plain and simple code.
+* **Simple**: Appropriate design, plain and easy code.
 * **General**: Cover the various utilities for business development.
-* **Reliable**: Higher testability and greater test coverage of base libs validated in the production environment.
-* **Robust**: Base libs are designed reasonably to reduce misuse.
-* **High-performance**: We give you optimal performance without using the hack-way approach like adding the *unsafe* package and guarantee compatibility and stability at the same time.
-* **Expandable**: Appropriate API design, you can expand utilities such as base libs to meet your further requirements.
+* **Highly efficient**: Speeding up the efficiency of businesses upgrading.
+* **Stable**: The base libs validated in the production environment which have the characters of the high testability, high coverage as well as high security and reliability.
+* **Robust**: Eliminating misusing through high quality of the base libs.
+* **High-performance**: Optimal performance excluding the optimization of hacking in case of *unsafe*. 
+* **Expandability**: Properly designed interfaces, you can expand utilities such as base libs to meet your further requirements.
 * **Fault-tolerance**: Designed against failure, enhance the understanding and exercising of SRE within Kratos to achieve more robustness.
-* **Toolchain**: Includes an extensive toolchain, such as cache code generation, lint tools, etc.
+* **Toolchain**: Includes an extensive toolchain, such as the code generation of cache, the lint tool, and so forth.
 
 ## Features
-* APIs: HTTP/gRPC based transport and Protobuf defined communication protocol.
-* Errors: We use ProtoBuf Enum to define error code and generate error handle code.
-* Metadata: Normalize the service metadata transmission by excellent middleware.
-* Config:  Multi-data source Support, well arranged, dynamic configuration via *Atomic* package.
-* Logger: Standard log API, easily integrate with third-party log lib, *Fluentd* logs collection.
-* Metrics: *Prometheus* integration by default. Furthermore, with the unified Metrics interface, you can implement your metrics system more flexible
-* Tracing: Complete micro-service link tracing followed by *OpenTracing* specification.
-* Encoding: Support *Accept* and *Content-Type* for auto content encoding.
-* Transport: Common HTTP/ GRPC transport layer offers you powerful Middleware support.
- * Registry: One pluggable API for the different registry.
+* APIs: The communication protocol is based on the HTTP/gRPC through the definition of Protobuf.
+* Errors: Both the definitions of error code and the handle interfaces of code generation for tools are defined by the Enum of the Protobuf.
+* Metadata: In the protocol of HTTP/gRPC, the transmission of service atomic information are formalized by the Middleware.
+* Config: Multiple data sources are supported for configurations and integrations such that dynamic configurations are offered through the manner of *Atomic* operations.
+* Logger: The standard log interfaces ease the integration of the third-party log libs and logs are collected through the *Fluentd*.
+* Metrics: *Prometheus* integrated by default. Furthermore, with the uniform metric interfaces, you can implement your own metric system more flexible.
+* Tracing: The OpenTracing is conformed to achieve the tracing of microservices chains.
+* Encoding: The selection of the content encoding is automatically supported by Accept and Content-Type.
+* Transport: The uniform plugins for Middleware are supported by HTTP/gRPC.
+ * Registry: The interfaces of the centralized registry is able to be connected with various other centralized registries through plug-ins.
 
 ## Getting Started
 ### Required
diff --git a/ROADMAP.md b/ROADMAP.md
new file mode 100644
index 00000000..b3095104
--- /dev/null
+++ b/ROADMAP.md
@@ -0,0 +1,60 @@
+# Kratos
+
+This document defines the roadmap for Kratos development.
+
+## Features
+- [x] Config
+    - [x] Local Files
+    - [x] K8s ConfigMap
+    - [ ] Consul
+    - [ ] Etcd
+    - [ ] Nacos
+- [ ] Registry
+    - [ ] Consul
+    - [ ] Etcd
+    - [ ] K8s
+    - [ ] Nacos
+- [x] Encoding
+    - [x] JSON
+    - [x] Protobuf
+- [x] Transport
+    - [x] HTTP
+    - [x] gRPC
+- [x] Middleware
+    - [x] Logging
+    - [x] metrics
+    - [x] recovery
+    - [x] gRPC status
+    - [x] transport tracing
+    - [x] validator
+    - [ ] authentication
+    - [ ] ratelimit
+    - [ ] circuitbreaker
+- [x] Metrics
+    - [x] Prometheus
+    - [x] DataDog
+- [x] Tracing
+    - [x] HTTP
+        - [ ] TLS
+        - [x] Client
+        - [x] Service Registrar
+        - [ ] javascript/typescript clients
+    - [x] gRPC
+        - [ ] TLS
+        - [x] Uarry Handler
+        - [ ] Streaming Handler
+- [ ] Cache
+    - [ ] go-redis
+- [ ] Queue
+    - [ ] Broker API
+    - [ ] Kafka
+    - [ ] Nats
+- [ ] Database
+    - [ ] Ent
+    - [ ] Gorm
+
+## Tools
+- [x] Kratos
+- [x] HTTP Generator
+    - [ ] API YAML
+- [x] Errors Generator
diff --git a/app.go b/app.go
index 509a2e51..3f6cd095 100644
--- a/app.go
+++ b/app.go
@@ -58,8 +58,8 @@ func (a *App) Server() []transport.Server {
 }
 
 // Registry returns registry.
-func (a *App) Registry() registry.Registry {
-	return a.opts.registry
+func (a *App) Registry() registry.Registrar {
+	return a.opts.registrar
 }
 
 // Run executes all OnStart hooks registered with the application's Lifecycle.
@@ -80,8 +80,8 @@ func (a *App) Run() error {
 			return srv.Start()
 		})
 	}
-	if a.opts.registry != nil {
-		if err := a.opts.registry.Register(a.opts.ctx, a.instance); err != nil {
+	if a.opts.registrar != nil {
+		if err := a.opts.registrar.Register(a.opts.ctx, a.instance); err != nil {
 			return err
 		}
 	}
@@ -105,8 +105,8 @@ func (a *App) Run() error {
 
 // Stop gracefully stops the application.
 func (a *App) Stop() error {
-	if a.opts.registry != nil {
-		if err := a.opts.registry.Deregister(a.opts.ctx, a.instance); err != nil {
+	if a.opts.registrar != nil {
+		if err := a.opts.registrar.Deregister(a.opts.ctx, a.instance); err != nil {
 			return err
 		}
 	}
diff --git a/cmd/kratos/internal/base/get.go b/cmd/kratos/internal/base/get.go
index 00c58c1e..afed66aa 100644
--- a/cmd/kratos/internal/base/get.go
+++ b/cmd/kratos/internal/base/get.go
@@ -1,6 +1,7 @@
 package base
 
 import (
+	"fmt"
 	"os"
 	"os/exec"
 )
@@ -8,6 +9,7 @@ import (
 // GoGet go get path.
 func GoGet(path ...string) error {
 	for _, p := range path {
+		fmt.Printf("go get -u %s\n", p)
 		cmd := exec.Command("go", "get", "-u", p)
 		cmd.Stdout = os.Stdout
 		cmd.Stderr = os.Stderr
diff --git a/cmd/kratos/internal/base/mod.go b/cmd/kratos/internal/base/mod.go
index fd0f6852..178a3f6d 100644
--- a/cmd/kratos/internal/base/mod.go
+++ b/cmd/kratos/internal/base/mod.go
@@ -4,7 +4,6 @@ import (
 	"bufio"
 	"bytes"
 	"io/ioutil"
-	"os"
 	"os/exec"
 	"path/filepath"
 	"strings"
@@ -46,10 +45,17 @@ func ModuleVersion(path string) (string, error) {
 
 // KratosMod returns kratos mod.
 func KratosMod() string {
-	gopath := os.Getenv("GOPATH")
+	// go 1.15+ read from env GOMODCACHE
+	cacheOut, _ := exec.Command("go", "env", "GOMODCACHE").Output()
+	cachePath := strings.Trim(string(cacheOut), "\n")
+	pathOut, _ := exec.Command("go", "env", "GOPATH").Output()
+	gopath := strings.Trim(string(pathOut), "\n")
+	if cachePath == "" {
+		cachePath = filepath.Join(gopath, "pkg", "mod")
+	}
 	if path, err := ModuleVersion("github.com/go-kratos/kratos/v2"); err == nil {
 		// $GOPATH/pkg/mod/github.com/go-kratos/kratos@v2
-		return filepath.Join(gopath, "pkg", "mod", path)
+		return filepath.Join(cachePath, path)
 	}
 	// $GOPATH/src/github.com/go-kratos/kratos
 	return filepath.Join(gopath, "src", "github.com", "go-kratos", "kratos")
diff --git a/cmd/kratos/internal/proto/client/client.go b/cmd/kratos/internal/proto/client/client.go
index 1bee3508..41938a1d 100644
--- a/cmd/kratos/internal/proto/client/client.go
+++ b/cmd/kratos/internal/proto/client/client.go
@@ -30,10 +30,14 @@ func run(cmd *cobra.Command, args []string) {
 		err   error
 		proto = strings.TrimSpace(args[0])
 	)
-	if _, err = exec.LookPath("protoc-gen-go-http"); err != nil {
+	if err = look("protoc-gen-go", "protoc-gen-go-grpc", "protoc-gen-go-http", "protoc-gen-go-errors"); err != nil {
 		// update the kratos plugins
-		if err := exec.Command("kratos", "upgrade").Run(); err != nil {
+		cmd := exec.Command("kratos", "upgrade")
+		cmd.Stdout = os.Stdout
+		cmd.Stderr = os.Stderr
+		if cmd.Run(); err != nil {
 			fmt.Println(err)
+			return
 		}
 	}
 	if strings.HasSuffix(proto, ".proto") {
@@ -46,6 +50,15 @@ func run(cmd *cobra.Command, args []string) {
 	}
 }
 
+func look(name ...string) error {
+	for _, n := range name {
+		if _, err := exec.LookPath(n); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
 func walk(dir string) error {
 	if dir == "" {
 		dir = "."
diff --git a/cmd/kratos/main.go b/cmd/kratos/main.go
index 19150b81..d0c9506c 100644
--- a/cmd/kratos/main.go
+++ b/cmd/kratos/main.go
@@ -10,14 +10,13 @@ import (
 )
 
 var (
-	// Version is the version of the compiled software.
-	Version string = "v2.0.0-alpha4"
+	version string = "v2.0.0-alpha5"
 
 	rootCmd = &cobra.Command{
 		Use:     "kratos",
 		Short:   "Kratos: An elegant toolkit for Go microservices.",
 		Long:    `Kratos: An elegant toolkit for Go microservices.`,
-		Version: Version,
+		Version: version,
 	}
 )
 
diff --git a/cmd/protoc-gen-go-errors/go.mod b/cmd/protoc-gen-go-errors/go.mod
index e1efebbe..8dfbaf14 100644
--- a/cmd/protoc-gen-go-errors/go.mod
+++ b/cmd/protoc-gen-go-errors/go.mod
@@ -3,6 +3,9 @@ module github.com/go-kratos/kratos/cmd/protoc-gen-go-errors/v2
 go 1.15
 
 require (
-	github.com/go-kratos/kratos/v2 v2.0.0-20210217083752-d86d233d93ce
+	github.com/go-kratos/kratos/v2 v2.0.0-alpha5
+	github.com/google/uuid v1.2.0 // indirect
+	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
+	google.golang.org/grpc v1.36.0 // indirect
 	google.golang.org/protobuf v1.25.0
 )
diff --git a/cmd/protoc-gen-go-errors/go.sum b/cmd/protoc-gen-go-errors/go.sum
index a391308f..4a2185cd 100644
--- a/cmd/protoc-gen-go-errors/go.sum
+++ b/cmd/protoc-gen-go-errors/go.sum
@@ -11,6 +11,8 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7
 github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
 github.com/go-kratos/kratos/v2 v2.0.0-20210217083752-d86d233d93ce h1:LfOsLN9s8tAxR8xIZGWQvEVWxHfipTnBSE0dvG4h3k8=
 github.com/go-kratos/kratos/v2 v2.0.0-20210217083752-d86d233d93ce/go.mod h1:oLvFyDBJkkWN8TPqb+NmpvRrSy9uM/K+XQubVRc11a8=
+github.com/go-kratos/kratos/v2 v2.0.0-alpha4 h1:MKkkSZigSMg7Kx8HzrobZ93zlgmi0tAKWM9bMf6YTpU=
+github.com/go-kratos/kratos/v2 v2.0.0-alpha4/go.mod h1:oLvFyDBJkkWN8TPqb+NmpvRrSy9uM/K+XQubVRc11a8=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
 github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
 github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -32,6 +34,8 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
 github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w=
 github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
+github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
 github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
 github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
@@ -53,6 +57,8 @@ golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAG
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
+golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -74,6 +80,8 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac
 google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
 google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
 google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
+google.golang.org/grpc v1.36.0 h1:o1bcQ6imQMIOpdrO3SWf2z5RV72WbDwdXuK0MDlc8As=
+google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
 google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
 google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
diff --git a/cmd/protoc-gen-go-errors/main.go b/cmd/protoc-gen-go-errors/main.go
index e590c0bc..12a8b192 100644
--- a/cmd/protoc-gen-go-errors/main.go
+++ b/cmd/protoc-gen-go-errors/main.go
@@ -8,7 +8,7 @@ import (
 	"google.golang.org/protobuf/types/pluginpb"
 )
 
-const version = "0.0.1"
+const version = "v2.0.0-alpha5"
 
 func main() {
 	showVersion := flag.Bool("version", false, "print the version and exit")
diff --git a/cmd/protoc-gen-go-http/go.mod b/cmd/protoc-gen-go-http/go.mod
index 3f2d1a90..89b32274 100644
--- a/cmd/protoc-gen-go-http/go.mod
+++ b/cmd/protoc-gen-go-http/go.mod
@@ -3,9 +3,10 @@ module github.com/go-kratos/kratos/cmd/protoc-gen-go-http/v2
 go 1.15
 
 require (
-	github.com/go-kratos/kratos/v2 v2.0.0-20210218084408-cf599c68a65f
+	github.com/go-kratos/kratos/v2 v2.0.0-20210305104106-278210c4755a
 	github.com/golang/protobuf v1.4.3
+	github.com/gorilla/mux v1.8.0
 	google.golang.org/genproto v0.0.0-20210202153253-cf70463f6119
-	google.golang.org/grpc v1.35.0
+	google.golang.org/grpc v1.36.0
 	google.golang.org/protobuf v1.25.0
 )
diff --git a/cmd/protoc-gen-go-http/go.sum b/cmd/protoc-gen-go-http/go.sum
index 9eecccc2..5c131b17 100644
--- a/cmd/protoc-gen-go-http/go.sum
+++ b/cmd/protoc-gen-go-http/go.sum
@@ -9,10 +9,8 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m
 github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
 github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
-github.com/go-kratos/kratos/v2 v2.0.0-20210217083752-d86d233d93ce h1:LfOsLN9s8tAxR8xIZGWQvEVWxHfipTnBSE0dvG4h3k8=
-github.com/go-kratos/kratos/v2 v2.0.0-20210217083752-d86d233d93ce/go.mod h1:oLvFyDBJkkWN8TPqb+NmpvRrSy9uM/K+XQubVRc11a8=
-github.com/go-kratos/kratos/v2 v2.0.0-20210218084408-cf599c68a65f h1:ocHPvNS53zBT6NiGmgOWV2SuAmcNyHbJZk1t2cqEjIU=
-github.com/go-kratos/kratos/v2 v2.0.0-20210218084408-cf599c68a65f/go.mod h1:oLvFyDBJkkWN8TPqb+NmpvRrSy9uM/K+XQubVRc11a8=
+github.com/go-kratos/kratos/v2 v2.0.0-alpha5 h1:lewh/J44ua+EFTTdjQ9F6+yVGzV8GBFi177aPHZyJCw=
+github.com/go-kratos/kratos/v2 v2.0.0-alpha5/go.mod h1:oLvFyDBJkkWN8TPqb+NmpvRrSy9uM/K+XQubVRc11a8=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
 github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
 github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -80,8 +78,9 @@ google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZi
 google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
 google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
 google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
-google.golang.org/grpc v1.35.0 h1:TwIQcH3es+MojMVojxxfQ3l3OF2KzlRxML2xZq0kRo8=
 google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
+google.golang.org/grpc v1.36.0 h1:o1bcQ6imQMIOpdrO3SWf2z5RV72WbDwdXuK0MDlc8As=
+google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
 google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
 google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
diff --git a/cmd/protoc-gen-go-http/http.go b/cmd/protoc-gen-go-http/http.go
index 22a4e69f..97147f7d 100644
--- a/cmd/protoc-gen-go-http/http.go
+++ b/cmd/protoc-gen-go-http/http.go
@@ -13,7 +13,9 @@ import (
 const (
 	contextPackage   = protogen.GoImportPath("context")
 	httpPackage      = protogen.GoImportPath("net/http")
+	muxPackage       = protogen.GoImportPath("github.com/gorilla/mux")
 	transportPackage = protogen.GoImportPath("github.com/go-kratos/kratos/v2/transport/http")
+	bindingPackage   = protogen.GoImportPath("github.com/go-kratos/kratos/v2/transport/http/binding")
 )
 
 var methodSets = make(map[string]int)
@@ -40,7 +42,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("// ", contextPackage.Ident(""), "/", httpPackage.Ident(""))
+	g.P("var _ = new(", httpPackage.Ident("Request"), ")")
+	g.P("var _ = new(", contextPackage.Ident("Context"), ")")
+	g.P("var _ = ", bindingPackage.Ident("MapProto"))
+	g.P("var _ = ", muxPackage.Ident("NewRouter"))
 	g.P("const _ = ", transportPackage.Ident("SupportPackageIsVersion1"))
 	g.P()
 
@@ -61,22 +66,24 @@ func genService(gen *protogen.Plugin, file *protogen.File, g *protogen.Generated
 		Metadata:    file.Desc.Path(),
 	}
 	for _, method := range service.Methods {
+		if method.Desc.IsStreamingClient() || method.Desc.IsStreamingServer() {
+			continue
+		}
 		rule, ok := proto.GetExtension(method.Desc.Options(), annotations.E_Http).(*annotations.HttpRule)
 		if rule != nil && ok {
 			for _, bind := range rule.AdditionalBindings {
-				sd.Methods = append(sd.Methods, buildHTTPRule(method, bind))
+				sd.Methods = append(sd.Methods, buildHTTPRule(g, method, bind))
 			}
-			sd.Methods = append(sd.Methods, buildHTTPRule(method, rule))
+			sd.Methods = append(sd.Methods, buildHTTPRule(g, method, rule))
 		} else {
 			path := fmt.Sprintf("/%s/%s", service.Desc.FullName(), method.Desc.Name())
-			sd.Methods = append(sd.Methods, buildMethodDesc(method, "POST", path))
-
+			sd.Methods = append(sd.Methods, buildMethodDesc(g, method, "POST", path))
 		}
 	}
 	g.P(sd.execute())
 }
 
-func buildHTTPRule(m *protogen.Method, rule *annotations.HttpRule) *methodDesc {
+func buildHTTPRule(g *protogen.GeneratedFile, m *protogen.Method, rule *annotations.HttpRule) *methodDesc {
 	var (
 		path         string
 		method       string
@@ -105,23 +112,29 @@ func buildHTTPRule(m *protogen.Method, rule *annotations.HttpRule) *methodDesc {
 	}
 	body = rule.Body
 	responseBody = rule.ResponseBody
-	md := buildMethodDesc(m, method, path)
+	md := buildMethodDesc(g, m, method, path)
+	if body == "*" {
+		body = ""
+	}
 	if body != "" {
 		md.Body = "." + camelCaseVars(body)
 	}
+	if responseBody == "*" {
+		responseBody = ""
+	}
 	if responseBody != "" {
 		md.ResponseBody = "." + camelCaseVars(responseBody)
 	}
 	return md
 }
 
-func buildMethodDesc(m *protogen.Method, method, path string) *methodDesc {
+func buildMethodDesc(g *protogen.GeneratedFile, m *protogen.Method, method, path string) *methodDesc {
 	defer func() { methodSets[m.GoName]++ }()
 	return &methodDesc{
 		Name:    m.GoName,
 		Num:     methodSets[m.GoName],
-		Request: m.Input.GoIdent.GoName,
-		Reply:   m.Output.GoIdent.GoName,
+		Request: g.QualifiedGoIdent(m.Input.GoIdent),
+		Reply:   g.QualifiedGoIdent(m.Output.GoIdent),
 		Path:    path,
 		Method:  method,
 		Vars:    buildPathVars(m, path),
diff --git a/cmd/protoc-gen-go-http/main.go b/cmd/protoc-gen-go-http/main.go
index 86c436ef..18418e43 100644
--- a/cmd/protoc-gen-go-http/main.go
+++ b/cmd/protoc-gen-go-http/main.go
@@ -8,7 +8,7 @@ import (
 	"google.golang.org/protobuf/types/pluginpb"
 )
 
-const version = "0.0.1"
+const version = "v2.0.0-alpha5"
 
 func main() {
 	showVersion := flag.Bool("version", false, "print the version and exit")
diff --git a/cmd/protoc-gen-go-http/template.go b/cmd/protoc-gen-go-http/template.go
index c4f5cc44..6a5e0849 100644
--- a/cmd/protoc-gen-go-http/template.go
+++ b/cmd/protoc-gen-go-http/template.go
@@ -7,54 +7,49 @@ import (
 )
 
 var httpTemplate = `
-type {{.ServiceType}}HTTPServer interface {
+type {{.ServiceType}}Handler interface {
 {{range .MethodSets}}
 	{{.Name}}(context.Context, *{{.Request}}) (*{{.Reply}}, error)
 {{end}}
 }
-func Register{{.ServiceType}}HTTPServer(s http1.ServiceRegistrar, srv {{.ServiceType}}HTTPServer) {
-	s.RegisterService(&_HTTP_{{.ServiceType}}_serviceDesc, srv)
-}
-{{range .Methods}}
-func _HTTP_{{$.ServiceType}}_{{.Name}}_{{.Num}}(srv interface{}, ctx context.Context, req *http.Request, dec func(interface{}) error) (interface{}, error) {
-	var in {{.Request}}
-{{if eq .Body ""}}
-	if err := http1.BindForm(req, &in); err != nil {
-		return nil, err
-	}
-{{else if eq .Body ".*"}}
-	if err := dec(&in); err != nil {
-		return nil, err
-	}
-{{else}}
-	if err := dec(in{{.Body}}); err != nil {
-		return nil, err
-	}
-{{end}}
-{{if ne (len .Vars) 0}}
-	if err := http1.BindVars(req, &in); err != nil {
-		return nil, err
-	}
-{{end}}
-	out, err := srv.({{$.ServiceType}}Server).{{.Name}}(ctx, &in)
-	if err != nil {
-		return nil, err
+
+func New{{.ServiceType}}Handler(srv {{.ServiceType}}Handler, opts ...http1.HandleOption) http.Handler {
+	h := http1.DefaultHandleOptions()
+	for _, o := range opts {
+		o(&h)
 	}
-	return out{{.ResponseBody}}, nil
-}
-{{end}}
-var _HTTP_{{.ServiceType}}_serviceDesc = http1.ServiceDesc{
-	ServiceName: "{{.ServiceName}}",
-	Methods: []http1.MethodDesc{
-{{range .Methods}}
-		{
-			Path:    "{{.Path}}",
-			Method:  "{{.Method}}",
-			Handler: _HTTP_{{$.ServiceType}}_{{.Name}}_{{.Num}},
-		},
-{{end}}
-	},
-	Metadata: "{{.Metadata}}",
+	r := mux.NewRouter()
+	{{range .Methods}}
+	r.HandleFunc("{{.Path}}", func(w http.ResponseWriter, r *http.Request) {
+		var in {{.Request}}
+		if err := h.Decode(r, &in{{.Body}}); err != nil {
+			h.Error(w, r, err)
+			return
+		}
+		{{if ne (len .Vars) 0}}
+		if err := binding.MapProto(&in, mux.Vars(r)); err != nil {
+			h.Error(w, r, err)
+			return
+		}
+		{{end}}
+		next := func(ctx context.Context, req interface{}) (interface{}, error) {
+			return srv.{{.Name}}(ctx, req.(*{{.Request}}))
+		}
+		if h.Middleware != nil {
+			next = h.Middleware(next)
+		}
+		out, err := next(r.Context(), &in)
+		if err != nil {
+			h.Error(w, r, err)
+			return
+		}
+		reply := out.(*{{.Reply}})
+		if err := h.Encode(w, r, reply{{.ResponseBody}}); err != nil {
+			h.Error(w, r, err)
+		}
+	}).Methods("{{.Method}}")
+	{{end}}
+	return r
 }
 `
 
diff --git a/encoding/encoding.go b/encoding/encoding.go
index 0e9141a7..f9defe60 100644
--- a/encoding/encoding.go
+++ b/encoding/encoding.go
@@ -1,6 +1,8 @@
 package encoding
 
-import "strings"
+import (
+	"strings"
+)
 
 // Codec defines the interface Transport uses to encode and decode messages.  Note
 // that implementations of this interface must be thread safe; a Codec's
diff --git a/encoding/json/json.go b/encoding/json/json.go
index 8c88f4ce..3aa94761 100644
--- a/encoding/json/json.go
+++ b/encoding/json/json.go
@@ -2,6 +2,7 @@ package json
 
 import (
 	"encoding/json"
+	"reflect"
 
 	"github.com/go-kratos/kratos/v2/encoding"
 
@@ -21,6 +22,8 @@ var (
 	UnmarshalOptions = protojson.UnmarshalOptions{
 		DiscardUnknown: true,
 	}
+
+	typeProtoMessage = reflect.TypeOf((*proto.Message)(nil)).Elem()
 )
 
 func init() {
@@ -38,6 +41,15 @@ func (codec) Marshal(v interface{}) ([]byte, error) {
 }
 
 func (codec) Unmarshal(data []byte, v interface{}) error {
+	rv := reflect.ValueOf(v)
+	for rv.Kind() == reflect.Ptr {
+		if rv.IsNil() {
+			rv.Set(reflect.New(rv.Type().Elem()))
+			v = rv.Interface()
+			break
+		}
+		rv = rv.Elem()
+	}
 	if m, ok := v.(proto.Message); ok {
 		return UnmarshalOptions.Unmarshal(data, m)
 	}
diff --git a/encoding/proto/proto.go b/encoding/proto/proto.go
index 26a29144..96051a96 100644
--- a/encoding/proto/proto.go
+++ b/encoding/proto/proto.go
@@ -3,7 +3,8 @@
 package proto
 
 import (
-	"google.golang.org/grpc/encoding"
+	"github.com/go-kratos/kratos/v2/encoding"
+
 	"google.golang.org/protobuf/proto"
 )
 
diff --git a/errors/errors.go b/errors/errors.go
index 69f7b8a6..ad991020 100644
--- a/errors/errors.go
+++ b/errors/errors.go
@@ -3,6 +3,7 @@ package errors
 import (
 	"errors"
 	"fmt"
+	"net/http"
 )
 
 const (
@@ -26,6 +27,48 @@ func (e *StatusError) Is(target error) bool {
 	return false
 }
 
+// HTTPStatus returns the Status represented by se.
+func (e *StatusError) HTTPStatus() int {
+	switch e.Code {
+	case 0:
+		return http.StatusOK
+	case 1:
+		return http.StatusInternalServerError
+	case 2:
+		return http.StatusInternalServerError
+	case 3:
+		return http.StatusBadRequest
+	case 4:
+		return http.StatusRequestTimeout
+	case 5:
+		return http.StatusNotFound
+	case 6:
+		return http.StatusConflict
+	case 7:
+		return http.StatusForbidden
+	case 8:
+		return http.StatusTooManyRequests
+	case 9:
+		return http.StatusPreconditionFailed
+	case 10:
+		return http.StatusConflict
+	case 11:
+		return http.StatusBadRequest
+	case 12:
+		return http.StatusNotImplemented
+	case 13:
+		return http.StatusInternalServerError
+	case 14:
+		return http.StatusServiceUnavailable
+	case 15:
+		return http.StatusInternalServerError
+	case 16:
+		return http.StatusUnauthorized
+	default:
+		return http.StatusInternalServerError
+	}
+}
+
 func (e *StatusError) Error() string {
 	return fmt.Sprintf("error: code = %d reason = %s message = %s details = %+v", e.Code, e.Reason, e.Message, e.Details)
 }
diff --git a/go.sum b/go.sum
index f4c90546..067f82f0 100644
--- a/go.sum
+++ b/go.sum
@@ -20,7 +20,6 @@ github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:x
 github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
 github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
 github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
-github.com/golang/protobuf v1.4.1 h1:ZFgWrT+bLgsYPirOnRfKLYJLvssAegOj/hgyMFdJZe0=
 github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
 github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
 github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
@@ -41,7 +40,6 @@ github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+
 github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM=
 github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
@@ -63,7 +61,6 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9 h1:L2auWcuQIvxz9xSEqzESnV/QN/gNRXNApHi3fYwl2w0=
 golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -79,7 +76,6 @@ google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9Ywl
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
 google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
-google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY=
 google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
 google.golang.org/genproto v0.0.0-20210114201628-6edceaf6022f h1:izedQ6yVIc5mZsRuXzmSreCOlzI0lCU1HpG8yEdMiKw=
 google.golang.org/genproto v0.0.0-20210114201628-6edceaf6022f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
diff --git a/middleware/recovery/recovery.go b/middleware/recovery/recovery.go
index 940d2fc5..51aaae3f 100644
--- a/middleware/recovery/recovery.go
+++ b/middleware/recovery/recovery.go
@@ -45,7 +45,7 @@ func Recovery(opts ...Option) middleware.Middleware {
 	for _, o := range opts {
 		o(&options)
 	}
-	log := log.NewHelper("middleware/recovery", log.DefaultLogger)
+	log := log.NewHelper("middleware/recovery", options.logger)
 	return func(handler middleware.Handler) middleware.Handler {
 		return func(ctx context.Context, req interface{}) (reply interface{}, err error) {
 			defer func() {
diff --git a/options.go b/options.go
index d5aa4c80..759fcca7 100644
--- a/options.go
+++ b/options.go
@@ -23,9 +23,10 @@ type options struct {
 	ctx  context.Context
 	sigs []os.Signal
 
-	logger   log.Logger
-	registry registry.Registry
-	servers  []transport.Server
+	logger    log.Logger
+	registrar registry.Registrar
+
+	servers []transport.Server
 }
 
 // ID with service id.
@@ -69,8 +70,8 @@ func Logger(logger log.Logger) Option {
 }
 
 // Registry with service registry.
-func Registry(r registry.Registry) Option {
-	return func(o *options) { o.registry = r }
+func Registry(r registry.Registrar) Option {
+	return func(o *options) { o.registrar = r }
 }
 
 // Server with transport servers.
diff --git a/registry/registry.go b/registry/registry.go
index 6f491455..58402855 100644
--- a/registry/registry.go
+++ b/registry/registry.go
@@ -2,16 +2,20 @@ package registry
 
 import "context"
 
-// Registry is service registry.
-type Registry interface {
+// Registrar is service registrar.
+type Registrar interface {
 	// Register the registration.
 	Register(ctx context.Context, service *ServiceInstance) error
 	// Deregister the registration.
 	Deregister(ctx context.Context, service *ServiceInstance) error
+}
+
+// Discoverer is service discovery.
+type Discoverer interface {
 	// Service return the service instances in memory according to the service name.
-	Service(ctx context.Context, name string) ([]*ServiceInstance, error)
+	Fetch(ctx context.Context, serviceName string) ([]*ServiceInstance, error)
 	// Watch creates a watcher according to the service name.
-	Watch(ctx context.Context, name string) (Watcher, error)
+	Watch(ctx context.Context, serviceName string) (Watcher, error)
 }
 
 // Watcher is service watcher.
diff --git a/transport/grpc/client.go b/transport/grpc/client.go
index c89ec488..b766fbe7 100644
--- a/transport/grpc/client.go
+++ b/transport/grpc/client.go
@@ -39,9 +39,9 @@ func WithMiddleware(m middleware.Middleware) ClientOption {
 }
 
 // WithRegistry with client registry.
-func WithRegistry(r registry.Registry) ClientOption {
+func WithRegistry(d registry.Discoverer) ClientOption {
 	return func(o *clientOptions) {
-		o.registry = r
+		o.discoverer = d
 	}
 }
 
@@ -57,7 +57,7 @@ type clientOptions struct {
 	endpoint   string
 	timeout    time.Duration
 	middleware middleware.Middleware
-	registry   registry.Registry
+	discoverer registry.Discoverer
 	grpcOpts   []grpc.DialOption
 }
 
@@ -86,8 +86,8 @@ func dial(ctx context.Context, insecure bool, opts ...ClientOption) (*grpc.Clien
 		grpc.WithTimeout(options.timeout),
 		grpc.WithUnaryInterceptor(UnaryClientInterceptor(options.middleware)),
 	}
-	if options.registry != nil {
-		grpc.WithResolvers(discovery.NewBuilder(options.registry))
+	if options.discoverer != nil {
+		grpcOpts = append(grpcOpts, grpc.WithResolvers(discovery.NewBuilder(options.discoverer)))
 	}
 	if insecure {
 		grpcOpts = append(grpcOpts, grpc.WithInsecure())
@@ -101,7 +101,7 @@ func dial(ctx context.Context, insecure bool, opts ...ClientOption) (*grpc.Clien
 // UnaryClientInterceptor retruns a unary client interceptor.
 func UnaryClientInterceptor(m middleware.Middleware) grpc.UnaryClientInterceptor {
 	return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
-		ctx = transport.NewContext(ctx, transport.Transport{Kind: "gRPC"})
+		ctx = transport.NewContext(ctx, transport.Transport{Kind: transport.KindGRPC})
 		ctx = NewClientContext(ctx, ClientInfo{FullMethod: method})
 		h := func(ctx context.Context, req interface{}) (interface{}, error) {
 			return reply, invoker(ctx, method, req, reply, cc, opts...)
diff --git a/transport/grpc/resolver/discovery/builder.go b/transport/grpc/resolver/discovery/builder.go
index 462c63c2..aa063a51 100644
--- a/transport/grpc/resolver/discovery/builder.go
+++ b/transport/grpc/resolver/discovery/builder.go
@@ -21,15 +21,15 @@ func WithLogger(logger log.Logger) Option {
 }
 
 type builder struct {
-	registry registry.Registry
-	logger   log.Logger
+	discoverer registry.Discoverer
+	logger     log.Logger
 }
 
 // NewBuilder creates a builder which is used to factory registry resolvers.
-func NewBuilder(r registry.Registry, opts ...Option) resolver.Builder {
+func NewBuilder(r registry.Discoverer, opts ...Option) resolver.Builder {
 	b := &builder{
-		registry: r,
-		logger:   log.DefaultLogger,
+		discoverer: r,
+		logger:     log.DefaultLogger,
 	}
 	for _, o := range opts {
 		o(b)
@@ -38,7 +38,7 @@ func NewBuilder(r registry.Registry, opts ...Option) resolver.Builder {
 }
 
 func (d *builder) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions) (resolver.Resolver, error) {
-	w, err := d.registry.Watch(context.Background(), target.Endpoint)
+	w, err := d.discoverer.Watch(context.Background(), target.Endpoint)
 	if err != nil {
 		return nil, err
 	}
diff --git a/transport/grpc/resolver/discovery/resolver.go b/transport/grpc/resolver/discovery/resolver.go
index a25f127a..af0ec5dc 100644
--- a/transport/grpc/resolver/discovery/resolver.go
+++ b/transport/grpc/resolver/discovery/resolver.go
@@ -70,5 +70,5 @@ func parseAttributes(md map[string]string) *attributes.Attributes {
 	for k, v := range md {
 		pairs = append(pairs, k, v)
 	}
-	return attributes.New(pairs)
+	return attributes.New(pairs...)
 }
diff --git a/transport/grpc/server.go b/transport/grpc/server.go
index ce118660..51b301d8 100644
--- a/transport/grpc/server.go
+++ b/transport/grpc/server.go
@@ -146,7 +146,7 @@ func UnaryTimeoutInterceptor(timeout time.Duration) grpc.UnaryServerInterceptor
 // UnaryServerInterceptor returns a unary server interceptor.
 func UnaryServerInterceptor(m middleware.Middleware) grpc.UnaryServerInterceptor {
 	return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
-		ctx = transport.NewContext(ctx, transport.Transport{Kind: "gRPC"})
+		ctx = transport.NewContext(ctx, transport.Transport{Kind: transport.KindGRPC})
 		ctx = NewServerContext(ctx, ServerInfo{Server: info.Server, FullMethod: info.FullMethod})
 		h := func(ctx context.Context, req interface{}) (interface{}, error) {
 			return handler(ctx, req)
diff --git a/transport/http/binding/bind.go b/transport/http/binding/bind.go
new file mode 100644
index 00000000..7549c363
--- /dev/null
+++ b/transport/http/binding/bind.go
@@ -0,0 +1,18 @@
+package binding
+
+import (
+	"net/http"
+
+	"google.golang.org/protobuf/proto"
+)
+
+// BindForm bind form parameters to target.
+func BindForm(req *http.Request, target interface{}) error {
+	if err := req.ParseForm(); err != nil {
+		return err
+	}
+	if msg, ok := target.(proto.Message); ok {
+		return mapProto(msg, req.Form)
+	}
+	return mapForm(target, req.Form)
+}
diff --git a/transport/http/binding/form.go b/transport/http/binding/form.go
new file mode 100644
index 00000000..5c83bc12
--- /dev/null
+++ b/transport/http/binding/form.go
@@ -0,0 +1,385 @@
+package binding
+
+// Copyright 2014 Manu Martinez-Almeida.  All rights reserved.
+// Use of this source code is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+import (
+	"encoding/json"
+	"errors"
+	"fmt"
+	"reflect"
+	"strconv"
+	"strings"
+	"time"
+)
+
+var (
+	errUnknownType = errors.New("unknown type")
+	emptyField     = reflect.StructField{}
+)
+
+func mapForm(ptr interface{}, form map[string][]string) error {
+	return mapFormByTag(ptr, form, "json")
+}
+
+func mapFormByTag(ptr interface{}, form map[string][]string, tag string) error {
+	ptrVal := reflect.ValueOf(ptr)
+	var pointed interface{}
+	if ptrVal.Kind() == reflect.Ptr {
+		ptrVal = ptrVal.Elem()
+		pointed = ptrVal.Interface()
+	}
+	if ptrVal.Kind() == reflect.Map &&
+		ptrVal.Type().Key().Kind() == reflect.String {
+		if pointed != nil {
+			ptr = pointed
+		}
+		return setFormMap(ptr, form)
+	}
+	return mappingByPtr(ptr, formSource(form), tag)
+}
+
+// setter tries to set value on a walking by fields of a struct
+type setter interface {
+	TrySet(value reflect.Value, field reflect.StructField, key string, opt setOptions) (isSetted bool, err error)
+}
+
+type formSource map[string][]string
+
+var _ setter = formSource(nil)
+
+// TrySet tries to set a value by request's form source (like map[string][]string)
+func (form formSource) TrySet(value reflect.Value, field reflect.StructField, tagValue string, opt setOptions) (isSetted bool, err error) {
+	return setByForm(value, field, form, tagValue, opt)
+}
+
+func mappingByPtr(ptr interface{}, setter setter, tag string) error {
+	_, err := mapping(reflect.ValueOf(ptr), emptyField, setter, tag)
+	return err
+}
+
+func mapping(value reflect.Value, field reflect.StructField, setter setter, tag string) (bool, error) {
+	if field.Tag.Get(tag) == "-" { // just ignoring this field
+		return false, nil
+	}
+
+	var vKind = value.Kind()
+
+	if vKind == reflect.Ptr {
+		var isNew bool
+		vPtr := value
+		if value.IsNil() {
+			isNew = true
+			vPtr = reflect.New(value.Type().Elem())
+		}
+		isSetted, err := mapping(vPtr.Elem(), field, setter, tag)
+		if err != nil {
+			return false, err
+		}
+		if isNew && isSetted {
+			value.Set(vPtr)
+		}
+		return isSetted, nil
+	}
+
+	if vKind != reflect.Struct || !field.Anonymous {
+		ok, err := tryToSetValue(value, field, setter, tag)
+		if err != nil {
+			return false, err
+		}
+		if ok {
+			return true, nil
+		}
+	}
+
+	if vKind == reflect.Struct {
+		tValue := value.Type()
+
+		var isSetted bool
+		for i := 0; i < value.NumField(); i++ {
+			sf := tValue.Field(i)
+			if sf.PkgPath != "" && !sf.Anonymous { // unexported
+				continue
+			}
+			ok, err := mapping(value.Field(i), tValue.Field(i), setter, tag)
+			if err != nil {
+				return false, err
+			}
+			isSetted = isSetted || ok
+		}
+		return isSetted, nil
+	}
+	return false, nil
+}
+
+type setOptions struct {
+	isDefaultExists bool
+	defaultValue    string
+}
+
+func tryToSetValue(value reflect.Value, field reflect.StructField, setter setter, tag string) (bool, error) {
+	var tagValue string
+	var setOpt setOptions
+
+	tagValue = field.Tag.Get(tag)
+	tagValue, opts := head(tagValue, ",")
+
+	if tagValue == "" { // default value is FieldName
+		tagValue = field.Name
+	}
+	if tagValue == "" { // when field is "emptyField" variable
+		return false, nil
+	}
+
+	var opt string
+	for len(opts) > 0 {
+		opt, opts = head(opts, ",")
+
+		if k, v := head(opt, "="); k == "default" {
+			setOpt.isDefaultExists = true
+			setOpt.defaultValue = v
+		}
+	}
+
+	return setter.TrySet(value, field, tagValue, setOpt)
+}
+
+func setByForm(value reflect.Value, field reflect.StructField, form map[string][]string, tagValue string, opt setOptions) (isSetted bool, err error) {
+	vs, ok := form[tagValue]
+	if !ok && !opt.isDefaultExists {
+		return false, nil
+	}
+
+	switch value.Kind() {
+	case reflect.Slice:
+		if !ok {
+			vs = []string{opt.defaultValue}
+		}
+		return true, setSlice(vs, value, field)
+	case reflect.Array:
+		if !ok {
+			vs = []string{opt.defaultValue}
+		}
+		if len(vs) != value.Len() {
+			return false, fmt.Errorf("%q is not valid value for %s", vs, value.Type().String())
+		}
+		return true, setArray(vs, value, field)
+	default:
+		var val string
+		if !ok {
+			val = opt.defaultValue
+		}
+
+		if len(vs) > 0 {
+			val = vs[0]
+		}
+		return true, setWithProperType(val, value, field)
+	}
+}
+
+func setWithProperType(val string, value reflect.Value, field reflect.StructField) error {
+	switch value.Kind() {
+	case reflect.Int:
+		return setIntField(val, 0, value)
+	case reflect.Int8:
+		return setIntField(val, 8, value)
+	case reflect.Int16:
+		return setIntField(val, 16, value)
+	case reflect.Int32:
+		return setIntField(val, 32, value)
+	case reflect.Int64:
+		switch value.Interface().(type) {
+		case time.Duration:
+			return setTimeDuration(val, value, field)
+		}
+		return setIntField(val, 64, value)
+	case reflect.Uint:
+		return setUintField(val, 0, value)
+	case reflect.Uint8:
+		return setUintField(val, 8, value)
+	case reflect.Uint16:
+		return setUintField(val, 16, value)
+	case reflect.Uint32:
+		return setUintField(val, 32, value)
+	case reflect.Uint64:
+		return setUintField(val, 64, value)
+	case reflect.Bool:
+		return setBoolField(val, value)
+	case reflect.Float32:
+		return setFloatField(val, 32, value)
+	case reflect.Float64:
+		return setFloatField(val, 64, value)
+	case reflect.String:
+		value.SetString(val)
+	case reflect.Struct:
+		switch value.Interface().(type) {
+		case time.Time:
+			return setTimeField(val, field, value)
+		}
+		return json.Unmarshal([]byte(val), value.Addr().Interface())
+	case reflect.Map:
+		return json.Unmarshal([]byte(val), value.Addr().Interface())
+	default:
+		return errUnknownType
+	}
+	return nil
+}
+
+func setIntField(val string, bitSize int, field reflect.Value) error {
+	if val == "" {
+		val = "0"
+	}
+	intVal, err := strconv.ParseInt(val, 10, bitSize)
+	if err == nil {
+		field.SetInt(intVal)
+	}
+	return err
+}
+
+func setUintField(val string, bitSize int, field reflect.Value) error {
+	if val == "" {
+		val = "0"
+	}
+	uintVal, err := strconv.ParseUint(val, 10, bitSize)
+	if err == nil {
+		field.SetUint(uintVal)
+	}
+	return err
+}
+
+func setBoolField(val string, field reflect.Value) error {
+	if val == "" {
+		val = "false"
+	}
+	boolVal, err := strconv.ParseBool(val)
+	if err == nil {
+		field.SetBool(boolVal)
+	}
+	return err
+}
+
+func setFloatField(val string, bitSize int, field reflect.Value) error {
+	if val == "" {
+		val = "0.0"
+	}
+	floatVal, err := strconv.ParseFloat(val, bitSize)
+	if err == nil {
+		field.SetFloat(floatVal)
+	}
+	return err
+}
+
+func setTimeField(val string, structField reflect.StructField, value reflect.Value) error {
+	timeFormat := structField.Tag.Get("time_format")
+	if timeFormat == "" {
+		timeFormat = time.RFC3339
+	}
+
+	switch tf := strings.ToLower(timeFormat); tf {
+	case "unix", "unixnano":
+		tv, err := strconv.ParseInt(val, 10, 64)
+		if err != nil {
+			return err
+		}
+
+		d := time.Duration(1)
+		if tf == "unixnano" {
+			d = time.Second
+		}
+
+		t := time.Unix(tv/int64(d), tv%int64(d))
+		value.Set(reflect.ValueOf(t))
+		return nil
+
+	}
+
+	if val == "" {
+		value.Set(reflect.ValueOf(time.Time{}))
+		return nil
+	}
+
+	l := time.Local
+	if isUTC, _ := strconv.ParseBool(structField.Tag.Get("time_utc")); isUTC {
+		l = time.UTC
+	}
+
+	if locTag := structField.Tag.Get("time_location"); locTag != "" {
+		loc, err := time.LoadLocation(locTag)
+		if err != nil {
+			return err
+		}
+		l = loc
+	}
+
+	t, err := time.ParseInLocation(timeFormat, val, l)
+	if err != nil {
+		return err
+	}
+
+	value.Set(reflect.ValueOf(t))
+	return nil
+}
+
+func setArray(vals []string, value reflect.Value, field reflect.StructField) error {
+	for i, s := range vals {
+		err := setWithProperType(s, value.Index(i), field)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func setSlice(vals []string, value reflect.Value, field reflect.StructField) error {
+	slice := reflect.MakeSlice(value.Type(), len(vals), len(vals))
+	err := setArray(vals, slice, field)
+	if err != nil {
+		return err
+	}
+	value.Set(slice)
+	return nil
+}
+
+func setTimeDuration(val string, value reflect.Value, field reflect.StructField) error {
+	d, err := time.ParseDuration(val)
+	if err != nil {
+		return err
+	}
+	value.Set(reflect.ValueOf(d))
+	return nil
+}
+
+func head(str, sep string) (head string, tail string) {
+	idx := strings.Index(str, sep)
+	if idx < 0 {
+		return str, ""
+	}
+	return str[:idx], str[idx+len(sep):]
+}
+
+func setFormMap(ptr interface{}, form map[string][]string) error {
+	el := reflect.TypeOf(ptr).Elem()
+
+	if el.Kind() == reflect.Slice {
+		ptrMap, ok := ptr.(map[string][]string)
+		if !ok {
+			return errors.New("cannot convert to map slices of strings")
+		}
+		for k, v := range form {
+			ptrMap[k] = v
+		}
+
+		return nil
+	}
+
+	ptrMap, ok := ptr.(map[string]string)
+	if !ok {
+		return errors.New("cannot convert to map of strings")
+	}
+	for k, v := range form {
+		ptrMap[k] = v[len(v)-1] // pick last
+	}
+
+	return nil
+}
diff --git a/transport/http/bind.go b/transport/http/binding/proto.go
similarity index 89%
rename from transport/http/bind.go
rename to transport/http/binding/proto.go
index d0a1d7fe..4a9dd994 100644
--- a/transport/http/bind.go
+++ b/transport/http/binding/proto.go
@@ -1,26 +1,26 @@
-package http
+package binding
 
 import (
 	"encoding/base64"
 	"errors"
 	"fmt"
 	"log"
-	"net/http"
 	"strconv"
 	"strings"
 	"time"
 
-	"github.com/golang/protobuf/ptypes"
-	"github.com/golang/protobuf/ptypes/wrappers"
 	"google.golang.org/genproto/protobuf/field_mask"
 	"google.golang.org/protobuf/proto"
 	"google.golang.org/protobuf/reflect/protoreflect"
 	"google.golang.org/protobuf/reflect/protoregistry"
+	"google.golang.org/protobuf/types/known/durationpb"
+	"google.golang.org/protobuf/types/known/timestamppb"
+	"google.golang.org/protobuf/types/known/wrapperspb"
 )
 
-// BindVars parses url parameters.
-func BindVars(req *http.Request, msg proto.Message) error {
-	for key, value := range Vars(req) {
+// MapProto sets a value in a nested Protobuf structure.
+func MapProto(msg proto.Message, values map[string]string) error {
+	for key, value := range values {
 		if err := populateFieldValues(msg.ProtoReflect(), strings.Split(key, "."), []string{value}); err != nil {
 			return err
 		}
@@ -28,12 +28,8 @@ func BindVars(req *http.Request, msg proto.Message) error {
 	return nil
 }
 
-// BindForm parses form parameters.
-func BindForm(req *http.Request, msg proto.Message) error {
-	if err := req.ParseForm(); err != nil {
-		return err
-	}
-	for key, values := range req.Form {
+func mapProto(msg proto.Message, values map[string][]string) error {
+	for key, values := range values {
 		if err := populateFieldValues(msg.ProtoReflect(), strings.Split(key, "."), values); err != nil {
 			return err
 		}
@@ -213,10 +209,7 @@ func parseMessage(md protoreflect.MessageDescriptor, value string) (protoreflect
 		if err != nil {
 			return protoreflect.Value{}, err
 		}
-		msg, err = ptypes.TimestampProto(t)
-		if err != nil {
-			return protoreflect.Value{}, err
-		}
+		msg = timestamppb.New(t)
 	case "google.protobuf.Duration":
 		if value == "null" {
 			break
@@ -225,57 +218,57 @@ func parseMessage(md protoreflect.MessageDescriptor, value string) (protoreflect
 		if err != nil {
 			return protoreflect.Value{}, err
 		}
-		msg = ptypes.DurationProto(d)
+		msg = durationpb.New(d)
 	case "google.protobuf.DoubleValue":
 		v, err := strconv.ParseFloat(value, 64)
 		if err != nil {
 			return protoreflect.Value{}, err
 		}
-		msg = &wrappers.DoubleValue{Value: v}
+		msg = wrapperspb.Double(v)
 	case "google.protobuf.FloatValue":
 		v, err := strconv.ParseFloat(value, 32)
 		if err != nil {
 			return protoreflect.Value{}, err
 		}
-		msg = &wrappers.FloatValue{Value: float32(v)}
+		msg = wrapperspb.Float(float32(v))
 	case "google.protobuf.Int64Value":
 		v, err := strconv.ParseInt(value, 10, 64)
 		if err != nil {
 			return protoreflect.Value{}, err
 		}
-		msg = &wrappers.Int64Value{Value: v}
+		msg = wrapperspb.Int64(v)
 	case "google.protobuf.Int32Value":
 		v, err := strconv.ParseInt(value, 10, 32)
 		if err != nil {
 			return protoreflect.Value{}, err
 		}
-		msg = &wrappers.Int32Value{Value: int32(v)}
+		msg = wrapperspb.Int32(int32(v))
 	case "google.protobuf.UInt64Value":
 		v, err := strconv.ParseUint(value, 10, 64)
 		if err != nil {
 			return protoreflect.Value{}, err
 		}
-		msg = &wrappers.UInt64Value{Value: v}
+		msg = wrapperspb.UInt64(v)
 	case "google.protobuf.UInt32Value":
 		v, err := strconv.ParseUint(value, 10, 32)
 		if err != nil {
 			return protoreflect.Value{}, err
 		}
-		msg = &wrappers.UInt32Value{Value: uint32(v)}
+		msg = wrapperspb.UInt32(uint32(v))
 	case "google.protobuf.BoolValue":
 		v, err := strconv.ParseBool(value)
 		if err != nil {
 			return protoreflect.Value{}, err
 		}
-		msg = &wrappers.BoolValue{Value: v}
+		msg = wrapperspb.Bool(v)
 	case "google.protobuf.StringValue":
-		msg = &wrappers.StringValue{Value: value}
+		msg = wrapperspb.String(value)
 	case "google.protobuf.BytesValue":
 		v, err := base64.StdEncoding.DecodeString(value)
 		if err != nil {
 			return protoreflect.Value{}, err
 		}
-		msg = &wrappers.BytesValue{Value: v}
+		msg = wrapperspb.Bytes(v)
 	case "google.protobuf.FieldMask":
 		fm := &field_mask.FieldMask{}
 		fm.Paths = append(fm.Paths, strings.Split(value, ",")...)
diff --git a/transport/http/client.go b/transport/http/client.go
index 35ba74d6..38e0fb4a 100644
--- a/transport/http/client.go
+++ b/transport/http/client.go
@@ -90,7 +90,7 @@ func (t *baseTransport) RoundTrip(req *http.Request) (*http.Response, error) {
 	if t.userAgent != "" && req.Header.Get("User-Agent") == "" {
 		req.Header.Set("User-Agent", t.userAgent)
 	}
-	ctx := transport.NewContext(req.Context(), transport.Transport{Kind: "HTTP"})
+	ctx := transport.NewContext(req.Context(), transport.Transport{Kind: transport.KindHTTP})
 	ctx = NewClientContext(ctx, ClientInfo{Request: req})
 	ctx, cancel := context.WithTimeout(ctx, t.timeout)
 	defer cancel()
@@ -115,22 +115,26 @@ func Do(client *http.Client, req *http.Request, target interface{}) error {
 	if err != nil {
 		return err
 	}
-	data, err := ioutil.ReadAll(res.Body)
-	if err != nil {
-		return err
-	}
 	defer res.Body.Close()
-	subtype := contentSubtype(res.Header.Get("content-type"))
-	codec := encoding.GetCodec(subtype)
-	if codec == nil {
-		codec = encoding.GetCodec("json")
-	}
 	if res.StatusCode < 200 || res.StatusCode > 299 {
-		se := &errors.StatusError{}
-		if err := codec.Unmarshal(data, se); err != nil {
+		se := &errors.StatusError{Code: 2}
+		if err := decodeResponse(res, se); err != nil {
 			return err
 		}
 		return se
 	}
+	return decodeResponse(res, target)
+}
+
+func decodeResponse(res *http.Response, target interface{}) error {
+	subtype := contentSubtype(res.Header.Get(contentTypeHeader))
+	codec := encoding.GetCodec(subtype)
+	if codec == nil {
+		codec = encoding.GetCodec("json")
+	}
+	data, err := ioutil.ReadAll(res.Body)
+	if err != nil {
+		return err
+	}
 	return codec.Unmarshal(data, target)
 }
diff --git a/transport/http/context.go b/transport/http/context.go
index d12856e3..f7200e91 100644
--- a/transport/http/context.go
+++ b/transport/http/context.go
@@ -3,8 +3,6 @@ package http
 import (
 	"context"
 	"net/http"
-
-	"github.com/gorilla/mux"
 )
 
 // ServerInfo is HTTP server infomation.
@@ -43,8 +41,3 @@ func FromClientContext(ctx context.Context) (info ClientInfo, ok bool) {
 	info, ok = ctx.Value(clientKey{}).(ClientInfo)
 	return
 }
-
-// Vars returns the route variables for the current request, if any.
-func Vars(req *http.Request) map[string]string {
-	return mux.Vars(req)
-}
diff --git a/transport/http/default.go b/transport/http/default.go
deleted file mode 100644
index d1a59e95..00000000
--- a/transport/http/default.go
+++ /dev/null
@@ -1,81 +0,0 @@
-package http
-
-import (
-	"fmt"
-	"io/ioutil"
-	"net/http"
-	"strings"
-
-	"github.com/go-kratos/kratos/v2/encoding"
-)
-
-const baseContentType = "application"
-
-func contentType(subtype string) string {
-	return strings.Join([]string{baseContentType, subtype}, "/")
-}
-
-func contentSubtype(contentType string) string {
-	if contentType == baseContentType {
-		return ""
-	}
-	if !strings.HasPrefix(contentType, baseContentType) {
-		return ""
-	}
-	// guaranteed since != baseContentType and has baseContentType prefix
-	switch contentType[len(baseContentType)] {
-	case '/', ';':
-		if i := strings.Index(contentType, ";"); i != -1 {
-			return contentType[len(baseContentType)+1 : i]
-		}
-		return contentType[len(baseContentType)+1:]
-	default:
-		return ""
-	}
-}
-
-func defaultRequestDecoder(req *http.Request, v interface{}) error {
-	data, err := ioutil.ReadAll(req.Body)
-	if err != nil {
-		return err
-	}
-	defer req.Body.Close()
-	subtype := contentSubtype(req.Header.Get("content-type"))
-	codec := encoding.GetCodec(subtype)
-	if codec == nil {
-		return fmt.Errorf("decoding request failed unknown content-type: %s", subtype)
-	}
-	return codec.Unmarshal(data, v)
-}
-
-func defaultResponseEncoder(res http.ResponseWriter, req *http.Request, v interface{}) error {
-	subtype := contentSubtype(req.Header.Get("accept"))
-	codec := encoding.GetCodec(subtype)
-	if codec == nil {
-		codec = encoding.GetCodec("json")
-	}
-	data, err := codec.Marshal(v)
-	if err != nil {
-		return err
-	}
-	res.Header().Set("content-type", contentType(codec.Name()))
-	res.Write(data)
-	return nil
-}
-
-func defaultErrorEncoder(res http.ResponseWriter, req *http.Request, err error) {
-	se, code := StatusError(err)
-	subtype := contentSubtype(req.Header.Get("accept"))
-	codec := encoding.GetCodec(subtype)
-	if codec == nil {
-		codec = encoding.GetCodec("json")
-	}
-	data, err := codec.Marshal(se)
-	if err != nil {
-		res.WriteHeader(http.StatusInternalServerError)
-		return
-	}
-	res.Header().Set("content-type", contentType(codec.Name()))
-	res.WriteHeader(code)
-	res.Write(data)
-}
diff --git a/transport/http/errors.go b/transport/http/errors.go
deleted file mode 100644
index a0c01653..00000000
--- a/transport/http/errors.go
+++ /dev/null
@@ -1,59 +0,0 @@
-package http
-
-import (
-	"net/http"
-
-	"github.com/go-kratos/kratos/v2/errors"
-)
-
-var (
-	// References: https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto
-	codesMapping = map[int32]int{
-		0:  http.StatusOK,
-		1:  http.StatusInternalServerError,
-		2:  http.StatusInternalServerError,
-		3:  http.StatusBadRequest,
-		4:  http.StatusRequestTimeout,
-		5:  http.StatusNotFound,
-		6:  http.StatusConflict,
-		7:  http.StatusForbidden,
-		8:  http.StatusTooManyRequests,
-		9:  http.StatusPreconditionFailed,
-		10: http.StatusConflict,
-		11: http.StatusBadRequest,
-		12: http.StatusNotImplemented,
-		13: http.StatusInternalServerError,
-		14: http.StatusServiceUnavailable,
-		15: http.StatusInternalServerError,
-		16: http.StatusUnauthorized,
-	}
-	statusMapping = map[int]int32{
-		http.StatusOK:                  0,
-		http.StatusBadRequest:          3,
-		http.StatusRequestTimeout:      4,
-		http.StatusNotFound:            5,
-		http.StatusConflict:            6,
-		http.StatusForbidden:           7,
-		http.StatusUnauthorized:        16,
-		http.StatusPreconditionFailed:  9,
-		http.StatusNotImplemented:      12,
-		http.StatusInternalServerError: 13,
-		http.StatusServiceUnavailable:  14,
-	}
-)
-
-// StatusError converts error to status error.
-func StatusError(err error) (*errors.StatusError, int) {
-	se, ok := errors.FromError(err)
-	if !ok {
-		se = &errors.StatusError{
-			Code:    2,
-			Reason:  "Unknown",
-			Message: "Unknown: " + err.Error(),
-		}
-	}
-	if status, ok := codesMapping[se.Code]; ok {
-		return se, status
-	}
-	return se, http.StatusInternalServerError
-}
diff --git a/transport/http/handle.go b/transport/http/handle.go
new file mode 100644
index 00000000..32e61e34
--- /dev/null
+++ b/transport/http/handle.go
@@ -0,0 +1,149 @@
+package http
+
+import (
+	"io/ioutil"
+	"net/http"
+	"strings"
+
+	"github.com/go-kratos/kratos/v2/encoding"
+	"github.com/go-kratos/kratos/v2/errors"
+	"github.com/go-kratos/kratos/v2/middleware"
+	"github.com/go-kratos/kratos/v2/transport/http/binding"
+)
+
+const (
+	// SupportPackageIsVersion1 These constants should not be referenced from any other code.
+	SupportPackageIsVersion1 = true
+
+	baseContentType = "application"
+)
+
+var (
+	acceptHeader      = http.CanonicalHeaderKey("Accept")
+	contentTypeHeader = http.CanonicalHeaderKey("Content-Type")
+)
+
+// 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.
+type HandleOptions struct {
+	Decode     DecodeRequestFunc
+	Encode     EncodeResponseFunc
+	Error      EncodeErrorFunc
+	Middleware middleware.Middleware
+}
+
+// DefaultHandleOptions returns a default handle options.
+func DefaultHandleOptions() HandleOptions {
+	return HandleOptions{
+		Decode: decodeRequest,
+		Encode: encodeResponse,
+		Error:  encodeError,
+	}
+}
+
+// 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 = m
+	}
+}
+
+// decodeRequest decodes the request body to object.
+func decodeRequest(req *http.Request, v interface{}) error {
+	subtype := contentSubtype(req.Header.Get(contentTypeHeader))
+	if codec := encoding.GetCodec(subtype); codec != nil {
+		data, err := ioutil.ReadAll(req.Body)
+		if err != nil {
+			return err
+		}
+		return codec.Unmarshal(data, v)
+	}
+	return binding.BindForm(req, v)
+}
+
+// encodeResponse encodes the object to the HTTP response.
+func encodeResponse(w http.ResponseWriter, r *http.Request, v interface{}) error {
+	var codec encoding.Codec
+	for _, accept := range r.Header[acceptHeader] {
+		if codec = encoding.GetCodec(contentSubtype(accept)); codec != nil {
+			break
+		}
+	}
+	if codec == nil {
+		codec = encoding.GetCodec("json")
+	}
+	data, err := codec.Marshal(v)
+	if err != nil {
+		return err
+	}
+	w.Header().Set(contentTypeHeader, contentType(codec.Name()))
+	w.Write(data)
+	return nil
+}
+
+// encodeError encodes the erorr to the HTTP response.
+func encodeError(w http.ResponseWriter, r *http.Request, err error) {
+	se, ok := errors.FromError(err)
+	if !ok {
+		se = &errors.StatusError{
+			Code:    2,
+			Message: err.Error(),
+		}
+	}
+	w.WriteHeader(se.HTTPStatus())
+	encodeResponse(w, r, se)
+}
+
+func contentType(subtype string) string {
+	return strings.Join([]string{baseContentType, subtype}, "/")
+}
+
+func contentSubtype(contentType string) string {
+	if contentType == baseContentType {
+		return ""
+	}
+	if !strings.HasPrefix(contentType, baseContentType) {
+		return ""
+	}
+	switch contentType[len(baseContentType)] {
+	case '/', ';':
+		if i := strings.Index(contentType, ";"); i != -1 {
+			return contentType[len(baseContentType)+1 : i]
+		}
+		return contentType[len(baseContentType)+1:]
+	default:
+		return ""
+	}
+}
diff --git a/transport/http/route.go b/transport/http/route.go
deleted file mode 100644
index 86bbcfc3..00000000
--- a/transport/http/route.go
+++ /dev/null
@@ -1,55 +0,0 @@
-package http
-
-import (
-	"net/http"
-
-	"github.com/gorilla/mux"
-)
-
-// RouteGroup adds a matcher for the URL path and method. This matches if the given
-// template is a prefix of the full URL path. See route.Path() for details on
-// the tpl argument.
-type RouteGroup struct {
-	prefix string
-	router *mux.Router
-}
-
-// ANY maps an HTTP Any request to the path and the specified handler.
-func (r *RouteGroup) ANY(path string, handler http.HandlerFunc) {
-	r.router.PathPrefix(r.prefix).Path(path).HandlerFunc(handler)
-}
-
-// GET maps an HTTP Get request to the path and the specified handler.
-func (r *RouteGroup) GET(path string, handler http.HandlerFunc) {
-	r.router.PathPrefix(r.prefix).Path(path).HandlerFunc(handler).Methods("GET")
-}
-
-// HEAD maps an HTTP Head request to the path and the specified handler.
-func (r *RouteGroup) HEAD(path string, handler http.HandlerFunc) {
-	r.router.PathPrefix(r.prefix).Path(path).HandlerFunc(handler).Methods("HEAD")
-}
-
-// POST maps an HTTP Post request to the path and the specified handler.
-func (r *RouteGroup) POST(path string, handler http.HandlerFunc) {
-	r.router.PathPrefix(r.prefix).Path(path).HandlerFunc(handler).Methods("POST")
-}
-
-// PUT maps an HTTP Put request to the path and the specified handler.
-func (r *RouteGroup) PUT(path string, handler http.HandlerFunc) {
-	r.router.PathPrefix(r.prefix).Path(path).HandlerFunc(handler).Methods("PUT")
-}
-
-// DELETE maps an HTTP Delete request to the path and the specified handler.
-func (r *RouteGroup) DELETE(path string, handler http.HandlerFunc) {
-	r.router.PathPrefix(r.prefix).Path(path).HandlerFunc(handler).Methods("DELETE")
-}
-
-// PATCH maps an HTTP Patch request to the path and the specified handler.
-func (r *RouteGroup) PATCH(path string, handler http.HandlerFunc) {
-	r.router.PathPrefix(r.prefix).Path(path).HandlerFunc(handler).Methods("PATCH")
-}
-
-// OPTIONS maps an HTTP Options request to the path and the specified handler.
-func (r *RouteGroup) OPTIONS(path string, handler http.HandlerFunc) {
-	r.router.PathPrefix(r.prefix).Path(path).HandlerFunc(handler).Methods("OPTIONS")
-}
diff --git a/transport/http/server.go b/transport/http/server.go
index 4cfd470d..b26bf601 100644
--- a/transport/http/server.go
+++ b/transport/http/server.go
@@ -10,8 +10,6 @@ import (
 
 	"github.com/go-kratos/kratos/v2/internal/host"
 	"github.com/go-kratos/kratos/v2/log"
-	"github.com/go-kratos/kratos/v2/middleware"
-	"github.com/go-kratos/kratos/v2/middleware/recovery"
 	"github.com/go-kratos/kratos/v2/transport"
 
 	"github.com/gorilla/mux"
@@ -21,15 +19,6 @@ const loggerName = "transport/http"
 
 var _ transport.Server = (*Server)(nil)
 
-// DecodeRequestFunc deocder request func.
-type DecodeRequestFunc func(req *http.Request, v interface{}) error
-
-// EncodeResponseFunc is encode response func.
-type EncodeResponseFunc func(res http.ResponseWriter, req *http.Request, v interface{}) error
-
-// EncodeErrorFunc is encode error func.
-type EncodeErrorFunc func(res http.ResponseWriter, req *http.Request, err error)
-
 // ServerOption is HTTP server option.
 type ServerOption func(*Server)
 
@@ -61,46 +50,24 @@ func Logger(logger log.Logger) ServerOption {
 	}
 }
 
-// Middleware with server middleware option.
-func Middleware(m middleware.Middleware) ServerOption {
-	return func(s *Server) {
-		s.middleware = m
-	}
-}
-
-// ErrorEncoder with error handler option.
-func ErrorEncoder(fn EncodeErrorFunc) ServerOption {
-	return func(s *Server) {
-		s.errorEncoder = fn
-	}
-}
-
 // Server is a HTTP server wrapper.
 type Server struct {
 	*http.Server
-	lis             net.Listener
-	network         string
-	address         string
-	timeout         time.Duration
-	middleware      middleware.Middleware
-	requestDecoder  DecodeRequestFunc
-	responseEncoder EncodeResponseFunc
-	errorEncoder    EncodeErrorFunc
-	router          *mux.Router
-	log             *log.Helper
+	lis     net.Listener
+	network string
+	address string
+	timeout time.Duration
+	router  *mux.Router
+	log     *log.Helper
 }
 
 // NewServer creates a HTTP server by options.
 func NewServer(opts ...ServerOption) *Server {
 	srv := &Server{
-		network:         "tcp",
-		address:         ":0",
-		timeout:         time.Second,
-		requestDecoder:  defaultRequestDecoder,
-		responseEncoder: defaultResponseEncoder,
-		errorEncoder:    defaultErrorEncoder,
-		middleware:      recovery.Recovery(),
-		log:             log.NewHelper(loggerName, log.DefaultLogger),
+		network: "tcp",
+		address: ":0",
+		timeout: time.Second,
+		log:     log.NewHelper(loggerName, log.DefaultLogger),
 	}
 	for _, o := range opts {
 		o(srv)
@@ -110,43 +77,28 @@ func NewServer(opts ...ServerOption) *Server {
 	return srv
 }
 
-// RouteGroup returns a new route group for the URL path prefix.
-func (s *Server) RouteGroup(prefix string) *RouteGroup {
-	return &RouteGroup{prefix: prefix, router: s.router}
-}
-
 // 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)
 }
 
+// HandlePrefix registers a new route with a matcher for the URL path prefix.
+func (s *Server) HandlePrefix(prefix string, h http.Handler) {
+	s.router.PathPrefix(prefix).Handler(h)
+}
+
 // HandleFunc registers a new route with a matcher for the URL path.
 func (s *Server) HandleFunc(path string, h http.HandlerFunc) {
 	s.router.HandleFunc(path, h)
 }
 
-// PrefixHanlde  registers a new route with a matcher for the URL path prefix.
-func (s *Server) PrefixHanlde(prefix string, h http.Handler) {
-	s.router.PathPrefix(prefix).Handler(h)
-}
-
 // 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 := context.WithTimeout(req.Context(), s.timeout)
 	defer cancel()
-	ctx = transport.NewContext(ctx, transport.Transport{Kind: "HTTP"})
+	ctx = transport.NewContext(ctx, transport.Transport{Kind: transport.KindHTTP})
 	ctx = NewServerContext(ctx, ServerInfo{Request: req, Response: res})
-
-	h := func(ctx context.Context, req interface{}) (interface{}, error) {
-		s.router.ServeHTTP(res, req.(*http.Request))
-		return res, nil
-	}
-	if s.middleware != nil {
-		h = s.middleware(h)
-	}
-	if _, err := h(ctx, req.WithContext(ctx)); err != nil {
-		s.errorEncoder(res, req, err)
-	}
+	s.router.ServeHTTP(res, req.WithContext(ctx))
 }
 
 // Endpoint return a real address to registry endpoint.
diff --git a/transport/http/service.go b/transport/http/service.go
deleted file mode 100644
index 3fa3595f..00000000
--- a/transport/http/service.go
+++ /dev/null
@@ -1,49 +0,0 @@
-package http
-
-import (
-	"context"
-	"net/http"
-)
-
-// SupportPackageIsVersion1 These constants should not be referenced from any other code.
-const SupportPackageIsVersion1 = true
-
-type methodHandler func(srv interface{}, ctx context.Context, req *http.Request, dec func(interface{}) error) (out interface{}, err error)
-
-// MethodDesc represents a Proto service's method specification.
-type MethodDesc struct {
-	Path    string
-	Method  string
-	Handler methodHandler
-}
-
-// ServiceDesc represents a Proto service's specification.
-type ServiceDesc struct {
-	ServiceName string
-	Methods     []MethodDesc
-	Metadata    interface{}
-}
-
-// ServiceRegistrar wraps a single method that supports service registration.
-type ServiceRegistrar interface {
-	RegisterService(desc *ServiceDesc, impl interface{})
-}
-
-// RegisterService .
-func (s *Server) RegisterService(desc *ServiceDesc, impl interface{}) {
-	for _, m := range desc.Methods {
-		h := m.Handler
-		s.router.HandleFunc(m.Path, func(res http.ResponseWriter, req *http.Request) {
-			out, err := h(impl, req.Context(), req, func(v interface{}) error {
-				return s.requestDecoder(req, v)
-			})
-			if err != nil {
-				s.errorEncoder(res, req, err)
-				return
-			}
-			if err := s.responseEncoder(res, req, out); err != nil {
-				s.errorEncoder(res, req, err)
-			}
-		}).Methods(m.Method)
-	}
-}
diff --git a/transport/transport.go b/transport/transport.go
index 64e74734..91ba7d48 100644
--- a/transport/transport.go
+++ b/transport/transport.go
@@ -17,9 +17,18 @@ type Server interface {
 
 // Transport is transport context value.
 type Transport struct {
-	Kind string
+	Kind Kind
 }
 
+// Kind defines the type of Transport
+type Kind string
+
+// Defines a set of transport kind
+const (
+	KindGRPC Kind = "gRPC"
+	KindHTTP Kind = "HTTP"
+)
+
 type transportKey struct{}
 
 // NewContext returns a new Context that carries value.


Test patch:
diff --git a/cmd/protoc-gen-go-http/internal/testproto/echo_service.pb.go b/cmd/protoc-gen-go-http/internal/testproto/echo_service.pb.go
index 5820c378..f8438f70 100644
--- a/cmd/protoc-gen-go-http/internal/testproto/echo_service.pb.go
+++ b/cmd/protoc-gen-go-http/internal/testproto/echo_service.pb.go
@@ -491,7 +491,7 @@ var file_echo_service_proto_rawDesc = []byte{
 	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, 0xbc, 0x04, 0x0a, 0x0b, 0x45, 0x63, 0x68, 0x6f, 0x53, 0x65,
+	0x44, 0x45, 0x4f, 0x10, 0x06, 0x32, 0xcc, 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, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70,
@@ -513,13 +513,22 @@ var file_echo_service_proto_rawDesc = []byte{
 	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, 0x61, 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, 0x1f,
-	0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x2a, 0x17, 0x2f, 0x76, 0x31, 0x2f, 0x65, 0x78, 0x61, 0x6d,
-	0x70, 0x6c, 0x65, 0x2f, 0x65, 0x63, 0x68, 0x6f, 0x5f, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12,
+	0x65, 0x63, 0x68, 0x6f, 0x5f, 0x62, 0x6f, 0x64, 0x79, 0x3a, 0x01, 0x2a, 0x12, 0x82, 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, 0x2c, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x26, 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, 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,
@@ -569,14 +578,16 @@ var file_echo_service_proto_depIdxs = []int32{
 	7,  // 6: testproto.DynamicMessageUpdate.update_mask:type_name -> google.protobuf.FieldMask
 	2,  // 7: testproto.EchoService.Echo:input_type -> testproto.SimpleMessage
 	2,  // 8: testproto.EchoService.EchoBody:input_type -> testproto.SimpleMessage
-	2,  // 9: testproto.EchoService.EchoDelete:input_type -> testproto.SimpleMessage
-	4,  // 10: testproto.EchoService.EchoPatch:input_type -> testproto.DynamicMessageUpdate
-	2,  // 11: testproto.EchoService.Echo:output_type -> testproto.SimpleMessage
-	2,  // 12: testproto.EchoService.EchoBody:output_type -> testproto.SimpleMessage
-	2,  // 13: testproto.EchoService.EchoDelete:output_type -> testproto.SimpleMessage
-	4,  // 14: testproto.EchoService.EchoPatch:output_type -> testproto.DynamicMessageUpdate
-	11, // [11:15] is the sub-list for method output_type
-	7,  // [7:11] is the sub-list for method input_type
+	4,  // 9: testproto.EchoService.EchoResponseBody:input_type -> testproto.DynamicMessageUpdate
+	2,  // 10: testproto.EchoService.EchoDelete:input_type -> testproto.SimpleMessage
+	4,  // 11: testproto.EchoService.EchoPatch:input_type -> testproto.DynamicMessageUpdate
+	2,  // 12: testproto.EchoService.Echo:output_type -> testproto.SimpleMessage
+	2,  // 13: testproto.EchoService.EchoBody:output_type -> testproto.SimpleMessage
+	4,  // 14: testproto.EchoService.EchoResponseBody:output_type -> testproto.DynamicMessageUpdate
+	2,  // 15: testproto.EchoService.EchoDelete:output_type -> testproto.SimpleMessage
+	4,  // 16: testproto.EchoService.EchoPatch:output_type -> testproto.DynamicMessageUpdate
+	12, // [12:17] is the sub-list for method output_type
+	7,  // [7:12] is the sub-list for method input_type
 	7,  // [7:7] is the sub-list for extension type_name
 	7,  // [7:7] is the sub-list for extension extendee
 	0,  // [0:7] is the sub-list for field type_name
diff --git a/cmd/protoc-gen-go-http/internal/testproto/echo_service.proto b/cmd/protoc-gen-go-http/internal/testproto/echo_service.proto
index adee8c6b..a0a6b743 100644
--- a/cmd/protoc-gen-go-http/internal/testproto/echo_service.proto
+++ b/cmd/protoc-gen-go-http/internal/testproto/echo_service.proto
@@ -85,10 +85,17 @@ service EchoService {
 			body: "*"
 		};
 	}
+	// EchoResponseBody method receives a simple message and returns it.
+	rpc EchoResponseBody(DynamicMessageUpdate) returns (DynamicMessageUpdate) {
+		option (google.api.http) = {
+			post: "/v1/example/echo_response_body"
+			response_body: "body"
+		};
+	}
 	// EchoDelete method receives a simple message and returns it.
 	rpc EchoDelete(SimpleMessage) returns (SimpleMessage) {
 		option (google.api.http) = {
-			delete: "/v1/example/echo_delete"
+			delete: "/v1/example/echo_delete/{id}/{num}"
 		};
 	}
 	// EchoPatch method receives a NonStandardUpdateRequest and returns it.
diff --git a/cmd/protoc-gen-go-http/internal/testproto/echo_service_errors.pb.go b/cmd/protoc-gen-go-http/internal/testproto/echo_service_errors.pb.go
new file mode 100644
index 00000000..715d8d0d
--- /dev/null
+++ b/cmd/protoc-gen-go-http/internal/testproto/echo_service_errors.pb.go
@@ -0,0 +1,11 @@
+// Code generated by protoc-gen-go-errors. DO NOT EDIT.
+
+package testproto
+
+import (
+	errors "github.com/go-kratos/kratos/v2/errors"
+)
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the kratos package it is being compiled against.
+const _ = errors.SupportPackageIsVersion1
diff --git a/cmd/protoc-gen-go-http/internal/testproto/echo_service_grpc.pb.go b/cmd/protoc-gen-go-http/internal/testproto/echo_service_grpc.pb.go
index ae03788d..23374bdc 100644
--- a/cmd/protoc-gen-go-http/internal/testproto/echo_service_grpc.pb.go
+++ b/cmd/protoc-gen-go-http/internal/testproto/echo_service_grpc.pb.go
@@ -25,6 +25,8 @@ type EchoServiceClient interface {
 	Echo(ctx context.Context, in *SimpleMessage, opts ...grpc.CallOption) (*SimpleMessage, error)
 	// EchoBody method receives a simple message and returns it.
 	EchoBody(ctx context.Context, in *SimpleMessage, opts ...grpc.CallOption) (*SimpleMessage, error)
+	// EchoResponseBody method receives a simple message and returns it.
+	EchoResponseBody(ctx context.Context, in *DynamicMessageUpdate, opts ...grpc.CallOption) (*DynamicMessageUpdate, error)
 	// EchoDelete method receives a simple message and returns it.
 	EchoDelete(ctx context.Context, in *SimpleMessage, opts ...grpc.CallOption) (*SimpleMessage, error)
 	// EchoPatch method receives a NonStandardUpdateRequest and returns it.
@@ -57,6 +59,15 @@ func (c *echoServiceClient) EchoBody(ctx context.Context, in *SimpleMessage, opt
 	return out, nil
 }
 
+func (c *echoServiceClient) EchoResponseBody(ctx context.Context, in *DynamicMessageUpdate, opts ...grpc.CallOption) (*DynamicMessageUpdate, error) {
+	out := new(DynamicMessageUpdate)
+	err := c.cc.Invoke(ctx, "/testproto.EchoService/EchoResponseBody", in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
 func (c *echoServiceClient) EchoDelete(ctx context.Context, in *SimpleMessage, opts ...grpc.CallOption) (*SimpleMessage, error) {
 	out := new(SimpleMessage)
 	err := c.cc.Invoke(ctx, "/testproto.EchoService/EchoDelete", in, out, opts...)
@@ -86,6 +97,8 @@ type EchoServiceServer interface {
 	Echo(context.Context, *SimpleMessage) (*SimpleMessage, error)
 	// EchoBody method receives a simple message and returns it.
 	EchoBody(context.Context, *SimpleMessage) (*SimpleMessage, error)
+	// EchoResponseBody method receives a simple message and returns it.
+	EchoResponseBody(context.Context, *DynamicMessageUpdate) (*DynamicMessageUpdate, error)
 	// EchoDelete method receives a simple message and returns it.
 	EchoDelete(context.Context, *SimpleMessage) (*SimpleMessage, error)
 	// EchoPatch method receives a NonStandardUpdateRequest and returns it.
@@ -103,6 +116,9 @@ func (UnimplementedEchoServiceServer) Echo(context.Context, *SimpleMessage) (*Si
 func (UnimplementedEchoServiceServer) EchoBody(context.Context, *SimpleMessage) (*SimpleMessage, error) {
 	return nil, status.Errorf(codes.Unimplemented, "method EchoBody not implemented")
 }
+func (UnimplementedEchoServiceServer) EchoResponseBody(context.Context, *DynamicMessageUpdate) (*DynamicMessageUpdate, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method EchoResponseBody not implemented")
+}
 func (UnimplementedEchoServiceServer) EchoDelete(context.Context, *SimpleMessage) (*SimpleMessage, error) {
 	return nil, status.Errorf(codes.Unimplemented, "method EchoDelete not implemented")
 }
@@ -158,6 +174,24 @@ func _EchoService_EchoBody_Handler(srv interface{}, ctx context.Context, dec fun
 	return interceptor(ctx, in, info, handler)
 }
 
+func _EchoService_EchoResponseBody_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(DynamicMessageUpdate)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(EchoServiceServer).EchoResponseBody(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/testproto.EchoService/EchoResponseBody",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(EchoServiceServer).EchoResponseBody(ctx, req.(*DynamicMessageUpdate))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
 func _EchoService_EchoDelete_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
 	in := new(SimpleMessage)
 	if err := dec(in); err != nil {
@@ -209,6 +243,10 @@ var EchoService_ServiceDesc = grpc.ServiceDesc{
 			MethodName: "EchoBody",
 			Handler:    _EchoService_EchoBody_Handler,
 		},
+		{
+			MethodName: "EchoResponseBody",
+			Handler:    _EchoService_EchoResponseBody_Handler,
+		},
 		{
 			MethodName: "EchoDelete",
 			Handler:    _EchoService_EchoDelete_Handler,
diff --git a/cmd/protoc-gen-go-http/internal/testproto/echo_service_http.pb.go b/cmd/protoc-gen-go-http/internal/testproto/echo_service_http.pb.go
index ccdae48b..cba12b92 100644
--- a/cmd/protoc-gen-go-http/internal/testproto/echo_service_http.pb.go
+++ b/cmd/protoc-gen-go-http/internal/testproto/echo_service_http.pb.go
@@ -5,15 +5,21 @@ package testproto
 import (
 	context "context"
 	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.
-// context./http.
+var _ = new(http.Request)
+var _ = new(context.Context)
+var _ = binding.MapProto
+var _ = mux.NewRouter
+
 const _ = http1.SupportPackageIsVersion1
 
-type EchoServiceHTTPServer interface {
+type EchoServiceHandler interface {
 	Echo(context.Context, *SimpleMessage) (*SimpleMessage, error)
 
 	EchoBody(context.Context, *SimpleMessage) (*SimpleMessage, error)
@@ -21,195 +27,262 @@ type EchoServiceHTTPServer interface {
 	EchoDelete(context.Context, *SimpleMessage) (*SimpleMessage, error)
 
 	EchoPatch(context.Context, *DynamicMessageUpdate) (*DynamicMessageUpdate, error)
-}
-
-func RegisterEchoServiceHTTPServer(s http1.ServiceRegistrar, srv EchoServiceHTTPServer) {
-	s.RegisterService(&_HTTP_EchoService_serviceDesc, srv)
-}
-
-func _HTTP_EchoService_Echo_0(srv interface{}, ctx context.Context, req *http.Request, dec func(interface{}) error) (interface{}, error) {
-	var in SimpleMessage
-
-	if err := http1.BindVars(req, &in); err != nil {
-		return nil, err
-	}
-
-	if err := http1.BindForm(req, &in); err != nil {
-		return nil, err
-	}
-
-	out, err := srv.(EchoServiceServer).Echo(ctx, &in)
-	if err != nil {
-		return nil, err
-	}
-	return out, nil
-}
-
-func _HTTP_EchoService_Echo_1(srv interface{}, ctx context.Context, req *http.Request, dec func(interface{}) error) (interface{}, error) {
-	var in SimpleMessage
-
-	if err := http1.BindVars(req, &in); err != nil {
-		return nil, err
-	}
 
-	if err := http1.BindForm(req, &in); err != nil {
-		return nil, err
-	}
-
-	out, err := srv.(EchoServiceServer).Echo(ctx, &in)
-	if err != nil {
-		return nil, err
-	}
-	return out, nil
+	EchoResponseBody(context.Context, *DynamicMessageUpdate) (*DynamicMessageUpdate, error)
 }
 
-func _HTTP_EchoService_Echo_2(srv interface{}, ctx context.Context, req *http.Request, dec func(interface{}) error) (interface{}, error) {
-	var in SimpleMessage
-
-	if err := http1.BindVars(req, &in); err != nil {
-		return nil, err
-	}
-
-	if err := http1.BindForm(req, &in); err != nil {
-		return nil, err
-	}
-
-	out, err := srv.(EchoServiceServer).Echo(ctx, &in)
-	if err != nil {
-		return nil, err
+func NewEchoServiceHandler(srv EchoServiceHandler, opts ...http1.HandleOption) http.Handler {
+	h := http1.DefaultHandleOptions()
+	for _, o := range opts {
+		o(&h)
 	}
-	return out, nil
-}
-
-func _HTTP_EchoService_Echo_3(srv interface{}, ctx context.Context, req *http.Request, dec func(interface{}) error) (interface{}, error) {
-	var in SimpleMessage
-
-	if err := http1.BindVars(req, &in); err != nil {
-		return nil, err
-	}
-
-	if err := http1.BindForm(req, &in); err != nil {
-		return nil, err
-	}
-
-	out, err := srv.(EchoServiceServer).Echo(ctx, &in)
-	if err != nil {
-		return nil, err
-	}
-	return out, nil
-}
-
-func _HTTP_EchoService_Echo_4(srv interface{}, ctx context.Context, req *http.Request, dec func(interface{}) error) (interface{}, error) {
-	var in SimpleMessage
-
-	if err := http1.BindVars(req, &in); err != nil {
-		return nil, err
-	}
-
-	if err := http1.BindForm(req, &in); err != nil {
-		return nil, err
-	}
-
-	out, err := srv.(EchoServiceServer).Echo(ctx, &in)
-	if err != nil {
-		return nil, err
-	}
-	return out, nil
-}
-
-func _HTTP_EchoService_EchoBody_0(srv interface{}, ctx context.Context, req *http.Request, dec func(interface{}) error) (interface{}, error) {
-	var in SimpleMessage
-
-	if err := dec(&in); err != nil {
-		return nil, err
-	}
-
-	out, err := srv.(EchoServiceServer).EchoBody(ctx, &in)
-	if err != nil {
-		return nil, err
-	}
-	return out, nil
-}
-
-func _HTTP_EchoService_EchoDelete_0(srv interface{}, ctx context.Context, req *http.Request, dec func(interface{}) error) (interface{}, error) {
-	var in SimpleMessage
-
-	if err := http1.BindForm(req, &in); err != nil {
-		return nil, err
-	}
-
-	out, err := srv.(EchoServiceServer).EchoDelete(ctx, &in)
-	if err != nil {
-		return nil, err
-	}
-	return out, nil
-}
-
-func _HTTP_EchoService_EchoPatch_0(srv interface{}, ctx context.Context, req *http.Request, dec func(interface{}) error) (interface{}, error) {
-	var in DynamicMessageUpdate
-
-	if err := dec(in.Body); err != nil {
-		return nil, err
-	}
-
-	out, err := srv.(EchoServiceServer).EchoPatch(ctx, &in)
-	if err != nil {
-		return nil, err
-	}
-	return out, nil
-}
-
-var _HTTP_EchoService_serviceDesc = http1.ServiceDesc{
-	ServiceName: "testproto.EchoService",
-	Methods: []http1.MethodDesc{
-
-		{
-			Path:    "/v1/example/echo/{id}/{num}",
-			Method:  "GET",
-			Handler: _HTTP_EchoService_Echo_0,
-		},
-
-		{
-			Path:    "/v1/example/echo/{id}/{num}/{lang}",
-			Method:  "GET",
-			Handler: _HTTP_EchoService_Echo_1,
-		},
-
-		{
-			Path:    "/v1/example/echo1/{id}/{line_num}/{status.note}",
-			Method:  "GET",
-			Handler: _HTTP_EchoService_Echo_2,
-		},
-
-		{
-			Path:    "/v1/example/echo2/{no.note}",
-			Method:  "GET",
-			Handler: _HTTP_EchoService_Echo_3,
-		},
-
-		{
-			Path:    "/v1/example/echo/{id}",
-			Method:  "POST",
-			Handler: _HTTP_EchoService_Echo_4,
-		},
-
-		{
-			Path:    "/v1/example/echo_body",
-			Method:  "POST",
-			Handler: _HTTP_EchoService_EchoBody_0,
-		},
-
-		{
-			Path:    "/v1/example/echo_delete",
-			Method:  "DELETE",
-			Handler: _HTTP_EchoService_EchoDelete_0,
-		},
-
-		{
-			Path:    "/v1/example/echo_patch",
-			Method:  "PATCH",
-			Handler: _HTTP_EchoService_EchoPatch_0,
-		},
-	},
-	Metadata: "echo_service.proto",
+	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.MapProto(&in, mux.Vars(r)); 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)
+		}
+		out, err := next(r.Context(), &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.MapProto(&in, mux.Vars(r)); 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)
+		}
+		out, err := next(r.Context(), &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.MapProto(&in, mux.Vars(r)); 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)
+		}
+		out, err := next(r.Context(), &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.MapProto(&in, mux.Vars(r)); 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)
+		}
+		out, err := next(r.Context(), &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.MapProto(&in, mux.Vars(r)); 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)
+		}
+		out, err := next(r.Context(), &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)
+		}
+		out, err := next(r.Context(), &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)
+		}
+		out, err := next(r.Context(), &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.MapProto(&in, mux.Vars(r)); 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)
+		}
+		out, err := next(r.Context(), &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)
+		}
+		out, err := next(r.Context(), &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
 }
diff --git a/cmd/protoc-gen-go-http/internal/testproto/echo_service_test.go b/cmd/protoc-gen-go-http/internal/testproto/echo_service_test.go
new file mode 100644
index 00000000..8b00e312
--- /dev/null
+++ b/cmd/protoc-gen-go-http/internal/testproto/echo_service_test.go
@@ -0,0 +1,212 @@
+package testproto
+
+import (
+	"bytes"
+	context "context"
+	"fmt"
+	"net"
+	http "net/http"
+	"testing"
+	"time"
+
+	"github.com/go-kratos/kratos/v2/encoding"
+	tr "github.com/go-kratos/kratos/v2/transport/http"
+	_struct "github.com/golang/protobuf/ptypes/struct"
+)
+
+type echoService struct {
+}
+
+func (s *echoService) Echo(ctx context.Context, m *SimpleMessage) (*SimpleMessage, error) {
+	return m, nil
+}
+
+func (s *echoService) EchoBody(ctx context.Context, m *SimpleMessage) (*SimpleMessage, error) {
+	return m, nil
+}
+
+func (s *echoService) EchoDelete(ctx context.Context, m *SimpleMessage) (*SimpleMessage, error) {
+	return m, nil
+}
+
+func (s *echoService) EchoPatch(ctx context.Context, m *DynamicMessageUpdate) (*DynamicMessageUpdate, error) {
+	return m, nil
+}
+
+func (s *echoService) EchoResponseBody(ctx context.Context, m *DynamicMessageUpdate) (*DynamicMessageUpdate, error) {
+	return m, nil
+}
+
+type echoClient struct {
+	baseURL string
+	client  *http.Client
+}
+
+// post: /v1/example/echo/{id}
+func (c *echoClient) Echo(ctx context.Context, in *SimpleMessage) (out *SimpleMessage, err error) {
+	codec := encoding.GetCodec("json")
+	data, err := codec.Marshal(in)
+	if err != nil {
+		return
+	}
+	req, err := http.NewRequest("POST", fmt.Sprintf("%s/v1/example/echo/%s", c.baseURL, in.Id), bytes.NewReader(data))
+	if err != nil {
+		return
+	}
+	req.Header.Set("content-type", "application/json")
+	out = new(SimpleMessage)
+	if err = tr.Do(c.client, req, out); err != nil {
+		return
+	}
+	return
+}
+
+// post: /v1/example/echo_body
+func (c *echoClient) EchoBody(ctx context.Context, in *SimpleMessage) (out *SimpleMessage, err error) {
+	codec := encoding.GetCodec("json")
+	data, err := codec.Marshal(in)
+	if err != nil {
+		return
+	}
+	req, err := http.NewRequest("POST", fmt.Sprintf("%s/v1/example/echo_body", c.baseURL), bytes.NewReader(data))
+	if err != nil {
+		return
+	}
+	req.Header.Set("content-type", "application/json")
+	out = new(SimpleMessage)
+	if err = tr.Do(c.client, req, out); err != nil {
+		return
+	}
+	return
+}
+
+// delete: /v1/example/echo_delete/{id}/{num}
+func (c *echoClient) EchoDelete(ctx context.Context, in *SimpleMessage) (out *SimpleMessage, err error) {
+	req, err := http.NewRequest("DELETE", fmt.Sprintf("%s/v1/example/echo_delete/%s/%d", c.baseURL, in.Id, in.Num), nil)
+	if err != nil {
+		return
+	}
+	out = new(SimpleMessage)
+	if err = tr.Do(c.client, req, out); err != nil {
+		return
+	}
+	return
+}
+
+// patch: /v1/example/echo_patch
+func (c *echoClient) EchoPatch(ctx context.Context, in *DynamicMessageUpdate) (out *DynamicMessageUpdate, err error) {
+	codec := encoding.GetCodec("json")
+	data, err := codec.Marshal(in.Body)
+	if err != nil {
+		return
+	}
+	req, err := http.NewRequest("PATCH", fmt.Sprintf("%s/v1/example/echo_patch", c.baseURL), bytes.NewReader(data))
+	if err != nil {
+		return
+	}
+	req.Header.Set("content-type", "application/json")
+	out = new(DynamicMessageUpdate)
+	if err = tr.Do(c.client, req, out); err != nil {
+		return
+	}
+	return
+}
+
+// post: /v1/example/echo_response_body
+func (c *echoClient) EchoResponseBody(ctx context.Context, in *DynamicMessageUpdate) (out *DynamicMessageUpdate, err error) {
+	codec := encoding.GetCodec("json")
+	data, err := codec.Marshal(in)
+	if err != nil {
+		return
+	}
+	req, err := http.NewRequest("POST", fmt.Sprintf("%s/v1/example/echo_response_body", c.baseURL), bytes.NewReader(data))
+	if err != nil {
+		return
+	}
+	req.Header.Set("content-type", "application/json")
+	out = new(DynamicMessageUpdate)
+	if err = tr.Do(c.client, req, out.Body); err != nil {
+		return
+	}
+	return
+}
+
+func TestEchoService(t *testing.T) {
+	s := &echoService{}
+	h := NewEchoServiceHandler(s)
+	srv := &http.Server{Addr: ":0", Handler: h}
+	lis, err := net.Listen("tcp", srv.Addr)
+	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("http://127.0.0.1:%d", addr.Port))
+	})
+	if err := srv.Serve(lis); err != nil && err != http.ErrServerClosed {
+		t.Fatal(err)
+	}
+}
+
+func testEchoClient(t *testing.T, baseURL string) {
+	var (
+		err error
+		in  = &SimpleMessage{Id: "test_id", Num: 100}
+		out = &SimpleMessage{}
+	)
+	check := func(in, out *SimpleMessage) {
+		if in.Id != out.Id || in.Num != out.Num {
+			t.Errorf("expected %s got %s", in, out)
+		}
+	}
+
+	cli := &echoClient{baseURL: baseURL, client: http.DefaultClient}
+
+	if out, err = cli.Echo(context.Background(), in); err != nil {
+		t.Fatal(err)
+	}
+	check(in, out)
+
+	if out, err = cli.EchoBody(context.Background(), in); err != nil {
+		t.Fatal(err)
+	}
+	check(in, out)
+
+	if out, err = cli.EchoDelete(context.Background(), in); err != nil {
+		t.Fatal(err)
+	}
+	check(in, out)
+
+	var (
+		din = &DynamicMessageUpdate{Body: &DynamicMessage{
+			ValueField: &_struct.Value{Kind: &_struct.Value_StringValue{StringValue: "test"}},
+		}}
+		dout *DynamicMessageUpdate
+	)
+	if dout, err = cli.EchoPatch(context.Background(), din); err != nil {
+		t.Fatal(err)
+	}
+	if din.Body.ValueField.GetStringValue() != dout.Body.ValueField.GetStringValue() {
+		t.Errorf("EchoPatch expected %s got %s", din, dout)
+	}
+	if dout, err = cli.EchoResponseBody(context.Background(), din); err != nil {
+		t.Fatal(err)
+	}
+	if din.Body.ValueField.GetStringValue() != dout.Body.ValueField.GetStringValue() {
+		t.Errorf("EchoResponseBody expected %s got %s", din, dout)
+	}
+}
+
+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)
+	}
+	if err := codec.Unmarshal(data, out); err != nil {
+		t.Fatal(err)
+	}
+}
diff --git a/cmd/protoc-gen-go-http/internal/testproto/stream.pb.go b/cmd/protoc-gen-go-http/internal/testproto/stream.pb.go
new file mode 100644
index 00000000..a2fed91d
--- /dev/null
+++ b/cmd/protoc-gen-go-http/internal/testproto/stream.pb.go
@@ -0,0 +1,91 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// 	protoc-gen-go v1.25.0
+// 	protoc        v3.13.0
+// source: stream.proto
+
+package testproto
+
+import (
+	proto "github.com/golang/protobuf/proto"
+	empty "github.com/golang/protobuf/ptypes/empty"
+	_ "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"
+	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)
+)
+
+// This is a compile-time assertion that a sufficiently up-to-date version
+// of the legacy proto package is being used.
+const _ = proto.ProtoPackageIsVersion4
+
+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{}{
+	(*empty.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/cmd/protoc-gen-go-http/internal/testproto/stream.proto b/cmd/protoc-gen-go-http/internal/testproto/stream.proto
new file mode 100644
index 00000000..f343c8f4
--- /dev/null
+++ b/cmd/protoc-gen-go-http/internal/testproto/stream.proto
@@ -0,0 +1,15 @@
+syntax = "proto3";
+option go_package = "github.com/go-kratos/kratos/cmd/protoc-gen-go-http/internal/testproto;testproto";
+package testproto;
+
+import "google/api/annotations.proto";
+import "google/api/httpbody.proto";
+import "google/protobuf/empty.proto";
+
+service StreamService {
+	rpc Download(google.protobuf.Empty) returns (stream google.api.HttpBody) {
+		option (google.api.http) = {
+		  get : "/v1/example/download"
+		};
+	}
+}
diff --git a/cmd/protoc-gen-go-http/internal/testproto/stream_grpc.pb.go b/cmd/protoc-gen-go-http/internal/testproto/stream_grpc.pb.go
new file mode 100644
index 00000000..ed744d6b
--- /dev/null
+++ b/cmd/protoc-gen-go-http/internal/testproto/stream_grpc.pb.go
@@ -0,0 +1,130 @@
+// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
+
+package testproto
+
+import (
+	context "context"
+	empty "github.com/golang/protobuf/ptypes/empty"
+	httpbody "google.golang.org/genproto/googleapis/api/httpbody"
+	grpc "google.golang.org/grpc"
+	codes "google.golang.org/grpc/codes"
+	status "google.golang.org/grpc/status"
+)
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the grpc package it is being compiled against.
+// Requires gRPC-Go v1.32.0 or later.
+const _ = grpc.SupportPackageIsVersion7
+
+// StreamServiceClient is the client API for StreamService service.
+//
+// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
+type StreamServiceClient interface {
+	Download(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (StreamService_DownloadClient, error)
+}
+
+type streamServiceClient struct {
+	cc grpc.ClientConnInterface
+}
+
+func NewStreamServiceClient(cc grpc.ClientConnInterface) StreamServiceClient {
+	return &streamServiceClient{cc}
+}
+
+func (c *streamServiceClient) Download(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (StreamService_DownloadClient, error) {
+	stream, err := c.cc.NewStream(ctx, &StreamService_ServiceDesc.Streams[0], "/testproto.StreamService/Download", opts...)
+	if err != nil {
+		return nil, err
+	}
+	x := &streamServiceDownloadClient{stream}
+	if err := x.ClientStream.SendMsg(in); err != nil {
+		return nil, err
+	}
+	if err := x.ClientStream.CloseSend(); err != nil {
+		return nil, err
+	}
+	return x, nil
+}
+
+type StreamService_DownloadClient interface {
+	Recv() (*httpbody.HttpBody, error)
+	grpc.ClientStream
+}
+
+type streamServiceDownloadClient struct {
+	grpc.ClientStream
+}
+
+func (x *streamServiceDownloadClient) Recv() (*httpbody.HttpBody, error) {
+	m := new(httpbody.HttpBody)
+	if err := x.ClientStream.RecvMsg(m); err != nil {
+		return nil, err
+	}
+	return m, nil
+}
+
+// StreamServiceServer is the server API for StreamService service.
+// All implementations must embed UnimplementedStreamServiceServer
+// for forward compatibility
+type StreamServiceServer interface {
+	Download(*empty.Empty, StreamService_DownloadServer) error
+	mustEmbedUnimplementedStreamServiceServer()
+}
+
+// UnimplementedStreamServiceServer must be embedded to have forward compatible implementations.
+type UnimplementedStreamServiceServer struct {
+}
+
+func (UnimplementedStreamServiceServer) Download(*empty.Empty, StreamService_DownloadServer) error {
+	return status.Errorf(codes.Unimplemented, "method Download not implemented")
+}
+func (UnimplementedStreamServiceServer) mustEmbedUnimplementedStreamServiceServer() {}
+
+// UnsafeStreamServiceServer may be embedded to opt out of forward compatibility for this service.
+// Use of this interface is not recommended, as added methods to StreamServiceServer will
+// result in compilation errors.
+type UnsafeStreamServiceServer interface {
+	mustEmbedUnimplementedStreamServiceServer()
+}
+
+func RegisterStreamServiceServer(s grpc.ServiceRegistrar, srv StreamServiceServer) {
+	s.RegisterService(&StreamService_ServiceDesc, srv)
+}
+
+func _StreamService_Download_Handler(srv interface{}, stream grpc.ServerStream) error {
+	m := new(empty.Empty)
+	if err := stream.RecvMsg(m); err != nil {
+		return err
+	}
+	return srv.(StreamServiceServer).Download(m, &streamServiceDownloadServer{stream})
+}
+
+type StreamService_DownloadServer interface {
+	Send(*httpbody.HttpBody) error
+	grpc.ServerStream
+}
+
+type streamServiceDownloadServer struct {
+	grpc.ServerStream
+}
+
+func (x *streamServiceDownloadServer) Send(m *httpbody.HttpBody) error {
+	return x.ServerStream.SendMsg(m)
+}
+
+// StreamService_ServiceDesc is the grpc.ServiceDesc for StreamService service.
+// It's only intended for direct use with grpc.RegisterService,
+// and not to be introspected or modified (even as a copy)
+var StreamService_ServiceDesc = grpc.ServiceDesc{
+	ServiceName: "testproto.StreamService",
+	HandlerType: (*StreamServiceServer)(nil),
+	Methods:     []grpc.MethodDesc{},
+	Streams: []grpc.StreamDesc{
+		{
+			StreamName:    "Download",
+			Handler:       _StreamService_Download_Handler,
+			ServerStreams: true,
+		},
+	},
+	Metadata: "stream.proto",
+}
diff --git a/cmd/protoc-gen-go-http/internal/testproto/stream_http.pb.go b/cmd/protoc-gen-go-http/internal/testproto/stream_http.pb.go
new file mode 100644
index 00000000..4112a458
--- /dev/null
+++ b/cmd/protoc-gen-go-http/internal/testproto/stream_http.pb.go
@@ -0,0 +1,33 @@
+// Code generated by protoc-gen-go-http. DO NOT EDIT.
+
+package testproto
+
+import (
+	context "context"
+	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 _ = binding.MapProto
+var _ = mux.NewRouter
+
+const _ = http1.SupportPackageIsVersion1
+
+type StreamServiceHandler interface {
+}
+
+func NewStreamServiceHandler(srv StreamServiceHandler, opts ...http1.HandleOption) http.Handler {
+	h := http1.DefaultHandleOptions()
+	for _, o := range opts {
+		o(&h)
+	}
+	r := mux.NewRouter()
+
+	return r
+}
diff --git a/transport/http/default_test.go b/transport/http/default_test.go
deleted file mode 100644
index 044f5be2..00000000
--- a/transport/http/default_test.go
+++ /dev/null
@@ -1,23 +0,0 @@
-package http
-
-import "testing"
-
-func TestSubtype(t *testing.T) {
-	tests := []struct {
-		input    string
-		expected string
-	}{
-		{"application/json", "json"},
-		{"application/json;", "json"},
-		{"application/json; charset=utf-8", "json"},
-		{"application/", ""},
-		{"application", ""},
-		{"foo", ""},
-		{"", ""},
-	}
-	for _, test := range tests {
-		if contentSubtype(test.input) != test.expected {
-			t.Errorf("expected %s got %s", test.expected, test.input)
-		}
-	}
-}
diff --git a/transport/http/handle_test.go b/transport/http/handle_test.go
new file mode 100644
index 00000000..e5049ffe
--- /dev/null
+++ b/transport/http/handle_test.go
@@ -0,0 +1,57 @@
+package http
+
+import (
+	"context"
+	"net/http"
+	"testing"
+
+	"github.com/gorilla/mux"
+)
+
+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 newGreeterHandler(srv *GreeterService, opts ...HandleOption) http.Handler {
+	h := DefaultHandleOptions()
+	for _, o := range opts {
+		o(&h)
+	}
+	r := mux.NewRouter()
+	r.HandleFunc("/helloworld", func(w http.ResponseWriter, r *http.Request) {
+		var in HelloRequest
+		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.SayHello(ctx, &in)
+		}
+		if h.Middleware != nil {
+			next = h.Middleware(next)
+		}
+		out, err := next(r.Context(), &in)
+		if err != nil {
+			h.Error(w, r, err)
+			return
+		}
+		if err := h.Encode(w, r, out); err != nil {
+			h.Error(w, r, err)
+		}
+	}).Methods("POST")
+	return r
+}
+
+func TestHandler(t *testing.T) {
+	s := &GreeterService{}
+	_ = newGreeterHandler(s)
+}
diff --git a/transport/http/server_test.go b/transport/http/server_test.go
index 06dbb311..2a9b70e6 100644
--- a/transport/http/server_test.go
+++ b/transport/http/server_test.go
@@ -21,16 +21,7 @@ func TestServer(t *testing.T) {
 		json.NewEncoder(w).Encode(data)
 	}
 	srv := NewServer()
-	group := srv.RouteGroup("/test")
-	{
-		group.GET("/", fn)
-		group.HEAD("/index", fn)
-		group.OPTIONS("/home", fn)
-		group.PUT("/products/{id}", fn)
-		group.POST("/products/{id}", fn)
-		group.PATCH("/products/{id}", fn)
-		group.DELETE("/products/{id}", fn)
-	}
+	srv.HandleFunc("/index", fn)
 
 	time.AfterFunc(time.Second, func() {
 		defer srv.Stop()
@@ -47,11 +38,11 @@ func testClient(t *testing.T, srv *Server) {
 		method string
 		path   string
 	}{
-		{"GET", "/test/"},
-		{"PUT", "/test/products/1"},
-		{"POST", "/test/products/2"},
-		{"PATCH", "/test/products/3"},
-		{"DELETE", "/test/products/4"},
+		{"GET", "/index"},
+		{"PUT", "/index"},
+		{"POST", "/index"},
+		{"PATCH", "/index"},
+		{"DELETE", "/index"},
 	}
 	client, err := NewClient(context.Background())
 	if err != nil {
diff --git a/transport/http/service_test.go b/transport/http/service_test.go
deleted file mode 100644
index 39e66d19..00000000
--- a/transport/http/service_test.go
+++ /dev/null
@@ -1,93 +0,0 @@
-package http
-
-import (
-	"bytes"
-	"context"
-	"encoding/json"
-	"fmt"
-	"net/http"
-	"testing"
-	"time"
-
-	"github.com/go-kratos/kratos/v2/internal/host"
-)
-
-type testRequest struct {
-	Name string `json:"name"`
-}
-type testReply struct {
-	Result string `json:"result"`
-}
-type testService struct{}
-
-func (s *testService) SayHello(ctx context.Context, req *testRequest) (*testReply, error) {
-	return &testReply{Result: req.Name}, nil
-}
-
-func TestService(t *testing.T) {
-	h := func(srv interface{}, ctx context.Context, req *http.Request, dec func(interface{}) error) (interface{}, error) {
-		var in testRequest
-		if err := dec(&in); err != nil {
-			return nil, err
-		}
-		out, err := srv.(*testService).SayHello(ctx, &in)
-		if err != nil {
-			return nil, err
-		}
-		return out, nil
-	}
-	sd := &ServiceDesc{
-		ServiceName: "helloworld.Greeter",
-		Methods: []MethodDesc{
-			{
-				Path:    "/helloworld",
-				Method:  "POST",
-				Handler: h,
-			},
-		},
-	}
-
-	svc := &testService{}
-	srv := NewServer()
-	srv.RegisterService(sd, svc)
-
-	time.AfterFunc(time.Second, func() {
-		defer srv.Stop()
-		testServiceClient(t, srv)
-	})
-
-	if err := srv.Start(); err != nil {
-		t.Fatal(err)
-	}
-}
-
-func testServiceClient(t *testing.T, srv *Server) {
-	client, err := NewClient(context.Background())
-	if err != nil {
-		t.Fatal(err)
-	}
-	port, ok := host.Port(srv.lis)
-	if !ok {
-		t.Fatalf("extract port error: %v", srv.lis)
-	}
-	var (
-		in  = testRequest{Name: "hello"}
-		out = testReply{}
-		url = fmt.Sprintf("http://127.0.0.1:%d/helloworld", port)
-	)
-	data, err := json.Marshal(in)
-	if err != nil {
-		t.Fatal(err)
-	}
-	req, err := http.NewRequest("POST", url, bytes.NewReader(data))
-	if err != nil {
-		t.Fatal(err)
-	}
-	req.Header.Set("content-type", "application/json")
-	if err := Do(client, req, &out); err != nil {
-		t.Fatal(err)
-	}
-	if out.Result != in.Name {
-		t.Fatalf("expected %s got %s", in.Name, out.Result)
-	}
-}