Make route guide implementation more go idiomatic

This commit is contained in:
Daniel Wang 2015-02-25 12:08:12 -08:00
parent a94c062d9b
commit 8fd7702f97
5 changed files with 128 additions and 106 deletions

View File

@ -2,7 +2,7 @@
The route guide server and client demonstrates how to use grpc go libraries to
perform unary, client streaming, server streaming and full duplex RPCs.
See the definition of the route guide service in route_guide.proto.
See the definition of the route guide service in proto/route_guide.proto.
# Run the sample code
To compile and run the server, assuming you are in the root of the route_guide
@ -18,8 +18,8 @@ Likewise, to run the client:
The server and client both take optional command line flags. For example, the
client and server run without TLS by default. To enable TSL:
`go run server/server.go -use_tls=true`
`go run server/server.go -tls=true`
and
`go run client/client.go -use_tls=true`
`go run client/client.go -tls=true`

View File

@ -31,6 +31,10 @@
*
*/
// Package main implements a simple grpc client that demonstrates how to use grpc go libraries
// to perform unary, client streaming, server streaming and full duplex RPCs.
//
// It interacts with the route guide service whose definition can be found in proto/route_guide.proto.
package main
import (
@ -38,64 +42,59 @@ import (
"io"
"log"
"math/rand"
"net"
"strconv"
"time"
"golang.org/x/net/context"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
pb "google.golang.org/grpc/examples/route_guide"
pb "google.golang.org/grpc/examples/route_guide/proto"
)
var (
useTLS = flag.Bool("use_tls", false, "Connection uses TLS if true, else plain TCP")
tls = flag.Bool("use_tls", false, "Connection uses TLS if true, else plain TCP")
caFile = flag.String("tls_ca_file", "testdata/ca.pem", "The file containning the CA root cert file")
serverHost = flag.String("server_host", "127.0.0.1", "The server host name")
serverPort = flag.Int("server_port", 10000, "The server port number")
serverAddr = flag.String("server_addr", "127.0.0.1:10000", "The server address in the format of host:port")
tlsServerName = flag.String("tls_server_name", "x.test.youtube.com", "The server name use to verify the hostname returned by TLS handshake")
)
// doGetFeature gets the feature for the given point.
func doGetFeature(client pb.RouteGuideClient, point *pb.Point) {
// printFeature gets the feature for the given point.
func printFeature(client pb.RouteGuideClient, point *pb.Point) {
log.Printf("Getting feature for point (%d, %d)", point.Latitude, point.Longitude)
reply, err := client.GetFeature(context.Background(), point)
feature, err := client.GetFeature(context.Background(), point)
if err != nil {
log.Fatalf("%v.GetFeatures(_) = _, %v: ", client, err)
return
}
log.Println(reply)
log.Println(feature)
}
// doListFeatures lists all the features within the given bounding Rectangle.
func doListFeatures(client pb.RouteGuideClient, rect *pb.Rectangle) {
// printFeatures lists all the features within the given bounding Rectangle.
func printFeatures(client pb.RouteGuideClient, rect *pb.Rectangle) {
log.Printf("Looking for features within %v", rect)
stream, err := client.ListFeatures(context.Background(), rect)
if err != nil {
log.Fatalf("%v.ListFeatures(_) = _, %v", client, err)
}
var rpcStatus error
for {
reply, err := stream.Recv()
if err != nil {
rpcStatus = err
feature, err := stream.Recv()
if err == io.EOF {
break
}
log.Println(reply)
}
if rpcStatus != io.EOF {
log.Fatalf("%v.ListFeatures(_) = _, %v", client, err)
if err != nil {
log.Fatalf("%v.ListFeatures(_) = _, %v", client, err)
}
log.Println(feature)
}
}
// doRecordRoute sends a sequence of points to server and expects to get a RouteSummary from server.
func doRecordRoute(client pb.RouteGuideClient) {
// runRecordRoute sends a sequence of points to server and expects to get a RouteSummary from server.
func runRecordRoute(client pb.RouteGuideClient) {
// Create a random number of random points
rand.Seed(time.Now().UnixNano())
pointCount := rand.Int31n(100) + 2 // Tranverse at least two points
points := make([]*pb.Point, pointCount)
for i, _ := range points {
points[i] = randomPoint()
r := rand.New(rand.NewSource(time.Now().UnixNano()))
pointCount := int(r.Int31n(100)) + 2 // Traverse at least two points
var points []*pb.Point
for i := 0; i < pointCount; i++ {
points = append(points, randomPoint(r))
}
log.Printf("Traversing %d points.", len(points))
stream, err := client.RecordRoute(context.Background())
@ -111,58 +110,57 @@ func doRecordRoute(client pb.RouteGuideClient) {
if err != nil {
log.Fatalf("%v.CloseAndRecv() got error %v, want %v", stream, err, nil)
}
log.Printf("Route summary: %v\n", reply)
log.Printf("Route summary: %v", reply)
}
// doRouteChat receives a sequence of route notes, while sending notes for various locations.
func doRouteChat(client pb.RouteGuideClient) {
// runRouteChat receives a sequence of route notes, while sending notes for various locations.
func runRouteChat(client pb.RouteGuideClient) {
notes := []*pb.RouteNote{
&pb.RouteNote{&pb.Point{0, 1}, "First message"},
&pb.RouteNote{&pb.Point{0, 2}, "Second message"},
&pb.RouteNote{&pb.Point{0, 3}, "Third message"},
&pb.RouteNote{&pb.Point{0, 1}, "Fourth message"},
&pb.RouteNote{&pb.Point{0, 2}, "Fifth message"},
&pb.RouteNote{&pb.Point{0, 3}, "Sixth message"},
{&pb.Point{0, 1}, "First message"},
{&pb.Point{0, 2}, "Second message"},
{&pb.Point{0, 3}, "Third message"},
{&pb.Point{0, 1}, "Fourth message"},
{&pb.Point{0, 2}, "Fifth message"},
{&pb.Point{0, 3}, "Sixth message"},
}
stream, err := client.RouteChat(context.Background())
if err != nil {
log.Fatalf("%v.RouteChat(_) = _, %v", client, err)
}
c := make(chan int)
waitc := make(chan int)
go func() {
for {
in, err := stream.Recv()
if err == io.EOF {
// read done.
c <- 1
waitc <- 1
return
}
if err != nil {
log.Fatalf("Failed to receive a note : %v\n", err)
log.Fatalf("Failed to receive a note : %v", err)
}
log.Printf("Got message %s at point(%d, %d)\n", in.Message, in.Location.Latitude, in.Location.Longitude)
log.Printf("Got message %s at point(%d, %d)", in.Message, in.Location.Latitude, in.Location.Longitude)
}
}()
for _, note := range notes {
if err := stream.Send(note); err != nil {
log.Fatalf("Failed to send a note: %v\n", err)
log.Fatalf("Failed to send a note: %v", err)
}
}
stream.CloseSend()
<-c
<-waitc
}
func randomPoint() *pb.Point {
lat := (rand.Int31n(180) - 90) * 1e7
long := (rand.Int31n(360) - 180) * 1e7
func randomPoint(r *rand.Rand) *pb.Point {
lat := (r.Int31n(180) - 90) * 1e7
long := (r.Int31n(360) - 180) * 1e7
return &pb.Point{lat, long}
}
func main() {
flag.Parse()
serverAddr := net.JoinHostPort(*serverHost, strconv.Itoa(*serverPort))
var opts []grpc.DialOption
if *useTLS {
if *tls {
var sn string
if *tlsServerName != "" {
sn = *tlsServerName
@ -179,7 +177,7 @@ func main() {
}
opts = append(opts, grpc.WithClientTLS(creds))
}
conn, err := grpc.Dial(serverAddr, opts...)
conn, err := grpc.Dial(*serverAddr, opts...)
if err != nil {
log.Fatalf("fail to dial: %v", err)
}
@ -187,17 +185,17 @@ func main() {
client := pb.NewRouteGuideClient(conn)
// Looking for a valid feature
doGetFeature(client, &pb.Point{409146138, -746188906})
printFeature(client, &pb.Point{409146138, -746188906})
// Feature missing.
doGetFeature(client, &pb.Point{0, 0})
printFeature(client, &pb.Point{0, 0})
// Looking for features between 40, -75 and 42, -73.
doListFeatures(client, &pb.Rectangle{&pb.Point{400000000, -750000000}, &pb.Point{420000000, -730000000}})
printFeatures(client, &pb.Rectangle{&pb.Point{400000000, -750000000}, &pb.Point{420000000, -730000000}})
// RecordRoute
doRecordRoute(client)
runRecordRoute(client)
// RouteChat
doRouteChat(client)
runRouteChat(client)
}

View File

@ -1,12 +1,12 @@
// Code generated by protoc-gen-go.
// source: route_guide.proto
// source: proto/route_guide.proto
// DO NOT EDIT!
/*
Package route_guide is a generated protocol buffer package.
Package proto is a generated protocol buffer package.
It is generated from these files:
route_guide.proto
proto/route_guide.proto
It has these top-level messages:
Point
@ -15,9 +15,9 @@ It has these top-level messages:
RouteNote
RouteSummary
*/
package route_guide
package proto
import proto "github.com/golang/protobuf/proto"
import proto1 "github.com/golang/protobuf/proto"
import (
context "golang.org/x/net/context"
@ -29,7 +29,7 @@ var _ context.Context
var _ grpc.ClientConn
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = proto1.Marshal
// Points are represented as latitude-longitude pairs in the E7 representation
// (degrees multiplied by 10**7 and rounded to the nearest integer).
@ -41,7 +41,7 @@ type Point struct {
}
func (m *Point) Reset() { *m = Point{} }
func (m *Point) String() string { return proto.CompactTextString(m) }
func (m *Point) String() string { return proto1.CompactTextString(m) }
func (*Point) ProtoMessage() {}
// A latitude-longitude rectangle, represented as two diagonally opposite
@ -54,7 +54,7 @@ type Rectangle struct {
}
func (m *Rectangle) Reset() { *m = Rectangle{} }
func (m *Rectangle) String() string { return proto.CompactTextString(m) }
func (m *Rectangle) String() string { return proto1.CompactTextString(m) }
func (*Rectangle) ProtoMessage() {}
func (m *Rectangle) GetLo() *Point {
@ -82,7 +82,7 @@ type Feature struct {
}
func (m *Feature) Reset() { *m = Feature{} }
func (m *Feature) String() string { return proto.CompactTextString(m) }
func (m *Feature) String() string { return proto1.CompactTextString(m) }
func (*Feature) ProtoMessage() {}
func (m *Feature) GetLocation() *Point {
@ -101,7 +101,7 @@ type RouteNote struct {
}
func (m *RouteNote) Reset() { *m = RouteNote{} }
func (m *RouteNote) String() string { return proto.CompactTextString(m) }
func (m *RouteNote) String() string { return proto1.CompactTextString(m) }
func (*RouteNote) ProtoMessage() {}
func (m *RouteNote) GetLocation() *Point {
@ -128,7 +128,7 @@ type RouteSummary struct {
}
func (m *RouteSummary) Reset() { *m = RouteSummary{} }
func (m *RouteSummary) String() string { return proto.CompactTextString(m) }
func (m *RouteSummary) String() string { return proto1.CompactTextString(m) }
func (*RouteSummary) ProtoMessage() {}
func init() {
@ -170,7 +170,7 @@ func NewRouteGuideClient(cc *grpc.ClientConn) RouteGuideClient {
func (c *routeGuideClient) GetFeature(ctx context.Context, in *Point, opts ...grpc.CallOption) (*Feature, error) {
out := new(Feature)
err := grpc.Invoke(ctx, "/route_guide.RouteGuide/GetFeature", in, out, c.cc, opts...)
err := grpc.Invoke(ctx, "/proto.RouteGuide/GetFeature", in, out, c.cc, opts...)
if err != nil {
return nil, err
}
@ -178,7 +178,7 @@ func (c *routeGuideClient) GetFeature(ctx context.Context, in *Point, opts ...gr
}
func (c *routeGuideClient) ListFeatures(ctx context.Context, in *Rectangle, opts ...grpc.CallOption) (RouteGuide_ListFeaturesClient, error) {
stream, err := grpc.NewClientStream(ctx, &_RouteGuide_serviceDesc.Streams[0], c.cc, "/route_guide.RouteGuide/ListFeatures", opts...)
stream, err := grpc.NewClientStream(ctx, &_RouteGuide_serviceDesc.Streams[0], c.cc, "/proto.RouteGuide/ListFeatures", opts...)
if err != nil {
return nil, err
}
@ -210,7 +210,7 @@ func (x *routeGuideListFeaturesClient) Recv() (*Feature, error) {
}
func (c *routeGuideClient) RecordRoute(ctx context.Context, opts ...grpc.CallOption) (RouteGuide_RecordRouteClient, error) {
stream, err := grpc.NewClientStream(ctx, &_RouteGuide_serviceDesc.Streams[1], c.cc, "/route_guide.RouteGuide/RecordRoute", opts...)
stream, err := grpc.NewClientStream(ctx, &_RouteGuide_serviceDesc.Streams[1], c.cc, "/proto.RouteGuide/RecordRoute", opts...)
if err != nil {
return nil, err
}
@ -244,7 +244,7 @@ func (x *routeGuideRecordRouteClient) CloseAndRecv() (*RouteSummary, error) {
}
func (c *routeGuideClient) RouteChat(ctx context.Context, opts ...grpc.CallOption) (RouteGuide_RouteChatClient, error) {
stream, err := grpc.NewClientStream(ctx, &_RouteGuide_serviceDesc.Streams[2], c.cc, "/route_guide.RouteGuide/RouteChat", opts...)
stream, err := grpc.NewClientStream(ctx, &_RouteGuide_serviceDesc.Streams[2], c.cc, "/proto.RouteGuide/RouteChat", opts...)
if err != nil {
return nil, err
}
@ -304,9 +304,9 @@ func RegisterRouteGuideServer(s *grpc.Server, srv RouteGuideServer) {
s.RegisterService(&_RouteGuide_serviceDesc, srv)
}
func _RouteGuide_GetFeature_Handler(srv interface{}, ctx context.Context, buf []byte) (proto.Message, error) {
func _RouteGuide_GetFeature_Handler(srv interface{}, ctx context.Context, buf []byte) (proto1.Message, error) {
in := new(Point)
if err := proto.Unmarshal(buf, in); err != nil {
if err := proto1.Unmarshal(buf, in); err != nil {
return nil, err
}
out, err := srv.(RouteGuideServer).GetFeature(ctx, in)
@ -390,7 +390,7 @@ func (x *routeGuideRouteChatServer) Recv() (*RouteNote, error) {
}
var _RouteGuide_serviceDesc = grpc.ServiceDesc{
ServiceName: "route_guide.RouteGuide",
ServiceName: "proto.RouteGuide",
HandlerType: (*RouteGuideServer)(nil),
Methods: []grpc.MethodDesc{
{

View File

@ -29,13 +29,16 @@
syntax = "proto3";
package route_guide;
package proto;
// Interface exported by the server.
service RouteGuide {
// A simple RPC.
//
// Obtains the feature at a given position.
//
// If no feature is found for the given point, a feature with an empty name
// should be returned.
rpc GetFeature(Point) returns (Feature) {}
// A server-to-client streaming RPC.

View File

@ -31,11 +31,16 @@
*
*/
// Package main implements a simple grpc server that demonstrates how to use grpc go libraries
// to perform unary, client streaming, server streaming and full duplex RPCs.
//
// It implements the route guide service whose definition can be found in proto/route_guide.proto.
package main
import (
"encoding/json"
"flag"
"fmt"
"io"
"io/ioutil"
"log"
@ -49,11 +54,13 @@ import (
"google.golang.org/grpc/credentials"
pb "google.golang.org/grpc/examples/route_guide"
proto "github.com/golang/protobuf/proto"
pb "google.golang.org/grpc/examples/route_guide/proto"
)
var (
useTLS = flag.Bool("use_tls", false, "Connection uses TLS if true, else plain TCP")
tls = flag.Bool("use_tls", false, "Connection uses TLS if true, else plain TCP")
certFile = flag.String("tls_cert_file", "testdata/server1.pem", "The TLS cert file")
keyFile = flag.String("tls_key_file", "testdata/server1.key", "The TLS key file")
jsonDBFile = flag.String("route_guide_db", "testdata/route_guide_db.json", "A json file containing a list of features")
@ -61,15 +68,15 @@ var (
)
type routeGuideServer struct {
savedFeatures []pb.Feature
routeNotes map[pb.Point][]*pb.RouteNote
savedFeatures []*pb.Feature
routeNotes map[string][]*pb.RouteNote
}
// GetFeature returns the feature at the given point.
func (s *routeGuideServer) GetFeature(ctx context.Context, point *pb.Point) (*pb.Feature, error) {
for _, feature := range s.savedFeatures {
if *(feature.Location) == *point {
return &feature, nil
if proto.Equal(feature.Location, point) {
return feature, nil
}
}
// No feature was found, return an unnamed feature
@ -78,16 +85,9 @@ func (s *routeGuideServer) GetFeature(ctx context.Context, point *pb.Point) (*pb
// ListFeatures lists all features comtained within the given bounding Rectangle.
func (s *routeGuideServer) ListFeatures(rect *pb.Rectangle, stream pb.RouteGuide_ListFeaturesServer) error {
left := math.Min(float64(rect.Lo.Longitude), float64(rect.Hi.Longitude))
right := math.Max(float64(rect.Lo.Longitude), float64(rect.Hi.Longitude))
top := math.Max(float64(rect.Lo.Latitude), float64(rect.Hi.Latitude))
bottom := math.Min(float64(rect.Lo.Latitude), float64(rect.Hi.Latitude))
for _, feature := range s.savedFeatures {
if float64(feature.Location.Longitude) >= left &&
float64(feature.Location.Longitude) <= right &&
float64(feature.Location.Latitude) >= bottom &&
float64(feature.Location.Latitude) <= top {
if err := stream.Send(&feature); err != nil {
if inRange(feature.Location, rect) {
if err := stream.Send(feature); err != nil {
return err
}
}
@ -120,7 +120,7 @@ func (s *routeGuideServer) RecordRoute(stream pb.RouteGuide_RecordRouteServer) e
}
pointCount++
for _, feature := range s.savedFeatures {
if *(feature.Location) == *point {
if proto.Equal(feature.Location, point) {
featureCount++
}
}
@ -142,14 +142,14 @@ func (s *routeGuideServer) RouteChat(stream pb.RouteGuide_RouteChatServer) error
if err != nil {
return err
}
point := *(in.Location)
if _, present := s.routeNotes[point]; !present {
s.routeNotes[point] = []*pb.RouteNote{in}
key := serialize(in.Location)
if _, present := s.routeNotes[key]; !present {
s.routeNotes[key] = []*pb.RouteNote{in}
} else {
s.routeNotes[point] = append(s.routeNotes[point], in)
s.routeNotes[key] = append(s.routeNotes[key], in)
}
for _, note := range s.routeNotes[point] {
for _, note := range s.routeNotes[key] {
if err := stream.Send(note); err != nil {
return err
}
@ -161,10 +161,10 @@ func (s *routeGuideServer) RouteChat(stream pb.RouteGuide_RouteChatServer) error
func (s *routeGuideServer) loadFeatures(filePath string) {
file, err := ioutil.ReadFile(filePath)
if err != nil {
log.Fatal("Failed to load default features: %v\n", err)
log.Fatal("Failed to load default features: %v", err)
}
if err := json.Unmarshal(file, &(s.savedFeatures)); err != nil {
log.Fatal("Failed to load default features: %v\n", err)
log.Fatal("Failed to load default features: %v", err)
}
}
@ -172,17 +172,19 @@ func toRadians(num float64) float64 {
return num * math.Pi / float64(180)
}
// calcDistance calculates the distance between two points using the "haversine" formula.
// This code was taken from http://www.movable-type.co.uk/scripts/latlong.html.
func calcDistance(p1 *pb.Point, p2 *pb.Point) int32 {
const COORD_FACTOR float64 = 1e7
const CordFactor float64 = 1e7
const R float64 = float64(6371000) // metres
lat1 := float64(p1.Latitude) / COORD_FACTOR
lat2 := float64(p2.Latitude) / COORD_FACTOR
lon1 := float64(p1.Longitude) / COORD_FACTOR
lon2 := float64(p2.Longitude) / COORD_FACTOR
lat1 := float64(p1.Latitude) / CordFactor
lat2 := float64(p2.Latitude) / CordFactor
lng1 := float64(p1.Longitude) / CordFactor
lng2 := float64(p2.Longitude) / CordFactor
φ1 := toRadians(lat1)
φ2 := toRadians(lat2)
Δφ := toRadians(lat2 - lat1)
Δλ := toRadians(lon2 - lon1)
Δλ := toRadians(lng2 - lng1)
a := math.Sin(Δφ/2)*math.Sin(Δφ/2) +
math.Cos(φ1)*math.Cos(φ2)*
@ -193,10 +195,29 @@ func calcDistance(p1 *pb.Point, p2 *pb.Point) int32 {
return int32(distance)
}
func inRange(point *pb.Point, rect *pb.Rectangle) bool {
left := math.Min(float64(rect.Lo.Longitude), float64(rect.Hi.Longitude))
right := math.Max(float64(rect.Lo.Longitude), float64(rect.Hi.Longitude))
top := math.Max(float64(rect.Lo.Latitude), float64(rect.Hi.Latitude))
bottom := math.Min(float64(rect.Lo.Latitude), float64(rect.Hi.Latitude))
if float64(point.Longitude) >= left &&
float64(point.Longitude) <= right &&
float64(point.Latitude) >= bottom &&
float64(point.Latitude) <= top {
return true
}
return false
}
func serialize(point *pb.Point) string {
return fmt.Sprintf("%d %d", point.Latitude, point.Longitude)
}
func newServer() *routeGuideServer {
s := new(routeGuideServer)
s.loadFeatures(*jsonDBFile)
s.routeNotes = make(map[pb.Point][]*pb.RouteNote, 0)
s.routeNotes = make(map[string][]*pb.RouteNote, 0)
return s
}
@ -209,7 +230,7 @@ func main() {
}
grpcServer := grpc.NewServer()
pb.RegisterRouteGuideServer(grpcServer, newServer())
if *useTLS {
if *tls {
creds, err := credentials.NewServerTLSFromFile(*certFile, *keyFile)
if err != nil {
log.Fatalf("Failed to generate credentials %v", err)