gateway

package module
v0.0.2 Latest Latest
Warning

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

Go to latest
Published: Jan 30, 2019 License: MIT Imports: 15 Imported by: 16

README

nautilus/gateway

Build Status Coverage Status Go Report Card

A standalone service designed to consolidate your graphql APIs into one endpoint.

For a more detailed description of this project's motivation read this post. For a guide to getting started read this post

Table of Contents

  1. Running the Executable
  2. Customizing the Gateway
    1. Integrating with an HTTP server
    2. Modifying Service Requests
    3. Authentication
  3. Examples
    1. Hello World
    2. Authentication and Authorization

Running the Executable

The simplest way to run a gateway is to download the executable from the latest release on GitHub and then run it directly on your machine:

$ ./gateway start --port 4000 --services http://localhost:3000,http://localhost:3001

This will start a server on port 4000 that wraps over the services running at http://localhost:3000 and http://localhost:3001. For more information on possible arguments to pass the executable, run ./gateway --help.

Customizing the Gateway

While the executable is good for getting started quickly, it isn't sufficient for most production usecases. Unfortunately, there is currently no story to plug in custom authentication/authorization logic or other extensions when running a gateway with the cli. If being able to run a custom gateway with the cli is something that interests you, please open in an issue! For now, these common situations require building your own executable.

The core object of the gateway is the Gateway struct exported by the module at github.com/nautilus/gateway. A Gateway is constructed by providing a list of graphql.RemoteSchemas for each service to gateway.New. The easiest way to get a graphql.RemoteSchema is to introspect the remote schema using a utility from github.com/nautilus/graphql:

package main

import (
	"github.com/nautilus/gateway"
	"github.com/nautilus/graphql"
)

func main() {
	// introspect the apis
	schemas, err := graphql.IntrospectRemoteSchemas(
		"http://localhost:3000",
		"http://localhost:3001",
    	)
	if err != nil {
		panic(err)
	}

	// create the gateway instance
	gw, err := gateway.New(schemas)
	if err != nil {
		panic(err)
	}
}
Integrating with an HTTP server

An instance of Gateway provides 2 different handlers which are both instances of http.HandlerFunc:

  • gw.GraphQLHandler responds to both GET and POST requests as described in the spec.
  • gw.PlaygroundHandler responds to GET requests with a web-based IDE for easy exploration and interprets POSTs as queries to process.
package main

import (
	"fmt"
	"net/http"

	// ... including the ones above
)

func main() {
	// ... including up above

	// add the playground endpoint to the router
	http.HandleFunc("/graphql", gw.PlaygroundHandler)

	// start the server
	fmt.Println("Starting server")
	err = http.ListenAndServe(":3001", nil)
	if err != nil {
		fmt.Println(err.Error())
	}
}
Modifying Service Requests

There are many situations where one might want to modify the network requests sent from the gateway to the other services. In order to do this, you can define a RequestMiddleware that will be called for every request sent. The context of this request is the same context of the incoming network request.

addHeader := gateway.RequestMiddleware(func(r *http.Request) error {
	r.Header.Set("AwesomeHeader", "MyValue")

	// return the modified request
	return nil
})

// ... somewhere else ...

gateway.New(..., gateway.withMiddleware(addHeader))

If you wanted to do something more complicated like pull something out of the incoming network request (its IP for example) and add it to the outbound requests, you would write it in 2 parts.

The first would grab the the value from the incoming request and set it in context

func withIP(handler http.HandlerFunc) http.HandlerFunc {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// invoke the wrapped handler with the new context
		handler.ServeHTTP(w, r.WithContext(
			context.WithValue(r.Context(), "source-ip", r.RemoteAddr),
		))
	})
}

Then, you would define a middleware similar to above that takes the value out of context and sets a header in the outbound requests:

addHeader := gateway.RequestMiddleware(func(r *http.Request) error {
	// i know i know ... context.Value is the worst. Feel free to put your favorite workaround here
	r.Header.Set("X-Forwarded-For", r.Context().Value("source-ip").(string))

	// no errors
	return nil
})

// ... somewhere else ...

gateway.New(..., gateway.withMiddleware(addHeader))
Authentication and Authorization

Currently the gateway has no opinion on a method for authentication and authorization. Descisions for wether a user can or cannot do something is pushed down to the services handling the queries. Authentication and Authorization should be modeled as a special case of above.

See the auth example for more information.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Configurator

type Configurator func(*Gateway)

Configurator is a function to be passed to New that configures the resulting schema

func WithExecutor

func WithExecutor(e Executor) Configurator

WithExecutor returns a Configurator that sets the executor of the gateway

func WithMerger

func WithMerger(m Merger) Configurator

WithMerger returns a Configurator that sets the merger of the gateway

func WithMiddleware

func WithMiddleware(middlewares ...Middleware) Configurator

WithMiddleware returns a Configurator that adds middlewares to the gateway

func WithPlanner

func WithPlanner(p QueryPlanner) Configurator

WithPlanner returns a Configurator that sets the planner of the gateway

type ErrExecutor

type ErrExecutor struct {
	Error error
}

ErrExecutor always returnes the internal error.

func (*ErrExecutor) Execute

func (e *ErrExecutor) Execute(ctx context.Context, plan *QueryPlan, variables map[string]interface{}) (map[string]interface{}, error)

Execute returns the internet error

type Executor

type Executor interface {
	Execute(ctx context.Context, plan *QueryPlan, variables map[string]interface{}) (map[string]interface{}, error)
}

Executor is responsible for executing a query plan against the remote schemas and returning the result

type ExecutorFunc

type ExecutorFunc func(ctx context.Context, plan *QueryPlan, variables map[string]interface{}) (map[string]interface{}, error)

ExecutorFunc wraps a function to be used as an executor.

func (ExecutorFunc) Execute

func (e ExecutorFunc) Execute(ctx context.Context, plan *QueryPlan, variables map[string]interface{}) (map[string]interface{}, error)

Execute invokes and returns the internal function

type FieldURLMap

type FieldURLMap map[string][]string

FieldURLMap holds the intformation for retrieving the valid locations one can find the value for the field

func (FieldURLMap) Concat

func (m FieldURLMap) Concat(other FieldURLMap) FieldURLMap

Concat returns a new field map url whose entries are the union of both maps

func (FieldURLMap) RegisterURL

func (m FieldURLMap) RegisterURL(parent string, field string, locations ...string)

RegisterURL adds a new location to the list of possible places to find the value for parent.field

func (FieldURLMap) URLFor

func (m FieldURLMap) URLFor(parent string, field string) ([]string, error)

URLFor returns the list of locations one can find parent.field.

type Gateway

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

Gateway is the top level entry for interacting with a gateway. It is responsible for merging a list of remote schemas into one, generating a query plan to execute based on an incoming request, and following that plan

func New

func New(sources []*graphql.RemoteSchema, configs ...Configurator) (*Gateway, error)

New instantiates a new schema with the required stuffs.

func (*Gateway) Execute

func (g *Gateway) Execute(ctx context.Context, query string, variables map[string]interface{}) (map[string]interface{}, error)

Execute takes a query string, executes it, and returns the response

func (*Gateway) GraphQLHandler

func (g *Gateway) GraphQLHandler(w http.ResponseWriter, r *http.Request)

GraphQLHandler returns a http.HandlerFunc that should be used as the primary endpoint for the gateway API. The endpoint will respond to queries on both GET and POST requests.

func (*Gateway) PlaygroundHandler

func (g *Gateway) PlaygroundHandler(w http.ResponseWriter, r *http.Request)

PlaygroundHandler returns a http.HandlerFunc which on GET requests shows the user an interface that they can use to interact with the API. On POSTs the endpoint executes the designated query

type Logger

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

Logger handles the logging in the gateway library

func (*Logger) Debug

func (l *Logger) Debug(args ...interface{})

Debug should be used for any logging that would be useful for debugging

func (*Logger) FormatSelectionSet

func (l *Logger) FormatSelectionSet(selection ast.SelectionSet) string

FormatSelectionSet returns a pretty printed version of a selection set

func (*Logger) Info

func (l *Logger) Info(args ...interface{})

Info should be used for any logging that doesn't necessarily need attention but is nice to see by default

func (*Logger) QueryPlanStep

func (l *Logger) QueryPlanStep(step *QueryPlanStep)

QueryPlanStep formats and logs a query plan step for human consumption

func (*Logger) Warn

func (l *Logger) Warn(args ...interface{})

Warn should be used for logging that needs attention

func (*Logger) WithFields

func (l *Logger) WithFields(fields LoggerFields) *Logger

WithFields adds the provided fields to the Log

type LoggerFields

type LoggerFields map[string]interface{}

LoggerFields is a wrapper over a map of key,value pairs to associate with the log

type Merger

type Merger interface {
	Merge([]*ast.Schema) (*ast.Schema, error)
}

Merger is an interface for structs that are capable of taking a list of schemas and returning something that resembles a "merge" of those schemas.

type MergerFunc

type MergerFunc func([]*ast.Schema) (*ast.Schema, error)

MergerFunc is a wrapper of a function of the same signature as Merger.Merge

func (MergerFunc) Merge

func (m MergerFunc) Merge(sources []*ast.Schema) (*ast.Schema, error)

Merge invokes and returns the wrapped function

type Middleware

type Middleware interface {
	Middleware()
}

Middleware are things that can modify a gateway normal execution

type MiddlewareList

type MiddlewareList []Middleware

MiddlewareList is a list of Middlewares

type MinQueriesPlanner

type MinQueriesPlanner struct {
	Planner
}

MinQueriesPlanner does the most basic level of query planning

func (*MinQueriesPlanner) Plan

func (p *MinQueriesPlanner) Plan(query string, schema *ast.Schema, locations FieldURLMap) ([]*QueryPlan, error)

Plan computes the nested selections that will need to be performed

type MockErrPlanner

type MockErrPlanner struct {
	Err error
}

MockErrPlanner always returns the provided error. Useful in testing.

func (*MockErrPlanner) Plan

type MockPlanner

type MockPlanner struct {
	Plans []*QueryPlan
}

MockPlanner always returns the provided list of plans. Useful in testing.

func (*MockPlanner) Plan

type ParallelExecutor

type ParallelExecutor struct{}

ParallelExecutor executes the given query plan by starting at the root of the plan and walking down the path stitching the results together

func (*ParallelExecutor) Execute

func (executor *ParallelExecutor) Execute(ctx context.Context, plan *QueryPlan, variables map[string]interface{}) (map[string]interface{}, error)

Execute returns the result of the query plan

type Planner

type Planner struct {
	QueryerFactory func(url string) graphql.Queryer
}

Planner is meant to be embedded in other QueryPlanners to share configuration

func (*Planner) GetQueryer

func (p *Planner) GetQueryer(url string, schema *ast.Schema) graphql.Queryer

GetQueryer returns the queryer that should be used to resolve the plan

type QueryPOSTBody

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

QueryPOSTBody is the incoming payload when sending POST requests to the gateway

type QueryPlan

type QueryPlan struct {
	Operation           *ast.OperationDefinition
	RootStep            *QueryPlanStep
	FragmentDefinitions ast.FragmentDefinitionList
}

QueryPlan is the full plan to resolve a particular query

type QueryPlanStep

type QueryPlanStep struct {
	Queryer             graphql.Queryer
	ParentType          string
	ParentID            string
	SelectionSet        ast.SelectionSet
	InsertionPoint      []string
	Then                []*QueryPlanStep
	QueryDocument       *ast.QueryDocument
	QueryString         string
	FragmentDefinitions ast.FragmentDefinitionList
	Variables           Set
}

QueryPlanStep represents a step in the plan required to fulfill a query.

type QueryPlanner

type QueryPlanner interface {
	Plan(string, *ast.Schema, FieldURLMap) ([]*QueryPlan, error)
}

QueryPlanner is responsible for taking a string with a graphql query and returns the steps to fulfill it

type RequestMiddleware

type RequestMiddleware graphql.NetworkMiddleware

RequestMiddleware is a middleware that can modify outbound requests to services

func (RequestMiddleware) Middleware

func (p RequestMiddleware) Middleware()

Middleware marks RequestMiddleware as a valid middleware

type SchemaQueryer

type SchemaQueryer struct {
	Schema *ast.Schema
}

SchemaQueryer is a queryer that knows how to resolve a query according to a particular schema

func (*SchemaQueryer) Query

func (q *SchemaQueryer) Query(ctx context.Context, input *graphql.QueryInput, receiver interface{}) error

Query takes a query definition and writes the result to the receiver

type Set

type Set map[string]bool

Set is a set

func (Set) Add

func (set Set) Add(k string)

Add adds the item to the set

func (Set) Has

func (set Set) Has(k string) bool

Has returns wether or not the string is in the set

func (Set) Remove

func (set Set) Remove(k string)

Remove removes the item from the set

Directories

Path Synopsis
cmd
gateway Module
examples module

Jump to

Keyboard shortcuts

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