From 36f3126920fe326b7874e730e3cc26546186d8f0 Mon Sep 17 00:00:00 2001 From: Menghan Li Date: Thu, 27 Dec 2018 14:30:17 -0800 Subject: [PATCH] example: name_resolving (#2514) --- examples/features/name_resolving/README.md | 41 ++++++ .../features/name_resolving/client/main.go | 135 ++++++++++++++++++ .../features/name_resolving/server/main.go | 67 +++++++++ 3 files changed, 243 insertions(+) create mode 100644 examples/features/name_resolving/README.md create mode 100644 examples/features/name_resolving/client/main.go create mode 100644 examples/features/name_resolving/server/main.go diff --git a/examples/features/name_resolving/README.md b/examples/features/name_resolving/README.md new file mode 100644 index 000000000000..6208dbc794c1 --- /dev/null +++ b/examples/features/name_resolving/README.md @@ -0,0 +1,41 @@ +# Name resolving + +This examples shows how `ClientConn` can pick different name resolvers. + +## What is a name resolver + +A name resolver can be seen as a `map[service-name][]backend-ip`. It takes a +service name, and returns a list of IPs of the backends. A commen used name +resolver is DNS. + +In this example, a resolver is created to resolve `resolver.example.grpc.io` to +`localhost:50051`. + +## Try it + +``` +go run server/main.go +``` + +``` +go run client/main.go +``` + +## Explanation + +The echo server is serving on ":50051". Two clients are created, one is dialing +to `passthrough:///localhost:50051`, while the other is dialing to +`example:///resolver.example.grpc.io`. Both of them can connect the the server. + +Name resolver is picked based on the `scheme` in the target string. See +https://github.com/grpc/grpc/blob/master/doc/naming.md for the target syntax. + +The first client picks the `passthrough` resolver, which takes the input, and +use it as the backend addresses. + +The second is connecting to service name `resolver.example.grpc.io`. Without a +proper name resolver, this would fail. In the example it picks the `example` +resolver that we installed. The `example` resolver can handle +`resolver.example.grpc.io` correctly by returning the backend address. So even +though the backend IP is not set when ClientConn is created, the connection will +be created to the correct backend. \ No newline at end of file diff --git a/examples/features/name_resolving/client/main.go b/examples/features/name_resolving/client/main.go new file mode 100644 index 000000000000..9feb80913e41 --- /dev/null +++ b/examples/features/name_resolving/client/main.go @@ -0,0 +1,135 @@ +/* + * + * 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 is an example client. +package main + +import ( + "context" + "fmt" + "log" + "time" + + "google.golang.org/grpc" + ecpb "google.golang.org/grpc/examples/features/proto/echo" + "google.golang.org/grpc/resolver" +) + +const ( + exampleScheme = "example" + exampleServiceName = "resolver.example.grpc.io" + + backendAddr = "localhost:50051" +) + +func callUnaryEcho(c ecpb.EchoClient, message string) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + r, err := c.UnaryEcho(ctx, &ecpb.EchoRequest{Message: message}) + if err != nil { + log.Fatalf("could not greet: %v", err) + } + fmt.Println(r.Message) +} + +func makeRPCs(cc *grpc.ClientConn, n int) { + hwc := ecpb.NewEchoClient(cc) + for i := 0; i < n; i++ { + callUnaryEcho(hwc, "this is examples/name_resolving") + } +} + +func main() { + passthroughConn, err := grpc.Dial( + fmt.Sprintf("passthrough:///%s", backendAddr), // Dial to "passthrough:///localhost:50051" + grpc.WithInsecure(), + ) + if err != nil { + log.Fatalf("did not connect: %v", err) + } + defer passthroughConn.Close() + + fmt.Printf("--- calling helloworld.Greeter/SayHello to \"passthrough:///%s\"\n", backendAddr) + makeRPCs(passthroughConn, 10) + + fmt.Println() + + exampleConn, err := grpc.Dial( + fmt.Sprintf("%s:///%s", exampleScheme, exampleServiceName), // Dial to "example:///resolver.example.grpc.io" + grpc.WithInsecure(), + ) + if err != nil { + log.Fatalf("did not connect: %v", err) + } + defer exampleConn.Close() + + fmt.Printf("--- calling helloworld.Greeter/SayHello to \"%s:///%s\"\n", exampleScheme, exampleServiceName) + makeRPCs(exampleConn, 10) +} + +// Following is an example name resolver. It includes a +// ResolverBuilder(https://godoc.org/google.golang.org/grpc/resolver#Builder) +// and a Resolver(https://godoc.org/google.golang.org/grpc/resolver#Resolver). +// +// A ResolverBuilder is registered for a scheme (in this example, "example" is +// the scheme). When a ClientConn is created for this scheme, the +// ResolverBuilder will be picked to build a Resolver. Note that a new Resolver +// is built for each ClientConn. The Resolver will watch the updates for the +// target, and send updates to the ClientConn. + +// exampleResolverBuilder is a +// ResolverBuilder(https://godoc.org/google.golang.org/grpc/resolver#Builder). +type exampleResolverBuilder struct{} + +func (*exampleResolverBuilder) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOption) (resolver.Resolver, error) { + r := &exampleResolver{ + target: target, + cc: cc, + addrsStore: map[string][]string{ + exampleServiceName: {backendAddr}, + }, + } + r.start() + return r, nil +} +func (*exampleResolverBuilder) Scheme() string { return exampleScheme } + +// exampleResolver is a +// Resolver(https://godoc.org/google.golang.org/grpc/resolver#Resolver). +type exampleResolver struct { + target resolver.Target + cc resolver.ClientConn + addrsStore map[string][]string +} + +func (r *exampleResolver) start() { + addrStrs := r.addrsStore[r.target.Endpoint] + addrs := make([]resolver.Address, len(addrStrs), len(addrStrs)) + for i, s := range addrStrs { + addrs[i] = resolver.Address{Addr: s} + } + r.cc.NewAddress(addrs) +} +func (*exampleResolver) ResolveNow(o resolver.ResolveNowOption) {} +func (*exampleResolver) Close() {} + +func init() { + // Register the example ResolverBuilder. This is usually done in a package's + // init() function. + resolver.Register(&exampleResolverBuilder{}) +} diff --git a/examples/features/name_resolving/server/main.go b/examples/features/name_resolving/server/main.go new file mode 100644 index 000000000000..f85d0a58daf6 --- /dev/null +++ b/examples/features/name_resolving/server/main.go @@ -0,0 +1,67 @@ +/* + * + * 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 is an example server. +package main + +import ( + "context" + "fmt" + "log" + "net" + + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + ecpb "google.golang.org/grpc/examples/features/proto/echo" + "google.golang.org/grpc/status" +) + +const addr = "localhost:50051" + +type ecServer struct { + addr string +} + +func (s *ecServer) UnaryEcho(ctx context.Context, req *ecpb.EchoRequest) (*ecpb.EchoResponse, error) { + return &ecpb.EchoResponse{Message: fmt.Sprintf("%s (from %s)", req.Message, s.addr)}, nil +} +func (s *ecServer) ServerStreamingEcho(*ecpb.EchoRequest, ecpb.Echo_ServerStreamingEchoServer) error { + return status.Errorf(codes.Unimplemented, "not implemented") +} +func (s *ecServer) ClientStreamingEcho(ecpb.Echo_ClientStreamingEchoServer) error { + return status.Errorf(codes.Unimplemented, "not implemented") +} +func (s *ecServer) BidirectionalStreamingEcho(ecpb.Echo_BidirectionalStreamingEchoServer) error { + return status.Errorf(codes.Unimplemented, "not implemented") +} + +func startServer(addr string) { +} + +func main() { + lis, err := net.Listen("tcp", addr) + if err != nil { + log.Fatalf("failed to listen: %v", err) + } + s := grpc.NewServer() + ecpb.RegisterEchoServer(s, &ecServer{addr: addr}) + log.Printf("serving on %s\n", addr) + if err := s.Serve(lis); err != nil { + log.Fatalf("failed to serve: %v", err) + } +}