rye: github.com/InVisionApp/rye Index | Examples | Files | Directories

package rye

import "github.com/InVisionApp/rye"

Package Rye is a simple library to support http services. Rye provides a middleware handler which can be used to chain http handlers together while providing simple statsd metrics for use with a monitoring solution such as DataDog or other logging aggregators. Rye also provides some additional middleware handlers that are entirely optional but easily consumed using Rye.

Setup

In order to use rye, you should vendor it and the statsd client within your project.

govendor fetch github.com/cactus/go-statsd-client/statsd

# Rye is a private repo, so we should clone it first
mkdir -p $GOPATH/github.com/InVisionApp
cd $GOPATH/github.com/InVisionApp
git clone git@github.com:InVisionApp/rye.git

govendor add github.com/InVisionApp/rye

Writing custom middleware handlers

Begin by importing the required libraries:

import (
	"github.com/cactus/go-statsd-client/statsd"
	"github.com/InVisionApp/rye"
)

Create a statsd client (if desired) and create a rye Config in order to pass in optional dependencies:

config := &rye.Config{
	Statter:	statsdClient,
	StatRate:	DEFAULT_STATSD_RATE,
}

Create a middleware handler. The purpose of the Handler is to keep Config and to provide an interface for chaining http handlers.

middlewareHandler := rye.NewMWHandler(config)

Build your http handlers using the Handler type from the **rye** package.

type Handler func(w http.ResponseWriter, r *http.Request) *rye.Response

Here are some example (custom) handlers:

func homeHandler(rw http.ResponseWriter, r *http.Request) *rye.Response {
	fmt.Fprint(rw, "Refer to README.md for auth-api API usage")
	return nil
}

func middlewareFirstHandler(rw http.ResponseWriter, r *http.Request) *rye.Response {
	fmt.Fprint(rw, "This handler fires first.")
	return nil
}

func errorHandler(rw http.ResponseWriter, r *http.Request) *rye.Response {
	return &rye.Response {
		StatusCode: http.StatusInternalServerError,
		Err:        errors.New(message),
	}
}

Finally, to setup your handlers in your API

routes := mux.NewRouter().StrictSlash(true)

routes.Handle("/", middlewareHandler.Handle(
	[]rye.Handler{
		a.middlewareFirstHandler,
		a.homeHandler,
	})).Methods("GET")

log.Infof("API server listening on %v", ListenAddress)

srv := &http.Server{
	Addr:		ListenAddress,
	Handler:	routes,
}

srv.ListenAndServe()

Statsd Generated by Rye

Rye comes with built-in configurable `statsd` statistics that you could record to your favorite monitoring system. To configure that, you'll need to set up a `Statter` based on the `github.com/cactus/go-statsd-client` and set it in your instantiation of `MWHandler` through the `rye.Config`.

When a middleware is called, it's timing is recorded and a counter is recorded associated directly with the http status code returned during the call. Additionally, an `errors` counter is also sent to the statter which allows you to count any errors that occur with a code equaling or above 500.

Example: If you have a middleware handler you've created with a method named `loginHandler`, successful calls to that will be recorded to `handlers.loginHandler.2xx`. Additionally you'll receive stats such as `handlers.loginHandler.400` or `handlers.loginHandler.500`. You also will receive an increase in the `errors` count.

If you're sending your logs into a system such as DataDog, be aware that your stats from Rye can have prefixes such as `statsd.my-service.my-k8s-cluster.handlers.loginHandler.2xx` or even `statsd.my-service.my-k8s-cluster.errors`. Just keep in mind your stats could end up in the destination sink system with prefixes.

Using With Golang Context

With Golang 1.7, a new feature has been added that supports a request specific context. This is a great feature that Rye supports out-of-the-box. The tricky part of this is how the context is modified on the request. In Golang, the Context is always available on a Request through `http.Request.Context()`. Great! However, if you want to add key/value pairs to the context, you will have to add the context to the request before it gets passed to the next Middleware. To support this, the `rye.Response` has a property called `Context`. This property takes a properly created context (pulled from the `request.Context()` function. When you return a `rye.Response` which has `Context`, the **rye** library will craft a new Request and make sure that the next middleware receives that request.

Here's the details of creating a middleware with a proper `Context`. You must first pull from the current request `Context`. In the example below, you see `ctx := r.Context()`. That pulls the current context. Then, you create a NEW context with your additional context key/value. Finally, you return `&rye.Response{Context:ctx}`

func addContextVar(rw http.ResponseWriter, r *http.Request) *rye.Response {
	// Retrieve the request's context
	ctx := r.Context()

	// Create a NEW context
	ctx = context.WithValue(ctx,"CONTEXT_KEY","my context value")

	// Return that in the Rye response
	// Rye will add it to the Request to
	// pass to the next middleware
	return &rye.Response{Context:ctx}
}

Now in a later middleware, you can easily retrieve the value you set!

func getContextVar(rw http.ResponseWriter, r *http.Request) *rye.Response {
	// Retrieving the value is easy!
	myVal := r.Context().Value("CONTEXT_KEY")

	// Log it to the server log?
	log.Infof("Context Value: %v", myVal)

	return nil
}

For another simple example, look in the JWT middleware - it adds the JWT into the context for use by other middlewares. It uses the `CONTEXT_JWT` key to push the JWT token into the `Context`.

Using built-in middleware handlers

Rye comes with various pre-built middleware handlers. Pre-built middlewares source (and docs) can be found in the package dir following the pattern `middleware_*.go`.

To use them, specify the constructor of the middleware as one of the middleware handlers when you define your routes:

// example
routes.Handle("/", middlewareHandler.Handle(
	[]rye.Handler{
		rye.MiddlewareCORS(), // to use the CORS middleware (with defaults)
		a.homeHandler,
	})).Methods("GET")

OR

routes.Handle("/", middlewareHandler.Handle(
	[]rye.Handler{
		rye.NewMiddlewareCORS("*", "GET, POST", "X-Access-Token"), // to use specific config when instantiating the middleware handler
		a.homeHandler,
	})).Methods("GET")

A Note on the JWT Middleware

The JWT Middleware pushes the JWT token onto the Context for use by other middlewares in the chain. This is a convenience that allows any part of your middleware chain quick access to the JWT. Example usage might include a middleware that needs access to your user id or email address stored in the JWT. To access this `Context` variable, the code is very simple:

func getJWTfromContext(rw http.ResponseWriter, r *http.Request) *rye.Response {
	// Retrieving the value is easy!
	// Just reference the rye.CONTEXT_JWT const as a key
	myVal := r.Context().Value(rye.CONTEXT_JWT)

	// Log it to the server log?
	log.Infof("Context Value: %v", myVal)

	return nil
}

Code:

package main

import (
    "context"
    "errors"
    "fmt"
    "net/http"

    "github.com/InVisionApp/rye"
    log "github.com/sirupsen/logrus"
    "github.com/cactus/go-statsd-client/statsd"
    "github.com/gorilla/mux"
)

func main() {
    statsdClient, err := statsd.NewBufferedClient("localhost:12345", "my_service", 1.0, 0)
    if err != nil {
        log.Fatalf("Unable to instantiate statsd client: %v", err.Error())
    }

    config := rye.Config{
        Statter:  statsdClient,
        StatRate: 1.0,
    }

    middlewareHandler := rye.NewMWHandler(config)

    middlewareHandler.Use(beforeAllHandler)

    routes := mux.NewRouter().StrictSlash(true)

    routes.Handle("/", middlewareHandler.Handle([]rye.Handler{
        middlewareFirstHandler,
        homeHandler,
    })).Methods("GET")

    // If you perform a `curl -i http://localhost:8181/cors -H "Origin: *.foo.com"`
    // you will see that the CORS middleware is adding required headers
    routes.Handle("/cors", middlewareHandler.Handle([]rye.Handler{
        rye.MiddlewareCORS(),
        homeHandler,
    })).Methods("GET", "OPTIONS")

    // If you perform an `curl -i http://localhost:8181/jwt \
    // -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ"
    // you will see that we are allowed through to the handler, if the sample token is changed, we will get a 401
    routes.Handle("/jwt", middlewareHandler.Handle([]rye.Handler{
        rye.NewMiddlewareJWT("secret"),
        getJwtFromContextHandler,
    })).Methods("GET")

    routes.Handle("/error", middlewareHandler.Handle([]rye.Handler{
        middlewareFirstHandler,
        errorHandler,
        homeHandler,
    })).Methods("GET")

    // In order to pass in a context variable, this set of
    // handlers works with "ctx" on the query string
    routes.Handle("/context", middlewareHandler.Handle(
        []rye.Handler{
            stashContextHandler,
            logContextHandler,
        })).Methods("GET")

    log.Infof("API server listening on %v", "localhost:8181")

    srv := &http.Server{
        Addr:    "localhost:8181",
        Handler: routes,
    }

    srv.ListenAndServe()
}

func beforeAllHandler(rw http.ResponseWriter, r *http.Request) *rye.Response {
    log.Infof("This handler is called before every endpoint: %+v", r)
    return nil
}

func homeHandler(rw http.ResponseWriter, r *http.Request) *rye.Response {
    log.Infof("Home handler has fired!")

    fmt.Fprint(rw, "This is the home handler")
    return nil
}

func middlewareFirstHandler(rw http.ResponseWriter, r *http.Request) *rye.Response {
    log.Infof("Middleware handler has fired!")
    return nil
}

func errorHandler(rw http.ResponseWriter, r *http.Request) *rye.Response {
    log.Infof("Error handler has fired!")

    message := "This is the error handler"

    return &rye.Response{
        StatusCode: http.StatusInternalServerError,
        Err:        errors.New(message),
    }
}

func stashContextHandler(rw http.ResponseWriter, r *http.Request) *rye.Response {
    log.Infof("Stash Context handler has fired!")

    // Retrieve the request's context
    ctx := r.Context()

    // A query string value to add to the context
    toContext := r.URL.Query().Get("ctx")

    if toContext != "" {
        log.Infof("Adding `query-string-ctx` to request.Context(). Val: %v", toContext)
    } else {
        log.Infof("Adding default `query-string-ctx` value to context")
        toContext = "No value added. Add querystring param `ctx` with a value to get it mirrored through context."
    }

    // Create a NEW context
    ctx = context.WithValue(ctx, "query-string-ctx", toContext)

    // Return that in the Rye response
    // Rye will add it to the Request to
    // pass to the next middleware
    return &rye.Response{Context: ctx}
}

func logContextHandler(rw http.ResponseWriter, r *http.Request) *rye.Response {
    log.Infof("Log Context handler has fired!")

    // Retrieving a context value is EASY in subsequent middlewares
    fromContext := r.Context().Value("query-string-ctx")

    // Reflect that on the http response
    fmt.Fprintf(rw, "Here's the `ctx` query string value you passed. Pulled from context: %v", fromContext)
    return nil
}

// This handler pulls the JWT from the Context and echoes it through the request
func getJwtFromContextHandler(rw http.ResponseWriter, r *http.Request) *rye.Response {
    log.Infof("Log Context handler has fired!")

    jwt := r.Context().Value(rye.CONTEXT_JWT)
    if jwt != nil {
        fmt.Fprintf(rw, "JWT found in Context: %v", jwt)
    }
    return nil
}

Index

Examples

Package Files

doc.go middleware_accesstoken.go middleware_auth.go middleware_cidr.go middleware_cors.go middleware_getheader.go middleware_jwt.go middleware_routelogger.go middleware_static_file.go middleware_static_filesystem.go rye.go

Constants

const (
    // CORS Specific constants
    DEFAULT_CORS_ALLOW_ORIGIN  = "*"
    DEFAULT_CORS_ALLOW_METHODS = "POST, GET, OPTIONS, PUT, DELETE"
    DEFAULT_CORS_ALLOW_HEADERS = "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, X-Access-Token"
)
const AUTH_USERNAME_KEY = "request-username"
const (
    CONTEXT_JWT = "rye-middlewarejwt-jwt"
)

func MiddlewareCORS Uses

func MiddlewareCORS() func(rw http.ResponseWriter, req *http.Request) *Response

MiddlewareCORS is the struct to represent configuration of the CORS handler.

func MiddlewareRouteLogger Uses

func MiddlewareRouteLogger() func(rw http.ResponseWriter, req *http.Request) *Response

MiddlewareRouteLogger creates a new handler to provide simple logging output for the specific route. You can use this middleware by specifying `rye.MiddlewareRouteLogger` when defining your routes.

Example use case:

routes.Handle("/some/route", a.Dependencies.MWHandler.Handle(
	[]rye.Handler{
		rye.MiddlewareRouteLogger(),
		yourHandler,
	})).Methods("PUT", "OPTIONS")

func NewMiddlewareAccessQueryToken Uses

func NewMiddlewareAccessQueryToken(queryParamName string, tokens []string) func(rw http.ResponseWriter, req *http.Request) *Response

NewMiddlewareAccessQueryToken creates a new handler to verify access tokens passed as a query parameter.

Example usage:

routes.Handle("/some/route", a.Dependencies.MWHandler.Handle(
	[]rye.Handler{
		rye.NewMiddlewareAccessQueryToken(queryParamName, []string{token1, token2}),
		yourHandler,
	})).Methods("POST")

func NewMiddlewareAccessToken Uses

func NewMiddlewareAccessToken(headerName string, tokens []string) func(rw http.ResponseWriter, req *http.Request) *Response

NewMiddlewareAccessToken creates a new handler to verify access tokens passed as a header.

Example usage:

routes.Handle("/some/route", a.Dependencies.MWHandler.Handle(
	[]rye.Handler{
		rye.NewMiddlewareAccessToken(tokenHeaderName, []string{token1, token2}),
		yourHandler,
	})).Methods("POST")

func NewMiddlewareAuth Uses

func NewMiddlewareAuth(authFunc AuthFunc) func(rw http.ResponseWriter, req *http.Request) *Response

func NewMiddlewareCIDR Uses

func NewMiddlewareCIDR(CIDRs []string) func(rw http.ResponseWriter, req *http.Request) *Response

NewMiddlewareCIDR creates a new handler to verify incoming IPs against a set of CIDR Notation strings in a rye chain. For reference on CIDR notation see https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing

Example usage:

routes.Handle("/some/route", a.Dependencies.MWHandler.Handle(
	[]rye.Handler{
		rye.NewMiddlewareCIDR(CIDRs), // []string of allowed CIDRs
		yourHandler,
	})).Methods("POST")

func NewMiddlewareCORS Uses

func NewMiddlewareCORS(origin, methods, headers string) func(rw http.ResponseWriter, req *http.Request) *Response

NewMiddlewareCORS creates a new handler to support CORS functionality. You can use this middleware by specifying `rye.MiddlewareCORS()` or `rye.NewMiddlewareCORS(origin, methods, headers)` when defining your routes.

Default CORS Values:

DEFAULT_CORS_ALLOW_ORIGIN**: "*"
DEFAULT_CORS_ALLOW_METHODS**: "POST, GET, OPTIONS, PUT, DELETE"
DEFAULT_CORS_ALLOW_HEADERS**: "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, X-Access-Token"

If you are planning to use this in production - you should probably use this middleware *with* params.

Example use case:

routes.Handle("/some/route", a.Dependencies.MWHandler.Handle(
	[]rye.Handler{
		rye.MiddlewareCORS(), // use defaults for allowed origin, headers, methods
		yourHandler,
	})).Methods("PUT", "OPTIONS")

OR:

routes.Handle("/some/route", a.Dependencies.MWHandler.Handle(
	[]rye.Handler{
		rye.NewMiddlewareCORS("*", "POST, GET", "SomeHeader, AnotherHeader"),
		yourHandler,
	})).Methods("PUT", "OPTIONS")

func NewMiddlewareGetHeader Uses

func NewMiddlewareGetHeader(headerName, contextKey string) func(rw http.ResponseWriter, req *http.Request) *Response

NewMiddlewareGetHeader creates a new handler to extract any header and save its value into the context.

headerName: the name of the header you want to extract
contextKey: the value key that you would like to store this header under in the context

Example usage:

routes.Handle("/some/route", a.Dependencies.MWHandler.Handle(
	[]rye.Handler{
		rye.NewMiddlewareGetHeader(headerName, contextKey),
		yourHandler,
	})).Methods("POST")

func NewMiddlewareJWT Uses

func NewMiddlewareJWT(secret string) func(rw http.ResponseWriter, req *http.Request) *Response

This middleware is deprecated. Use NewMiddlewareAuth with NewJWTAuthFunc instead.

This remains here as a shim for backwards compatibility.

---------------------------------------------------------------------------

This middleware provides JWT verification functionality

You can use this middleware by specifying `rye.NewMiddlewareJWT(shared_secret)` when defining your routes.

This middleware has no default version, it must be configured with a shared secret.

Example use case:

routes.Handle("/some/route", a.Dependencies.MWHandler.Handle(
	[]rye.Handler{
		rye.NewMiddlewareJWT("this is a big secret"),
		yourHandler,
	})).Methods("PUT", "OPTIONS")

Additionally, this middleware puts the JWT token into the context for use by other middlewares in your chain.

Access to that is simple (using the CONTEXT_JWT constant as a key)

func getJWTfromContext(rw http.ResponseWriter, r *http.Request) *rye.Response {

	// Retrieving the value is easy!
	// Just reference the rye.CONTEXT_JWT const as a key
	myVal := r.Context().Value(rye.CONTEXT_JWT)

	// Log it to the server log?
	log.Infof("Context Value: %v", myVal)

	return nil
}

func NewStaticFile Uses

func NewStaticFile(path string) func(rw http.ResponseWriter, req *http.Request) *Response

NewStaticFile creates a new handler to serve a file from a path on the local filesystem. The path should be an absolute path -> i.e., it's up to the program using Rye to correctly determine what path it should be serving from. An example is available in the `static_example.go` file which shows setting up a path relative to the go executable.

The purpose of this handler is to serve a specific file for any requests through the route handler. For instance, in the example below, any requests made to `/ui` will always be routed to /dist/index.html. This is important for single page applications which happen to use client-side routers. Therefore, you might have a webpack application with it's entrypoint `/dist/index.html`. That file may point at your `bundle.js`. Every request into the app will need to always be routed to `/dist/index.html`

Example use case:

routes.PathPrefix("/ui/").Handler(middlewareHandler.Handle([]rye.Handler{
	rye.MiddlewareRouteLogger(),
	rye.NewStaticFile(pwd + "/dist/index.html"),
}))

func NewStaticFilesystem Uses

func NewStaticFilesystem(path string, stripPrefix string) func(rw http.ResponseWriter, req *http.Request) *Response

NewStaticFilesystem creates a new handler to serve a filesystem from a path on the local filesystem. The path should be an absolute path -> i.e., it's up to the program using Rye to correctly determine what path it should be serving from. An example is available in the `static_example.go` file which shows setting up a path relative to the go executable.

The primary benefit of this is to serve an entire set of files. You can pre-pend typical Rye middlewares to the chain. The static filesystem middleware should always be last in a chain, however. The `stripPrefix` allows you to ignore the prefix on requests so that the proper files will be matched.

Example use case:

routes.PathPrefix("/dist/").Handler(middlewareHandler.Handle([]rye.Handler{
	rye.MiddlewareRouteLogger(),
	rye.NewStaticFilesystem(pwd+"/dist/", "/dist/"),
}))

func WriteJSONResponse Uses

func WriteJSONResponse(rw http.ResponseWriter, statusCode int, content []byte)

WriteJSONResponse writes data and status code to the ResponseWriter

func WriteJSONStatus Uses

func WriteJSONStatus(rw http.ResponseWriter, status, message string, statusCode int)

WriteJSONStatus is a wrapper for WriteJSONResponse that returns a marshalled JSONStatus blob

type AuthFunc Uses

type AuthFunc func(context.Context, string) *Response

func NewBasicAuthFunc Uses

func NewBasicAuthFunc(userPass map[string]string) AuthFunc

func NewJWTAuthFunc Uses

func NewJWTAuthFunc(secret string) AuthFunc

type Config Uses

type Config struct {
    Statter  statsd.Statter
    StatRate float32

    // toggle types of stats sent
    NoErrStats        bool
    NoDurationStats   bool
    NoStatusCodeStats bool

    // Customer Statter for the client
    CustomStatter CustomStatter
}

Config struct allows you to set a reference to a statsd.Statter and include it's stats rate.

type CustomStatter Uses

type CustomStatter interface {
    ReportStats(handlerName string, elapsedTime time.Duration, req *http.Request, resp *Response) error
}

CustomStatter allows the client to log any additional statsD metrics Rye computes around the request handler.

type Handler Uses

type Handler func(w http.ResponseWriter, r *http.Request) *Response

Handler is the primary type that any rye middleware must implement to be called in the Handle() function. In order to use this you must return a *rye.Response.

type JSONStatus Uses

type JSONStatus struct {
    Message string `json:"message"`
    Status  string `json:"status"`
}

JSONStatus is a simple container used for conveying status messages.

type MWHandler Uses

type MWHandler struct {
    Config Config
    // contains filtered or unexported fields
}

MWHandler struct is used to configure and access rye's basic functionality.

func NewMWHandler Uses

func NewMWHandler(config Config) *MWHandler

Constructor for new instantiating new rye instances It returns a constructed *MWHandler instance.

func (*MWHandler) Handle Uses

func (m *MWHandler) Handle(customHandlers []Handler) http.Handler

The Handle function is the primary way to set up your chain of middlewares to be called by rye. It returns a http.HandlerFunc from net/http that can be set as a route in your http server.

func (*MWHandler) Use Uses

func (m *MWHandler) Use(handler Handler)

Use adds a handler to every request. All handlers set up with use are fired first and then any route specific handlers are called

type Response Uses

type Response struct {
    Err           error
    StatusCode    int
    StopExecution bool
    Context       context.Context
}

Response struct is utilized by middlewares as a way to share state; ie. a middleware can return a *Response as a way to indicate that further middleware execution should stop (without an error) or return a a hard error by setting `Err` + `StatusCode`.

func (*Response) Error Uses

func (r *Response) Error() string

Error bubbles a response error providing an implementation of the Error interface. It returns the error as a string.

Directories

PathSynopsis
example
fakes/statsdfakesThis file was generated by counterfeiter
static-examples

Package rye imports 15 packages (graph) and is imported by 10 packages. Updated 2018-10-18. Refresh now. Tools for package owners.