| /* |
| * |
| * Copyright 2015 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. |
| * |
| */ |
| |
| //go:generate protoc -I ../routeguide --go_out=plugins=grpc:../routeguide ../routeguide/route_guide.proto |
| |
| // 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 routeguide/route_guide.proto. |
| package main |
| |
| import ( |
| "context" |
| "encoding/json" |
| "flag" |
| "fmt" |
| "io" |
| "io/ioutil" |
| "log" |
| "math" |
| "net" |
| "sync" |
| "time" |
| |
| "google.golang.org/grpc" |
| |
| "google.golang.org/grpc/credentials" |
| "google.golang.org/grpc/testdata" |
| |
| "github.com/golang/protobuf/proto" |
| |
| pb "google.golang.org/grpc/examples/route_guide/routeguide" |
| ) |
| |
| var ( |
| tls = flag.Bool("tls", false, "Connection uses TLS if true, else plain TCP") |
| certFile = flag.String("cert_file", "", "The TLS cert file") |
| keyFile = flag.String("key_file", "", "The TLS key file") |
| jsonDBFile = flag.String("json_db_file", "testdata/route_guide_db.json", "A json file containing a list of features") |
| port = flag.Int("port", 10000, "The server port") |
| ) |
| |
| type routeGuideServer struct { |
| savedFeatures []*pb.Feature // read-only after initialized |
| |
| mu sync.Mutex // protects routeNotes |
| 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 proto.Equal(feature.Location, point) { |
| return feature, nil |
| } |
| } |
| // No feature was found, return an unnamed feature |
| return &pb.Feature{Location: point}, nil |
| } |
| |
| // ListFeatures lists all features contained within the given bounding Rectangle. |
| func (s *routeGuideServer) ListFeatures(rect *pb.Rectangle, stream pb.RouteGuide_ListFeaturesServer) error { |
| for _, feature := range s.savedFeatures { |
| if inRange(feature.Location, rect) { |
| if err := stream.Send(feature); err != nil { |
| return err |
| } |
| } |
| } |
| return nil |
| } |
| |
| // RecordRoute records a route composited of a sequence of points. |
| // |
| // It gets a stream of points, and responds with statistics about the "trip": |
| // number of points, number of known features visited, total distance traveled, and |
| // total time spent. |
| func (s *routeGuideServer) RecordRoute(stream pb.RouteGuide_RecordRouteServer) error { |
| var pointCount, featureCount, distance int32 |
| var lastPoint *pb.Point |
| startTime := time.Now() |
| for { |
| point, err := stream.Recv() |
| if err == io.EOF { |
| endTime := time.Now() |
| return stream.SendAndClose(&pb.RouteSummary{ |
| PointCount: pointCount, |
| FeatureCount: featureCount, |
| Distance: distance, |
| ElapsedTime: int32(endTime.Sub(startTime).Seconds()), |
| }) |
| } |
| if err != nil { |
| return err |
| } |
| pointCount++ |
| for _, feature := range s.savedFeatures { |
| if proto.Equal(feature.Location, point) { |
| featureCount++ |
| } |
| } |
| if lastPoint != nil { |
| distance += calcDistance(lastPoint, point) |
| } |
| lastPoint = point |
| } |
| } |
| |
| // RouteChat receives a stream of message/location pairs, and responds with a stream of all |
| // previous messages at each of those locations. |
| func (s *routeGuideServer) RouteChat(stream pb.RouteGuide_RouteChatServer) error { |
| for { |
| in, err := stream.Recv() |
| if err == io.EOF { |
| return nil |
| } |
| if err != nil { |
| return err |
| } |
| key := serialize(in.Location) |
| |
| s.mu.Lock() |
| s.routeNotes[key] = append(s.routeNotes[key], in) |
| // Note: this copy prevents blocking other clients while serving this one. |
| // We don't need to do a deep copy, because elements in the slice are |
| // insert-only and never modified. |
| rn := make([]*pb.RouteNote, len(s.routeNotes[key])) |
| copy(rn, s.routeNotes[key]) |
| s.mu.Unlock() |
| |
| for _, note := range rn { |
| if err := stream.Send(note); err != nil { |
| return err |
| } |
| } |
| } |
| } |
| |
| // loadFeatures loads features from a JSON file. |
| func (s *routeGuideServer) loadFeatures(filePath string) { |
| file, err := ioutil.ReadFile(filePath) |
| if err != nil { |
| log.Fatalf("Failed to load default features: %v", err) |
| } |
| if err := json.Unmarshal(file, &s.savedFeatures); err != nil { |
| log.Fatalf("Failed to load default features: %v", err) |
| } |
| } |
| |
| func toRadians(num float64) float64 { |
| return num * math.Pi / float64(180) |
| } |
| |
| // calcDistance calculates the distance between two points using the "haversine" formula. |
| // The formula is based on http://mathforum.org/library/drmath/view/51879.html. |
| func calcDistance(p1 *pb.Point, p2 *pb.Point) int32 { |
| const CordFactor float64 = 1e7 |
| const R float64 = float64(6371000) // earth radius in metres |
| lat1 := toRadians(float64(p1.Latitude) / CordFactor) |
| lat2 := toRadians(float64(p2.Latitude) / CordFactor) |
| lng1 := toRadians(float64(p1.Longitude) / CordFactor) |
| lng2 := toRadians(float64(p2.Longitude) / CordFactor) |
| dlat := lat2 - lat1 |
| dlng := lng2 - lng1 |
| |
| a := math.Sin(dlat/2)*math.Sin(dlat/2) + |
| math.Cos(lat1)*math.Cos(lat2)* |
| math.Sin(dlng/2)*math.Sin(dlng/2) |
| c := 2 * math.Atan2(math.Sqrt(a), math.Sqrt(1-a)) |
| |
| distance := R * c |
| 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 := &routeGuideServer{routeNotes: make(map[string][]*pb.RouteNote)} |
| s.loadFeatures(*jsonDBFile) |
| return s |
| } |
| |
| func main() { |
| flag.Parse() |
| lis, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", *port)) |
| if err != nil { |
| log.Fatalf("failed to listen: %v", err) |
| } |
| var opts []grpc.ServerOption |
| if *tls { |
| if *certFile == "" { |
| *certFile = testdata.Path("server1.pem") |
| } |
| if *keyFile == "" { |
| *keyFile = testdata.Path("server1.key") |
| } |
| creds, err := credentials.NewServerTLSFromFile(*certFile, *keyFile) |
| if err != nil { |
| log.Fatalf("Failed to generate credentials %v", err) |
| } |
| opts = []grpc.ServerOption{grpc.Creds(creds)} |
| } |
| grpcServer := grpc.NewServer(opts...) |
| pb.RegisterRouteGuideServer(grpcServer, newServer()) |
| grpcServer.Serve(lis) |
| } |