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  }