Repository: go-kratos/kratos
Pull request #1284 (2021-08-01 13:00:44)

feat: kratos tool support generate stream service code
<!--
🎉 Thanks for sending a pull request to Kratos! Here are some tips for you:

1. If this is your first time contributing to Kratos, please read our contribution guide: https://go-kratos.dev/en/docs/community/contribution/
2. Ensure you have added or ran the appropriate tests for your PR, such as using `go test ./...`
3. If the PR is unfinished, you may need mark it as a WIP(Work In Progress) PR or draft PR
4. Please use a conventional commits format title: `<type>[optional scope]: <description>`
    
    Some suggestion on <type>:

    fix: A bug fix
    feat: A new feature
    deps: Changes external dependencies
    break: Changes has break change

    docs: Documentation only changes
    refactor: A code change that neither fixes a bug nor adds a feature
    style: Changes that do not affect the meaning of the code (white-space, formatting, etc)
    test: Adding missing tests or correcting existing tests
    chore Daily work, examples, etc.
    ci: Changes to our CI configuration files and scripts
-->

#### Description (what this PR does / why we need it):
<!--
* The description should include the motivation for this PR or contrast this with previous behavior
-->


#### Which issue(s) this PR fixes:
<!--
* Automatically closes linked issue when PR is merged.

Usage: `fixes/resolves #<issue number>`, or `fixes/resolves (paste link of issue)`.
-->
fixes #

#### Other special notes for reviewer:

feat #796 

支持stream service代码生成


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

[![Impacted file tree graph](https://codecov.io/gh/go-kratos/kratos/pull/1284/graphs/tree.svg?width=650&height=150&src=pr&token=XwnmBTz78x&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos)](https://codecov.io/gh/go-kratos/kratos/pull/1284?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos)

```diff
@@           Coverage Diff           @@
##             main    #1284   +/-   ##
=======================================
  Coverage   51.27%   51.27%           
=======================================
  Files          53       53           
  Lines        2545     2545           
=======================================
  Hits         1305     1305           
  Misses       1097     1097           
  Partials      143      143           
```


| [Impacted Files](https://codecov.io/gh/go-kratos/kratos/pull/1284?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos) | Coverage Δ | |
|---|---|---|
| [middleware/selector/selector.go](https://codecov.io/gh/go-kratos/kratos/pull/1284/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos#diff-bWlkZGxld2FyZS9zZWxlY3Rvci9zZWxlY3Rvci5nbw==) | `92.45% <0.00%> (ø)` | |

------

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


Patch:
diff --git a/app.go b/app.go
index 0545f5f8..7ef71ce3 100644
--- a/app.go
+++ b/app.go
@@ -69,7 +69,12 @@ func (a *App) Version() string { return a.opts.version }
 func (a *App) Metadata() map[string]string { return a.opts.metadata }
 
 // Endpoint returns endpoints.
-func (a *App) Endpoint() []string { return a.instance.Endpoints }
+func (a *App) Endpoint() []string {
+	if a.instance == nil {
+		return []string{}
+	}
+	return a.instance.Endpoints
+}
 
 // Run executes all OnStart hooks registered with the application's Lifecycle.
 func (a *App) Run() error {
diff --git a/cmd/kratos/internal/proto/server/server.go b/cmd/kratos/internal/proto/server/server.go
index a30f3caa..f3970e7e 100644
--- a/cmd/kratos/internal/proto/server/server.go
+++ b/cmd/kratos/internal/proto/server/server.go
@@ -60,7 +60,8 @@ func run(cmd *cobra.Command, args []string) {
 			for _, e := range s.Elements {
 				r, ok := e.(*proto.RPC)
 				if ok {
-					cs.Methods = append(cs.Methods, &Method{Service: s.Name, Name: r.Name, Request: r.RequestType, Reply: r.ReturnsType})
+					cs.Methods = append(cs.Methods, &Method{Service: s.Name, Name: r.Name, Request: r.RequestType,
+						Reply: r.ReturnsType, Type: getMethodType(r.StreamsRequest, r.StreamsReturns)})
 				}
 			}
 			res = append(res, cs)
@@ -86,3 +87,16 @@ func run(cmd *cobra.Command, args []string) {
 		fmt.Println(to)
 	}
 }
+
+func getMethodType(streamsRequest, streamsReturns bool) MethodType {
+	if !streamsRequest && !streamsReturns {
+		return unaryType
+	} else if streamsRequest && streamsReturns {
+		return twoWayStreamsType
+	} else if streamsRequest {
+		return requestStreamsType
+	} else if streamsReturns {
+		return returnsStreamsType
+	}
+	return unaryType
+}
diff --git a/cmd/kratos/internal/proto/server/template.go b/cmd/kratos/internal/proto/server/template.go
index 343ef289..54ca8169 100644
--- a/cmd/kratos/internal/proto/server/template.go
+++ b/cmd/kratos/internal/proto/server/template.go
@@ -10,7 +10,12 @@ var serviceTemplate = `
 package service
 
 import (
+	{{- if .UseContext }}
     "context"
+	{{- end }}
+	{{- if .UseIO }}
+	"io"
+	{{- end }}
 
     pb "{{ .Package }}"
     {{- if .GoogleEmpty }}
@@ -28,18 +33,74 @@ func New{{ .Service }}Service() *{{ .Service }}Service {
 
 {{- $s1 := "google.protobuf.Empty" }}
 {{ range .Methods }}
+{{- if eq .Type 1 }}
 func (s *{{ .Service }}Service) {{ .Name }}(ctx context.Context, req {{ if eq .Request $s1 }}*emptypb.Empty{{ else }}*pb.{{ .Request }}{{ end }}) ({{ if eq .Reply $s1 }}*emptypb.Empty{{ else }}*pb.{{ .Reply }}{{ end }}, error) {
 	return {{ if eq .Reply $s1 }}&emptypb.Empty{}{{ else }}&pb.{{ .Reply }}{}{{ end }}, nil
 }
+
+{{- else if eq .Type 2 }}
+func (s *{{ .Service }}Service) {{ .Name }}(conn pb.{{ .Service }}_{{ .Name }}Server) error {
+	for {
+		req, err := conn.Recv()
+		if err == io.EOF {
+			return nil
+		}
+		if err != nil {
+			return err
+		}
+		
+		err = conn.Send(&pb.{{ .Reply }}{})
+		if err != nil {
+			return err
+		}
+	}
+}
+
+{{- else if eq .Type 3 }}
+func (s *{{ .Service }}Service) {{ .Name }}(conn pb.{{ .Service }}_{{ .Name }}Server) error {
+	for {
+		req, err := conn.Recv()
+		if err == io.EOF {
+			return conn.SendAndClose(&pb.{{ .Reply }}{})
+		}
+		if err != nil {
+			return err
+		}
+	}
+}
+
+{{- else if eq .Type 4 }}
+func (s *{{ .Service }}Service) {{ .Name }}(req {{ if eq .Request $s1 }}*emptypb.Empty{{ else }}*pb.{{ .Request }}{{ end }}, conn pb.{{ .Service }}_{{ .Name }}Server) error {
+	for {
+		err := conn.Send(&pb.{{ .Reply }}{})
+		if err != nil {
+			return err
+		}
+	}
+}
+
+{{- end }}
 {{- end }}
 `
 
+type MethodType uint8
+
+const (
+	unaryType          MethodType = 1
+	twoWayStreamsType  MethodType = 2
+	requestStreamsType MethodType = 3
+	returnsStreamsType MethodType = 4
+)
+
 // Service is a proto service.
 type Service struct {
 	Package     string
 	Service     string
 	Methods     []*Method
 	GoogleEmpty bool
+
+	UseIO      bool
+	UseContext bool
 }
 
 // Method is a proto method.
@@ -48,14 +109,24 @@ type Method struct {
 	Name    string
 	Request string
 	Reply   string
+
+	// type: unary or stream
+	Type MethodType
 }
 
 func (s *Service) execute() ([]byte, error) {
 	buf := new(bytes.Buffer)
 	for _, method := range s.Methods {
-		if method.Request == "google.protobuf.Empty" || method.Reply == "google.protobuf.Empty" {
+		if (method.Type == unaryType && (method.Request == "google.protobuf.Empty" || method.Reply == "google.protobuf.Empty")) ||
+			(method.Type == returnsStreamsType && method.Request == "google.protobuf.Empty") {
 			s.GoogleEmpty = true
 		}
+		if method.Type == twoWayStreamsType || method.Type == returnsStreamsType {
+			s.UseIO = true
+		}
+		if method.Type == unaryType {
+			s.UseContext = true
+		}
 	}
 	tmpl, err := template.New("service").Parse(serviceTemplate)
 	if err != nil {
diff --git a/examples/blog/Makefile b/examples/blog/Makefile
index 16bedc4d..ec133d4d 100644
--- a/examples/blog/Makefile
+++ b/examples/blog/Makefile
@@ -37,7 +37,7 @@ http:
 # generate errors code
 errors:
 	protoc --proto_path=. \
-           --proto_path=./third_party \
+           --proto_path=$(KRATOS)/third_party \
            --go_out=paths=source_relative:. \
            --go-errors_out=paths=source_relative:. \
            $(API_PROTO_FILES)
diff --git a/examples/go.mod b/examples/go.mod
index 8b0a6b84..21db4c99 100644
--- a/examples/go.mod
+++ b/examples/go.mod
@@ -10,10 +10,10 @@ require (
 	github.com/go-kratos/consul v0.1.4
 	github.com/go-kratos/etcd v0.1.0
 	github.com/go-kratos/gin v0.1.0
-	github.com/go-kratos/kratos/v2 v2.0.1
+	github.com/go-kratos/kratos/v2 v2.0.3
 	github.com/go-kratos/nacos v0.1.0
 	github.com/go-kratos/prometheus v0.0.0-20210522055322-137e29e7cf47
-	github.com/go-kratos/swagger-api v0.1.8
+	github.com/go-kratos/swagger-api v1.0.0
 	github.com/go-kratos/zookeeper v0.0.0-20210721021028-9e7498619b89
 	github.com/go-redis/redis/extra/redisotel v0.3.0
 	github.com/go-redis/redis/v8 v8.9.0
@@ -22,9 +22,9 @@ require (
 	github.com/gorilla/handlers v1.5.1
 	github.com/gorilla/mux v1.8.0
 	github.com/gorilla/websocket v1.4.2
+	github.com/grpc-ecosystem/grpc-gateway/v2 v2.5.0
 	github.com/hashicorp/consul/api v1.9.1
 	github.com/labstack/echo/v4 v4.3.0
-	github.com/longXboy/grpc-gateway/v2 v2.0.0-20210707031540-bd2d73d86cee
 	github.com/nacos-group/nacos-sdk-go v1.0.7
 	github.com/nicksnyder/go-i18n/v2 v2.1.2
 	github.com/prometheus/client_golang v1.11.0
@@ -32,10 +32,10 @@ require (
 	github.com/sirupsen/logrus v1.8.1
 	github.com/stretchr/testify v1.7.0
 	go.etcd.io/etcd/client/v3 v3.5.0
-	go.opentelemetry.io/otel v1.0.0-RC1
-	go.opentelemetry.io/otel/exporters/jaeger v1.0.0-RC1
-	go.opentelemetry.io/otel/sdk v1.0.0-RC1
-	go.opentelemetry.io/otel/trace v1.0.0-RC1
+	go.opentelemetry.io/otel v1.0.0-RC2
+	go.opentelemetry.io/otel/exporters/jaeger v1.0.0-RC2
+	go.opentelemetry.io/otel/sdk v1.0.0-RC2
+	go.opentelemetry.io/otel/trace v1.0.0-RC2
 	go.uber.org/zap v1.18.1
 	golang.org/x/text v0.3.6
 	google.golang.org/genproto v0.0.0-20210701191553-46259e63a0a9
diff --git a/examples/go.sum b/examples/go.sum
index 5b9627e9..0c54363f 100644
--- a/examples/go.sum
+++ b/examples/go.sum
@@ -180,6 +180,8 @@ github.com/go-kratos/etcd v0.1.0 h1:ewAfloZcpcg2L1QB1VLXYUMdvfUU0i3kAlToHrxTv2g=
 github.com/go-kratos/etcd v0.1.0/go.mod h1:RzWYbka3LjS0v2RbCtADN7d2Ng8eH774ehHlTGgbma4=
 github.com/go-kratos/gin v0.1.0 h1:yq5GfZnSNo8cOIqxqPE0FVNQ8fm++oKQBd3/rTTp4oI=
 github.com/go-kratos/gin v0.1.0/go.mod h1:KzE88+mV1QJdmiT+sU2D560ucOXnWZx+4m0OnVcSyzQ=
+github.com/go-kratos/grpc-gateway/v2 v2.0.0-20210804092615-bdc92eb5ce83 h1:YN1KsqX3mB8hkSrOLZv3xZaZh+lZ/L7QC00RATFLJSg=
+github.com/go-kratos/grpc-gateway/v2 v2.0.0-20210804092615-bdc92eb5ce83/go.mod h1:qV5/s5A7wfY7GkNzptcUFRGAG4Y8H4mlXpSrOkDEIk0=
 github.com/go-kratos/kratos/examples v0.0.0-20210718121552-f0d077a3286f/go.mod h1:R5ZvhHSeFgk+JynHp3yMSkjaATRA3FRatecP4kQibsg=
 github.com/go-kratos/nacos v0.1.0 h1:sUvTsgZ4u88YaKF/jAi2Rdr+hzPCfQtmNjBpiyEbbJ0=
 github.com/go-kratos/nacos v0.1.0/go.mod h1:qhVsyp6RyDDEKLtT0/iTMaN3D7SheRMrYb2bV0jYqjw=
@@ -187,6 +189,8 @@ github.com/go-kratos/prometheus v0.0.0-20210522055322-137e29e7cf47 h1:uJennLdg0r
 github.com/go-kratos/prometheus v0.0.0-20210522055322-137e29e7cf47/go.mod h1:e3D0pnFDX5SG+ZGmGONd83WvrbmFWCdx5l4Hkl3I1eY=
 github.com/go-kratos/swagger-api v0.1.8 h1:4afwLcDwMut7LyS5b0iarB5p6XTbaJngRvAqDb4Skig=
 github.com/go-kratos/swagger-api v0.1.8/go.mod h1:HMI1NY/OWbjbkZF2z//tEKoL1XpZOzyxOedtBddEyZE=
+github.com/go-kratos/swagger-api v1.0.0 h1:R1JcAqLSvfDzqHoDnpK3Jt1MEJAXGNifV7AnBEhP3PE=
+github.com/go-kratos/swagger-api v1.0.0/go.mod h1:BPeS1OUN4jtMitb99ptZAagAgyQ51S2F2vd9dT/J3eM=
 github.com/go-kratos/zookeeper v0.0.0-20210721021028-9e7498619b89 h1:OgoIdQOUOA0bCjaA2Mu6YNTGDpKDeu4Ced+nVUkqXqk=
 github.com/go-kratos/zookeeper v0.0.0-20210721021028-9e7498619b89/go.mod h1:c3T3e5hA+/cfiha99+un7TXHvOQFaDuFiYVyS3LvXU4=
 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
@@ -320,7 +324,10 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgf
 github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
 github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
 github.com/grpc-ecosystem/grpc-gateway v1.14.6/go.mod h1:zdiPV4Yse/1gnckTHtghG4GkDEdKCRJduHpTxT3/jcw=
+github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
 github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.5.0 h1:ajue7SzQMywqRjg2fK7dcpc0QhFGpTR2plWfV4EZWR4=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.5.0/go.mod h1:r1hZAcvfFXuYmcKyCJI9wlyOPIZUJl6FCB8Cpca/NLE=
 github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
 github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
 github.com/hashicorp/consul/api v1.9.1 h1:SngrdG2L62qqLsUz85qcPhFZ78rPf8tcD5qjMgs6MME=
@@ -702,8 +709,12 @@ go.opentelemetry.io/otel v0.17.0/go.mod h1:Oqtdxmf7UtEvL037ohlgnaYa1h7GtMh0NcSd9
 go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo=
 go.opentelemetry.io/otel v1.0.0-RC1 h1:4CeoX93DNTWt8awGK9JmNXzF9j7TyOu9upscEdtcdXc=
 go.opentelemetry.io/otel v1.0.0-RC1/go.mod h1:x9tRa9HK4hSSq7jf2TKbqFbtt58/TGk0f9XiEYISI1I=
+go.opentelemetry.io/otel v1.0.0-RC2 h1:SHhxSjB+omnGZPgGlKe+QMp3MyazcOHdQ8qwo89oKbg=
+go.opentelemetry.io/otel v1.0.0-RC2/go.mod h1:w1thVQ7qbAy8MHb0IFj8a5Q2QU0l2ksf8u/CN8m3NOM=
 go.opentelemetry.io/otel/exporters/jaeger v1.0.0-RC1 h1:tVhw2BMSAk248rhdeirOe9hlXKwGHDvVtF7P8F+H2DU=
 go.opentelemetry.io/otel/exporters/jaeger v1.0.0-RC1/go.mod h1:FXJnjGCoTQL6nQ8OpFJ0JI1DrdOvMoVx49ic0Hg4+D4=
+go.opentelemetry.io/otel/exporters/jaeger v1.0.0-RC2 h1:RF0nWsIDpDBe+s06lkLxUw9CWQUAhO6hBSxxB7dz45s=
+go.opentelemetry.io/otel/exporters/jaeger v1.0.0-RC2/go.mod h1:sZZqN3Vb0iT+NE6mZ1S7sNyH3t4PFk6ElK5TLGFBZ7E=
 go.opentelemetry.io/otel/internal/metric v0.21.0 h1:gZlIBo5O51hZOOZz8vEcuRx/l5dnADadKfpT70AELoo=
 go.opentelemetry.io/otel/internal/metric v0.21.0/go.mod h1:iOfAaY2YycsXfYD4kaRSbLx2LKmfpKObWBEv9QK5zFo=
 go.opentelemetry.io/otel/metric v0.17.0/go.mod h1:hUz9lH1rNXyEwWAhIWCMFWKhYtpASgSnObJFnU26dJ0=
@@ -716,10 +727,14 @@ go.opentelemetry.io/otel/oteltest v1.0.0-RC1 h1:G685iP3XiskCwk/z0eIabL55XUl2gk0c
 go.opentelemetry.io/otel/oteltest v1.0.0-RC1/go.mod h1:+eoIG0gdEOaPNftuy1YScLr1Gb4mL/9lpDkZ0JjMRq4=
 go.opentelemetry.io/otel/sdk v1.0.0-RC1 h1:Sy2VLOOg24bipyC29PhuMXYNJrLsxkie8hyI7kUlG9Q=
 go.opentelemetry.io/otel/sdk v1.0.0-RC1/go.mod h1:kj6yPn7Pgt5ByRuwesbaWcRLA+V7BSDg3Hf8xRvsvf8=
+go.opentelemetry.io/otel/sdk v1.0.0-RC2 h1:ROuteeSCBaZNjiT9JcFzZepmInDvLktR28Y6qKo8bCs=
+go.opentelemetry.io/otel/sdk v1.0.0-RC2/go.mod h1:fgwHyiDn4e5k40TD9VX243rOxXR+jzsWBZYA2P5jpEw=
 go.opentelemetry.io/otel/trace v0.17.0/go.mod h1:bIujpqg6ZL6xUTubIUgziI1jSaUPthmabA/ygf/6Cfg=
 go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw=
 go.opentelemetry.io/otel/trace v1.0.0-RC1 h1:jrjqKJZEibFrDz+umEASeU3LvdVyWKlnTh7XEfwrT58=
 go.opentelemetry.io/otel/trace v1.0.0-RC1/go.mod h1:86UHmyHWFEtWjfWPSbu0+d0Pf9Q6e1U+3ViBOc+NXAg=
+go.opentelemetry.io/otel/trace v1.0.0-RC2 h1:dunAP0qDULMIT82atj34m5RgvsIK6LcsXf1c/MsYg1w=
+go.opentelemetry.io/otel/trace v1.0.0-RC2/go.mod h1:JPQ+z6nNw9mqEGT8o3eoPTdnNI+Aj5JcxEsVGREIAy4=
 go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
 go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
 go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
@@ -841,6 +856,7 @@ golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4Iltr
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20210615190721-d04028783cf1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 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=
@@ -914,6 +930,7 @@ golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
@@ -989,6 +1006,7 @@ golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4f
 golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
 golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/tools v0.1.4 h1:cVngSRcfgyZCzys3KYOpCFa+4dqX/Oub9tAq00ttGVs=
 golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -1052,6 +1070,7 @@ google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6D
 google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
+google.golang.org/genproto v0.0.0-20210617175327-b9e0b3197ced/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=
 google.golang.org/genproto v0.0.0-20210629200056-84d6f6074151/go.mod h1:yiaVoXHpRzHGyxV3o4DktVWY4mSUErTKaeEOq6C3t3U=
 google.golang.org/genproto v0.0.0-20210701191553-46259e63a0a9 h1:HBPuvo39L0DgfVn9eHR3ki/RjZoUFWa+em77e7KFDfs=
 google.golang.org/genproto v0.0.0-20210701191553-46259e63a0a9/go.mod h1:yiaVoXHpRzHGyxV3o4DktVWY4mSUErTKaeEOq6C3t3U=
diff --git a/examples/stream/client/main.go b/examples/stream/client/main.go
new file mode 100644
index 00000000..5086ae7e
--- /dev/null
+++ b/examples/stream/client/main.go
@@ -0,0 +1,103 @@
+package main
+
+import (
+	"context"
+	"fmt"
+	"io"
+	"log"
+	"strconv"
+	"sync"
+	"time"
+
+	"github.com/go-kratos/kratos/examples/stream/hello"
+	"github.com/go-kratos/kratos/v2/middleware/recovery"
+	transgrpc "github.com/go-kratos/kratos/v2/transport/grpc"
+)
+
+var wg = sync.WaitGroup{}
+
+func main() {
+	conn, err := transgrpc.DialInsecure(
+		context.Background(),
+		transgrpc.WithEndpoint("127.0.0.1:9001"),
+		transgrpc.WithMiddleware(
+			recovery.Recovery(),
+		),
+	)
+	if err != nil {
+		panic(err)
+	}
+	defer conn.Close()
+
+	client := hello.NewHelloClient(conn)
+
+	wg.Add(3)
+
+	go getNumber(client)
+	go uploadLog(client)
+	go chat(client)
+
+	wg.Wait()
+}
+
+func getNumber(client hello.HelloClient) {
+	defer wg.Done()
+	stream, err := client.GetNumber(context.Background(), &hello.GetNumberRequest{Data: "2021/08/01"})
+	if err != nil {
+		log.Fatal(err)
+	}
+	for {
+		res, err := stream.Recv()
+		if err == io.EOF {
+			break
+		}
+		if err != nil {
+			log.Fatalf("ListStr get stream err: %v", err)
+		}
+		// 打印返回值
+		log.Println(res.Number)
+	}
+}
+
+func uploadLog(client hello.HelloClient) {
+	defer wg.Done()
+	stream, err := client.UploadLog(context.Background())
+	if err != nil {
+		log.Fatal(err)
+	}
+	var number int
+	for {
+		err := stream.Send(&hello.UploadLogRequest{Log: "log:" + strconv.Itoa(number)})
+		if err != nil {
+			log.Fatalf("ListStr get stream err: %v", err)
+		}
+		time.Sleep(time.Millisecond * 50)
+		number++
+	}
+	return
+}
+
+func chat(client hello.HelloClient) {
+	defer wg.Done()
+	stream, err := client.Chat(context.Background())
+	if err != nil {
+		log.Fatal(err)
+	}
+	var number int
+	for {
+		err = stream.Send(&hello.ChatRequest{UpMsg: "kratos:" + strconv.Itoa(number)})
+		if err != nil {
+			log.Fatalf("ListStr get stream err: %v", err)
+		}
+		res, err := stream.Recv()
+		if err == io.EOF {
+			break
+		}
+		if err != nil {
+			log.Fatalf("ListStr get stream err: %v", err)
+		}
+		fmt.Println(res.DownMsg)
+		time.Sleep(time.Millisecond * 50)
+		number++
+	}
+}
diff --git a/examples/stream/hello/hello.pb.go b/examples/stream/hello/hello.pb.go
new file mode 100644
index 00000000..b1b00122
--- /dev/null
+++ b/examples/stream/hello/hello.pb.go
@@ -0,0 +1,479 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// 	protoc-gen-go v1.27.1
+// 	protoc        v3.17.3
+// source: stream/hello/hello.proto
+
+package hello
+
+import (
+	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+	reflect "reflect"
+	sync "sync"
+)
+
+const (
+	// Verify that this generated code is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+	// Verify that runtime/protoimpl is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+type GetNumberRequest struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Data string `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"`
+}
+
+func (x *GetNumberRequest) Reset() {
+	*x = GetNumberRequest{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_stream_hello_hello_proto_msgTypes[0]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *GetNumberRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*GetNumberRequest) ProtoMessage() {}
+
+func (x *GetNumberRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_stream_hello_hello_proto_msgTypes[0]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use GetNumberRequest.ProtoReflect.Descriptor instead.
+func (*GetNumberRequest) Descriptor() ([]byte, []int) {
+	return file_stream_hello_hello_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *GetNumberRequest) GetData() string {
+	if x != nil {
+		return x.Data
+	}
+	return ""
+}
+
+type GetNumberReply struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Number int64 `protobuf:"varint,1,opt,name=number,proto3" json:"number,omitempty"`
+}
+
+func (x *GetNumberReply) Reset() {
+	*x = GetNumberReply{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_stream_hello_hello_proto_msgTypes[1]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *GetNumberReply) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*GetNumberReply) ProtoMessage() {}
+
+func (x *GetNumberReply) ProtoReflect() protoreflect.Message {
+	mi := &file_stream_hello_hello_proto_msgTypes[1]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use GetNumberReply.ProtoReflect.Descriptor instead.
+func (*GetNumberReply) Descriptor() ([]byte, []int) {
+	return file_stream_hello_hello_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *GetNumberReply) GetNumber() int64 {
+	if x != nil {
+		return x.Number
+	}
+	return 0
+}
+
+type UploadLogRequest struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Log string `protobuf:"bytes,1,opt,name=log,proto3" json:"log,omitempty"`
+}
+
+func (x *UploadLogRequest) Reset() {
+	*x = UploadLogRequest{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_stream_hello_hello_proto_msgTypes[2]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *UploadLogRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*UploadLogRequest) ProtoMessage() {}
+
+func (x *UploadLogRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_stream_hello_hello_proto_msgTypes[2]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use UploadLogRequest.ProtoReflect.Descriptor instead.
+func (*UploadLogRequest) Descriptor() ([]byte, []int) {
+	return file_stream_hello_hello_proto_rawDescGZIP(), []int{2}
+}
+
+func (x *UploadLogRequest) GetLog() string {
+	if x != nil {
+		return x.Log
+	}
+	return ""
+}
+
+type UploadLogReply struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Res string `protobuf:"bytes,1,opt,name=res,proto3" json:"res,omitempty"`
+}
+
+func (x *UploadLogReply) Reset() {
+	*x = UploadLogReply{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_stream_hello_hello_proto_msgTypes[3]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *UploadLogReply) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*UploadLogReply) ProtoMessage() {}
+
+func (x *UploadLogReply) ProtoReflect() protoreflect.Message {
+	mi := &file_stream_hello_hello_proto_msgTypes[3]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use UploadLogReply.ProtoReflect.Descriptor instead.
+func (*UploadLogReply) Descriptor() ([]byte, []int) {
+	return file_stream_hello_hello_proto_rawDescGZIP(), []int{3}
+}
+
+func (x *UploadLogReply) GetRes() string {
+	if x != nil {
+		return x.Res
+	}
+	return ""
+}
+
+type ChatRequest struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	UpMsg string `protobuf:"bytes,1,opt,name=up_msg,json=upMsg,proto3" json:"up_msg,omitempty"`
+}
+
+func (x *ChatRequest) Reset() {
+	*x = ChatRequest{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_stream_hello_hello_proto_msgTypes[4]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *ChatRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ChatRequest) ProtoMessage() {}
+
+func (x *ChatRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_stream_hello_hello_proto_msgTypes[4]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use ChatRequest.ProtoReflect.Descriptor instead.
+func (*ChatRequest) Descriptor() ([]byte, []int) {
+	return file_stream_hello_hello_proto_rawDescGZIP(), []int{4}
+}
+
+func (x *ChatRequest) GetUpMsg() string {
+	if x != nil {
+		return x.UpMsg
+	}
+	return ""
+}
+
+type ChatReply struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	DownMsg string `protobuf:"bytes,1,opt,name=down_msg,json=downMsg,proto3" json:"down_msg,omitempty"`
+}
+
+func (x *ChatReply) Reset() {
+	*x = ChatReply{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_stream_hello_hello_proto_msgTypes[5]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *ChatReply) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ChatReply) ProtoMessage() {}
+
+func (x *ChatReply) ProtoReflect() protoreflect.Message {
+	mi := &file_stream_hello_hello_proto_msgTypes[5]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use ChatReply.ProtoReflect.Descriptor instead.
+func (*ChatReply) Descriptor() ([]byte, []int) {
+	return file_stream_hello_hello_proto_rawDescGZIP(), []int{5}
+}
+
+func (x *ChatReply) GetDownMsg() string {
+	if x != nil {
+		return x.DownMsg
+	}
+	return ""
+}
+
+var File_stream_hello_hello_proto protoreflect.FileDescriptor
+
+var file_stream_hello_hello_proto_rawDesc = []byte{
+	0x0a, 0x18, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2f, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x2f, 0x68,
+	0x65, 0x6c, 0x6c, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0c, 0x73, 0x74, 0x72, 0x65,
+	0x61, 0x6d, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x22, 0x26, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x4e,
+	0x75, 0x6d, 0x62, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04,
+	0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61,
+	0x22, 0x28, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x52, 0x65, 0x70,
+	0x6c, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01,
+	0x28, 0x03, 0x52, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x22, 0x24, 0x0a, 0x10, 0x55, 0x70,
+	0x6c, 0x6f, 0x61, 0x64, 0x4c, 0x6f, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10,
+	0x0a, 0x03, 0x6c, 0x6f, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6c, 0x6f, 0x67,
+	0x22, 0x22, 0x0a, 0x0e, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x4c, 0x6f, 0x67, 0x52, 0x65, 0x70,
+	0x6c, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x72, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
+	0x03, 0x72, 0x65, 0x73, 0x22, 0x24, 0x0a, 0x0b, 0x43, 0x68, 0x61, 0x74, 0x52, 0x65, 0x71, 0x75,
+	0x65, 0x73, 0x74, 0x12, 0x15, 0x0a, 0x06, 0x75, 0x70, 0x5f, 0x6d, 0x73, 0x67, 0x18, 0x01, 0x20,
+	0x01, 0x28, 0x09, 0x52, 0x05, 0x75, 0x70, 0x4d, 0x73, 0x67, 0x22, 0x26, 0x0a, 0x09, 0x43, 0x68,
+	0x61, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x19, 0x0a, 0x08, 0x64, 0x6f, 0x77, 0x6e, 0x5f,
+	0x6d, 0x73, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x64, 0x6f, 0x77, 0x6e, 0x4d,
+	0x73, 0x67, 0x32, 0xe1, 0x01, 0x0a, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x12, 0x4b, 0x0a, 0x09,
+	0x47, 0x65, 0x74, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x1e, 0x2e, 0x73, 0x74, 0x72, 0x65,
+	0x61, 0x6d, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x2e, 0x47, 0x65, 0x74, 0x4e, 0x75, 0x6d, 0x62,
+	0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x73, 0x74, 0x72, 0x65,
+	0x61, 0x6d, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x2e, 0x47, 0x65, 0x74, 0x4e, 0x75, 0x6d, 0x62,
+	0x65, 0x72, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x30, 0x01, 0x12, 0x4b, 0x0a, 0x09, 0x55, 0x70, 0x6c,
+	0x6f, 0x61, 0x64, 0x4c, 0x6f, 0x67, 0x12, 0x1e, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e,
+	0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x2e, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x4c, 0x6f, 0x67, 0x52,
+	0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e,
+	0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x2e, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x4c, 0x6f, 0x67, 0x52,
+	0x65, 0x70, 0x6c, 0x79, 0x28, 0x01, 0x12, 0x3e, 0x0a, 0x04, 0x43, 0x68, 0x61, 0x74, 0x12, 0x19,
+	0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x2e, 0x43, 0x68,
+	0x61, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x73, 0x74, 0x72, 0x65,
+	0x61, 0x6d, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x2e, 0x43, 0x68, 0x61, 0x74, 0x52, 0x65, 0x70,
+	0x6c, 0x79, 0x28, 0x01, 0x30, 0x01, 0x42, 0x49, 0x0a, 0x0c, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d,
+	0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x50, 0x01, 0x5a, 0x37, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62,
+	0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x2d, 0x6b, 0x72, 0x61, 0x74, 0x6f, 0x73, 0x2f, 0x6b,
+	0x72, 0x61, 0x74, 0x6f, 0x73, 0x2f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x2f, 0x73,
+	0x74, 0x72, 0x65, 0x61, 0x6d, 0x2f, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x3b, 0x68, 0x65, 0x6c, 0x6c,
+	0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+}
+
+var (
+	file_stream_hello_hello_proto_rawDescOnce sync.Once
+	file_stream_hello_hello_proto_rawDescData = file_stream_hello_hello_proto_rawDesc
+)
+
+func file_stream_hello_hello_proto_rawDescGZIP() []byte {
+	file_stream_hello_hello_proto_rawDescOnce.Do(func() {
+		file_stream_hello_hello_proto_rawDescData = protoimpl.X.CompressGZIP(file_stream_hello_hello_proto_rawDescData)
+	})
+	return file_stream_hello_hello_proto_rawDescData
+}
+
+var file_stream_hello_hello_proto_msgTypes = make([]protoimpl.MessageInfo, 6)
+var file_stream_hello_hello_proto_goTypes = []interface{}{
+	(*GetNumberRequest)(nil), // 0: stream.hello.GetNumberRequest
+	(*GetNumberReply)(nil),   // 1: stream.hello.GetNumberReply
+	(*UploadLogRequest)(nil), // 2: stream.hello.UploadLogRequest
+	(*UploadLogReply)(nil),   // 3: stream.hello.UploadLogReply
+	(*ChatRequest)(nil),      // 4: stream.hello.ChatRequest
+	(*ChatReply)(nil),        // 5: stream.hello.ChatReply
+}
+var file_stream_hello_hello_proto_depIdxs = []int32{
+	0, // 0: stream.hello.Hello.GetNumber:input_type -> stream.hello.GetNumberRequest
+	2, // 1: stream.hello.Hello.UploadLog:input_type -> stream.hello.UploadLogRequest
+	4, // 2: stream.hello.Hello.Chat:input_type -> stream.hello.ChatRequest
+	1, // 3: stream.hello.Hello.GetNumber:output_type -> stream.hello.GetNumberReply
+	3, // 4: stream.hello.Hello.UploadLog:output_type -> stream.hello.UploadLogReply
+	5, // 5: stream.hello.Hello.Chat:output_type -> stream.hello.ChatReply
+	3, // [3:6] is the sub-list for method output_type
+	0, // [0:3] 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_hello_hello_proto_init() }
+func file_stream_hello_hello_proto_init() {
+	if File_stream_hello_hello_proto != nil {
+		return
+	}
+	if !protoimpl.UnsafeEnabled {
+		file_stream_hello_hello_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*GetNumberRequest); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_stream_hello_hello_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*GetNumberReply); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_stream_hello_hello_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*UploadLogRequest); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_stream_hello_hello_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*UploadLogReply); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_stream_hello_hello_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*ChatRequest); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_stream_hello_hello_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*ChatReply); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+	}
+	type x struct{}
+	out := protoimpl.TypeBuilder{
+		File: protoimpl.DescBuilder{
+			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+			RawDescriptor: file_stream_hello_hello_proto_rawDesc,
+			NumEnums:      0,
+			NumMessages:   6,
+			NumExtensions: 0,
+			NumServices:   1,
+		},
+		GoTypes:           file_stream_hello_hello_proto_goTypes,
+		DependencyIndexes: file_stream_hello_hello_proto_depIdxs,
+		MessageInfos:      file_stream_hello_hello_proto_msgTypes,
+	}.Build()
+	File_stream_hello_hello_proto = out.File
+	file_stream_hello_hello_proto_rawDesc = nil
+	file_stream_hello_hello_proto_goTypes = nil
+	file_stream_hello_hello_proto_depIdxs = nil
+}
diff --git a/examples/stream/hello/hello_grpc.pb.go b/examples/stream/hello/hello_grpc.pb.go
new file mode 100644
index 00000000..11590f88
--- /dev/null
+++ b/examples/stream/hello/hello_grpc.pb.go
@@ -0,0 +1,266 @@
+// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
+
+package hello
+
+import (
+	context "context"
+	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
+
+// HelloClient is the client API for Hello 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 HelloClient interface {
+	GetNumber(ctx context.Context, in *GetNumberRequest, opts ...grpc.CallOption) (Hello_GetNumberClient, error)
+	UploadLog(ctx context.Context, opts ...grpc.CallOption) (Hello_UploadLogClient, error)
+	Chat(ctx context.Context, opts ...grpc.CallOption) (Hello_ChatClient, error)
+}
+
+type helloClient struct {
+	cc grpc.ClientConnInterface
+}
+
+func NewHelloClient(cc grpc.ClientConnInterface) HelloClient {
+	return &helloClient{cc}
+}
+
+func (c *helloClient) GetNumber(ctx context.Context, in *GetNumberRequest, opts ...grpc.CallOption) (Hello_GetNumberClient, error) {
+	stream, err := c.cc.NewStream(ctx, &Hello_ServiceDesc.Streams[0], "/stream.hello.Hello/GetNumber", opts...)
+	if err != nil {
+		return nil, err
+	}
+	x := &helloGetNumberClient{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 Hello_GetNumberClient interface {
+	Recv() (*GetNumberReply, error)
+	grpc.ClientStream
+}
+
+type helloGetNumberClient struct {
+	grpc.ClientStream
+}
+
+func (x *helloGetNumberClient) Recv() (*GetNumberReply, error) {
+	m := new(GetNumberReply)
+	if err := x.ClientStream.RecvMsg(m); err != nil {
+		return nil, err
+	}
+	return m, nil
+}
+
+func (c *helloClient) UploadLog(ctx context.Context, opts ...grpc.CallOption) (Hello_UploadLogClient, error) {
+	stream, err := c.cc.NewStream(ctx, &Hello_ServiceDesc.Streams[1], "/stream.hello.Hello/UploadLog", opts...)
+	if err != nil {
+		return nil, err
+	}
+	x := &helloUploadLogClient{stream}
+	return x, nil
+}
+
+type Hello_UploadLogClient interface {
+	Send(*UploadLogRequest) error
+	CloseAndRecv() (*UploadLogReply, error)
+	grpc.ClientStream
+}
+
+type helloUploadLogClient struct {
+	grpc.ClientStream
+}
+
+func (x *helloUploadLogClient) Send(m *UploadLogRequest) error {
+	return x.ClientStream.SendMsg(m)
+}
+
+func (x *helloUploadLogClient) CloseAndRecv() (*UploadLogReply, error) {
+	if err := x.ClientStream.CloseSend(); err != nil {
+		return nil, err
+	}
+	m := new(UploadLogReply)
+	if err := x.ClientStream.RecvMsg(m); err != nil {
+		return nil, err
+	}
+	return m, nil
+}
+
+func (c *helloClient) Chat(ctx context.Context, opts ...grpc.CallOption) (Hello_ChatClient, error) {
+	stream, err := c.cc.NewStream(ctx, &Hello_ServiceDesc.Streams[2], "/stream.hello.Hello/Chat", opts...)
+	if err != nil {
+		return nil, err
+	}
+	x := &helloChatClient{stream}
+	return x, nil
+}
+
+type Hello_ChatClient interface {
+	Send(*ChatRequest) error
+	Recv() (*ChatReply, error)
+	grpc.ClientStream
+}
+
+type helloChatClient struct {
+	grpc.ClientStream
+}
+
+func (x *helloChatClient) Send(m *ChatRequest) error {
+	return x.ClientStream.SendMsg(m)
+}
+
+func (x *helloChatClient) Recv() (*ChatReply, error) {
+	m := new(ChatReply)
+	if err := x.ClientStream.RecvMsg(m); err != nil {
+		return nil, err
+	}
+	return m, nil
+}
+
+// HelloServer is the service API for Hello service.
+// All implementations must embed UnimplementedHelloServer
+// for forward compatibility
+type HelloServer interface {
+	GetNumber(*GetNumberRequest, Hello_GetNumberServer) error
+	UploadLog(Hello_UploadLogServer) error
+	Chat(Hello_ChatServer) error
+	mustEmbedUnimplementedHelloServer()
+}
+
+// UnimplementedHelloServer must be embedded to have forward compatible implementations.
+type UnimplementedHelloServer struct {
+}
+
+func (UnimplementedHelloServer) GetNumber(*GetNumberRequest, Hello_GetNumberServer) error {
+	return status.Errorf(codes.Unimplemented, "method GetNumber not implemented")
+}
+func (UnimplementedHelloServer) UploadLog(Hello_UploadLogServer) error {
+	return status.Errorf(codes.Unimplemented, "method UploadLog not implemented")
+}
+func (UnimplementedHelloServer) Chat(Hello_ChatServer) error {
+	return status.Errorf(codes.Unimplemented, "method Chat not implemented")
+}
+func (UnimplementedHelloServer) mustEmbedUnimplementedHelloServer() {}
+
+// UnsafeHelloServer may be embedded to opt out of forward compatibility for this service.
+// Use of this interface is not recommended, as added methods to HelloServer will
+// result in compilation errors.
+type UnsafeHelloServer interface {
+	mustEmbedUnimplementedHelloServer()
+}
+
+func RegisterHelloServer(s grpc.ServiceRegistrar, srv HelloServer) {
+	s.RegisterService(&Hello_ServiceDesc, srv)
+}
+
+func _Hello_GetNumber_Handler(srv interface{}, stream grpc.ServerStream) error {
+	m := new(GetNumberRequest)
+	if err := stream.RecvMsg(m); err != nil {
+		return err
+	}
+	return srv.(HelloServer).GetNumber(m, &helloGetNumberServer{stream})
+}
+
+type Hello_GetNumberServer interface {
+	Send(*GetNumberReply) error
+	grpc.ServerStream
+}
+
+type helloGetNumberServer struct {
+	grpc.ServerStream
+}
+
+func (x *helloGetNumberServer) Send(m *GetNumberReply) error {
+	return x.ServerStream.SendMsg(m)
+}
+
+func _Hello_UploadLog_Handler(srv interface{}, stream grpc.ServerStream) error {
+	return srv.(HelloServer).UploadLog(&helloUploadLogServer{stream})
+}
+
+type Hello_UploadLogServer interface {
+	SendAndClose(*UploadLogReply) error
+	Recv() (*UploadLogRequest, error)
+	grpc.ServerStream
+}
+
+type helloUploadLogServer struct {
+	grpc.ServerStream
+}
+
+func (x *helloUploadLogServer) SendAndClose(m *UploadLogReply) error {
+	return x.ServerStream.SendMsg(m)
+}
+
+func (x *helloUploadLogServer) Recv() (*UploadLogRequest, error) {
+	m := new(UploadLogRequest)
+	if err := x.ServerStream.RecvMsg(m); err != nil {
+		return nil, err
+	}
+	return m, nil
+}
+
+func _Hello_Chat_Handler(srv interface{}, stream grpc.ServerStream) error {
+	return srv.(HelloServer).Chat(&helloChatServer{stream})
+}
+
+type Hello_ChatServer interface {
+	Send(*ChatReply) error
+	Recv() (*ChatRequest, error)
+	grpc.ServerStream
+}
+
+type helloChatServer struct {
+	grpc.ServerStream
+}
+
+func (x *helloChatServer) Send(m *ChatReply) error {
+	return x.ServerStream.SendMsg(m)
+}
+
+func (x *helloChatServer) Recv() (*ChatRequest, error) {
+	m := new(ChatRequest)
+	if err := x.ServerStream.RecvMsg(m); err != nil {
+		return nil, err
+	}
+	return m, nil
+}
+
+// Hello_ServiceDesc is the grpc.ServiceDesc for Hello service.
+// It's only intended for direct use with grpc.RegisterService,
+// and not to be introspected or modified (even as a copy)
+var Hello_ServiceDesc = grpc.ServiceDesc{
+	ServiceName: "stream.hello.Hello",
+	HandlerType: (*HelloServer)(nil),
+	Methods:     []grpc.MethodDesc{},
+	Streams: []grpc.StreamDesc{
+		{
+			StreamName:    "GetNumber",
+			Handler:       _Hello_GetNumber_Handler,
+			ServerStreams: true,
+		},
+		{
+			StreamName:    "UploadLog",
+			Handler:       _Hello_UploadLog_Handler,
+			ClientStreams: true,
+		},
+		{
+			StreamName:    "Chat",
+			Handler:       _Hello_Chat_Handler,
+			ServerStreams: true,
+			ClientStreams: true,
+		},
+	},
+	Metadata: "stream/hello/hello.proto",
+}
diff --git a/examples/stream/server/main.go b/examples/stream/server/main.go
new file mode 100644
index 00000000..b2561217
--- /dev/null
+++ b/examples/stream/server/main.go
@@ -0,0 +1,31 @@
+package main
+
+import (
+	"log"
+
+	"github.com/go-kratos/kratos/examples/stream/hello"
+	"github.com/go-kratos/kratos/examples/stream/service"
+	"github.com/go-kratos/kratos/v2"
+	"github.com/go-kratos/kratos/v2/middleware/recovery"
+	"github.com/go-kratos/kratos/v2/transport/grpc"
+)
+
+func main() {
+	grpcSrv := grpc.NewServer(
+		grpc.Address(":9001"),
+		grpc.Middleware(
+			recovery.Recovery(),
+		),
+	)
+	hello.RegisterHelloServer(grpcSrv, service.NewHelloService())
+
+	app := kratos.New(
+		kratos.Name("hello"),
+		kratos.Server(
+			grpcSrv,
+		),
+	)
+	if err := app.Run(); err != nil {
+		log.Fatal(err)
+	}
+}
diff --git a/examples/stream/service/hello.go b/examples/stream/service/hello.go
new file mode 100644
index 00000000..b0b531ae
--- /dev/null
+++ b/examples/stream/service/hello.go
@@ -0,0 +1,58 @@
+package service
+
+import (
+	"fmt"
+	"io"
+	"time"
+
+	pb "github.com/go-kratos/kratos/examples/stream/hello"
+)
+
+type HelloService struct {
+	pb.UnimplementedHelloServer
+}
+
+func NewHelloService() *HelloService {
+	return &HelloService{}
+}
+
+func (s *HelloService) GetNumber(req *pb.GetNumberRequest, conn pb.Hello_GetNumberServer) error {
+	var number int64
+	for {
+		fmt.Println(req.Data)
+		err := conn.Send(&pb.GetNumberReply{Number: number})
+		if err != nil {
+			return err
+		}
+		number++
+		time.Sleep(time.Second)
+	}
+}
+func (s *HelloService) UploadLog(conn pb.Hello_UploadLogServer) error {
+	for {
+		req, err := conn.Recv()
+		if err == io.EOF {
+			return conn.SendAndClose(&pb.UploadLogReply{Res: "ok"})
+		}
+		if err != nil {
+			return err
+		}
+		fmt.Println(req.Log)
+	}
+}
+func (s *HelloService) Chat(conn pb.Hello_ChatServer) error {
+	for {
+		req, err := conn.Recv()
+		if err == io.EOF {
+			return nil
+		}
+		if err != nil {
+			return err
+		}
+
+		err = conn.Send(&pb.ChatReply{DownMsg: "hello " + req.UpMsg})
+		if err != nil {
+			return err
+		}
+	}
+}
diff --git a/examples/swagger/helloworld/helloworld.pb.go b/examples/swagger/helloworld/helloworld.pb.go
index a6f055bf..4091f8b3 100644
--- a/examples/swagger/helloworld/helloworld.pb.go
+++ b/examples/swagger/helloworld/helloworld.pb.go
@@ -1,14 +1,14 @@
 // Code generated by protoc-gen-go. DO NOT EDIT.
 // versions:
 // 	protoc-gen-go v1.27.1-devel
-// 	protoc        v3.17.2
+// 	protoc        v3.17.3
 // source: helloworld/helloworld.proto
 
 package helloworld
 
 import (
 	reply "github.com/go-kratos/kratos/examples/swagger/reply"
-	_ "github.com/longXboy/grpc-gateway/v2/protoc-gen-openapiv2/options"
+	_ "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2/options"
 	_ "google.golang.org/genproto/googleapis/api/annotations"
 	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
 	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
@@ -146,28 +146,28 @@ var file_helloworld_helloworld_proto_rawDesc = []byte{
 	0x79, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x51, 0x75, 0x65, 0x72, 0x79, 0x12, 0x2e, 0x68, 0x74,
 	0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d,
 	0x2f, 0x67, 0x72, 0x70, 0x63, 0x2d, 0x65, 0x63, 0x6f, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2f,
-	0x67, 0x72, 0x70, 0x63, 0x2d, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x42, 0xc9, 0x02, 0x5a,
-	0x4a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x2d, 0x6b,
-	0x72, 0x61, 0x74, 0x6f, 0x73, 0x2f, 0x73, 0x77, 0x61, 0x67, 0x67, 0x65, 0x72, 0x2d, 0x61, 0x70,
-	0x69, 0x2f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x2f, 0x68, 0x65, 0x6c, 0x6c, 0x6f,
-	0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2f, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64,
-	0x3b, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x92, 0x41, 0xf9, 0x01, 0x12,
-	0xf6, 0x01, 0x0a, 0x13, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x20, 0x65,
-	0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x22, 0x58, 0x0a, 0x14, 0x67, 0x52, 0x50, 0x43, 0x2d,
-	0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x20, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x12,
-	0x2e, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e,
-	0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2d, 0x65, 0x63, 0x6f, 0x73, 0x79, 0x73, 0x74,
-	0x65, 0x6d, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2d, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x1a,
-	0x10, 0x6e, 0x6f, 0x6e, 0x65, 0x40, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f,
-	0x6d, 0x2a, 0x5e, 0x0a, 0x14, 0x42, 0x53, 0x44, 0x20, 0x33, 0x2d, 0x43, 0x6c, 0x61, 0x75, 0x73,
-	0x65, 0x20, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x12, 0x46, 0x68, 0x74, 0x74, 0x70, 0x73,
-	0x3a, 0x2f, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x72,
-	0x70, 0x63, 0x2d, 0x65, 0x63, 0x6f, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2f, 0x67, 0x72, 0x70,
-	0x63, 0x2d, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x2f, 0x62, 0x6c, 0x6f, 0x62, 0x2f, 0x6d,
-	0x61, 0x73, 0x74, 0x65, 0x72, 0x2f, 0x4c, 0x49, 0x43, 0x45, 0x4e, 0x53, 0x45, 0x2e, 0x74, 0x78,
-	0x74, 0x32, 0x03, 0x31, 0x2e, 0x30, 0x3a, 0x20, 0x0a, 0x15, 0x78, 0x2d, 0x73, 0x6f, 0x6d, 0x65,
-	0x74, 0x68, 0x69, 0x6e, 0x67, 0x2d, 0x73, 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x12,
-	0x07, 0x1a, 0x05, 0x79, 0x61, 0x64, 0x64, 0x61, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+	0x67, 0x72, 0x70, 0x63, 0x2d, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x42, 0xc1, 0x02, 0x5a,
+	0x42, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x2d, 0x6b,
+	0x72, 0x61, 0x74, 0x6f, 0x73, 0x2f, 0x6b, 0x72, 0x61, 0x74, 0x6f, 0x73, 0x2f, 0x65, 0x78, 0x61,
+	0x6d, 0x70, 0x6c, 0x65, 0x73, 0x2f, 0x73, 0x77, 0x61, 0x67, 0x67, 0x65, 0x72, 0x2f, 0x68, 0x65,
+	0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x3b, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f,
+	0x72, 0x6c, 0x64, 0x92, 0x41, 0xf9, 0x01, 0x12, 0xf6, 0x01, 0x0a, 0x13, 0x48, 0x65, 0x6c, 0x6c,
+	0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x20, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x22,
+	0x58, 0x0a, 0x14, 0x67, 0x52, 0x50, 0x43, 0x2d, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x20,
+	0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x2e, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f,
+	0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x72, 0x70, 0x63,
+	0x2d, 0x65, 0x63, 0x6f, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2d,
+	0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x1a, 0x10, 0x6e, 0x6f, 0x6e, 0x65, 0x40, 0x65, 0x78,
+	0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2a, 0x5e, 0x0a, 0x14, 0x42, 0x53, 0x44,
+	0x20, 0x33, 0x2d, 0x43, 0x6c, 0x61, 0x75, 0x73, 0x65, 0x20, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73,
+	0x65, 0x12, 0x46, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75,
+	0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2d, 0x65, 0x63, 0x6f, 0x73, 0x79,
+	0x73, 0x74, 0x65, 0x6d, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2d, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61,
+	0x79, 0x2f, 0x62, 0x6c, 0x6f, 0x62, 0x2f, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x2f, 0x4c, 0x49,
+	0x43, 0x45, 0x4e, 0x53, 0x45, 0x2e, 0x74, 0x78, 0x74, 0x32, 0x03, 0x31, 0x2e, 0x30, 0x3a, 0x20,
+	0x0a, 0x15, 0x78, 0x2d, 0x73, 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x2d, 0x73, 0x6f,
+	0x6d, 0x65, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x12, 0x07, 0x1a, 0x05, 0x79, 0x61, 0x64, 0x64, 0x61,
+	0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
 }
 
 var (
diff --git a/examples/swagger/helloworld/helloworld_http.pb.go b/examples/swagger/helloworld/helloworld_http.pb.go
index 8515ed38..d5c73d03 100644
--- a/examples/swagger/helloworld/helloworld_http.pb.go
+++ b/examples/swagger/helloworld/helloworld_http.pb.go
@@ -1,6 +1,6 @@
 // Code generated by protoc-gen-go-http. DO NOT EDIT.
 // versions:
-// protoc-gen-go-http v2.0.0
+// protoc-gen-go-http v2.0.3
 
 package helloworld
 
diff --git a/examples/traces/app/message/main.go b/examples/traces/app/message/main.go
index 4f81b025..a882d835 100644
--- a/examples/traces/app/message/main.go
+++ b/examples/traces/app/message/main.go
@@ -12,12 +12,12 @@ import (
 	"github.com/go-kratos/kratos/v2/middleware/recovery"
 	"github.com/go-kratos/kratos/v2/middleware/tracing"
 	"github.com/go-kratos/kratos/v2/transport/grpc"
+	"go.opentelemetry.io/otel"
 	"go.opentelemetry.io/otel/attribute"
 	"go.opentelemetry.io/otel/exporters/jaeger"
 	"go.opentelemetry.io/otel/sdk/resource"
 	tracesdk "go.opentelemetry.io/otel/sdk/trace"
 	semconv "go.opentelemetry.io/otel/semconv/v1.4.0"
-	"go.opentelemetry.io/otel/trace"
 )
 
 // go build -ldflags "-X main.Version=x.y.z"
@@ -31,28 +31,28 @@ var (
 // server is used to implement helloworld.GreeterServer.
 type server struct {
 	v1.UnimplementedMessageServiceServer
-	tracer trace.TracerProvider
 }
 
-// Get trace provider
-func tracerProvider(url string) (*tracesdk.TracerProvider, error) {
+// set trace provider
+func setTracerProvider(url string) error {
 	// Create the Jaeger exporter
 	exp, err := jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint(url)))
 	if err != nil {
-		return nil, err
+		return err
 	}
 	tp := tracesdk.NewTracerProvider(
-		tracesdk.WithSampler(tracesdk.AlwaysSample()),
+		// Set the sampling rate based on the parent span to 100%
+		tracesdk.WithSampler(tracesdk.ParentBased(tracesdk.TraceIDRatioBased(1.0))),
 		// Always be sure to batch in production.
 		tracesdk.WithBatcher(exp),
 		// Record information about this application in an Resource.
 		tracesdk.WithResource(resource.NewSchemaless(
-			semconv.ServiceNameKey.String(v1.MessageService_ServiceDesc.ServiceName),
-			attribute.String("environment", "development"),
-			attribute.Int64("ID", 1),
+			semconv.ServiceNameKey.String(Name),
+			attribute.String("env", "dev"),
 		)),
 	)
-	return tp, nil
+	otel.SetTracerProvider(tp)
+	return nil
 }
 
 func (s *server) GetUserMessage(ctx context.Context, request *v1.GetUserMessageRequest) (*v1.GetUserMessageReply, error) {
@@ -69,12 +69,16 @@ func main() {
 	logger = log.With(logger, "span_id", log.SpanID())
 	log := log.NewHelper(logger)
 
-	tp, err := tracerProvider("http://jaeger:14268/api/traces")
+	url := "http://jaeger:14268/api/traces"
+	if os.Getenv("jaeger_url") != "" {
+		url = os.Getenv("jaeger_url")
+	}
+	err := setTracerProvider(url)
 	if err != nil {
 		log.Error(err)
 	}
 
-	s := &server{tracer: tp}
+	s := &server{}
 	// grpc server
 	grpcSrv := grpc.NewServer(
 		grpc.Address(":9000"),
@@ -82,9 +86,7 @@ func main() {
 			middleware.Chain(
 				recovery.Recovery(),
 				// Configuring tracing Middleware
-				tracing.Server(
-					tracing.WithTracerProvider(tp),
-				),
+				tracing.Server(),
 				logging.Server(logger),
 			),
 		))
diff --git a/examples/traces/app/user/main.go b/examples/traces/app/user/main.go
index 48b838ac..0ca46932 100644
--- a/examples/traces/app/user/main.go
+++ b/examples/traces/app/user/main.go
@@ -14,12 +14,14 @@ import (
 	"github.com/go-kratos/kratos/v2/middleware/tracing"
 	"github.com/go-kratos/kratos/v2/transport/grpc"
 	"github.com/go-kratos/kratos/v2/transport/http"
+
+	"go.opentelemetry.io/otel"
 	"go.opentelemetry.io/otel/attribute"
 	"go.opentelemetry.io/otel/exporters/jaeger"
 	"go.opentelemetry.io/otel/sdk/resource"
 	tracesdk "go.opentelemetry.io/otel/sdk/trace"
 	semconv "go.opentelemetry.io/otel/semconv/v1.4.0"
-	"go.opentelemetry.io/otel/trace"
+	grpcx "google.golang.org/grpc"
 )
 
 // go build -ldflags "-X main.Version=x.y.z"
@@ -33,41 +35,42 @@ var (
 // server is used to implement helloworld.GreeterServer.
 type server struct {
 	v1.UnimplementedUserServer
-	tracer trace.TracerProvider
 }
 
-// Get trace provider
-func tracerProvider(url string) (*tracesdk.TracerProvider, error) {
+// Set global trace provider
+func setTracerProvider(url string) error {
 	// Create the Jaeger exporter
 	exp, err := jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint(url)))
 	if err != nil {
-		return nil, err
+		return err
 	}
 	tp := tracesdk.NewTracerProvider(
-		tracesdk.WithSampler(tracesdk.AlwaysSample()),
+		// Set the sampling rate based on the parent span to 100%
+		tracesdk.WithSampler(tracesdk.ParentBased(tracesdk.TraceIDRatioBased(1.0))),
 		// Always be sure to batch in production.
 		tracesdk.WithBatcher(exp),
 		// Record information about this application in an Resource.
 		tracesdk.WithResource(resource.NewSchemaless(
-			semconv.ServiceNameKey.String(v1.User_ServiceDesc.ServiceName),
-			attribute.String("environment", "development"),
-			attribute.Int64("ID", 1),
+			semconv.ServiceNameKey.String(Name),
+			attribute.String("env", "dev"),
 		)),
 	)
-	return tp, nil
+	otel.SetTracerProvider(tp)
+	return nil
 }
 
 func (s *server) GetMyMessages(ctx context.Context, in *v1.GetMyMessagesRequest) (*v1.GetMyMessagesReply, error) {
 	// create grpc conn
+	// only for demo, use single instance in production env
 	conn, err := grpc.DialInsecure(ctx,
 		grpc.WithEndpoint("127.0.0.1:9000"),
 		grpc.WithMiddleware(
 			recovery.Recovery(),
-			tracing.Client(
-				tracing.WithTracerProvider(s.tracer),
-			),
+			tracing.Client(),
 		),
 		grpc.WithTimeout(2*time.Second),
+		// for tracing remote ip recording
+		grpc.WithOptions(grpcx.WithStatsHandler(&tracing.ClientHandler{})),
 	)
 	if err != nil {
 		return nil, err
@@ -91,7 +94,11 @@ func main() {
 	logger = log.With(logger, "span_id", log.SpanID())
 	log := log.NewHelper(logger)
 
-	tp, err := tracerProvider("http://jaeger:14268/api/traces")
+	url := "http://jaeger:14268/api/traces"
+	if os.Getenv("jaeger_url") != "" {
+		url = os.Getenv("jaeger_url")
+	}
+	err := setTracerProvider(url)
 	if err != nil {
 		log.Error(err)
 	}
@@ -101,13 +108,11 @@ func main() {
 		http.Middleware(
 			recovery.Recovery(),
 			// Configuring tracing middleware
-			tracing.Server(
-				tracing.WithTracerProvider(tp),
-			),
+			tracing.Server(),
 			logging.Server(logger),
 		),
 	)
-	s := &server{tracer: tp}
+	s := &server{}
 	v1.RegisterUserHTTPServer(httpSrv, s)
 
 	app := kratos.New(
diff --git a/go.mod b/go.mod
index 4705ecef..2ada1a4d 100644
--- a/go.mod
+++ b/go.mod
@@ -9,9 +9,9 @@ require (
 	github.com/gorilla/mux v1.8.0
 	github.com/imdario/mergo v0.3.12
 	github.com/stretchr/testify v1.7.0
-	go.opentelemetry.io/otel v1.0.0-RC1
-	go.opentelemetry.io/otel/sdk v1.0.0-RC1
-	go.opentelemetry.io/otel/trace v1.0.0-RC1
+	go.opentelemetry.io/otel v1.0.0-RC2
+	go.opentelemetry.io/otel/sdk v1.0.0-RC2
+	go.opentelemetry.io/otel/trace v1.0.0-RC2
 	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
 	google.golang.org/genproto v0.0.0-20210629200056-84d6f6074151
 	google.golang.org/grpc v1.39.0
diff --git a/go.sum b/go.sum
index 22827354..73930d1e 100644
--- a/go.sum
+++ b/go.sum
@@ -64,14 +64,12 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
 github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
-go.opentelemetry.io/otel v1.0.0-RC1 h1:4CeoX93DNTWt8awGK9JmNXzF9j7TyOu9upscEdtcdXc=
-go.opentelemetry.io/otel v1.0.0-RC1/go.mod h1:x9tRa9HK4hSSq7jf2TKbqFbtt58/TGk0f9XiEYISI1I=
-go.opentelemetry.io/otel/oteltest v1.0.0-RC1 h1:G685iP3XiskCwk/z0eIabL55XUl2gk0cljhGk9sB0Yk=
-go.opentelemetry.io/otel/oteltest v1.0.0-RC1/go.mod h1:+eoIG0gdEOaPNftuy1YScLr1Gb4mL/9lpDkZ0JjMRq4=
-go.opentelemetry.io/otel/sdk v1.0.0-RC1 h1:Sy2VLOOg24bipyC29PhuMXYNJrLsxkie8hyI7kUlG9Q=
-go.opentelemetry.io/otel/sdk v1.0.0-RC1/go.mod h1:kj6yPn7Pgt5ByRuwesbaWcRLA+V7BSDg3Hf8xRvsvf8=
-go.opentelemetry.io/otel/trace v1.0.0-RC1 h1:jrjqKJZEibFrDz+umEASeU3LvdVyWKlnTh7XEfwrT58=
-go.opentelemetry.io/otel/trace v1.0.0-RC1/go.mod h1:86UHmyHWFEtWjfWPSbu0+d0Pf9Q6e1U+3ViBOc+NXAg=
+go.opentelemetry.io/otel v1.0.0-RC2 h1:SHhxSjB+omnGZPgGlKe+QMp3MyazcOHdQ8qwo89oKbg=
+go.opentelemetry.io/otel v1.0.0-RC2/go.mod h1:w1thVQ7qbAy8MHb0IFj8a5Q2QU0l2ksf8u/CN8m3NOM=
+go.opentelemetry.io/otel/sdk v1.0.0-RC2 h1:ROuteeSCBaZNjiT9JcFzZepmInDvLktR28Y6qKo8bCs=
+go.opentelemetry.io/otel/sdk v1.0.0-RC2/go.mod h1:fgwHyiDn4e5k40TD9VX243rOxXR+jzsWBZYA2P5jpEw=
+go.opentelemetry.io/otel/trace v1.0.0-RC2 h1:dunAP0qDULMIT82atj34m5RgvsIK6LcsXf1c/MsYg1w=
+go.opentelemetry.io/otel/trace v1.0.0-RC2/go.mod h1:JPQ+z6nNw9mqEGT8o3eoPTdnNI+Aj5JcxEsVGREIAy4=
 go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@@ -108,6 +106,7 @@ golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE=
 golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
diff --git a/middleware/tracing/metadata.go b/middleware/tracing/metadata.go
new file mode 100644
index 00000000..ec5acb20
--- /dev/null
+++ b/middleware/tracing/metadata.go
@@ -0,0 +1,43 @@
+package tracing
+
+import (
+	"context"
+
+	"github.com/go-kratos/kratos/v2"
+	"github.com/go-kratos/kratos/v2/metadata"
+	"go.opentelemetry.io/otel/propagation"
+)
+
+const serviceHeader = "x-md-service-name"
+
+// Metadata is tracing metadata propagator
+type Metadata struct{}
+
+var _ propagation.TextMapPropagator = Metadata{}
+
+// Inject sets metadata key-values from ctx into the carrier.
+func (b Metadata) Inject(ctx context.Context, carrier propagation.TextMapCarrier) {
+	app, _ := kratos.FromContext(ctx)
+	carrier.Set(serviceHeader, app.Name())
+}
+
+// Extract returns a copy of parent with the metadata from the carrier added.
+func (b Metadata) Extract(parent context.Context, carrier propagation.TextMapCarrier) context.Context {
+	name := carrier.Get(serviceHeader)
+	if name != "" {
+		if md, ok := metadata.FromServerContext(parent); ok {
+			md.Set(serviceHeader, name)
+		} else {
+			md := metadata.New()
+			md.Set(serviceHeader, name)
+			parent = metadata.NewServerContext(parent, md)
+		}
+	}
+
+	return parent
+}
+
+// Fields returns the keys who's values are set with Inject.
+func (b Metadata) Fields() []string {
+	return []string{serviceHeader}
+}
diff --git a/middleware/tracing/span.go b/middleware/tracing/span.go
new file mode 100644
index 00000000..b8acf370
--- /dev/null
+++ b/middleware/tracing/span.go
@@ -0,0 +1,144 @@
+package tracing
+
+import (
+	"context"
+	"net"
+	"net/url"
+	"strings"
+
+	"github.com/go-kratos/kratos/v2/metadata"
+	"github.com/go-kratos/kratos/v2/transport"
+	"github.com/go-kratos/kratos/v2/transport/http"
+
+	"go.opentelemetry.io/otel/attribute"
+	semconv "go.opentelemetry.io/otel/semconv/v1.4.0"
+	"go.opentelemetry.io/otel/trace"
+	"google.golang.org/grpc/peer"
+	"google.golang.org/protobuf/proto"
+)
+
+func setClientSpan(ctx context.Context, span trace.Span, m interface{}) {
+	attrs := []attribute.KeyValue{}
+	var remote string
+	var operation string
+	var rpcKind string
+	if tr, ok := transport.FromClientContext(ctx); ok {
+		operation = tr.Operation()
+		rpcKind = tr.Kind().String()
+		if tr.Kind() == transport.KindHTTP {
+			if ht, ok := tr.(*http.Transport); ok {
+				method := ht.Request().Method
+				route := ht.PathTemplate()
+				path := ht.Request().URL.Path
+				attrs = append(attrs, semconv.HTTPMethodKey.String(method))
+				attrs = append(attrs, semconv.HTTPRouteKey.String(route))
+				attrs = append(attrs, semconv.HTTPTargetKey.String(path))
+				remote = ht.Request().Host
+			}
+		} else if tr.Kind() == transport.KindGRPC {
+			remote, _ = parseTarget(tr.Endpoint())
+		}
+	}
+	attrs = append(attrs, semconv.RPCSystemKey.String(rpcKind))
+	_, mAttrs := parseFullMethod(operation)
+	attrs = append(attrs, mAttrs...)
+	if remote != "" {
+		attrs = append(attrs, peerAttr(remote)...)
+	}
+	if p, ok := m.(proto.Message); ok {
+		attrs = append(attrs, attribute.Key("send_msg.size").Int(proto.Size(p)))
+	}
+
+	span.SetAttributes(attrs...)
+}
+
+func setServerSpan(ctx context.Context, span trace.Span, m interface{}) {
+	attrs := []attribute.KeyValue{}
+	var remote string
+	var operation string
+	var rpcKind string
+	if tr, ok := transport.FromServerContext(ctx); ok {
+		operation = tr.Operation()
+		rpcKind = tr.Kind().String()
+		if tr.Kind() == transport.KindHTTP {
+			if ht, ok := tr.(*http.Transport); ok {
+				method := ht.Request().Method
+				route := ht.PathTemplate()
+				path := ht.Request().URL.Path
+				attrs = append(attrs, semconv.HTTPMethodKey.String(method))
+				attrs = append(attrs, semconv.HTTPRouteKey.String(route))
+				attrs = append(attrs, semconv.HTTPTargetKey.String(path))
+				remote = ht.Request().RemoteAddr
+			}
+		} else if tr.Kind() == transport.KindGRPC {
+			if p, ok := peer.FromContext(ctx); ok {
+				remote = p.Addr.String()
+			}
+		}
+	}
+	attrs = append(attrs, semconv.RPCSystemKey.String(rpcKind))
+	_, mAttrs := parseFullMethod(operation)
+	attrs = append(attrs, mAttrs...)
+	attrs = append(attrs, peerAttr(remote)...)
+	if p, ok := m.(proto.Message); ok {
+		attrs = append(attrs, attribute.Key("recv_msg.size").Int(proto.Size(p)))
+	}
+	if md, ok := metadata.FromServerContext(ctx); ok {
+		attrs = append(attrs, semconv.PeerServiceKey.String(md.Get(serviceHeader)))
+	}
+
+	span.SetAttributes(attrs...)
+}
+
+// parseFullMethod returns a span name following the OpenTelemetry semantic
+// conventions as well as all applicable span attribute.KeyValue attributes based
+// on a gRPC's FullMethod.
+func parseFullMethod(fullMethod string) (string, []attribute.KeyValue) {
+	name := strings.TrimLeft(fullMethod, "/")
+	parts := strings.SplitN(name, "/", 2)
+	if len(parts) != 2 {
+		// Invalid format, does not follow `/package.service/method`.
+		return name, []attribute.KeyValue{attribute.Key("rpc.operation").String(fullMethod)}
+	}
+
+	var attrs []attribute.KeyValue
+	if service := parts[0]; service != "" {
+		attrs = append(attrs, semconv.RPCServiceKey.String(service))
+	}
+	if method := parts[1]; method != "" {
+		attrs = append(attrs, semconv.RPCMethodKey.String(method))
+	}
+	return name, attrs
+}
+
+// peerAttr returns attributes about the peer address.
+func peerAttr(addr string) []attribute.KeyValue {
+	host, port, err := net.SplitHostPort(addr)
+	if err != nil {
+		return []attribute.KeyValue(nil)
+	}
+
+	if host == "" {
+		host = "127.0.0.1"
+	}
+
+	return []attribute.KeyValue{
+		semconv.NetPeerIPKey.String(host),
+		semconv.NetPeerPortKey.String(port),
+	}
+}
+
+func parseTarget(endpoint string) (address string, err error) {
+	var u *url.URL
+	u, err = url.Parse(endpoint)
+	if err != nil {
+		if u, err = url.Parse("http://" + endpoint); err != nil {
+			return "", err
+		}
+		return u.Host, nil
+	}
+	if len(u.Path) > 1 {
+		return u.Path[1:], nil
+	}
+	return endpoint, nil
+}
diff --git a/middleware/tracing/statsHandler.go b/middleware/tracing/statsHandler.go
new file mode 100644
index 00000000..70c9efdc
--- /dev/null
+++ b/middleware/tracing/statsHandler.go
@@ -0,0 +1,39 @@
+package tracing
+
+import (
+	"context"
+
+	"go.opentelemetry.io/otel/trace"
+	"google.golang.org/grpc/peer"
+	"google.golang.org/grpc/stats"
+)
+
+// ClientHandler is tracing ClientHandler
+type ClientHandler struct {
+}
+
+// HandleConn exists to satisfy gRPC stats.Handler.
+func (c *ClientHandler) HandleConn(ctx context.Context, cs stats.ConnStats) {
+}
+
+// TagConn exists to satisfy gRPC stats.Handler.
+func (c *ClientHandler) TagConn(ctx context.Context, cti *stats.ConnTagInfo) context.Context {
+	return ctx
+}
+
+// HandleRPC implements per-RPC tracing and stats instrumentation.
+func (c *ClientHandler) HandleRPC(ctx context.Context, rs stats.RPCStats) {
+	if _, ok := rs.(*stats.OutHeader); ok {
+		if p, ok := peer.FromContext(ctx); ok {
+			remoteAddr := p.Addr.String()
+			if span := trace.SpanFromContext(ctx); span.SpanContext().IsValid() {
+				span.SetAttributes(peerAttr(remoteAddr)...)
+			}
+		}
+	}
+}
+
+// TagRPC implements per-RPC context management.
+func (c *ClientHandler) TagRPC(ctx context.Context, rti *stats.RPCTagInfo) context.Context {
+	return ctx
+}
diff --git a/middleware/tracing/tracer.go b/middleware/tracing/tracer.go
index e3ce2af4..8bad3987 100644
--- a/middleware/tracing/tracer.go
+++ b/middleware/tracing/tracer.go
@@ -4,70 +4,77 @@ import (
 	"context"
 	"fmt"
 
+	"github.com/go-kratos/kratos/v2/errors"
 	"go.opentelemetry.io/otel"
 	"go.opentelemetry.io/otel/attribute"
 	"go.opentelemetry.io/otel/codes"
 	"go.opentelemetry.io/otel/propagation"
 	"go.opentelemetry.io/otel/trace"
+	"google.golang.org/protobuf/proto"
 )
 
 // Tracer is otel span tracer
 type Tracer struct {
 	tracer trace.Tracer
 	kind   trace.SpanKind
+	opt    *options
 }
 
 // NewTracer create tracer instance
 func NewTracer(kind trace.SpanKind, opts ...Option) *Tracer {
-	options := options{}
+	options := options{
+		propagator: propagation.NewCompositeTextMapPropagator(Metadata{}, propagation.Baggage{}, propagation.TraceContext{}),
+	}
 	for _, o := range opts {
 		o(&options)
 	}
-	if options.TracerProvider != nil {
-		otel.SetTracerProvider(options.TracerProvider)
-	}
-	if options.Propagator != nil {
-		otel.SetTextMapPropagator(options.Propagator)
-	} else {
-		otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.Baggage{}, propagation.TraceContext{}))
+	if options.tracerProvider != nil {
+		otel.SetTracerProvider(options.tracerProvider)
 	}
+
 	switch kind {
 	case trace.SpanKindClient:
-		return &Tracer{tracer: otel.Tracer("client"), kind: kind}
+		return &Tracer{tracer: otel.Tracer("kartos"), kind: kind, opt: &options}
 	case trace.SpanKindServer:
-		return &Tracer{tracer: otel.Tracer("server"), kind: kind}
+		return &Tracer{tracer: otel.Tracer("kartos"), kind: kind, opt: &options}
 	default:
 		panic(fmt.Sprintf("unsupported span kind: %v", kind))
 	}
 }
 
 // Start start tracing span
-func (t *Tracer) Start(ctx context.Context, component string, operation string, carrier propagation.TextMapCarrier) (context.Context, trace.Span) {
+func (t *Tracer) Start(ctx context.Context, operation string, carrier propagation.TextMapCarrier) (context.Context, trace.Span) {
 	if t.kind == trace.SpanKindServer {
-		ctx = otel.GetTextMapPropagator().Extract(ctx, carrier)
+		ctx = t.opt.propagator.Extract(ctx, carrier)
 	}
 	ctx, span := t.tracer.Start(ctx,
 		operation,
-		trace.WithAttributes(attribute.String("component", component)),
 		trace.WithSpanKind(t.kind),
 	)
 	if t.kind == trace.SpanKindClient {
-		otel.GetTextMapPropagator().Inject(ctx, carrier)
+		t.opt.propagator.Inject(ctx, carrier)
 	}
 	return ctx, span
 }
 
 // End finish tracing span
-func (t *Tracer) End(ctx context.Context, span trace.Span, err error) {
+func (t *Tracer) End(ctx context.Context, span trace.Span, m interface{}, err error) {
 	if err != nil {
 		span.RecordError(err)
-		span.SetAttributes(
-			attribute.String("event", "error"),
-			attribute.String("message", err.Error()),
-		)
+		if e := errors.FromError(err); e != nil {
+			span.SetAttributes(attribute.Key("rpc.status_code").Int64(int64(e.Code)))
+		}
 		span.SetStatus(codes.Error, err.Error())
 	} else {
 		span.SetStatus(codes.Ok, "OK")
 	}
+
+	if p, ok := m.(proto.Message); ok {
+		if t.kind == trace.SpanKindServer {
+			span.SetAttributes(attribute.Key("send_msg.size").Int(proto.Size(p)))
+		} else {
+			span.SetAttributes(attribute.Key("recv_msg.size").Int(proto.Size(p)))
+		}
+	}
 	span.End()
 }
diff --git a/middleware/tracing/tracing.go b/middleware/tracing/tracing.go
index 5f968724..3e03c157 100644
--- a/middleware/tracing/tracing.go
+++ b/middleware/tracing/tracing.go
@@ -13,21 +13,22 @@ import (
 type Option func(*options)
 
 type options struct {
-	TracerProvider trace.TracerProvider
-	Propagator     propagation.TextMapPropagator
+	tracerProvider trace.TracerProvider
+	propagator     propagation.TextMapPropagator
 }
 
 // WithPropagator with tracer propagator.
 func WithPropagator(propagator propagation.TextMapPropagator) Option {
 	return func(opts *options) {
-		opts.Propagator = propagator
+		opts.propagator = propagator
 	}
 }
 
 // WithTracerProvider with tracer provider.
+// Deprecated: use otel.SetTracerProvider(provider) instead.
 func WithTracerProvider(provider trace.TracerProvider) Option {
 	return func(opts *options) {
-		opts.TracerProvider = provider
+		opts.tracerProvider = provider
 	}
 }
 
@@ -38,8 +39,9 @@ func Server(opts ...Option) middleware.Middleware {
 		return func(ctx context.Context, req interface{}) (reply interface{}, err error) {
 			if tr, ok := transport.FromServerContext(ctx); ok {
 				var span trace.Span
-				ctx, span = tracer.Start(ctx, tr.Kind().String(), tr.Operation(), tr.RequestHeader())
-				defer func() { tracer.End(ctx, span, err) }()
+				ctx, span = tracer.Start(ctx, tr.Operation(), tr.RequestHeader())
+				setServerSpan(ctx, span, req)
+				defer func() { tracer.End(ctx, span, reply, err) }()
 			}
 			return handler(ctx, req)
 		}
@@ -53,8 +55,9 @@ func Client(opts ...Option) middleware.Middleware {
 		return func(ctx context.Context, req interface{}) (reply interface{}, err error) {
 			if tr, ok := transport.FromClientContext(ctx); ok {
 				var span trace.Span
-				ctx, span = tracer.Start(ctx, tr.Kind().String(), tr.Operation(), tr.RequestHeader())
-				defer func() { tracer.End(ctx, span, err) }()
+				ctx, span = tracer.Start(ctx, tr.Operation(), tr.RequestHeader())
+				setClientSpan(ctx, span, req)
+				defer func() { tracer.End(ctx, span, reply, err) }()
 			}
 			return handler(ctx, req)
 		}
diff --git a/third_party/protoc-gen-openapiv2/options/annotations.proto b/third_party/protoc-gen-openapiv2/options/annotations.proto
index 0ece3169..1c189e20 100644
--- a/third_party/protoc-gen-openapiv2/options/annotations.proto
+++ b/third_party/protoc-gen-openapiv2/options/annotations.proto
@@ -2,7 +2,7 @@ syntax = "proto3";
 
 package grpc.gateway.protoc_gen_openapiv2.options;
 
-option go_package = "github.com/longXboy/grpc-gateway/v2/protoc-gen-openapiv2/options";
+option go_package = "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2/options";
 
 import "google/protobuf/descriptor.proto";
 import "protoc-gen-openapiv2/options/openapiv2.proto";
diff --git a/third_party/protoc-gen-openapiv2/options/openapiv2.proto b/third_party/protoc-gen-openapiv2/options/openapiv2.proto
index 17fba868..85ab6a53 100644
--- a/third_party/protoc-gen-openapiv2/options/openapiv2.proto
+++ b/third_party/protoc-gen-openapiv2/options/openapiv2.proto
@@ -2,7 +2,7 @@ syntax = "proto3";
 
 package grpc.gateway.protoc_gen_openapiv2.options;
 
-option go_package = "github.com/longXboy/grpc-gateway/v2/protoc-gen-openapiv2/options";
+option go_package = "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2/options";
 
 import "google/protobuf/struct.proto";
 


Test patch:
diff --git a/app_test.go b/app_test.go
index bea39401..9281b6fd 100644
--- a/app_test.go
+++ b/app_test.go
@@ -1,11 +1,15 @@
 package kratos
 
 import (
+	"context"
+	"reflect"
 	"testing"
 	"time"
 
+	"github.com/go-kratos/kratos/v2/registry"
 	"github.com/go-kratos/kratos/v2/transport/grpc"
 	"github.com/go-kratos/kratos/v2/transport/http"
+	"github.com/stretchr/testify/assert"
 )
 
 func TestApp(t *testing.T) {
@@ -23,3 +27,126 @@ func TestApp(t *testing.T) {
 		t.Fatal(err)
 	}
 }
+
+func TestApp_ID(t *testing.T) {
+	v := "123"
+	o := New(ID(v))
+	assert.Equal(t, v, o.ID())
+}
+
+func TestApp_Name(t *testing.T) {
+	v := "123"
+	o := New(Name(v))
+	assert.Equal(t, v, o.Name())
+}
+
+func TestApp_Version(t *testing.T) {
+	v := "123"
+	o := New(Version(v))
+	assert.Equal(t, v, o.Version())
+}
+
+func TestApp_Metadata(t *testing.T) {
+	v := map[string]string{
+		"a": "1",
+		"b": "2",
+	}
+	o := New(Metadata(v))
+	assert.Equal(t, v, o.Metadata())
+}
+
+func TestApp_Endpoint(t *testing.T) {
+	type fields struct {
+		id       string
+		version  string
+		name     string
+		instance *registry.ServiceInstance
+		metadata map[string]string
+		want     struct {
+			id       string
+			version  string
+			name     string
+			endpoint []string
+			metadata map[string]string
+		}
+	}
+	tests := []fields{
+		{
+			id:       "1",
+			name:     "kratos-v1",
+			instance: &registry.ServiceInstance{Endpoints: []string{"https://go-kratos.dev", "localhost"}},
+			metadata: map[string]string{},
+			version:  "v1",
+			want: struct {
+				id       string
+				version  string
+				name     string
+				endpoint []string
+				metadata map[string]string
+			}{id: "1", version: "v1", name: "kratos-v1", endpoint: []string{"https://go-kratos.dev", "localhost"},
+				metadata: map[string]string{}},
+		},
+		{
+			id:       "2",
+			name:     "kratos-v2",
+			instance: &registry.ServiceInstance{Endpoints: []string{"test"}},
+			metadata: map[string]string{"kratos": "https://github.com/go-kratos/kratos"},
+			version:  "v2",
+			want: struct {
+				id       string
+				version  string
+				name     string
+				endpoint []string
+				metadata map[string]string
+			}{id: "2", version: "v2", name: "kratos-v2", endpoint: []string{"test"},
+				metadata: map[string]string{"kratos": "https://github.com/go-kratos/kratos"}},
+		},
+		{
+			id:       "3",
+			name:     "kratos-v3",
+			instance: nil,
+			metadata: make(map[string]string),
+			version:  "v3",
+			want: struct {
+				id       string
+				version  string
+				name     string
+				endpoint []string
+				metadata map[string]string
+			}{id: "3", version: "v3", name: "kratos-v3", endpoint: []string{},
+				metadata: map[string]string{}},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			a := &App{
+				opts:     options{id: tt.id, name: tt.name, metadata: tt.metadata, version: tt.version},
+				ctx:      context.Background(),
+				cancel:   nil,
+				instance: tt.instance,
+			}
+
+			ctx := NewContext(context.Background(), a)
+
+			if got, ok := FromContext(ctx); ok {
+				if got.ID() != tt.want.id {
+					t.Errorf("ID() = %v, want %v", got.ID(), tt.want.id)
+				}
+				if got.Name() != tt.want.name {
+					t.Errorf("Name() = %v, want %v", got.Name(), tt.want.name)
+				}
+				if got.Version() != tt.want.version {
+					t.Errorf("Version() = %v, want %v", got.Version(), tt.want.version)
+				}
+				if !reflect.DeepEqual(got.Endpoint(), tt.want.endpoint) {
+					t.Errorf("Endpoint() = %v, want %v", got.Endpoint(), tt.want.endpoint)
+				}
+				if !reflect.DeepEqual(got.Metadata(), tt.want.metadata) {
+					t.Errorf("Metadata() = %v, want %v", got.Metadata(), tt.want.metadata)
+				}
+			} else {
+				t.Errorf("ok() = %v, want %v", ok, true)
+			}
+		})
+	}
+}
diff --git a/config/env/env_test.go b/config/env/env_test.go
index 0e19577b..910e6a17 100644
--- a/config/env/env_test.go
+++ b/config/env/env_test.go
@@ -31,7 +31,7 @@ const _testJSON = `
 
 func TestEnvWithPrefix(t *testing.T) {
 	var (
-		path     = filepath.Join(os.TempDir(), "test_config")
+		path     = filepath.Join(t.TempDir(), "test_config")
 		filename = filepath.Join(path, "test.json")
 		data     = []byte(_testJSON)
 	)
@@ -143,7 +143,7 @@ func TestEnvWithPrefix(t *testing.T) {
 
 func TestEnvWithoutPrefix(t *testing.T) {
 	var (
-		path     = filepath.Join(os.TempDir(), "test_config")
+		path     = filepath.Join(t.TempDir(), "test_config")
 		filename = filepath.Join(path, "test.json")
 		data     = []byte(_testJSON)
 	)
diff --git a/config/file/file_test.go b/config/file/file_test.go
index dec6dcb8..57fae145 100644
--- a/config/file/file_test.go
+++ b/config/file/file_test.go
@@ -84,7 +84,7 @@ const (
 
 func TestFile(t *testing.T) {
 	var (
-		path = filepath.Join(os.TempDir(), "test_config")
+		path = filepath.Join(t.TempDir(), "test_config")
 		file = filepath.Join(path, "test.json")
 		data = []byte(_testJSON)
 	)
@@ -178,7 +178,7 @@ func testSource(t *testing.T, path string, data []byte) {
 }
 
 func TestConfig(t *testing.T) {
-	path := filepath.Join(os.TempDir(), "test_config.json")
+	path := filepath.Join(t.TempDir(), "test_config.json")
 	defer os.Remove(path)
 	if err := ioutil.WriteFile(path, []byte(_testJSON), 0666); err != nil {
 		t.Error(err)
@@ -192,7 +192,7 @@ func TestConfig(t *testing.T) {
 }
 
 func testConfig(t *testing.T, c config.Config) {
-	var expected = map[string]interface{}{
+	expected := map[string]interface{}{
 		"test.settings.int_key":      int64(1000),
 		"test.settings.float_key":    float64(1000.1),
 		"test.settings.string_key":   "string_value",
@@ -258,7 +258,6 @@ func testConfig(t *testing.T, c config.Config) {
 	if _, err := c.Value("not_found_key").Bool(); errors.Is(err, config.ErrNotFound) {
 		t.Logf("not_found_key not match: %v", err)
 	}
-
 }
 
 func testScan(t *testing.T, c config.Config) {
diff --git a/middleware/tracing/tracing_test.go b/middleware/tracing/tracing_test.go
index 10d69880..7725c030 100644
--- a/middleware/tracing/tracing_test.go
+++ b/middleware/tracing/tracing_test.go
@@ -57,15 +57,15 @@ func TestTracing(t *testing.T) {
 	tracer := NewTracer(trace.SpanKindClient, WithTracerProvider(tp), WithPropagator(propagation.NewCompositeTextMapPropagator(propagation.Baggage{}, propagation.TraceContext{})))
 	ts := &Transport{kind: transport.KindHTTP, header: carrier}
 
-	ctx, aboveSpan := tracer.Start(transport.NewClientContext(context.Background(), ts), ts.Kind().String(), ts.Operation(), ts.RequestHeader())
-	defer tracer.End(ctx, aboveSpan, nil)
+	ctx, aboveSpan := tracer.Start(transport.NewClientContext(context.Background(), ts), ts.Operation(), ts.RequestHeader())
+	defer tracer.End(ctx, aboveSpan, nil, nil)
 
 	// server use Extract fetch traceInfo from carrier
 	tracer = NewTracer(trace.SpanKindServer, WithPropagator(propagation.NewCompositeTextMapPropagator(propagation.Baggage{}, propagation.TraceContext{})))
 	ts = &Transport{kind: transport.KindHTTP, header: carrier}
 
-	ctx, span := tracer.Start(transport.NewServerContext(ctx, ts), ts.Kind().String(), ts.Operation(), ts.RequestHeader())
-	defer tracer.End(ctx, span, nil)
+	ctx, span := tracer.Start(transport.NewServerContext(ctx, ts), ts.Operation(), ts.RequestHeader())
+	defer tracer.End(ctx, span, nil, nil)
 
 	if aboveSpan.SpanContext().TraceID() != span.SpanContext().TraceID() {
 		t.Fatalf("TraceID failed to deliver")
diff --git a/options_test.go b/options_test.go
new file mode 100644
index 00000000..34bce67c
--- /dev/null
+++ b/options_test.go
@@ -0,0 +1,121 @@
+package kratos
+
+import (
+	"context"
+	"log"
+	"net/url"
+	"os"
+	"testing"
+	"time"
+
+	xlog "github.com/go-kratos/kratos/v2/log"
+	"github.com/go-kratos/kratos/v2/registry"
+	"github.com/go-kratos/kratos/v2/transport"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestID(t *testing.T) {
+	o := &options{}
+	v := "123"
+	ID(v)(o)
+	assert.Equal(t, v, o.id)
+}
+
+func TestName(t *testing.T) {
+	o := &options{}
+	v := "abc"
+	Name(v)(o)
+	assert.Equal(t, v, o.name)
+}
+
+func TestVersion(t *testing.T) {
+	o := &options{}
+	v := "123"
+	Version(v)(o)
+	assert.Equal(t, v, o.version)
+}
+
+func TestMetadata(t *testing.T) {
+	o := &options{}
+	v := map[string]string{
+		"a": "1",
+		"b": "2",
+	}
+	Metadata(v)(o)
+	assert.Equal(t, v, o.metadata)
+}
+
+func TestEndpoint(t *testing.T) {
+	o := &options{}
+	v := []*url.URL{
+		{Host: "example.com"},
+		{Host: "foo.com"},
+	}
+	Endpoint(v...)(o)
+	assert.Equal(t, v, o.endpoints)
+}
+
+func TestContext(t *testing.T) {
+	o := &options{}
+	v := context.WithValue(context.TODO(), "a", "b")
+	Context(v)(o)
+	assert.Equal(t, v, o.ctx)
+}
+
+func TestLogger(t *testing.T) {
+	o := &options{}
+	v := xlog.NewStdLogger(log.Writer())
+	Logger(v)(o)
+	assert.Equal(t, v, o.logger)
+}
+
+type mockServer struct{}
+
+func (m *mockServer) Start(ctx context.Context) error { return nil }
+func (m *mockServer) Stop(ctx context.Context) error  { return nil }
+
+func TestServer(t *testing.T) {
+	o := &options{}
+	v := []transport.Server{
+		&mockServer{}, &mockServer{},
+	}
+	Server(v...)(o)
+	assert.Equal(t, v, o.servers)
+}
+
+type mockSignal struct{}
+
+func (m *mockSignal) String() string { return "sig" }
+func (m *mockSignal) Signal()        {}
+
+func TestSignal(t *testing.T) {
+	o := &options{}
+	v := []os.Signal{
+		&mockSignal{}, &mockSignal{},
+	}
+	Signal(v...)(o)
+	assert.Equal(t, v, o.sigs)
+}
+
+type mockRegistrar struct{}
+
+func (m *mockRegistrar) Register(ctx context.Context, service *registry.ServiceInstance) error {
+	return nil
+}
+func (m *mockRegistrar) Deregister(ctx context.Context, service *registry.ServiceInstance) error {
+	return nil
+}
+
+func TestRegistrar(t *testing.T) {
+	o := &options{}
+	v := &mockRegistrar{}
+	Registrar(v)(o)
+	assert.Equal(t, v, o.registrar)
+}
+
+func TestRegistrarTimeout(t *testing.T) {
+	o := &options{}
+	v := time.Duration(123)
+	RegistrarTimeout(v)(o)
+	assert.Equal(t, v, o.registrarTimeout)
+}
diff --git a/transport/grpc/client_test.go b/transport/grpc/client_test.go
new file mode 100644
index 00000000..9349a16b
--- /dev/null
+++ b/transport/grpc/client_test.go
@@ -0,0 +1,76 @@
+package grpc
+
+import (
+	"context"
+	"crypto/tls"
+	"testing"
+
+	"github.com/go-kratos/kratos/v2/middleware"
+	"github.com/go-kratos/kratos/v2/registry"
+	"github.com/stretchr/testify/assert"
+	"google.golang.org/grpc"
+)
+
+func TestWithEndpoint(t *testing.T) {
+	o := &clientOptions{}
+	v := "abc"
+	WithEndpoint(v)(o)
+	assert.Equal(t, v, o.endpoint)
+}
+
+func TestWithMiddleware(t *testing.T) {
+	o := &clientOptions{}
+	v := []middleware.Middleware{
+		func(middleware.Handler) middleware.Handler { return nil },
+	}
+	WithMiddleware(v...)(o)
+	assert.Equal(t, v, o.middleware)
+}
+
+type mockRegistry struct{}
+
+func (m *mockRegistry) GetService(ctx context.Context, serviceName string) ([]*registry.ServiceInstance, error) {
+	return nil, nil
+}
+func (m *mockRegistry) Watch(ctx context.Context, serviceName string) (registry.Watcher, error) {
+	return nil, nil
+}
+
+func TestWithDiscovery(t *testing.T) {
+	o := &clientOptions{}
+	v := &mockRegistry{}
+	WithDiscovery(v)(o)
+	assert.Equal(t, v, o.discovery)
+}
+
+func TestWithTLSConfig(t *testing.T) {
+	o := &clientOptions{}
+	v := &tls.Config{}
+	WithTLSConfig(v)(o)
+	assert.Equal(t, v, o.tlsConf)
+}
+
+func TestWithUnaryInterceptor(t *testing.T) {
+	o := &clientOptions{}
+	v := []grpc.UnaryClientInterceptor{
+		func(ctx context.Context, method string, req, reply interface{},
+			cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
+			return nil
+		},
+		func(ctx context.Context, method string, req, reply interface{},
+			cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
+			return nil
+		},
+	}
+	WithUnaryInterceptor(v...)(o)
+	assert.Equal(t, v, o.ints)
+}
+
+func TestWithOptions(t *testing.T) {
+	o := &clientOptions{}
+	v := []grpc.DialOption{
+		grpc.EmptyDialOption{},
+	}
+	WithOptions(v...)(o)
+	assert.Equal(t, v, o.grpcOpts)
+}
diff --git a/transport/grpc/server_test.go b/transport/grpc/server_test.go
index 1f281adc..5dd287ca 100644
--- a/transport/grpc/server_test.go
+++ b/transport/grpc/server_test.go
@@ -2,9 +2,14 @@ package grpc
 
 import (
 	"context"
+	"crypto/tls"
+	"github.com/go-kratos/kratos/v2/middleware"
+	"google.golang.org/grpc"
 	"strings"
 	"testing"
 	"time"
+
+	"github.com/stretchr/testify/assert"
 )
 
 type testKey struct{}
@@ -41,3 +46,67 @@ func testClient(t *testing.T, srv *Server) {
 	}
 	conn.Close()
 }
+
+func TestNetwork(t *testing.T) {
+	o := &Server{}
+	v := "abc"
+	Network(v)(o)
+	assert.Equal(t, v, o.network)
+}
+
+func TestAddress(t *testing.T) {
+	o := &Server{}
+	v := "abc"
+	Address(v)(o)
+	assert.Equal(t, v, o.address)
+}
+
+func TestTimeout(t *testing.T) {
+	o := &Server{}
+	v := time.Duration(123)
+	Timeout(v)(o)
+	assert.Equal(t, v, o.timeout)
+}
+
+func TestMiddleware(t *testing.T) {
+	o := &clientOptions{}
+	v := []middleware.Middleware{
+		func(middleware.Handler) middleware.Handler { return nil },
+	}
+	WithMiddleware(v...)(o)
+	assert.Equal(t, v, o.middleware)
+}
+
+func TestLogger(t *testing.T) {
+	//todo
+}
+
+func TestTLSConfig(t *testing.T) {
+	o := &Server{}
+	v := &tls.Config{}
+	TLSConfig(v)(o)
+	assert.Equal(t, v, o.tlsConf)
+}
+
+func TestUnaryInterceptor(t *testing.T) {
+	o := &Server{}
+	v := []grpc.UnaryServerInterceptor{
+		func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
+			return nil, nil
+		},
+		func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
+			return nil, nil
+		},
+	}
+	UnaryInterceptor(v...)(o)
+	assert.Equal(t, v, o.ints)
+}
+
+func TestOptions(t *testing.T) {
+	o := &Server{}
+	v := []grpc.ServerOption{
+		grpc.EmptyServerOption{},
+	}
+	Options(v...)(o)
+	assert.Equal(t, v, o.grpcOpts)
+}
diff --git a/transport/grpc/transport_test.go b/transport/grpc/transport_test.go
new file mode 100644
index 00000000..5d19ff51
--- /dev/null
+++ b/transport/grpc/transport_test.go
@@ -0,0 +1,46 @@
+package grpc
+
+import (
+	"github.com/stretchr/testify/assert"
+	"testing"
+
+	"github.com/go-kratos/kratos/v2/transport"
+)
+
+func TestTransport_Kind(t *testing.T) {
+	o := &Transport{}
+	assert.Equal(t, transport.KindGRPC, o.Kind())
+}
+
+func TestTransport_Endpoint(t *testing.T) {
+	v := "hello"
+	o := &Transport{endpoint: v}
+	assert.Equal(t, v, o.Endpoint())
+}
+
+func TestTransport_Operation(t *testing.T) {
+	v := "hello"
+	o := &Transport{operation: v}
+	assert.Equal(t, v, o.Operation())
+}
+
+func TestTransport_RequestHeader(t *testing.T) {
+	v := headerCarrier{}
+	v.Set("a", "1")
+	o := &Transport{reqHeader: v}
+	assert.Equal(t, "1", o.RequestHeader().Get("a"))
+}
+
+func TestTransport_ReplyHeader(t *testing.T) {
+	v := headerCarrier{}
+	v.Set("a", "1")
+	o := &Transport{replyHeader: v}
+	assert.Equal(t, "1", o.ReplyHeader().Get("a"))
+}
+
+func TestHeaderCarrier_Keys(t *testing.T) {
+	v := headerCarrier{}
+	v.Set("abb", "1")
+	v.Set("bcc", "2")
+	assert.ElementsMatch(t, []string{"abb", "bcc"}, v.Keys())
+}
diff --git a/transport/http/client_test.go b/transport/http/client_test.go
index 22b5d835..f95d42fc 100644
--- a/transport/http/client_test.go
+++ b/transport/http/client_test.go
@@ -10,10 +10,9 @@ import (
 	"time"
 
 	"github.com/go-kratos/kratos/v2/errors"
-
-	"github.com/stretchr/testify/assert"
-
+	"github.com/go-kratos/kratos/v2/middleware"
 	"github.com/go-kratos/kratos/v2/registry"
+	"github.com/stretchr/testify/assert"
 )
 
 type mockRoundTripper struct {
@@ -52,6 +51,12 @@ func TestWithUserAgent(t *testing.T) {
 }
 
 func TestWithMiddleware(t *testing.T) {
+	o := &clientOptions{}
+	v := []middleware.Middleware{
+		func(middleware.Handler) middleware.Handler { return nil },
+	}
+	WithMiddleware(v...)(o)
+	assert.Equal(t, v, o.middleware)
 }
 
 func TestWithEndpoint(t *testing.T) {
@@ -63,14 +68,26 @@ func TestWithEndpoint(t *testing.T) {
 }
 
 func TestWithRequestEncoder(t *testing.T) {
-
+	o := &clientOptions{}
+	v := func(ctx context.Context, contentType string, in interface{}) (body []byte, err error) {
+		return nil, nil
+	}
+	WithRequestEncoder(v)(o)
+	assert.NotNil(t, o.encoder)
 }
 
 func TestWithResponseDecoder(t *testing.T) {
-
+	o := &clientOptions{}
+	v := func(ctx context.Context, res *nethttp.Response, out interface{}) error { return nil }
+	WithResponseDecoder(v)(o)
+	assert.NotNil(t, o.decoder)
 }
 
 func TestWithErrorDecoder(t *testing.T) {
+	o := &clientOptions{}
+	v := func(ctx context.Context, res *nethttp.Response) error { return nil }
+	WithErrorDecoder(v)(o)
+	assert.NotNil(t, o.errorDecoder)
 }
 
 type mockDiscovery struct {
diff --git a/transport/http/server_test.go b/transport/http/server_test.go
index 734edc8f..9c726cf5 100644
--- a/transport/http/server_test.go
+++ b/transport/http/server_test.go
@@ -2,9 +2,11 @@ package http
 
 import (
 	"context"
+	"crypto/tls"
 	"encoding/json"
 	"fmt"
 	"github.com/go-kratos/kratos/v2/errors"
+	"github.com/go-kratos/kratos/v2/middleware"
 	"io/ioutil"
 	"net/http"
 	"strings"
@@ -153,3 +155,65 @@ func BenchmarkServer(b *testing.B) {
 	}
 	srv.Stop(ctx)
 }
+
+func TestNetwork(t *testing.T) {
+	o := &Server{}
+	v := "abc"
+	Network(v)(o)
+	assert.Equal(t, v, o.network)
+}
+
+func TestAddress(t *testing.T) {
+	o := &Server{}
+	v := "abc"
+	Address(v)(o)
+	assert.Equal(t, v, o.address)
+}
+
+func TestTimeout(t *testing.T) {
+	o := &Server{}
+	v := time.Duration(123)
+	Timeout(v)(o)
+	assert.Equal(t, v, o.timeout)
+}
+
+func TestLogger(t *testing.T) {
+	//todo
+}
+
+func TestMiddleware(t *testing.T) {
+	o := &clientOptions{}
+	v := []middleware.Middleware{
+		func(middleware.Handler) middleware.Handler { return nil },
+	}
+	WithMiddleware(v...)(o)
+	assert.Equal(t, v, o.middleware)
+}
+
+func TestRequestDecoder(t *testing.T) {
+	o := &Server{}
+	v := func(*http.Request, interface{}) error { return nil }
+	RequestDecoder(v)(o)
+	assert.NotNil(t, o.dec)
+}
+
+func TestResponseEncoder(t *testing.T) {
+	o := &Server{}
+	v := func(http.ResponseWriter, *http.Request, interface{}) error { return nil }
+	ResponseEncoder(v)(o)
+	assert.NotNil(t, o.enc)
+}
+
+func TestErrorEncoder(t *testing.T) {
+	o := &Server{}
+	v := func(http.ResponseWriter, *http.Request, error) {}
+	ErrorEncoder(v)(o)
+	assert.NotNil(t, o.ene)
+}
+
+func TestTLSConfig(t *testing.T) {
+	o := &Server{}
+	v := &tls.Config{}
+	TLSConfig(v)(o)
+	assert.Equal(t, v, o.tlsConf)
+}
diff --git a/transport/http/transport_test.go b/transport/http/transport_test.go
new file mode 100644
index 00000000..50da37d4
--- /dev/null
+++ b/transport/http/transport_test.go
@@ -0,0 +1,59 @@
+package http
+
+import (
+	"net/http"
+	"testing"
+
+	"github.com/go-kratos/kratos/v2/transport"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestTransport_Kind(t *testing.T) {
+	o := &Transport{}
+	assert.Equal(t, transport.KindHTTP, o.Kind())
+}
+
+func TestTransport_Endpoint(t *testing.T) {
+	v := "hello"
+	o := &Transport{endpoint: v}
+	assert.Equal(t, v, o.Endpoint())
+}
+
+func TestTransport_Operation(t *testing.T) {
+	v := "hello"
+	o := &Transport{operation: v}
+	assert.Equal(t, v, o.Operation())
+}
+
+func TestTransport_Request(t *testing.T) {
+	v := &http.Request{}
+	o := &Transport{request: v}
+	assert.Same(t, v, o.Request())
+}
+
+func TestTransport_RequestHeader(t *testing.T) {
+	v := headerCarrier{}
+	v.Set("a", "1")
+	o := &Transport{reqHeader: v}
+	assert.Equal(t, "1", o.RequestHeader().Get("a"))
+}
+
+func TestTransport_ReplyHeader(t *testing.T) {
+	v := headerCarrier{}
+	v.Set("a", "1")
+	o := &Transport{replyHeader: v}
+	assert.Equal(t, "1", o.ReplyHeader().Get("a"))
+}
+
+func TestTransport_PathTemplate(t *testing.T) {
+	v := "template"
+	o := &Transport{pathTemplate: v}
+	assert.Equal(t, v, o.PathTemplate())
+}
+
+func TestHeaderCarrier_Keys(t *testing.T) {
+	v := headerCarrier{}
+	v.Set("abb", "1")
+	v.Set("bcc", "2")
+	assert.ElementsMatch(t, []string{"Abb", "Bcc"}, v.Keys())
+}