code.vegaprotocol.io/vega@v0.79.0/datanode/gateway/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 "strings" 24 25 "code.vegaprotocol.io/vega/datanode/gateway" 26 "code.vegaprotocol.io/vega/logging" 27 "code.vegaprotocol.io/vega/paths" 28 protoapiv2 "code.vegaprotocol.io/vega/protos/data-node/api/v2" 29 vegaprotoapi "code.vegaprotocol.io/vega/protos/vega/api/v1" 30 31 "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" 32 "github.com/tmc/grpc-websocket-proxy/wsproxy" 33 "go.elastic.co/apm/module/apmhttp" 34 "google.golang.org/grpc" 35 "google.golang.org/protobuf/encoding/protojson" 36 ) 37 38 const ( 39 namedLogger = "restproxy" 40 ) 41 42 // ProxyServer implement a rest server acting as a proxy to the grpc api. 43 type ProxyServer struct { 44 gateway.Config 45 log *logging.Logger 46 vegaPaths paths.Paths 47 srv *http.Server 48 } 49 50 // NewProxyServer returns a new instance of the rest proxy server. 51 func NewProxyServer(log *logging.Logger, config gateway.Config, vegaPaths paths.Paths) *ProxyServer { 52 // setup logger 53 log = log.Named(namedLogger) 54 log.SetLevel(config.Level.Get()) 55 56 return &ProxyServer{ 57 log: log, 58 Config: config, 59 srv: nil, 60 vegaPaths: vegaPaths, 61 } 62 } 63 64 // ReloadConf update the internal configuration of the server. 65 func (s *ProxyServer) ReloadConf(cfg gateway.Config) { 66 s.log.Info("reloading configuration") 67 if s.log.GetLevel() != cfg.Level.Get() { 68 s.log.Info("updating log level", 69 logging.String("old", s.log.GetLevel().String()), 70 logging.String("new", cfg.Level.String()), 71 ) 72 s.log.SetLevel(cfg.Level.Get()) 73 } 74 75 // TODO(): not updating the the actual server for now, may need to look at this later 76 // e.g restart the http server on another port or whatever 77 s.Config = cfg 78 } 79 80 // This is because by default the marshaller wants to put a newline between chunks of the stream response. 81 type HTTPBodyDelimitedMarshaler struct { 82 runtime.HTTPBodyMarshaler 83 84 delimiter []byte 85 } 86 87 func (o *HTTPBodyDelimitedMarshaler) Delimiter() []byte { 88 return o.delimiter 89 } 90 91 // Start start the server. 92 func (s *ProxyServer) Start(ctx context.Context) (http.Handler, error) { 93 logger := s.log 94 95 grpcAddr := net.JoinHostPort(s.Node.IP, strconv.Itoa(s.Node.Port)) 96 97 mux := runtime.NewServeMux( 98 // this is a settings specially made for websockets 99 runtime.WithMarshalerOption("application/json+stream", &JSONPb{ 100 EnumsAsInts: true, 101 EmitDefaults: false, 102 OrigName: false, 103 }), 104 // prettified, just for JonRay 105 // append ?pretty to any query to make it... pretty 106 runtime.WithMarshalerOption("application/json+pretty", &JSONPb{ 107 EnumsAsInts: false, 108 EmitDefaults: true, 109 OrigName: false, 110 Indent: " ", 111 }), 112 runtime.WithMarshalerOption("text/csv", &HTTPBodyDelimitedMarshaler{ 113 delimiter: []byte(""), // Don't append newline between stream sends 114 // Default HTTPBodyMarshaler 115 HTTPBodyMarshaler: runtime.HTTPBodyMarshaler{ 116 Marshaler: &runtime.JSONPb{ 117 MarshalOptions: protojson.MarshalOptions{ 118 EmitUnpopulated: true, 119 }, 120 UnmarshalOptions: protojson.UnmarshalOptions{ 121 DiscardUnknown: true, 122 }, 123 }, 124 }, 125 }), 126 // default for REST request 127 runtime.WithMarshalerOption(runtime.MIMEWildcard, &JSONPb{ 128 EmitDefaults: true, 129 OrigName: false, 130 }), 131 132 runtime.WithOutgoingHeaderMatcher(func(s string) (string, bool) { return s, true }), 133 ) 134 135 opts := []grpc.DialOption{ 136 grpc.WithInsecure(), 137 grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(s.Config.Node.MaxMsgSize)), 138 } 139 140 marshalW := func(h http.Handler) http.Handler { 141 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 142 // if we are dealing with a stream, let's add some header to use the proper marshaller 143 if strings.HasPrefix(r.URL.Path, "/api/v2/stream/") { 144 r.Header.Set("Accept", "application/json+stream") 145 } else if strings.HasPrefix(r.URL.Path, "/api/v2/networkhistory/export") || 146 strings.HasPrefix(r.URL.Path, "/api/v2/ledgerentry/export") { 147 r.Header.Set("Accept", "text/csv") 148 } else if _, ok := r.URL.Query()["pretty"]; ok { 149 // checking Values as map[string][]string also catches ?pretty and ?pretty= 150 // r.URL.Query().Get("pretty") would not. 151 r.Header.Set("Accept", "application/json+pretty") 152 } 153 h.ServeHTTP(w, r) 154 }) 155 } 156 157 if err := vegaprotoapi.RegisterCoreServiceHandlerFromEndpoint(ctx, mux, grpcAddr, opts); err != nil { 158 logger.Panic("Failure registering trading handler for REST proxy endpoints", logging.Error(err)) 159 } 160 if err := protoapiv2.RegisterTradingDataServiceHandlerFromEndpoint(ctx, mux, grpcAddr, opts); err != nil { 161 logger.Panic("Failure registering trading handler for REST proxy endpoints", logging.Error(err)) 162 } 163 164 handler := marshalW(mux) 165 handler = healthCheckMiddleware(handler) 166 handler = gateway.RemoteAddrMiddleware(logger, handler) 167 // Gzip encoding support 168 handler = NewGzipHandler(*logger, handler.(http.HandlerFunc)) 169 // Metric support 170 handler = gateway.MetricCollectionMiddleware(handler) 171 handler = wsproxy.WebsocketProxy(handler) 172 173 // APM 174 if s.REST.APMEnabled { 175 handler = apmhttp.Wrap(handler) 176 } 177 178 return handler, nil 179 } 180 181 func healthCheckMiddleware(f http.Handler) http.HandlerFunc { 182 return func(w http.ResponseWriter, r *http.Request) { 183 if r.URL.Path == "/health" { 184 _, _ = w.Write([]byte("ok")) 185 w.WriteHeader(http.StatusOK) 186 return 187 } 188 f.ServeHTTP(w, r) 189 } 190 }