gws

package module
v0.5.0 Latest Latest
Warning

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

Go to latest
Published: Sep 4, 2020 License: MIT Imports: 14 Imported by: 0

README

Go Report Card GoDoc build codecov Mentioned in Awesome Go

gws

A WebSocket client and server for GraphQL.

gws implements Apollos' "GraphQL over WebSocket" protocol, which can be found in PROTOCOL.md. It provides an RPC-like API for both client and server usage.

Getting Started

gws provides both a client and server implementation for the "GraphQL over Websocket" protocol.

Client

To create a client, you must first dial the server like:

conn, err := gws.Dial(context.TODO(), "ws://example.com")
// Don't forget to handle the error

gws.Dial will return high-level abstraction around the underlying connection. This is very similar to gRPCs' behaviour. After dialing the server, now simply wrap the return conn and begin performing queries, like:

client := gws.NewClient(conn)

resp, err := client.Query(context.TODO(), &gws.Request{Query: "{ hello { world } }"})
// Don't forget to handle the error

var data struct{
  Hello struct{
    World string
  }
}
err = json.Unmarshal(resp.Data, &data)
Server

The server implementation provided isn't actually a standalone server but instead just a specially configured http.Handler. This is because the underlying protocol is simply WebSocket, which runs on top of HTTP. Creating a bare bones handler is as simple as the following:

msgHandler := func(s *Stream, req *Request) error {
  // Handle request
  return nil
}

http.ListenAndServe(":8080", gws.NewHandler(gws.HandlerFunc(msgHandler)))

Documentation

Overview

Package gws implements a client and server for the GraphQL over Websocket protocol.

Index

Examples

Constants

View Source
const (
	MessageText   = MessageType(websocket.MessageText)
	MessageBinary = MessageType(websocket.MessageBinary)
)

Re-export message type provided by the underlying Websocket package.

Variables

View Source
var DefaultConnectParams = ConnectParams{
	Backoff:           backoff.DefaultConfig,
	MinConnectTimeout: 20 * time.Second,
}

DefaultConnectParams is a default configuration for retrying with a backoff.

View Source
var ErrStreamClosed = errors.New("gws: stream is closed")

ErrStreamClosed is returned by Send if the stream is closed before Send is called.

View Source
var ErrUnsubscribed = errors.New("gws: received cancelled due to unsubscribe")

ErrUnsubscribed is returned by a subscription receive when the subscription is unsubscribed to or completed before the next response is received.

Functions

func NewHandler

func NewHandler(h Handler, opts ...ServerOption) http.Handler

NewHandler configures an http.Handler, which will upgrade incoming connections to WebSocket and serve the "graphql-ws" subprotocol.

Example
h := func(s *Stream, req *Request) error {
	// Remember to always close the stream when done sending.
	defer s.Close()

	// Use your choice of a GraphQL runtime to execute the query
	// Then, return the results JSON encoded with a *Response.
	r := &Response{
		Data: []byte(`{"hello":{"world":"this is example data"}}`),
	}
	return s.Send(context.TODO(), r)
}

// Simply register the handler with a http mux of your choice
// and it will handle the rest.
//
http.Handle("graphql", NewHandler(HandlerFunc(h)))

err := http.ListenAndServe(":80", nil)
if err != nil {
	// Always handle your errors
	return
}
Output:

Types

type Client

type Client interface {
	// Query provides an RPC like API for performing GraphQL queries.
	Query(context.Context, *Request) (*Response, error)

	// Subscribe provides an RPC like API for performing GraphQL subscription queries.
	Subscribe(context.Context, *Request) (*Subscription, error)
}

Client provides high-level API for making GraphQL requests over WebSocket.

func NewClient

func NewClient(conn *Conn) Client

NewClient takes a connection and initializes a client over it.

Example (Concurrent)
conn, err := Dial(context.TODO(), "ws://example.com")
if err != nil {
	// Make sure to handle the error
	return
}
defer conn.Close()

// Performing queries is completely concurrent safe.
client := NewClient(conn)

respCh := make(chan *Response)
go func() {
	resp, err := client.Query(context.TODO(), &Request{Query: "{ hello { world } }"})
	if err != nil {
		// Remember, always handle errors
		return
	}
	respCh <- resp
}()

go func() {
	resp, err := client.Query(context.TODO(), &Request{Query: "{ hello { world } }"})
	if err != nil {
		// Remember, always handle errors
		return
	}
	respCh <- resp
}()

for resp := range respCh {
	// Always check resp.Errors
	fmt.Println(resp)
}
Output:

Example (Query)
conn, err := Dial(context.TODO(), "ws://example.com")
if err != nil {
	// Make sure to handle the error
	return
}
defer conn.Close()

client := NewClient(conn)

resp, err := client.Query(context.TODO(), &Request{Query: "{ hello { world } }"})
if err != nil {
	// Remember, always handle errors
	return
}
// Always check resp.Errors

var exampleResp struct {
	Hello struct {
		World string `json:"world"`
	} `json:"hello"`
}

err = json.Unmarshal(resp.Data, &exampleResp)
if err != nil {
	return
}

// Now, exampleResp.Hello.World would be your query result.
Output:

type CompressionMode

type CompressionMode websocket.CompressionMode

CompressionMode represents the modes available to the deflate extension. See https://tools.ietf.org/html/rfc7692

A compatibility layer is implemented for the older deflate-frame extension used by safari. See https://tools.ietf.org/html/draft-tyoshino-hybi-websocket-perframe-deflate-06 It will work the same in every way except that we cannot signal to the peer we want to use no context takeover on our side, we can only signal that they should. It is however currently disabled due to Safari bugs. See https://github.com/nhooyr/websocket/issues/218

const (
	// CompressionNoContextTakeover grabs a new flate.Reader and flate.Writer as needed
	// for every message. This applies to both server and client side.
	//
	// This means less efficient compression as the sliding window from previous messages
	// will not be used but the memory overhead will be lower if the connections
	// are long lived and seldom used.
	//
	// The message will only be compressed if greater than 512 bytes.
	//
	CompressionNoContextTakeover CompressionMode = iota

	// CompressionContextTakeover uses a flate.Reader and flate.Writer per connection.
	// This enables reusing the sliding window from previous messages.
	// As most WebSocket protocols are repetitive, this can be very efficient.
	// It carries an overhead of 8 kB for every connection compared to CompressionNoContextTakeover.
	//
	// If the peer negotiates NoContextTakeover on the client or server side, it will be
	// used instead as this is required by the RFC.
	//
	CompressionContextTakeover

	// CompressionDisabled disables the deflate extension.
	//
	// Use this if you are using a predominantly binary protocol with very
	// little duplication in between messages or CPU and memory are more
	// important than bandwidth.
	//
	CompressionDisabled
)

type Conn

type Conn struct {
	// contains filtered or unexported fields
}

Conn is a client connection that should be closed by the client.

func Dial

func Dial(ctx context.Context, endpoint string, opts ...DialOption) (*Conn, error)

Dial creates a connection to the given endpoint. By default, it's a non-blocking dial (the function won't wait for connections to be established, and connecting happens in the background).

Example
conn, err := Dial(context.TODO(), "ws://example.com")
if err != nil {
	// Make sure to handle the error
	return
}
defer conn.Close()

// Create a single client with the connection.
// There is no need to create multiple connections or clients
// because it will all be managed for you.
Output:

func (*Conn) Close

func (c *Conn) Close() error

Close closes the underlying WebSocket connection.

type ConnOption

type ConnOption interface {
	DialOption
	ServerOption
}

ConnOption represents a configuration that applies symmetrically on both sides, client and server.

func WithCompression

func WithCompression(mode CompressionMode, threshold int) ConnOption

WithCompression configures compression over the WebSocket. By default, compression is disabled and for now is considered an experimental feature.

func WithMessageType added in v0.4.0

func WithMessageType(typ MessageType) ConnOption

WithMessageType allows users to set the underlying WebSocket message encoding. Default is MessageBinary.

Note: for browser clients like Apollo GraphQL the MessageText encoding should be used.

type ConnectParams added in v0.5.0

type ConnectParams struct {
	// Backoff specifies the configuration options for connection backoff.
	Backoff backoff.Config
	// MinConnectTimeout is the minimum amount of time we are willing to give a
	// connection to complete.
	MinConnectTimeout time.Duration
}

ConnectParams defines the parameters for connecting and retrying. Users are encouraged to use this instead of the BackoffConfig type defined above. See here for more details: https://github.com/grpc/grpc/blob/master/doc/connection-backoff.md.

This API is EXPERIMENTAL.

type DialOption

type DialOption interface {
	SetDial(*dialOpts)
}

DialOption configures how we set up the connection.

func WithConnectParams added in v0.5.0

func WithConnectParams(p ConnectParams) DialOption

WithConnectParams configures the client to use the provided ConnectParams.

func WithHTTPClient

func WithHTTPClient(client *http.Client) DialOption

WithHTTPClient provides an http.Client to override the default one used.

func WithHeaders

func WithHeaders(headers http.Header) DialOption

WithHeaders adds custom headers to every dial HTTP request.

type ErrIO

type ErrIO struct {
	// Msg
	Msg string

	// Err
	Err error
}

ErrIO represents a wrapped I/O error.

func (ErrIO) Error

func (e ErrIO) Error() string

Error implements the error interface.

func (ErrIO) Unwrap

func (e ErrIO) Unwrap() error

Unwrap is for the errors package to use within its As, Is, and Unwrap functions.

type ErrUnexpectedMessage

type ErrUnexpectedMessage struct {
	// Expected was the expected message type.
	Expected string

	// Received was the received message type.
	Received string
}

ErrUnexpectedMessage represents a unexpected message type.

func (ErrUnexpectedMessage) Error

func (e ErrUnexpectedMessage) Error() string

Error implements the error interface.

type ErrUnsupportedMsgType added in v0.5.0

type ErrUnsupportedMsgType string

ErrUnsupportedMsgType represents an unsupported message type, per the GraphQL over Websocket protocol.

func (ErrUnsupportedMsgType) Error added in v0.5.0

func (e ErrUnsupportedMsgType) Error() string

Error implements the error interface.

type Handler

type Handler interface {
	ServeGraphQL(*Stream, *Request) error
}

Handler is for handling incoming GraphQL queries. All other "GraphQL over Websocket" protocol messages are automatically handled internally.

All resolvers errors should be included in *Response and any validation error should be returned as error.

type HandlerFunc

type HandlerFunc func(*Stream, *Request) error

HandlerFunc is an adapter to allow the use of ordinary functions as Request handlers.

func (HandlerFunc) ServeGraphQL

func (f HandlerFunc) ServeGraphQL(s *Stream, req *Request) error

ServeGraphQL implements the Handler interface.

type MessageType added in v0.4.0

type MessageType websocket.MessageType

MessageType represents the type of a Websocket message.

type Request

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

Request represents a payload sent from the client.

type Response

type Response struct {
	Data   json.RawMessage   `json:"data"`
	Errors []json.RawMessage `json:"errors"`
}

Response represents a payload returned from the server. It supports lazy decoding by leaving the inner data for the user to decode.

type ServerError

type ServerError struct {
	Msg string `json:"msg"`
}

ServerError represents a payload which is sent by the server if it encounters a non-GraphQL resolver error.

func (*ServerError) Error

func (e *ServerError) Error() string

Error implements the error interface.

type ServerOption

type ServerOption interface {
	SetServer(*options)
}

ServerOption allows the user to configure the handler.

func WithKeepAlive added in v0.5.0

func WithKeepAlive(period time.Duration) ServerOption

WithKeepAlive configures the server to send a GQL_CONNECTION_ACK message periodically to keep the client connection alive.

func WithOrigins

func WithOrigins(origins ...string) ServerOption

WithOrigins lists the host patterns for authorized origins. The request host is always authorized. Use this to allow cross origin WebSockets.

type Stream

type Stream struct {
	// contains filtered or unexported fields
}

Stream is used for streaming responses back to the client.

func (*Stream) Close

func (s *Stream) Close() error

Close notifies the client that no more results will be sent and closes the stream. It also frees any resources associated with the stream, thus meaning Close should always be called to prevent any leaks.

func (*Stream) Send

func (s *Stream) Send(ctx context.Context, resp *Response) error

Send sends a response to the client. It is safe for concurrent use.

type Subscription

type Subscription struct {
	// contains filtered or unexported fields
}

Subscription represents a stream of results corresponding to a GraphQL subscription query.

func (*Subscription) Recv

func (s *Subscription) Recv(ctx context.Context) (*Response, error)

Recv is a blocking call which waits for either a response from the server or the context to be cancelled. Context cancellation does not cancel the subscription as a whole just the current recv call.

Recv is safe for concurrent use but it is a first come first serve basis. In other words, the response is not duplicated across all receivers.

func (*Subscription) Unsubscribe

func (s *Subscription) Unsubscribe() error

Unsubscribe tells the server to stop sending anymore results and cleans up any resources associated with the subscription.

Directories

Path Synopsis
Package backoff provides configuration options for backoff.
Package backoff provides configuration options for backoff.
internal
backoff
Package backoff implement the backoff strategy for gws.
Package backoff implement the backoff strategy for gws.
rand
Package rand implements math/rand functions in a concurrent-safe way with a global random source, independent of math/rand's global source.
Package rand implements math/rand functions in a concurrent-safe way with a global random source, independent of math/rand's global source.

Jump to

Keyboard shortcuts

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