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  }