1/* 2 * 3 * Copyright 2015 gRPC authors. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 * 17 */ 18 19//go:generate protoc -I ../routeguide --go_out=plugins=grpc:../routeguide ../routeguide/route_guide.proto 20 21// Package main implements a simple gRPC server that demonstrates how to use gRPC-Go libraries 22// to perform unary, client streaming, server streaming and full duplex RPCs. 23// 24// It implements the route guide service whose definition can be found in routeguide/route_guide.proto. 25package main 26 27import ( 28 "context" 29 "encoding/json" 30 "flag" 31 "fmt" 32 "io" 33 "io/ioutil" 34 "log" 35 "math" 36 "net" 37 "sync" 38 "time" 39 40 "google.golang.org/grpc" 41 42 "google.golang.org/grpc/credentials" 43 "google.golang.org/grpc/testdata" 44 45 "github.com/golang/protobuf/proto" 46 47 pb "google.golang.org/grpc/examples/route_guide/routeguide" 48) 49 50var ( 51 tls = flag.Bool("tls", false, "Connection uses TLS if true, else plain TCP") 52 certFile = flag.String("cert_file", "", "The TLS cert file") 53 keyFile = flag.String("key_file", "", "The TLS key file") 54 jsonDBFile = flag.String("json_db_file", "testdata/route_guide_db.json", "A json file containing a list of features") 55 port = flag.Int("port", 10000, "The server port") 56) 57 58type routeGuideServer struct { 59 savedFeatures []*pb.Feature // read-only after initialized 60 61 mu sync.Mutex // protects routeNotes 62 routeNotes map[string][]*pb.RouteNote 63} 64 65// GetFeature returns the feature at the given point. 66func (s *routeGuideServer) GetFeature(ctx context.Context, point *pb.Point) (*pb.Feature, error) { 67 for _, feature := range s.savedFeatures { 68 if proto.Equal(feature.Location, point) { 69 return feature, nil 70 } 71 } 72 // No feature was found, return an unnamed feature 73 return &pb.Feature{Location: point}, nil 74} 75 76// ListFeatures lists all features contained within the given bounding Rectangle. 77func (s *routeGuideServer) ListFeatures(rect *pb.Rectangle, stream pb.RouteGuide_ListFeaturesServer) error { 78 for _, feature := range s.savedFeatures { 79 if inRange(feature.Location, rect) { 80 if err := stream.Send(feature); err != nil { 81 return err 82 } 83 } 84 } 85 return nil 86} 87 88// RecordRoute records a route composited of a sequence of points. 89// 90// It gets a stream of points, and responds with statistics about the "trip": 91// number of points, number of known features visited, total distance traveled, and 92// total time spent. 93func (s *routeGuideServer) RecordRoute(stream pb.RouteGuide_RecordRouteServer) error { 94 var pointCount, featureCount, distance int32 95 var lastPoint *pb.Point 96 startTime := time.Now() 97 for { 98 point, err := stream.Recv() 99 if err == io.EOF { 100 endTime := time.Now() 101 return stream.SendAndClose(&pb.RouteSummary{ 102 PointCount: pointCount, 103 FeatureCount: featureCount, 104 Distance: distance, 105 ElapsedTime: int32(endTime.Sub(startTime).Seconds()), 106 }) 107 } 108 if err != nil { 109 return err 110 } 111 pointCount++ 112 for _, feature := range s.savedFeatures { 113 if proto.Equal(feature.Location, point) { 114 featureCount++ 115 } 116 } 117 if lastPoint != nil { 118 distance += calcDistance(lastPoint, point) 119 } 120 lastPoint = point 121 } 122} 123 124// RouteChat receives a stream of message/location pairs, and responds with a stream of all 125// previous messages at each of those locations. 126func (s *routeGuideServer) RouteChat(stream pb.RouteGuide_RouteChatServer) error { 127 for { 128 in, err := stream.Recv() 129 if err == io.EOF { 130 return nil 131 } 132 if err != nil { 133 return err 134 } 135 key := serialize(in.Location) 136 137 s.mu.Lock() 138 s.routeNotes[key] = append(s.routeNotes[key], in) 139 // Note: this copy prevents blocking other clients while serving this one. 140 // We don't need to do a deep copy, because elements in the slice are 141 // insert-only and never modified. 142 rn := make([]*pb.RouteNote, len(s.routeNotes[key])) 143 copy(rn, s.routeNotes[key]) 144 s.mu.Unlock() 145 146 for _, note := range rn { 147 if err := stream.Send(note); err != nil { 148 return err 149 } 150 } 151 } 152} 153 154// loadFeatures loads features from a JSON file. 155func (s *routeGuideServer) loadFeatures(filePath string) { 156 file, err := ioutil.ReadFile(filePath) 157 if err != nil { 158 log.Fatalf("Failed to load default features: %v", err) 159 } 160 if err := json.Unmarshal(file, &s.savedFeatures); err != nil { 161 log.Fatalf("Failed to load default features: %v", err) 162 } 163} 164 165func toRadians(num float64) float64 { 166 return num * math.Pi / float64(180) 167} 168 169// calcDistance calculates the distance between two points using the "haversine" formula. 170// The formula is based on http://mathforum.org/library/drmath/view/51879.html. 171func calcDistance(p1 *pb.Point, p2 *pb.Point) int32 { 172 const CordFactor float64 = 1e7 173 const R float64 = float64(6371000) // earth radius in metres 174 lat1 := toRadians(float64(p1.Latitude) / CordFactor) 175 lat2 := toRadians(float64(p2.Latitude) / CordFactor) 176 lng1 := toRadians(float64(p1.Longitude) / CordFactor) 177 lng2 := toRadians(float64(p2.Longitude) / CordFactor) 178 dlat := lat2 - lat1 179 dlng := lng2 - lng1 180 181 a := math.Sin(dlat/2)*math.Sin(dlat/2) + 182 math.Cos(lat1)*math.Cos(lat2)* 183 math.Sin(dlng/2)*math.Sin(dlng/2) 184 c := 2 * math.Atan2(math.Sqrt(a), math.Sqrt(1-a)) 185 186 distance := R * c 187 return int32(distance) 188} 189 190func inRange(point *pb.Point, rect *pb.Rectangle) bool { 191 left := math.Min(float64(rect.Lo.Longitude), float64(rect.Hi.Longitude)) 192 right := math.Max(float64(rect.Lo.Longitude), float64(rect.Hi.Longitude)) 193 top := math.Max(float64(rect.Lo.Latitude), float64(rect.Hi.Latitude)) 194 bottom := math.Min(float64(rect.Lo.Latitude), float64(rect.Hi.Latitude)) 195 196 if float64(point.Longitude) >= left && 197 float64(point.Longitude) <= right && 198 float64(point.Latitude) >= bottom && 199 float64(point.Latitude) <= top { 200 return true 201 } 202 return false 203} 204 205func serialize(point *pb.Point) string { 206 return fmt.Sprintf("%d %d", point.Latitude, point.Longitude) 207} 208 209func newServer() *routeGuideServer { 210 s := &routeGuideServer{routeNotes: make(map[string][]*pb.RouteNote)} 211 s.loadFeatures(*jsonDBFile) 212 return s 213} 214 215func main() { 216 flag.Parse() 217 lis, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", *port)) 218 if err != nil { 219 log.Fatalf("failed to listen: %v", err) 220 } 221 var opts []grpc.ServerOption 222 if *tls { 223 if *certFile == "" { 224 *certFile = testdata.Path("server1.pem") 225 } 226 if *keyFile == "" { 227 *keyFile = testdata.Path("server1.key") 228 } 229 creds, err := credentials.NewServerTLSFromFile(*certFile, *keyFile) 230 if err != nil { 231 log.Fatalf("Failed to generate credentials %v", err) 232 } 233 opts = []grpc.ServerOption{grpc.Creds(creds)} 234 } 235 grpcServer := grpc.NewServer(opts...) 236 pb.RegisterRouteGuideServer(grpcServer, newServer()) 237 grpcServer.Serve(lis) 238} 239