8000 GitHub - nautilus/gateway at v0.0.3
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

nautilus/gateway

Repository files navigation

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 
9AE8
!= 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.

0