Documentation ¶
Overview ¶
graphql over websocket transport using apollo websocket protocol
Usage ¶
When subscription operation is present in query, it is called repeatedly until it would return an error or cancel context associated with this operation.
Context provided to operation is also an instance of mutcontext.MutableContext, which supports setting additional values on same instance and holds cancel() function within it.
Implementors of subscribable operation are expected to cast provided context to mutcontext.MutableContext and on first invocation initialize data persisted across calls to it (like, external connection, database cursor or anything like that) as well as cleanup function using mutcontext.MutableContext.SetCleanup(func()), which would be called once operation is complete.
After initialization, subscription would be called repeatedly, expected to return next value at each invocation, until an error is encountered, interruption is requested, or data is normally exhausted, in which case mutcontext.MutableContext is supposed to be issued with .Complete()
After any of that, cleanup function (if any provided) would be called, ensuring operation code could release any resources allocated
Non-subscribable operations are straight-forward stateless invocations.
Subscriptions are also not required to be stateful, however that would mean they would return values as fast as they could produce them, without any timeouts and delays implemented, which would likely to require some state, e.g. time of last invocation.
By default, implementation allows calling any operation once via non-websocket plain http request.
Example ¶
value := 0 var changechan chan int query := graphql.NewObject(graphql.ObjectConfig{ Name: "TestQuery", Fields: graphql.Fields{ "get": &graphql.Field{ Type: graphql.NewNonNull(graphql.Int), Resolve: func(p graphql.ResolveParams) (i interface{}, e error) { return value, nil }, }, }, }) mutation := graphql.NewObject(graphql.ObjectConfig{ Name: "TestMutation", Fields: graphql.Fields{ "set": &graphql.Field{ Type: graphql.Int, Args: graphql.FieldConfigArgument{ "value": &graphql.ArgumentConfig{ Type: graphql.NewNonNull(graphql.Int), }, }, Resolve: func(p graphql.ResolveParams) (i interface{}, e error) { value = p.Args["value"].(int) if changechan != nil { changechan <- value } return nil, nil }, }, "stop": &graphql.Field{ Type: graphql.Int, Resolve: func(p graphql.ResolveParams) (i interface{}, e error) { if changechan != nil { close(changechan) changechan = nil } return nil, nil }, }, }, }) subscription := graphql.NewObject(graphql.ObjectConfig{ Name: "TestSubscription", Fields: graphql.Fields{ "watch": &graphql.Field{ Type: graphql.NewNonNull(graphql.Int), Resolve: func(p graphql.ResolveParams) (i interface{}, e error) { ctx := p.Context.(mutcontext.MutableContext) c := ctx.Value("ch") if c == nil { newc := make(chan int) ctx.Set("ch", newc) ctx.SetCleanup(func() { c := ctx.Value("ch") if c != nil { close(c.(chan int)) } }) c = newc changechan = newc } ch := c.(chan int) v, ok := <-ch if !ok { ctx.Set("ch", nil) _ = ctx.Cancel() } return v, nil }, }, "countdown": &graphql.Field{ Type: graphql.Int, Args: graphql.FieldConfigArgument{ "value": &graphql.ArgumentConfig{ Type: graphql.Int, DefaultValue: 0, }, }, Resolve: func(p graphql.ResolveParams) (interface{}, error) { ctx := p.Context.(mutcontext.MutableContext) iter := ctx.Value("iter") if iter == nil { v, ok := p.Args["value"].(int) if !ok { return nil, nil } ctx.Set("iter", v) iter = v } i := iter.(int) if i <= 0 { ctx.Complete() return 0, nil } ctx.Set("iter", i-1) return i, nil }, }, }, }) schema, err := graphql.NewSchema(graphql.SchemaConfig{ Query: graphql.NewObject(graphql.ObjectConfig{ Name: "RootQuery", Fields: graphql.Fields{ "test": &graphql.Field{ Type: query, Resolve: func(p graphql.ResolveParams) (i interface{}, e error) { return query, nil }, }, }, }), Mutation: graphql.NewObject(graphql.ObjectConfig{ Name: "RootMutation", Fields: graphql.Fields{ "test": &graphql.Field{ Type: mutation, Resolve: func(p graphql.ResolveParams) (i interface{}, e error) { return mutation, nil }, }, }, }), Subscription: graphql.NewObject(graphql.ObjectConfig{ Name: "RootSubscription", Fields: graphql.Fields{ "test": &graphql.Field{ Type: subscription, Resolve: func(p graphql.ResolveParams) (i interface{}, e error) { return subscription, nil }, }, }, }), }) server, err := NewServer(Config{ Schema: &schema, }) if err != nil { panic(err) } http.Handle("/graphql", server) err = http.ListenAndServe(":8080", nil) if err != nil { fmt.Println(err) }
Output:
Example (Authentication) ¶
var schema graphql.Schema server, err := NewServer(Config{ Schema: &schema, OnPlainInit: func(globalctx mutcontext.MutableContext, r *http.Request, w http.ResponseWriter) { remote := r.RemoteAddr idx := strings.LastIndex(remote, ":") globalctx.Set("remote", remote[:idx]) }, OnConnect: func(globalctx mutcontext.MutableContext, parameters interface{}) error { mapparams := parameters.(map[string]interface{}) v, ok := mapparams["token"].(string) if !ok { return errors.New("invalid token type") } remoteip := globalctx.Value("remote").(string) type User struct { Name string } authenticateUser := func(token, remote string) *User { if token == "123" { return &User{Name: "admin"} } return nil } user := authenticateUser(v, remoteip) globalctx.Set("user", user) return nil }, }) if err != nil { panic(err) } http.Handle("/graphql", server) err = http.ListenAndServe(":8080", nil) if err != nil { fmt.Println(err) }
Output:
Index ¶
Examples ¶
Constants ¶
const (
KeyHttpRequest = common.KeyHttpRequest
)
Variables ¶
var ( ErrSchemaRequired = common.ErrSchemaRequired ErrPlainHttpIgnored = common.ErrPlainHttpIgnored )
Functions ¶
Types ¶
type Config ¶
type Config struct { // websocket upgrader // default: one that simply negotiates 'graphql-ws' protocol Upgrader *websocket.Upgrader // graphql schema, required // default: nil Schema *graphql.Schema // called when new client is connecting with new parameters or new plain request started // default: nothing OnConnect FuncConnectCallback // called when new operation started // default: nothing OnOperation FuncOperationCallback // called when operation is complete // default: nothing OnOperationDone FuncOperationDoneCallback // called when websocket connection is closed or plain request is served // default: nothing OnDisconnect FuncDisconnectCallback // called when new http connection is established // default: nothing OnPlainInit FuncPlainInit // called when failure occured at plain http stages // default: writes back error text OnPlainFail FuncPlainFail // if true, plain http connections that can't be upgraded would be ignored and not served as one-off requests // default: false IgnorePlainHttp bool // keep alive period, at which server would send keep-alive messages // default: 20 seconds KeepAlive time.Duration }
Config for websocket graphql server
type FuncConnectCallback ¶
type FuncConnectCallback common.FuncConnectCallback
type FuncDisconnectCallback ¶
type FuncDisconnectCallback common.FuncDisconnectCallback
type FuncOperationCallback ¶
type FuncOperationCallback common.FuncOperationCallback
type FuncOperationDoneCallback ¶
type FuncOperationDoneCallback common.FuncOperationDoneCallback
type FuncPlainFail ¶
type FuncPlainFail common.FuncPlainFail
type FuncPlainInit ¶
type FuncPlainInit common.FuncPlainInit
Directories ¶
Path | Synopsis |
---|---|
Mutable context, allows for easy setting additional values shared across all context and it's children
|
Mutable context, allows for easy setting additional values shared across all context and it's children |
Implemenentation of GraphQL over WebSocket Protocol by apollographql https://github.com/apollographql/subscriptions-transport-ws/blob/master/PROTOCOL.md
|
Implemenentation of GraphQL over WebSocket Protocol by apollographql https://github.com/apollographql/subscriptions-transport-ws/blob/master/PROTOCOL.md |
graphql websocket http handler implementation
|
graphql websocket http handler implementation |