github.com/fortexxx/gqlgen@v0.10.3-0.20191216030626-ca5ea8b21ead/graphql/handler/server.go (about)

     1  package handler
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"net/http"
     8  	"time"
     9  
    10  	"github.com/99designs/gqlgen/graphql"
    11  	"github.com/99designs/gqlgen/graphql/handler/extension"
    12  	"github.com/99designs/gqlgen/graphql/handler/lru"
    13  	"github.com/99designs/gqlgen/graphql/handler/transport"
    14  	"github.com/vektah/gqlparser/gqlerror"
    15  )
    16  
    17  type (
    18  	Server struct {
    19  		es         graphql.ExecutableSchema
    20  		transports []graphql.Transport
    21  		extensions []graphql.HandlerExtension
    22  		exec       executor
    23  
    24  		errorPresenter graphql.ErrorPresenterFunc
    25  		recoverFunc    graphql.RecoverFunc
    26  		queryCache     graphql.Cache
    27  	}
    28  )
    29  
    30  func New(es graphql.ExecutableSchema) *Server {
    31  	s := &Server{
    32  		es:             es,
    33  		errorPresenter: graphql.DefaultErrorPresenter,
    34  		recoverFunc:    graphql.DefaultRecover,
    35  		queryCache:     graphql.NoCache{},
    36  	}
    37  	s.exec = newExecutor(s)
    38  	return s
    39  }
    40  
    41  func NewDefaultServer(es graphql.ExecutableSchema) *Server {
    42  	srv := New(es)
    43  
    44  	srv.AddTransport(transport.Websocket{
    45  		KeepAlivePingInterval: 10 * time.Second,
    46  	})
    47  	srv.AddTransport(transport.Options{})
    48  	srv.AddTransport(transport.GET{})
    49  	srv.AddTransport(transport.POST{})
    50  	srv.AddTransport(transport.MultipartForm{})
    51  
    52  	srv.SetQueryCache(lru.New(1000))
    53  
    54  	srv.Use(extension.Introspection{})
    55  	srv.Use(extension.AutomaticPersistedQuery{
    56  		Cache: lru.New(100),
    57  	})
    58  
    59  	return srv
    60  }
    61  
    62  func (s *Server) AddTransport(transport graphql.Transport) {
    63  	s.transports = append(s.transports, transport)
    64  }
    65  
    66  func (s *Server) SetErrorPresenter(f graphql.ErrorPresenterFunc) {
    67  	s.errorPresenter = f
    68  }
    69  
    70  func (s *Server) SetRecoverFunc(f graphql.RecoverFunc) {
    71  	s.recoverFunc = f
    72  }
    73  
    74  func (s *Server) SetQueryCache(cache graphql.Cache) {
    75  	s.queryCache = cache
    76  }
    77  
    78  func (s *Server) Use(extension graphql.HandlerExtension) {
    79  	if err := extension.Validate(s.es); err != nil {
    80  		panic(err)
    81  	}
    82  
    83  	switch extension.(type) {
    84  	case graphql.OperationParameterMutator,
    85  		graphql.OperationContextMutator,
    86  		graphql.OperationInterceptor,
    87  		graphql.FieldInterceptor,
    88  		graphql.ResponseInterceptor:
    89  		s.extensions = append(s.extensions, extension)
    90  		s.exec = newExecutor(s)
    91  
    92  	default:
    93  		panic(fmt.Errorf("cannot Use %T as a gqlgen handler extension because it does not implement any extension hooks", extension))
    94  	}
    95  }
    96  
    97  // AroundFields is a convenience method for creating an extension that only implements field middleware
    98  func (s *Server) AroundFields(f graphql.FieldMiddleware) {
    99  	s.Use(FieldFunc(f))
   100  }
   101  
   102  // AroundOperations is a convenience method for creating an extension that only implements operation middleware
   103  func (s *Server) AroundOperations(f graphql.OperationMiddleware) {
   104  	s.Use(OperationFunc(f))
   105  }
   106  
   107  // AroundResponses is a convenience method for creating an extension that only implements response middleware
   108  func (s *Server) AroundResponses(f graphql.ResponseMiddleware) {
   109  	s.Use(ResponseFunc(f))
   110  }
   111  
   112  func (s *Server) getTransport(r *http.Request) graphql.Transport {
   113  	for _, t := range s.transports {
   114  		if t.Supports(r) {
   115  			return t
   116  		}
   117  	}
   118  	return nil
   119  }
   120  
   121  func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
   122  	defer func() {
   123  		if err := recover(); err != nil {
   124  			err := s.errorPresenter(r.Context(), s.recoverFunc(r.Context(), err))
   125  			resp := &graphql.Response{Errors: []*gqlerror.Error{err}}
   126  			b, _ := json.Marshal(resp)
   127  			w.WriteHeader(http.StatusUnprocessableEntity)
   128  			w.Write(b)
   129  		}
   130  	}()
   131  
   132  	r = r.WithContext(graphql.StartOperationTrace(r.Context()))
   133  
   134  	transport := s.getTransport(r)
   135  	if transport == nil {
   136  		sendErrorf(w, http.StatusBadRequest, "transport not supported")
   137  		return
   138  	}
   139  
   140  	transport.Do(w, r, s.exec)
   141  }
   142  
   143  func sendError(w http.ResponseWriter, code int, errors ...*gqlerror.Error) {
   144  	w.WriteHeader(code)
   145  	b, err := json.Marshal(&graphql.Response{Errors: errors})
   146  	if err != nil {
   147  		panic(err)
   148  	}
   149  	w.Write(b)
   150  }
   151  
   152  func sendErrorf(w http.ResponseWriter, code int, format string, args ...interface{}) {
   153  	sendError(w, code, &gqlerror.Error{Message: fmt.Sprintf(format, args...)})
   154  }
   155  
   156  type OperationFunc func(ctx context.Context, next graphql.OperationHandler) graphql.ResponseHandler
   157  
   158  func (r OperationFunc) ExtensionName() string {
   159  	return "InlineOperationFunc"
   160  }
   161  
   162  func (r OperationFunc) Validate(schema graphql.ExecutableSchema) error {
   163  	if r == nil {
   164  		return fmt.Errorf("OperationFunc can not be nil")
   165  	}
   166  	return nil
   167  }
   168  
   169  func (r OperationFunc) InterceptOperation(ctx context.Context, next graphql.OperationHandler) graphql.ResponseHandler {
   170  	return r(ctx, next)
   171  }
   172  
   173  type ResponseFunc func(ctx context.Context, next graphql.ResponseHandler) *graphql.Response
   174  
   175  func (r ResponseFunc) ExtensionName() string {
   176  	return "InlineResponseFunc"
   177  }
   178  
   179  func (r ResponseFunc) Validate(schema graphql.ExecutableSchema) error {
   180  	if r == nil {
   181  		return fmt.Errorf("ResponseFunc can not be nil")
   182  	}
   183  	return nil
   184  }
   185  
   186  func (r ResponseFunc) InterceptResponse(ctx context.Context, next graphql.ResponseHandler) *graphql.Response {
   187  	return r(ctx, next)
   188  }
   189  
   190  type FieldFunc func(ctx context.Context, next graphql.Resolver) (res interface{}, err error)
   191  
   192  func (f FieldFunc) ExtensionName() string {
   193  	return "InlineFieldFunc"
   194  }
   195  
   196  func (f FieldFunc) Validate(schema graphql.ExecutableSchema) error {
   197  	if f == nil {
   198  		return fmt.Errorf("FieldFunc can not be nil")
   199  	}
   200  	return nil
   201  }
   202  
   203  func (f FieldFunc) InterceptField(ctx context.Context, next graphql.Resolver) (res interface{}, err error) {
   204  	return f(ctx, next)
   205  }