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
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:
130
examples/features/interceptor/README.md
Normal file
130
examples/features/interceptor/README.md
Normal 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`.
|
||||
167
examples/features/interceptor/client/main.go
Normal file
167
examples/features/interceptor/client/main.go
Normal 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)
|
||||
}
|
||||
166
examples/features/interceptor/server/main.go
Normal file
166
examples/features/interceptor/server/main.go
Normal 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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user