snapshot of https://github.com/grpc/grpc-go.git (master, 2026-09-15; blobs > 131072B omitted)
Some checks failed
codecov / upload (push) Failing after 13m59s
CodeQL / Analyze (push) Failing after 1s
Testing / vet-proto (push) Failing after 1s
Testing / tests (arm, latest) (push) Waiting to run
Testing / extras (latest) (push) Failing after 1s
Testing / static checks (latest-1) (push) Failing after 1s
Testing / tests (-race, latest) (push) Failing after 1s
Testing / tests (i386, latest) (push) Failing after 1s
Testing / tests (latest) (push) Failing after 1s
Testing / tests (latest-1) (push) Failing after 1s
Lock Threads / lock (push) Failing after 1s
govulncheck / govulncheck (.) (push) Failing after 1s
govulncheck / govulncheck (./cmd/protoc-gen-go-grpc) (push) Failing after 1s
govulncheck / govulncheck (./gcp/observability) (push) Failing after 1s
govulncheck / govulncheck (./security/advancedtls) (push) Failing after 1s
govulncheck / govulncheck (./stats/opencensus) (push) Failing after 1s
Stale bot / stale (push) Failing after 1s

This commit is contained in:
upstream-mirror
2026-09-15 20:40:15 +05:00
commit e241b33f26
1365 changed files with 339570 additions and 0 deletions

View File

@@ -0,0 +1,130 @@
# Interceptor
gRPC provides simple APIs to implement and install interceptors on a per
ClientConn/Server basis. Interceptors act as a layer between the application and
gRPC and can be used to observe or control the behavior of gRPC. Interceptors
can be used for logging, authentication/authorization, metrics collection, and
other functionality that is shared across RPCs.
## Try it
```
go run server/main.go
```
```
go run client/main.go
```
## Explanation
gRPC has separate interceptors for unary RPCs and streaming RPCs. See the
[gRPC docs](https://grpc.io/docs/guides/concepts.html#rpc-life-cycle) for an
explanation about unary and streaming RPCs. Both the client and the server have
their own types of unary and stream interceptors. Thus, there are four different
types of interceptors in total.
### Client-side
#### Unary Interceptor
The type for client-side unary interceptors is
[`UnaryClientInterceptor`](https://godoc.org/google.golang.org/grpc#UnaryClientInterceptor).
It is a function type with signature:
```golang
func(ctx context.Context, method string, req, reply interface{}, cc *ClientConn, invoker UnaryInvoker, opts ...CallOption) error
```
Unary interceptor implementations can usually be divided into three parts:
pre-processing, invoking the RPC method, and post-processing.
For pre-processing, users can get info about the current RPC call by examining
the args passed in. The args include the RPC context, method string, request to
be sent, and the CallOptions configured. With this info, users can even modify
the RPC call. For instance, in the example, we examine the list of CallOptions
and check if the call credentials have been configured. If not, the interceptor
configures the RPC call to use oauth2 with a token "some-secret-token" as a
fallback. In our example, we intentionally omit configuring the per RPC
credential to resort to the fallback.
After pre-processing, users can invoke the RPC call by calling the `invoker`.
Once the invoker returns, users can post-process the RPC call. This usually
involves dealing with the returned reply and error. In the example, we log the
RPC timing and error info.
To install a unary interceptor on a ClientConn, configure `Dial` with the
[`WithUnaryInterceptor`](https://godoc.org/google.golang.org/grpc#WithUnaryInterceptor)
`DialOption`.
#### Stream Interceptor
The type for client-side stream interceptors is
[`StreamClientInterceptor`](https://godoc.org/google.golang.org/grpc#StreamClientInterceptor).
It is a function type with signature:
```golang
func(ctx context.Context, desc *StreamDesc, cc *ClientConn, method string, streamer Streamer, opts ...CallOption) (ClientStream, error)
```
An implementation of a stream interceptor usually includes pre-processing, and
stream operation interception.
The pre-processing is similar to unary interceptors.
However, rather than invoking the RPC method followed by post-processing, stream
interceptors intercept the users' operations on the stream. The interceptor
first calls the passed-in `streamer` to get a `ClientStream`, and then wraps the
`ClientStream` while overriding its methods with the interception logic.
Finally, the interceptor returns the wrapped `ClientStream` to user to operate
on.
In the example, we define a new struct `wrappedStream`, which embeds a
`ClientStream`. We then implement (override) the `SendMsg` and `RecvMsg` methods
on `wrappedStream` to intercept these two operations on the embedded
`ClientStream`. In the example, we log the message type info and time info for
interception purpose.
To install a stream interceptor for a ClientConn, configure `Dial` with the
[`WithStreamInterceptor`](https://godoc.org/google.golang.org/grpc#WithStreamInterceptor)
`DialOption`.
### Server-side
Server side interceptors are similar to client side interceptors, with slightly
different information provided as args.
#### Unary Interceptor
The type for server-side unary interceptors is
[`UnaryServerInterceptor`](https://godoc.org/google.golang.org/grpc#UnaryServerInterceptor).
It is a function type with signature:
```golang
func(ctx context.Context, req interface{}, info *UnaryServerInfo, handler UnaryHandler) (resp interface{}, err error)
```
Refer to the client-side unary interceptor section for a detailed implementation
and explanation.
To install a unary interceptor on a Server, configure `NewServer` with the
[`UnaryInterceptor`](https://godoc.org/google.golang.org/grpc#UnaryInterceptor)
`ServerOption`.
#### Stream Interceptor
The type for server-side stream interceptors is
[`StreamServerInterceptor`](https://godoc.org/google.golang.org/grpc#StreamServerInterceptor).
It is a function type with the signature:
```golang
func(srv interface{}, ss ServerStream, info *StreamServerInfo, handler StreamHandler) error
```
Refer to the client-side stream interceptor section for a detailed
implementation and explanation.
To install a stream interceptor on a Server, configure `NewServer` with the
[`StreamInterceptor`](https://godoc.org/google.golang.org/grpc#StreamInterceptor)
`ServerOption`.

View File

@@ -0,0 +1,167 @@
/*
*
* Copyright 2018 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
// Binary client demonstrates how to use interceptors to observe or control the
// behavior of gRPC including logging, authentication,metrics collection, etc.
package main
import (
"context"
"flag"
"fmt"
"io"
"log"
"time"
"golang.org/x/oauth2"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/oauth"
"google.golang.org/grpc/examples/data"
ecpb "google.golang.org/grpc/examples/features/proto/echo"
)
var addr = flag.String("addr", "localhost:50051", "the address to connect to")
const fallbackToken = "some-secret-token"
// logger is to mock a sophisticated logging system. To simplify the example, we just print out the content.
func logger(format string, a ...any) {
fmt.Printf("LOG:\t"+format+"\n", a...)
}
// unaryInterceptor is an example unary interceptor.
func unaryInterceptor(ctx context.Context, method string, req, reply any, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
var credsConfigured bool
for _, o := range opts {
_, ok := o.(grpc.PerRPCCredsCallOption)
if ok {
credsConfigured = true
break
}
}
if !credsConfigured {
opts = append(opts, grpc.PerRPCCredentials(oauth.TokenSource{
TokenSource: oauth2.StaticTokenSource(&oauth2.Token{AccessToken: fallbackToken}),
}))
}
start := time.Now()
err := invoker(ctx, method, req, reply, cc, opts...)
end := time.Now()
logger("RPC: %s, start time: %s, end time: %s, err: %v", method, start.Format("Basic"), end.Format(time.RFC3339), err)
return err
}
// wrappedStream wraps around the embedded grpc.ClientStream, and intercepts the RecvMsg and
// SendMsg method call.
type wrappedStream struct {
grpc.ClientStream
}
func (w *wrappedStream) RecvMsg(m any) error {
logger("Receive a message (Type: %T) at %v", m, time.Now().Format(time.RFC3339))
return w.ClientStream.RecvMsg(m)
}
func (w *wrappedStream) SendMsg(m any) error {
logger("Send a message (Type: %T) at %v", m, time.Now().Format(time.RFC3339))
return w.ClientStream.SendMsg(m)
}
func newWrappedStream(s grpc.ClientStream) grpc.ClientStream {
return &wrappedStream{s}
}
// streamInterceptor is an example stream interceptor.
func streamInterceptor(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) {
var credsConfigured bool
for _, o := range opts {
_, ok := o.(*grpc.PerRPCCredsCallOption)
if ok {
credsConfigured = true
break
}
}
if !credsConfigured {
opts = append(opts, grpc.PerRPCCredentials(oauth.TokenSource{
TokenSource: oauth2.StaticTokenSource(&oauth2.Token{AccessToken: fallbackToken}),
}))
}
s, err := streamer(ctx, desc, cc, method, opts...)
if err != nil {
return nil, err
}
return newWrappedStream(s), nil
}
func callUnaryEcho(client ecpb.EchoClient, message string) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
resp, err := client.UnaryEcho(ctx, &ecpb.EchoRequest{Message: message})
if err != nil {
log.Fatalf("client.UnaryEcho(_) = _, %v: ", err)
}
fmt.Println("UnaryEcho: ", resp.Message)
}
func callBidiStreamingEcho(client ecpb.EchoClient) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
c, err := client.BidirectionalStreamingEcho(ctx)
if err != nil {
return
}
for i := 0; i < 5; i++ {
if err := c.Send(&ecpb.EchoRequest{Message: fmt.Sprintf("Request %d", i+1)}); err != nil {
log.Fatalf("failed to send request due to error: %v", err)
}
}
c.CloseSend()
for {
resp, err := c.Recv()
if err == io.EOF {
break
}
if err != nil {
log.Fatalf("failed to receive response due to error: %v", err)
}
fmt.Println("BidiStreaming Echo: ", resp.Message)
}
}
func main() {
flag.Parse()
// Create tls based credential.
creds, err := credentials.NewClientTLSFromFile(data.Path("x509/ca_cert.pem"), "x.test.example.com")
if err != nil {
log.Fatalf("failed to load credentials: %v", err)
}
// Set up a connection to the server.
conn, err := grpc.NewClient(*addr, grpc.WithTransportCredentials(creds), grpc.WithUnaryInterceptor(unaryInterceptor), grpc.WithStreamInterceptor(streamInterceptor))
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
// Make an echo client and send RPCs.
rgc := ecpb.NewEchoClient(conn)
callUnaryEcho(rgc, "hello world")
callBidiStreamingEcho(rgc)
}

View File

@@ -0,0 +1,166 @@
/*
*
* Copyright 2018 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
// Binary server demonstrates how to use interceptors to observe or control the
// behavior of gRPC including logging, authentication,metrics collection, etc.
package main
import (
"context"
"flag"
"fmt"
"io"
"log"
"net"
"strings"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/examples/data"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
pb "google.golang.org/grpc/examples/features/proto/echo"
)
var (
port = flag.Int("port", 50051, "the port to serve on")
errMissingMetadata = status.Errorf(codes.InvalidArgument, "missing metadata")
errInvalidToken = status.Errorf(codes.Unauthenticated, "invalid token")
)
// logger is to mock a sophisticated logging system. To simplify the example, we just print out the content.
func logger(format string, a ...any) {
fmt.Printf("LOG:\t"+format+"\n", a...)
}
type server struct {
pb.UnimplementedEchoServer
}
func (s *server) UnaryEcho(_ context.Context, in *pb.EchoRequest) (*pb.EchoResponse, error) {
fmt.Printf("unary echoing message %q\n", in.Message)
return &pb.EchoResponse{Message: in.Message}, nil
}
func (s *server) BidirectionalStreamingEcho(stream pb.Echo_BidirectionalStreamingEchoServer) error {
for {
in, err := stream.Recv()
if err != nil {
if err == io.EOF {
return nil
}
fmt.Printf("server: error receiving from stream: %v\n", err)
return err
}
fmt.Printf("bidi echoing message %q\n", in.Message)
stream.Send(&pb.EchoResponse{Message: in.Message})
}
}
// valid validates the authorization.
func valid(authorization []string) bool {
if len(authorization) < 1 {
return false
}
token := strings.TrimPrefix(authorization[0], "Bearer ")
// Perform the token validation here. For the sake of this example, the code
// here forgoes any of the usual OAuth2 token validation and instead checks
// for a token matching an arbitrary string.
return token == "some-secret-token"
}
func unaryInterceptor(ctx context.Context, req any, _ *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
// authentication (token verification)
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return nil, errMissingMetadata
}
if !valid(md["authorization"]) {
return nil, errInvalidToken
}
m, err := handler(ctx, req)
if err != nil {
logger("RPC failed with error: %v", err)
}
return m, err
}
// wrappedStream wraps around the embedded grpc.ServerStream, and intercepts the RecvMsg and
// SendMsg method call.
type wrappedStream struct {
grpc.ServerStream
}
func (w *wrappedStream) RecvMsg(m any) error {
logger("Receive a message (Type: %T) at %s", m, time.Now().Format(time.RFC3339))
return w.ServerStream.RecvMsg(m)
}
func (w *wrappedStream) SendMsg(m any) error {
logger("Send a message (Type: %T) at %v", m, time.Now().Format(time.RFC3339))
return w.ServerStream.SendMsg(m)
}
func newWrappedStream(s grpc.ServerStream) grpc.ServerStream {
return &wrappedStream{s}
}
func streamInterceptor(srv any, ss grpc.ServerStream, _ *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
// authentication (token verification)
md, ok := metadata.FromIncomingContext(ss.Context())
if !ok {
return errMissingMetadata
}
if !valid(md["authorization"]) {
return errInvalidToken
}
err := handler(srv, newWrappedStream(ss))
if err != nil {
logger("RPC failed with error: %v", err)
}
return err
}
func main() {
flag.Parse()
lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
// Create tls based credential.
creds, err := credentials.NewServerTLSFromFile(data.Path("x509/server_cert.pem"), data.Path("x509/server_key.pem"))
if err != nil {
log.Fatalf("failed to create credentials: %v", err)
}
s := grpc.NewServer(grpc.Creds(creds), grpc.UnaryInterceptor(unaryInterceptor), grpc.StreamInterceptor(streamInterceptor))
// Register EchoServer on the server.
pb.RegisterEchoServer(s, &server{})
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}