code.vegaprotocol.io/vega@v0.79.0/datanode/gateway/graphql/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 gql 17 18 import ( 19 "context" 20 "errors" 21 "fmt" 22 "net/http" 23 "runtime/debug" 24 "strings" 25 "time" 26 27 "code.vegaprotocol.io/vega/datanode/gateway" 28 "code.vegaprotocol.io/vega/datanode/metrics" 29 "code.vegaprotocol.io/vega/datanode/ratelimit" 30 "code.vegaprotocol.io/vega/logging" 31 "code.vegaprotocol.io/vega/paths" 32 v2 "code.vegaprotocol.io/vega/protos/data-node/api/v2" 33 "code.vegaprotocol.io/vega/protos/vega" 34 vegaprotoapi "code.vegaprotocol.io/vega/protos/vega/api/v1" 35 36 "github.com/99designs/gqlgen/graphql" 37 "github.com/99designs/gqlgen/graphql/playground" 38 "github.com/99designs/gqlgen/handler" 39 "github.com/gorilla/websocket" 40 "github.com/vektah/gqlparser/v2/gqlerror" 41 "google.golang.org/grpc" 42 "google.golang.org/grpc/metadata" 43 "google.golang.org/grpc/status" 44 ) 45 46 const ( 47 namedLogger = "gql" 48 ) 49 50 // GraphServer is the graphql server. 51 type GraphServer struct { 52 gateway.Config 53 54 log *logging.Logger 55 vegaPaths paths.Paths 56 57 coreProxyClient CoreProxyServiceClient 58 tradingDataClientV2 v2.TradingDataServiceClient 59 rl *gateway.SubscriptionRateLimiter 60 rateLimit *ratelimit.RateLimit 61 } 62 63 // New returns a new instance of the grapqhl server. 64 func New( 65 log *logging.Logger, 66 config gateway.Config, 67 vegaPaths paths.Paths, 68 ) (*GraphServer, error) { 69 // setup logger 70 log = log.Named(namedLogger) 71 log.SetLevel(config.Level.Get()) 72 73 serverAddr := fmt.Sprintf("%v:%v", config.Node.IP, config.Node.Port) 74 75 tdconn, err := grpc.Dial( 76 serverAddr, 77 grpc.WithInsecure(), 78 ratelimit.WithSecret(), 79 grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(config.Node.MaxMsgSize)), 80 ) 81 if err != nil { 82 return nil, err 83 } 84 tradingDataClientV2 := v2.NewTradingDataServiceClient(&clientConn{tdconn}) 85 86 tconn, err := grpc.Dial( 87 serverAddr, 88 grpc.WithInsecure(), 89 grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(config.MaxMsgSize)), 90 ) 91 if err != nil { 92 return nil, err 93 } 94 95 tradingClient := vegaprotoapi.NewCoreServiceClient(&clientConn{tconn}) 96 97 return &GraphServer{ 98 log: log, 99 Config: config, 100 vegaPaths: vegaPaths, 101 coreProxyClient: tradingClient, 102 tradingDataClientV2: tradingDataClientV2, 103 rl: gateway.NewSubscriptionRateLimiter( 104 log, config.MaxSubscriptionPerClient), 105 rateLimit: ratelimit.NewFromConfig(&config.RateLimit, log), 106 }, nil 107 } 108 109 // ReloadConf update the internal configuration of the graphql server. 110 func (g *GraphServer) ReloadConf(cfg gateway.Config) { 111 g.log.Info("reloading configuration") 112 if g.log.GetLevel() != cfg.Level.Get() { 113 g.log.Info("updating log level", 114 logging.String("old", g.log.GetLevel().String()), 115 logging.String("new", cfg.Level.String()), 116 ) 117 g.log.SetLevel(cfg.Level.Get()) 118 } 119 120 // TODO(): not updating the actual server for now, may need to look at this later 121 // e.g restart the http server on another port or whatever 122 g.Config = cfg 123 g.rateLimit.ReloadConfig(&cfg.RateLimit) 124 } 125 126 type ( 127 clientConn struct { 128 *grpc.ClientConn 129 } 130 metadataKey struct{} 131 ) 132 133 func (c *clientConn) Invoke(ctx context.Context, method string, args, reply interface{}, opts ...grpc.CallOption) error { 134 mdi := ctx.Value(metadataKey{}) 135 if md, ok := mdi.(*metadata.MD); ok { 136 opts = append(opts, grpc.Header(md)) 137 } 138 return c.ClientConn.Invoke(ctx, method, args, reply, opts...) 139 } 140 141 // Start starts the server in order receive http request. 142 func (g *GraphServer) Start() (http.Handler, error) { 143 up := websocket.Upgrader{ 144 ReadBufferSize: 1024, 145 WriteBufferSize: 1024, 146 CheckOrigin: func(r *http.Request) bool { 147 return true 148 }, 149 } 150 151 resolverRoot := NewResolverRoot( 152 g.log, 153 g.Config, 154 g.coreProxyClient, 155 g.tradingDataClientV2, 156 ) 157 config := Config{ 158 Resolvers: resolverRoot, 159 } 160 161 loggingMiddleware := handler.ResolverMiddleware(func(ctx context.Context, next graphql.Resolver) (res interface{}, err error) { 162 resctx := graphql.GetResolverContext(ctx) 163 clockstart := time.Now() 164 res, err = next(ctx) 165 metrics.APIRequestAndTimeGraphQL(resctx.Field.Name, time.Since(clockstart).Seconds()) 166 return res, err 167 }) 168 169 headersMiddleware := handler.ResolverMiddleware(func(ctx context.Context, next graphql.Resolver) (res interface{}, err error) { 170 if ctx.Value(metadataKey{}) != nil { 171 res, err = next(ctx) 172 return 173 } 174 175 md := metadata.MD{} 176 ctx = context.WithValue(ctx, metadataKey{}, &md) 177 res, err = next(ctx) 178 rw, ok := gateway.InjectableWriterFromContext(ctx) 179 if !ok { 180 return 181 } 182 rw.SetHeaders(http.Header(md)) 183 return 184 }) 185 186 errMiddleware := handler.ErrorPresenter(func(ctx context.Context, e error) *gqlerror.Error { 187 if e == nil { 188 return nil 189 } 190 191 st, ok := status.FromError(errors.Unwrap(e)) 192 if !ok { 193 return graphql.DefaultErrorPresenter(ctx, e) 194 } 195 196 errsStr := []string{} 197 for _, v := range st.Details() { 198 v, ok := v.(*vega.ErrorDetail) 199 if !ok { 200 continue 201 } 202 errsStr = append(errsStr, v.Message) 203 } 204 205 ge := graphql.DefaultErrorPresenter( 206 ctx, errors.New(strings.Join(errsStr, ", "))) 207 ge.Extensions = map[string]interface{}{ 208 "code": st.Code(), 209 "type": st.Code().String(), 210 } 211 212 return ge 213 }) 214 215 handlr := http.NewServeMux() 216 217 if g.GraphQLPlaygroundEnabled { 218 g.log.Warn("graphql playground enabled, this is not a recommended setting for production") 219 handlr.Handle("/", playground.Handler("VEGA", g.GraphQL.Endpoint)) 220 } 221 options := []handler.Option{ 222 handler.WebsocketKeepAliveDuration(10 * time.Second), 223 handler.WebsocketUpgrader(up), 224 loggingMiddleware, 225 headersMiddleware, 226 errMiddleware, 227 handler.RecoverFunc(func(ctx context.Context, err interface{}) error { 228 g.log.Warn("Recovering from error on graphQL handler", 229 logging.String("error", fmt.Sprintf("%s", err))) 230 debug.PrintStack() 231 return errors.New("an internal error occurred") 232 }), 233 234 handler.ComplexityLimit(3750), 235 } 236 if g.GraphQL.ComplexityLimit > 0 { 237 options = append(options, handler.ComplexityLimit(g.GraphQL.ComplexityLimit)) 238 } 239 240 middleware := gateway.Chain( 241 gateway.RemoteAddrMiddleware(g.log, handler.GraphQL(NewExecutableSchema(config), options...)), 242 gateway.WithAddHeadersMiddleware, 243 g.rl.WithSubscriptionRateLimiter, 244 g.rateLimit.HTTPMiddleware, 245 ) 246 247 handlr.Handle(g.GraphQL.Endpoint, middleware) 248 return handlr, nil 249 }