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 }