github.com/leovct/zkevm-bridge-service@v0.4.4/server/server.go (about) 1 package server 2 3 import ( 4 "context" 5 "fmt" 6 "net" 7 "net/http" 8 "os" 9 "os/signal" 10 "strings" 11 "time" 12 13 "github.com/0xPolygonHermez/zkevm-bridge-service/bridgectrl/pb" 14 "github.com/0xPolygonHermez/zkevm-bridge-service/log" 15 "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" 16 "google.golang.org/grpc" 17 "google.golang.org/grpc/credentials/insecure" 18 "google.golang.org/grpc/health/grpc_health_v1" 19 "google.golang.org/protobuf/encoding/protojson" 20 ) 21 22 // RunServer runs gRPC server and HTTP gateway 23 func RunServer(cfg Config, bridgeService pb.BridgeServiceServer) error { 24 ctx := context.Background() 25 26 if len(cfg.GRPCPort) == 0 { 27 return fmt.Errorf("invalid TCP port for gRPC server: '%s'", cfg.GRPCPort) 28 } 29 30 if len(cfg.HTTPPort) == 0 { 31 return fmt.Errorf("invalid TCP port for HTTP gateway: '%s'", cfg.HTTPPort) 32 } 33 34 go func() { 35 _ = runRestServer(ctx, cfg.GRPCPort, cfg.HTTPPort) 36 }() 37 38 go func() { 39 _ = runGRPCServer(ctx, bridgeService, cfg.GRPCPort) 40 }() 41 42 return nil 43 } 44 45 // HealthChecker will provide an implementation of the HealthCheck interface. 46 type healthChecker struct{} 47 48 // NewHealthChecker returns a health checker according to standard package 49 // grpc.health.v1. 50 func newHealthChecker() *healthChecker { 51 return &healthChecker{} 52 } 53 54 // HealthCheck interface implementation. 55 56 // Check returns the current status of the server for unary gRPC health requests, 57 // for now if the server is up and able to respond we will always return SERVING. 58 func (s *healthChecker) Check(ctx context.Context, req *grpc_health_v1.HealthCheckRequest) (*grpc_health_v1.HealthCheckResponse, error) { 59 return &grpc_health_v1.HealthCheckResponse{ 60 Status: grpc_health_v1.HealthCheckResponse_SERVING, 61 }, nil 62 } 63 64 // Watch returns the current status of the server for stream gRPC health requests, 65 // for now if the server is up and able to respond we will always return SERVING. 66 func (s *healthChecker) Watch(req *grpc_health_v1.HealthCheckRequest, server grpc_health_v1.Health_WatchServer) error { 67 return server.Send(&grpc_health_v1.HealthCheckResponse{ 68 Status: grpc_health_v1.HealthCheckResponse_SERVING, 69 }) 70 } 71 72 func runGRPCServer(ctx context.Context, bridgeServer pb.BridgeServiceServer, port string) error { 73 listen, err := net.Listen("tcp", ":"+port) 74 if err != nil { 75 return err 76 } 77 78 server := grpc.NewServer() 79 pb.RegisterBridgeServiceServer(server, bridgeServer) 80 81 healthService := newHealthChecker() 82 grpc_health_v1.RegisterHealthServer(server, healthService) 83 84 c := make(chan os.Signal, 1) 85 signal.Notify(c, os.Interrupt) 86 go func() { 87 for range c { 88 server.GracefulStop() 89 <-ctx.Done() 90 } 91 }() 92 93 log.Info("gRPC Server is serving at ", port) 94 return server.Serve(listen) 95 } 96 97 func preflightHandler(w http.ResponseWriter, r *http.Request) { 98 headers := []string{"Content-Type", "Accept"} 99 w.Header().Set("Access-Control-Allow-Headers", strings.Join(headers, ",")) 100 methods := []string{"GET", "HEAD", "POST", "PUT", "DELETE"} 101 w.Header().Set("Access-Control-Allow-Methods", strings.Join(methods, ",")) 102 } 103 104 // allowCORS allows Cross Origin Resource Sharing from any origin. 105 // Don't do this without consideration in production systems. 106 func allowCORS(h http.Handler) http.Handler { 107 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 108 if origin := r.Header.Get("Origin"); origin != "" { 109 w.Header().Set("Access-Control-Allow-Origin", origin) 110 if r.Method == "OPTIONS" && r.Header.Get("Access-Control-Request-Method") != "" { 111 preflightHandler(w, r) 112 return 113 } 114 } 115 h.ServeHTTP(w, r) 116 }) 117 } 118 119 func runRestServer(ctx context.Context, grpcPort, httpPort string) error { 120 ctx, cancel := context.WithCancel(ctx) 121 defer cancel() 122 123 opts := []grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())} 124 endpoint := "localhost:" + grpcPort 125 conn, err := grpc.Dial(endpoint, opts...) 126 if err != nil { 127 return err 128 } 129 130 muxHealthOpt := runtime.WithHealthzEndpoint(grpc_health_v1.NewHealthClient(conn)) 131 muxJSONOpt := runtime.WithMarshalerOption(runtime.MIMEWildcard, &runtime.JSONPb{ 132 MarshalOptions: protojson.MarshalOptions{ 133 UseProtoNames: true, 134 EmitUnpopulated: true, 135 }, 136 UnmarshalOptions: protojson.UnmarshalOptions{ 137 DiscardUnknown: true, 138 }, 139 }) 140 mux := runtime.NewServeMux(muxJSONOpt, muxHealthOpt) 141 142 if err := pb.RegisterBridgeServiceHandler(ctx, mux, conn); err != nil { 143 return err 144 } 145 146 srv := &http.Server{ 147 ReadTimeout: 1 * time.Second, //nolint:gomnd 148 Addr: ":" + httpPort, 149 Handler: allowCORS(mux), 150 } 151 152 c := make(chan os.Signal, 1) 153 signal.Notify(c, os.Interrupt) 154 go func() { 155 for range c { 156 _ = srv.Shutdown(ctx) 157 <-ctx.Done() 158 } 159 160 _, cancel := context.WithTimeout(ctx, 5*time.Second) //nolint:gomnd 161 defer cancel() 162 163 _ = srv.Shutdown(ctx) 164 }() 165 166 log.Info("Restful Server is serving at ", httpPort) 167 return srv.ListenAndServe() 168 }