Skip to content

Commit

Permalink
Add websocket transport
Browse files Browse the repository at this point in the history
  • Loading branch information
vektah committed Oct 28, 2019
1 parent eed1515 commit cb99b42
Show file tree
Hide file tree
Showing 10 changed files with 476 additions and 416 deletions.
2 changes: 1 addition & 1 deletion graphql/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ type (

Transport interface {
Supports(r *http.Request) bool
Do(w http.ResponseWriter, r *http.Request) (*RequestContext, Writer)
Do(w http.ResponseWriter, r *http.Request, handler Handler)
}
)

Expand Down
10 changes: 2 additions & 8 deletions graphql/handler/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,19 +53,13 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return
}

rc, writer := transport.Do(w, r)
if rc == nil {
return
}

// todo: Should be done statically at server init rather than per request.
handler := s.executableSchemaHandler

for i := len(s.middlewares) - 1; i >= 0; i-- {
handler = s.middlewares[i](handler)
}

ctx := graphql.WithRequestContext(r.Context(), rc)
handler(ctx, writer)
transport.Do(w, r, handler)
}

// executableSchemaHandler is the inner most handler, it invokes the graph directly after all middleware
Expand Down
26 changes: 26 additions & 0 deletions graphql/handler/transport/error.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package transport

import (
"encoding/json"
"fmt"
"net/http"

"github.com/99designs/gqlgen/graphql"
"github.com/vektah/gqlparser/gqlerror"
)

// SendError sends a best effort error to a raw response writer. It assumes the client can understand the standard
// json error response
func SendError(w http.ResponseWriter, code int, errors ...*gqlerror.Error) {
w.WriteHeader(code)
b, err := json.Marshal(&graphql.Response{Errors: errors})
if err != nil {
panic(err)
}
w.Write(b)
}

// SendErrorf wraps SendError to add formatted messages
func SendErrorf(w http.ResponseWriter, code int, format string, args ...interface{}) {
SendError(w, code, &gqlerror.Error{Message: fmt.Sprintf(format, args...)})
}
21 changes: 12 additions & 9 deletions graphql/handler/transport/http_get.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import (

type HTTPGet struct{}

var _ graphql.Transport = HTTPGet{}

func (H HTTPGet) Supports(r *http.Request) bool {
if r.Header.Get("Upgrade") != "" {
return false
Expand All @@ -19,10 +21,10 @@ func (H HTTPGet) Supports(r *http.Request) bool {
return r.Method == "GET"
}

func (H HTTPGet) Do(w http.ResponseWriter, r *http.Request) (*graphql.RequestContext, graphql.Writer) {
reqParams := newRequestContext()
reqParams.RawQuery = r.URL.Query().Get("query")
reqParams.OperationName = r.URL.Query().Get("operationName")
func (H HTTPGet) Do(w http.ResponseWriter, r *http.Request, handler graphql.Handler) {
rc := newRequestContext()
rc.RawQuery = r.URL.Query().Get("query")
rc.OperationName = r.URL.Query().Get("operationName")

writer := graphql.Writer(func(response *graphql.Response) {
b, err := json.Marshal(response)
Expand All @@ -33,16 +35,16 @@ func (H HTTPGet) Do(w http.ResponseWriter, r *http.Request) (*graphql.RequestCon
})

if variables := r.URL.Query().Get("variables"); variables != "" {
if err := jsonDecode(strings.NewReader(variables), &reqParams.Variables); err != nil {
if err := jsonDecode(strings.NewReader(variables), &rc.Variables); err != nil {
writer.Errorf("variables could not be decoded")
return nil, nil
return
}
}

if extensions := r.URL.Query().Get("extensions"); extensions != "" {
if err := jsonDecode(strings.NewReader(extensions), &reqParams.Extensions); err != nil {
if err := jsonDecode(strings.NewReader(extensions), &rc.Extensions); err != nil {
writer.Errorf("extensions could not be decoded")
return nil, nil
return
}
}

Expand All @@ -51,7 +53,8 @@ func (H HTTPGet) Do(w http.ResponseWriter, r *http.Request) (*graphql.RequestCon
// return ctx, nil, nil, gqlerror.List{gqlerror.Errorf("GET requests only allow query operations")}
//}

return reqParams, writer
ctx := graphql.WithRequestContext(r.Context(), rc)
handler(ctx, writer)
}

func jsonDecode(r io.Reader, val interface{}) error {
Expand Down
24 changes: 10 additions & 14 deletions graphql/handler/transport/jsonpost.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ func (H JsonPostTransport) Supports(r *http.Request) bool {
return r.Method == "POST" && mediaType == "application/json"
}

func (H JsonPostTransport) Do(w http.ResponseWriter, r *http.Request) (*graphql.RequestContext, graphql.Writer) {
func (H JsonPostTransport) Do(w http.ResponseWriter, r *http.Request, handler graphql.Handler) {
w.Header().Set("Content-Type", "application/json")

write := graphql.Writer(func(response *graphql.Response) {
Expand All @@ -36,23 +36,19 @@ func (H JsonPostTransport) Do(w http.ResponseWriter, r *http.Request) (*graphql.
w.Write(b)
})

var params struct {
Query string `json:"query"`
OperationName string `json:"operationName"`
Variables map[string]interface{} `json:"variables"`
Extensions map[string]interface{} `json:"extensions"`
}
var params rawParams
if err := jsonDecode(r.Body, &params); err != nil {
w.WriteHeader(http.StatusBadRequest)
write.Errorf("json body could not be decoded: " + err.Error())
return nil, nil
return
}

reqParams := newRequestContext()
reqParams.RawQuery = params.Query
reqParams.OperationName = params.OperationName
reqParams.Variables = params.Variables
reqParams.Extensions = params.Extensions
rc := newRequestContext()
rc.RawQuery = params.Query
rc.OperationName = params.OperationName
rc.Variables = params.Variables
rc.Extensions = params.Extensions

return reqParams, write
ctx := graphql.WithRequestContext(r.Context(), rc)
handler(ctx, write)
}
8 changes: 8 additions & 0 deletions graphql/handler/transport/raw.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package transport

type rawParams struct {
Query string `json:"query"`
OperationName string `json:"operationName"`
Variables map[string]interface{} `json:"variables"`
Extensions map[string]interface{} `json:"extensions"`
}
Loading

0 comments on commit cb99b42

Please sign in to comment.