code.vegaprotocol.io/vega@v0.79.0/core/api/rest/server.go (about)

     1  // Copyright (C) 2023 Gobalsky Labs Limited
     2  //
     3  // This program is free software: you can redistribute it and/or modify
     4  // it under the terms of the GNU Affero General Public License as
     5  // published by the Free Software Foundation, either version 3 of the
     6  // License, or (at your option) any later version.
     7  //
     8  // This program is distributed in the hope that it will be useful,
     9  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    10  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    11  // GNU Affero General Public License for more details.
    12  //
    13  // You should have received a copy of the GNU Affero General Public License
    14  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    15  
    16  package rest
    17  
    18  import (
    19  	"context"
    20  	"net"
    21  	"net/http"
    22  	"strconv"
    23  
    24  	"code.vegaprotocol.io/vega/core/api"
    25  	libhttp "code.vegaprotocol.io/vega/libs/http"
    26  	"code.vegaprotocol.io/vega/logging"
    27  	protoapi "code.vegaprotocol.io/vega/protos/vega/api/v1"
    28  
    29  	"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
    30  	"github.com/rs/cors"
    31  	"go.elastic.co/apm/module/apmhttp"
    32  	"google.golang.org/grpc"
    33  )
    34  
    35  const (
    36  	namedLogger = "api.restproxy"
    37  )
    38  
    39  // ProxyServer implement a rest server acting as a proxy to the grpc api.
    40  type ProxyServer struct {
    41  	log *logging.Logger
    42  	cfg api.Config
    43  	srv *http.Server
    44  }
    45  
    46  // NewProxyServer returns a new instance of the rest proxy server.
    47  func NewProxyServer(log *logging.Logger, config api.Config) *ProxyServer {
    48  	// setup logger
    49  	log = log.Named(namedLogger)
    50  	log.SetLevel(config.Level.Get())
    51  
    52  	return &ProxyServer{
    53  		log: log,
    54  		cfg: config,
    55  		srv: nil,
    56  	}
    57  }
    58  
    59  // ReloadConf update the internal configuration of the server.
    60  func (s *ProxyServer) ReloadConf(cfg api.Config) {
    61  	s.log.Info("reloading configuration")
    62  	if s.log.GetLevel() != cfg.Level.Get() {
    63  		s.log.Info("updating log level",
    64  			logging.String("old", s.log.GetLevel().String()),
    65  			logging.String("new", cfg.Level.String()),
    66  		)
    67  		s.log.SetLevel(cfg.Level.Get())
    68  	}
    69  
    70  	// TODO(): not updating the actual server for now, may need to look at this later
    71  	// e.g restart the http server on another port or whatever
    72  	s.cfg = cfg
    73  }
    74  
    75  // Start starts the server.
    76  func (s *ProxyServer) Start() {
    77  	logger := s.log
    78  
    79  	logger.Info("Starting REST<>GRPC based API",
    80  		logging.String("addr", s.cfg.REST.IP),
    81  		logging.Int("port", s.cfg.REST.Port))
    82  
    83  	ctx := context.Background()
    84  	ctx, cancel := context.WithCancel(ctx)
    85  	defer cancel()
    86  
    87  	restAddr := net.JoinHostPort(s.cfg.REST.IP, strconv.Itoa(s.cfg.REST.Port))
    88  	grpcAddr := net.JoinHostPort(s.cfg.IP, strconv.Itoa(s.cfg.Port))
    89  	jsonPB := &JSONPb{
    90  		EmitDefaults: true,
    91  		Indent:       "  ", // formatted json output
    92  		OrigName:     false,
    93  	}
    94  
    95  	mux := runtime.NewServeMux(
    96  		runtime.WithMarshalerOption(runtime.MIMEWildcard, jsonPB),
    97  	)
    98  
    99  	opts := []grpc.DialOption{grpc.WithInsecure()}
   100  	if err := protoapi.RegisterCoreServiceHandlerFromEndpoint(ctx, mux, grpcAddr, opts); err != nil {
   101  		logger.Panic("Failure registering trading handler for REST proxy endpoints", logging.Error(err))
   102  	}
   103  	if err := protoapi.RegisterCoreStateServiceHandlerFromEndpoint(ctx, mux, grpcAddr, opts); err != nil {
   104  		logger.Panic("Failure registering trading handler for REST proxy endpoints", logging.Error(err))
   105  	}
   106  
   107  	// CORS support
   108  	corsOptions := libhttp.CORSOptions(s.cfg.REST.CORS)
   109  	handler := cors.New(corsOptions).Handler(mux)
   110  	handler = healthCheckMiddleware(handler)
   111  	handler = RemoteAddrMiddleware(logger, handler)
   112  	// Gzip encoding support
   113  	handler = newGzipHandler(*logger, handler.(http.HandlerFunc))
   114  	// Metric support
   115  	handler = MetricCollectionMiddleware(handler)
   116  
   117  	// APM
   118  	if s.cfg.REST.APMEnabled {
   119  		handler = apmhttp.Wrap(handler)
   120  	}
   121  
   122  	s.srv = &http.Server{
   123  		Addr:    restAddr,
   124  		Handler: handler,
   125  	}
   126  
   127  	// Start http server on port specified
   128  	err := s.srv.ListenAndServe()
   129  	if err != nil && err != http.ErrServerClosed {
   130  		logger.Panic("Failure serving REST proxy API", logging.Error(err))
   131  	}
   132  }
   133  
   134  // Stop stops the server.
   135  func (s *ProxyServer) Stop() {
   136  	if s.srv != nil {
   137  		s.log.Info("Stopping REST<>GRPC based API")
   138  
   139  		if err := s.srv.Shutdown(context.Background()); err != nil {
   140  			s.log.Error("Failed to stop REST<>GRPC based API cleanly",
   141  				logging.Error(err))
   142  		}
   143  	}
   144  }
   145  
   146  func healthCheckMiddleware(f http.Handler) http.HandlerFunc {
   147  	return func(w http.ResponseWriter, r *http.Request) {
   148  		if r.URL.Path == "/health" {
   149  			w.Write([]byte("ok"))
   150  			w.WriteHeader(http.StatusOK)
   151  			return
   152  		}
   153  		f.ServeHTTP(w, r)
   154  	}
   155  }