graphqlws

package module
v0.0.0-...-210cb94 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 13, 2019 License: MIT Imports: 16 Imported by: 0

README

graphqlws

Implementation of the GraphQL over WebSocket protocol in Go. Brought to you by Functional Foundry.

API Documentation

Build Status Go Report

Getting started

  1. Install dependencies:
    go get github.com/sirupsen/logrus
    go get github.com/x-cray/logrus-prefixed-formatter
    go get github.com/google/uuid
    go get github.com/gorilla/websocket
    go get github.com/graphql-go/graphql
    
  2. Clone the repository:
    mkdir -p "$GOPATH/src/github.com/functionalfoundry"
    cd "$GOPATH/src/github.com/functionalfoundry"
    git clone https://github.com/functionalfoundry/graphqlws
    
  3. Run the tests:
    cd graphqlws
    go test
    
  4. Run the example server:
    go run graphqlws/examples/server
    

Usage

Setup
package main

import (
	"net/http"

	"github.com/functionalfoundry/graphqlws"
	"github.com/graphql-go/graphql"
)

func main() {
	// Create a GraphQL schema
	schema, err := graphql.NewSchema(...)

	// Create a subscription manager
	subscriptionManager := graphqlws.NewSubscriptionManager(&schema)

	// Create a WebSocket/HTTP handler
	graphqlwsHandler := graphqlws.NewHandler(graphqlws.HandlerConfig{
		// Wire up the GraphqL WebSocket handler with the subscription manager
		SubscriptionManager: subscriptionManager,

		// Optional: Add a hook to resolve auth tokens into users that are
		// then stored on the GraphQL WS connections
		Authenticate: func(authToken string) (interface{}, error) {
			// This is just a dumb example
			return "Joe", nil
		},
	})

	// The handler integrates seamlessly with existing HTTP servers
	http.Handle("/subscriptions", graphqlwsHandler)
	http.ListenAndServe(":8080", nil)
}
Working with subscriptions
// This assumes you have access to the above subscription manager
subscriptions := subscriptionManager.Subscriptions()

for conn, _ := range subscriptions {
	// Things you have access to here:
	conn.ID()   // The connection ID
	conn.User() // The user returned from the Authenticate function

	for _, subscription := range subscriptions[conn] {
		// Things you have access to here:
		subscription.ID            // The subscription ID (unique per conn)
		subscription.OperationName // The name of the operation
		subscription.Query         // The subscription query/queries string
		subscription.Variables     // The subscription variables
		subscription.Document      // The GraphQL AST for the subscription
		subscription.Fields        // The names of top-level queries
		subscription.Connection    // The GraphQL WS connection

		// Prepare an execution context for running the query
		ctx := context.Context()

		// Re-execute the subscription query
		params := graphql.Params{
			Schema:         schema, // The GraphQL schema
			RequestString:  subscription.Query,
			VariableValues: subscription.Variables,
			OperationName:  subscription.OperationName,
			Context:        ctx,
		}
		result := graphql.Do(params)

		// Send query results back to the subscriber at any point
		data := graphqlws.DataMessagePayload{
			// Data can be anything (interface{})
			Data:   result.Data,
			// Errors is optional ([]error)
			Errors: graphqlws.ErrorsFromGraphQLErrors(result.Errors),
		}
		subscription.SendData(&data)
	}
}
Logging

graphqlws uses logrus for logging. In the future we might remove those logs entirely to leave logging entirely to developers using graphqlws. Given the current solution, you can control the logging level of graphqlws by setting it through logrus:

import (
  log "github.com/sirupsen/logrus"
)

...

log.SetLevel(log.WarnLevel)

License

Copyright © 2017-2018 Functional Foundry, LLC.

Licensed under the MIT License.

Documentation

Index

Constants

View Source
const PUBLISHED_DATA = "PUBLISHED_DATA"

Variables

This section is empty.

Functions

func ErrorsFromGraphQLErrors

func ErrorsFromGraphQLErrors(errors []gqlerrors.FormattedError) []error

ErrorsFromGraphQLErrors convert from GraphQL errors to regular errors.

func NewHandler

func NewHandler(config HandlerConfig) http.Handler

NewHandler creates a WebSocket handler for GraphQL WebSocket connections. This handler takes a SubscriptionManager and adds/removes subscriptions as they are started/stopped by the client.

func NewLogger

func NewLogger(prefix string) *log.Entry

NewLogger returns a beautiful logger that logs messages with a given prefix (typically the name of a system component / subsystem).

func SubscriptionFieldNamesFromDocument

func SubscriptionFieldNamesFromDocument(doc *ast.Document) []string

func ValidateSubscription

func ValidateSubscription(s SubscriptionInterface) []error

Types

type AuthenticateFunc

type AuthenticateFunc func(token string) (interface{}, error)

AuthenticateFunc is a function that resolves an auth token into a user (or returns an error if that isn't possible).

type Connection

type Connection interface {
	// ID returns the unique ID of the connection.
	ID() string

	// Conn is the direct websocket connection with the client.
	Conn() *websocket.Conn

	// User returns the user associated with the connection (or nil).
	User() interface{}

	// SendData sends results of executing an operation (typically a
	// subscription) to the client.
	SendData(string, *DataMessagePayload)

	// SendError sends an error to the client.
	SendError(error)
}

Connection is an interface to represent GraphQL WebSocket connections. Each connection is associated with an ID that is unique to the server.

func NewConnection

func NewConnection(ws *websocket.Conn, config ConnectionConfig) Connection

NewConnection establishes a GraphQL WebSocket connection. It implements the GraphQL WebSocket protocol by managing its internal state and handling the client-server communication.

type ConnectionConfig

type ConnectionConfig struct {
	Authenticate           AuthenticateFunc
	EventHandlers          ConnectionEventHandlers
	ControlMessageHandlers ConnectionControlMessageHandlers
	ReadLimit              int64
}

ConnectionConfig defines the configuration parameters of a GraphQL WebSocket connection.

type ConnectionControlMessageHandlers

type ConnectionControlMessageHandlers struct {
	// CloseHandler is the handler for close messages received from the peer.
	// The code argument to h is the received close code or CloseNoStatusReceived
	// if the close message is empty. The default close handler sends a close message back to the peer.
	//
	// The handler function is called from the NextReader, ReadMessage and message
	// reader Read methods. The application must read the connection to process close
	// messages as described in the section on Control Messages above.
	//
	// The connection read methods return a CloseError when a close message is received.
	// Most applications should handle close messages as part of their normal error handling.
	// Applications should only set a close handler when the application must perform some
	// action before sending a close message back to the peer.
	CloseHandler func(Connection, int, string) error

	// PingHandler is the handler for ping messages received from the peer. The appData
	// argument to h is the PING message application data. The default ping handler sends
	// a pong to the peer.
	//
	// The handler function is called from the NextReader, ReadMessage and message reader
	// Read methods. The application must read the connection to process ping messages as
	// described in the section on Control Messages above.
	PingHandler func(Connection, string) error

	// PongHandler is the handler for pong messages received from the peer. The appData
	// argument to h is the PONG message application data. The default pong handler
	// does nothing.
	//
	// The handler function is called from the NextReader, ReadMessage and message reader
	// Read methods. The application must read the connection to process pong messages as
	// described in the section on Control Messages above.
	PongHandler func(Connection, string) error
}

ConnectionControlMessageHandlers define the control message handlers for a connection. The WebSocket protocol defines three types of control messages: close, ping and pong.

type ConnectionEventHandlers

type ConnectionEventHandlers struct {
	// Init is called whenever the connection is initilized.
	Init func(Connection)

	// Close is called whenever the connection is closed, regardless of
	// whether this happens because of an error or a deliberate termination
	// by the client.
	Close func(Connection)

	// StartOperation is called whenever the client demands that a GraphQL
	// operation be started (typically a subscription). Event handlers
	// are expected to take the necessary steps to register the operation
	// and send data back to the client with the results eventually.
	StartOperation func(Connection, string, *StartMessagePayload) []error

	// StopOperation is called whenever the client stops a previously
	// started GraphQL operation (typically a subscription). Event handlers
	// are expected to unregister the operation and stop sending result
	// data to the client.
	StopOperation func(Connection, string)
}

ConnectionEventHandlers define the event handlers for a connection. Event handlers allow other system components to react to events such as the connection closing or an operation being started or stopped.

type ConnectionFactory

type ConnectionFactory interface {
	Create(*websocket.Conn, ConnectionEventHandlers) Connection
}

ConnectionFactory is an interface that creates concrete connections. It is used by the handler and enables it to create different "instances" of connections, depending on the need of the developer.

An use case for this is to add new PubSub system (e.g. Redis PubSub, ...).

func NewConnectionFactory

func NewConnectionFactory(connConfig ConnectionConfig, sManager SubscriptionManager, logger log.Logger) ConnectionFactory

type ConnectionSubscriptions

type ConnectionSubscriptions map[string]SubscriptionInterface

ConnectionSubscriptions defines a map of all subscriptions of a connection by their IDs.

type DataMessagePayload

type DataMessagePayload struct {
	Data   interface{} `json:"data"`
	Errors []error     `json:"errors"`
}

DataMessagePayload defines the result data of an operation.

type HandlerConfig

type HandlerConfig struct {
	Schema              *graphql.Schema
	SubscriptionManager SubscriptionManager
	ConnectionFactory   ConnectionFactory
	Authenticate        AuthenticateFunc
	ConnectionEvents    ConnectionEventHandlers
}

HandlerConfig stores the configuration of a GraphQL WebSocket handler.

type InitMessagePayload

type InitMessagePayload struct {
	AuthToken string `json:"authToken"`
}

InitMessagePayload defines the parameters of a connection init message.

type MultipleTopic

type MultipleTopic []interface{}

func (MultipleTopic) ID

func (topic MultipleTopic) ID() interface{}

type OperationMessage

type OperationMessage struct {
	ID      string      `json:"id"`
	Type    string      `json:"type"`
	Payload interface{} `json:"payload"`
}

OperationMessage represents a GraphQL WebSocket message.

func (OperationMessage) String

func (msg OperationMessage) String() string

type StartMessagePayload

type StartMessagePayload struct {
	Query         string                 `json:"query"`
	Variables     map[string]interface{} `json:"variables"`
	OperationName string                 `json:"operationName"`
}

StartMessagePayload defines the parameters of an operation that a client requests to be started.

type StringTopic

type StringTopic string

StringTopic is a simple implementaiton of `Topic` for those PubSub systems that use simple strings as topics.

func (StringTopic) ID

func (topic StringTopic) ID() interface{}

type Subscriber

type Subscriber interface {
	// Subscription the subscription this subscriber is representing.
	Subscription() SubscriptionInterface

	// Topics returns the array of topics subscribed.
	// It is designed for accumulating subscriptions before applying it to a
	// connection.
	Topics() []Topic

	// Subscribe does a subcription, or accumulate it (depends on the
	// implementation).
	Subscribe(topic Topic) error
}

SubscriptionSubscriber does subscriptions in behalf a single Subscription

func NewInMemorySubscriber

func NewInMemorySubscriber(subscription SubscriptionInterface) Subscriber

type Subscription

type Subscription struct {
	ID            string
	Query         string
	Variables     map[string]interface{}
	OperationName string
	Document      *ast.Document
	Fields        []string
	Connection    Connection
	SendData      SubscriptionSendDataFunc
}

Subscription holds all information about a GraphQL subscription made by a client, including a function to send data back to the client when there are updates to the subscription query result.

func (*Subscription) GetConnection

func (s *Subscription) GetConnection() Connection

func (*Subscription) GetDocument

func (s *Subscription) GetDocument() *ast.Document

func (*Subscription) GetFields

func (s *Subscription) GetFields() []string

func (*Subscription) GetID

func (s *Subscription) GetID() string

func (*Subscription) GetOperationName

func (s *Subscription) GetOperationName() string

func (*Subscription) GetQuery

func (s *Subscription) GetQuery() string

func (*Subscription) GetSendData

func (s *Subscription) GetSendData() SubscriptionSendDataFunc

func (*Subscription) GetVariables

func (s *Subscription) GetVariables() map[string]interface{}

func (*Subscription) MatchesField

func (s *Subscription) MatchesField(field string) bool

MatchesField returns true if the subscription is for data that belongs to the given field.

func (*Subscription) SetDocument

func (s *Subscription) SetDocument(value *ast.Document)

func (*Subscription) SetFields

func (s *Subscription) SetFields(value []string)

type SubscriptionField

type SubscriptionField struct {
	graphql.Field
	Subscribe func(subscriber Subscriber) error
}

type SubscriptionInterface

type SubscriptionInterface interface {
	GetID() string
	GetQuery() string
	GetVariables() map[string]interface{}
	GetOperationName() string
	GetDocument() *ast.Document
	GetFields() []string
	GetConnection() Connection
	GetSendData() SubscriptionSendDataFunc
	SetFields(document []string)
	SetDocument(document *ast.Document)
}

type SubscriptionManager

type SubscriptionManager interface {
	// AddSubscription adds a new subscription to the manager.
	AddSubscription(Connection, SubscriptionInterface) []error

	// RemoveSubscription removes a subscription from the manager.
	RemoveSubscription(Connection, string)

	// RemoveSubscriptions removes all subscriptions of a client connection.
	RemoveSubscriptions(Connection)

	CreateSubscriptionSubscriber(subscription SubscriptionInterface) Subscriber

	Publish(topic Topic, data interface{}) error

	Subscribe(Subscriber) error
}

SubscriptionManager provides a high-level interface to managing and accessing the subscriptions made by GraphQL WS clients.

func NewInMemorySubscriptionManager

func NewInMemorySubscriptionManager(schema *graphql.Schema) SubscriptionManager

NewSubscriptionManager creates a new subscription manager.

func NewSubscriptionManagerWithLogger

func NewSubscriptionManagerWithLogger(schema *graphql.Schema, logger *log.Entry) SubscriptionManager

type SubscriptionSendDataFunc

type SubscriptionSendDataFunc func(*DataMessagePayload)

SubscriptionSendDataFunc is a function that sends updated data for a specific subscription to the corresponding subscriber.

type Subscriptions

type Subscriptions map[Connection]ConnectionSubscriptions

Subscriptions defines a map of connections to a map of subscription IDs to subscriptions.

type Topic

type Topic interface {
	// ID will return the structure ID of the topic on the technology used for
	// that purpose. For example, using a Redis PubSub system, this method would
	// return a string containing identifier of the channel.
	ID() interface{}
}

Topic represents a custom interface that represents a topic that will be used along with a PubSub system.

Directories

Path Synopsis
examples

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL