httpbp

package
v0.0.0-...-bde19ca Latest Latest
Warning

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

Go to latest
Published: Mar 28, 2020 License: BSD-3-Clause Imports: 12 Imported by: 0

Documentation

Overview

Package httpbp provides Baseplate specific helpers and integrations for http services using go-kit.

Index

Examples

Constants

View Source
const (
	// EdgeContextHeader is the key use to get the raw edge context from
	// the HTTP request headers.
	EdgeContextHeader = "X-Edge-Request"

	// ParentIDHeader is the key use to get the span parent ID from
	// the HTTP request headers.
	ParentIDHeader = "X-Parent"

	// SpanIDHeader is the key use to get the span ID from the HTTP
	// request headers.
	SpanIDHeader = "X-Span"

	// SpanFlagsHeader is the key use to get the span flags from the HTTP
	// request headers.
	SpanFlagsHeader = "X-Flags"

	// SpanSampledHeader is the key use to get the sampled flag from the
	// HTTP request headers.
	SpanSampledHeader = "X-Sampled"

	// TraceIDHeader is the key use to get the trace ID from the HTTP
	// request headers.
	TraceIDHeader = "X-Trace"
)
View Source
const (
	// ContentTypeHeader is the 'Content-Type' header key.
	ContentTypeHeader = "Content-Type"

	// JSONContentType is the Content-Type header for JSON responses.
	JSONContentType = "application/json; charset=utf-8"

	// HTMLContentType is the Content-Type header for HTML responses.
	HTMLContentType = "text/html; charset=utf-8"
)
View Source
const (
	// EdgeContextSignatureHeader is the key use to get the signature for
	// the edge context headers from the HTTP request headers.
	EdgeContextSignatureHeader = "X-Edge-Request-Signature"

	// SpanSignatureHeader is the key use to get the signature for
	// the span headers from the HTTP request headers.
	SpanSignatureHeader = "X-Span-Signature"
)

Variables

This section is empty.

Functions

func BuildEncodeTemplatedResponse

func BuildEncodeTemplatedResponse(t *template.Template) httpgk.EncodeResponseFunc

BuildEncodeTemplatedResponse returns a function that implements go-kits http.EncodeResponseFunc interface and wraps EncodeTemplatedResponse with the template passed in.

func EncodeJSONResponse

func EncodeJSONResponse(_ context.Context, w http.ResponseWriter, response interface{}) error

EncodeJSONResponse implements go-kit's http.EncodeResponseFunc interface and encodes the given response as json.

If the response implements go-kit's http.Headerer interface, then the headers will be applied to the response, after the Content-Type header is set.

If the response implements the ResponseCookie interface, then any cookies returned will be applied to the response, after the headers are set.

If the response implements the ErrorResponse interface, then an error response will be returned if Err() is non-nil. You can use the HTTPError object to customize the error response.

If the response implements go-kit's http.StatusCoder interface, then the status code returned will be used rather than 200. If a response implements this but returns the default integer value of 0, then the code will still be set to 200. If the response also implements the ErrorResponse interface, then this status code is ignored in favor of the error status code.

func EncodeTemplatedResponse

func EncodeTemplatedResponse(_ context.Context, w http.ResponseWriter, response interface{}, t *template.Template) error

EncodeTemplatedResponse encodes the given response as text/html with the given template.

This method does not implement the go-kit http.EncodeResponseFunc interface, if you want to use this with go-kit, use BuildEncodeTemplatedResponse to return a function that wraps EncodeTemplatedResponse with the a single template and does implement the http.EncodeResponseFunc interface.

If the response implements go-kit's http.Headerer interface, then the headers will be applied to the response, after the Content-Type header is set.

If the response implements the ResponseCookie interface, then any cookies returned will be applied to the response, after the headers are set.

If the response implements the ErrorResponse interface, then an error response will be returned if Err() is non-nil. You can use the HTTPError object to customize the error response.

If the response implements go-kit's http.StatusCoder interface, then the status code returned will be used rather than 200. If a response implements this but returns the default integer value of 0, then the code will still be set to 200. If the response also implements the ErrorResponse interface, then this status code is ignored in favor of the error status code.

func GetHeader

func GetHeader(ctx context.Context, key HeaderContextKey) (header string, ok bool)

GetHeader returns the HTTP header stored on the context at key.

func InjectTrustedContext

func InjectTrustedContext(ctx context.Context, t HeaderTrustHandler, r *http.Request) context.Context

InjectTrustedContext takes baseplate HTTP headers from the request, verifies that it should trust the headers using the provided HeaderTrustHandler, and attaches the trusted headers to the context.

These headers can be retrieved using httpbp.GetHeader.

This method does not implement the go-kit http.RequestFunc interface, if you want to use this with go-kit, use PopulateRequestContext to return a function that wraps InjectTrustedContext with the HeaderTrustHandler and does implement the http.RequestFunc interface.

func PopulateRequestContext

func PopulateRequestContext(t HeaderTrustHandler) httpgk.RequestFunc

PopulateRequestContext returns a function that calls InjectTrustedContext with the HeaderTrustHandler you pass to it. The function that this produces implements go-kit's http.RequestFunc interface and can be passed to go-kit's http.ServerBefore ServerOption.

Example

This example demonstrates how to use PopulateRequestContext

package main

import (
	"log"
	"net/http"

	"github.com/go-kit/kit/endpoint"
	httpgk "github.com/go-kit/kit/transport/http"

	"github.com/fizx/baseplate.go/httpbp"
)

func main() {
	// variables should be properly initialized in production code
	var (
		IsHealthy              endpoint.Endpoint
		DecodeIsHealthyRequest httpgk.DecodeRequestFunc
		trustHandler           httpbp.NeverTrustHeaders
	)
	handler := http.NewServeMux()
	handler.Handle("/health", httpgk.NewServer(
		IsHealthy,
		DecodeIsHealthyRequest,
		httpbp.EncodeJSONResponse,
		httpgk.ServerBefore(
			httpbp.PopulateRequestContext(trustHandler),
		),
	))
	log.Fatal(http.ListenAndServe(":8080", handler))
}
Output:

func SetHeader

func SetHeader(ctx context.Context, key HeaderContextKey, value string) context.Context

SetHeader sets the value on the context at key.

Types

type AlwaysTrustHeaders

type AlwaysTrustHeaders struct{}

AlwaysTrustHeaders implements the HeaderTrustHandler interface and always returns true.

This handler is appropriate when your service only accept calls from within a secure network and you feel comfortable always trusting these headers.

func (AlwaysTrustHeaders) TrustEdgeContext

func (h AlwaysTrustHeaders) TrustEdgeContext(r *http.Request) bool

TrustEdgeContext always returns true. The edge context headers will always be added to the context.

func (AlwaysTrustHeaders) TrustSpan

func (h AlwaysTrustHeaders) TrustSpan(r *http.Request) bool

TrustSpan always returns true. The span headers will always be added to the context.

type BaseResponse

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

BaseResponse can be embedded into other response structs to allow them to implement the go-kit Headerer and StatusCoder interfaces as well as the baseplate.go ResponseCookies and ErrorResponse interfaces.

BaseResponse must be initalized using NewBaseResponse before use, if it is not, some methods will panic.

type Response struct {
	httpbp.BaseResponse
}

func NewResponse() *Response {
	return &Response{
		BaseResponse: httpbp.NewBaseResponse(),
	}
}

func NewBaseResponse

func NewBaseResponse() BaseResponse

NewBaseResponse returns an initialized BaseResponse.

Intended to be used by the constructor methods for Response structs that embed BaseResponse.

func (*BaseResponse) ClearCookies

func (r *BaseResponse) ClearCookies()

ClearCookies clears all cookies set on the response.

func (BaseResponse) Cookies

func (r BaseResponse) Cookies() []*http.Cookie

Cookies returns the a copy of the current list of cookies to set on the response.

func (BaseResponse) Err

func (r BaseResponse) Err() error

Err returns the error to send back to the client.

func (BaseResponse) Headers

func (r BaseResponse) Headers() http.Header

Headers returns the http.Header collection of headers to set on the response.

func (*BaseResponse) SetCode

func (r *BaseResponse) SetCode(code int)

SetCode sets the status code for this response.

func (*BaseResponse) SetCookie

func (r *BaseResponse) SetCookie(cookie *http.Cookie)

SetCookie adds a cookie to set on the response.

func (*BaseResponse) SetError

func (r *BaseResponse) SetError(e error)

SetError sets the error to return as an error response.

func (BaseResponse) StatusCode

func (r BaseResponse) StatusCode() int

StatusCode returns the current status code set for this response.

type EdgeContextHeaders

type EdgeContextHeaders struct {
	EdgeRequest string
}

EdgeContextHeaders implements the Headers interface for HTTP EdgeContext headers.

func NewEdgeContextHeaders

func NewEdgeContextHeaders(h http.Header) EdgeContextHeaders

NewEdgeContextHeaders returns a new EdgeContextHeaders object from the given HTTP headers.

func (EdgeContextHeaders) AsMap

func (s EdgeContextHeaders) AsMap() map[string]string

AsMap returns the EdgeContextHeaders as a map of header keys to header values.

type ErrorResponse

type ErrorResponse interface {
	// Err returns the HTTPError set on the response.
	Err() error
}

ErrorResponse is an interface that your Response objects can implement in order to have the httpbp.Encode methods automatically return http errors.

Example

This example demonstrates how to use HTTPError along with ErrorResponse to return errors to API clients.

Example request and response:

request: {"error": false} response:

code: 200
content-type: "application/json; charset=utf-8"
body: {"message": "hello world!"}

request: {"error": true} response:

code: 502
content-type: "text/plain; charset=utf-8"
body: Disruption dowstream
package main

import (
	"context"
	"encoding/json"
	"errors"
	"log"
	"net/http"

	"github.com/fizx/baseplate.go/httpbp"
	"github.com/go-kit/kit/endpoint"
	httpgk "github.com/go-kit/kit/transport/http"
)

// ExampleResponse is an example response that implements the ErrorResponse
// interface.
type ExampleResponse struct {
	httpbp.BaseResponse

	Message string `json:"message,omitempty"`
}

// NewExampleResponse returns a pointer to a new, initialized ExampleResponse.
func NewExampleResponse() *ExampleResponse {
	return &ExampleResponse{
		BaseResponse: httpbp.NewBaseResponse(),
	}
}

var (
	// Verify that both ExampleResponse and *ExampleResponse implement the
	// ResponseCookies interface.
	_ httpbp.ResponseCookies = ExampleResponse{}
	_ httpbp.ResponseCookies = (*ExampleResponse)(nil)
	// Verify that both ExampleResponse and *ExampleResponse implement the
	// go-kit http.Headerer interface.
	_ httpgk.Headerer = ExampleResponse{}
	_ httpgk.Headerer = (*ExampleResponse)(nil)
	// Verify that both ExampleResponse and *ExampleResponse implement the
	// go-kit http.StatusCoder interface.
	_ httpgk.StatusCoder = ExampleResponse{}
	_ httpgk.StatusCoder = (*ExampleResponse)(nil)
	// Verify that both ExampleResponse and *ExampleResponse implement the
	// ErrorResponse interface.
	_ httpbp.ErrorResponse = ExampleResponse{}
	_ httpbp.ErrorResponse = (*ExampleResponse)(nil)
)

// ExampleRequest is the request struct for our Example endpoint.
type ExampleRequest struct {
	// Error signals to the example endpoint whether it should return an error
	// or not.
	Error bool `json:"error"`
}

// DecodeExampleRequest decodes the request body into an ExampleRequest.
func DecodeExampleRequest(_ context.Context, r *http.Request) (interface{}, error) {
	var req ExampleRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		return req, err
	}
	return req, nil
}

// MakeExampleEndpoint builds a go-kit endpoint.Endpoint function that simply
// returns an ExampleResponse with an error set.
func MakeExampleEndpoint() endpoint.Endpoint {
	return func(ctx context.Context, request interface{}) (interface{}, error) {
		resp := NewExampleResponse()
		req := request.(ExampleRequest)
		if req.Error {
			// Return a response that returns a non-nil error when
			// httpbp.EncodeJSONResponse checks response.Err() which will signal it
			// to send an error response rather than a normal one.
			resp.Message = "you'll never see this"
			resp.SetError(
				httpbp.HTTPError{
					// Code sets the status code to return, defaults to
					// http.InternalServerError (500).
					Code: http.StatusBadGateway,

					// Message sets a custom message for the error response
					// body, defaults to http.StatusText for the status code.
					Message: "Disruption downstream",

					// Cause holds the error that triggered an error response.
					// This is not communicated to the client but inspected by
					// your service.
					Cause: errors.New("database offline"),
				},
			)
		} else {
			// Return a non-error response.
			resp.Message = "hello world!"
		}
		return resp, nil
	}
}

// This example demonstrates how to use HTTPError along with ErrorResponse to
// return errors to API clients.
//
// Example request and response:
//
// request: {"error": false}
// response:
//
//	code: 200
//	content-type: "application/json; charset=utf-8"
//	body: {"message": "hello world!"}
//
// request: {"error": true}
// response:
//
//	code: 502
//	content-type: "text/plain; charset=utf-8"
//	body: Disruption dowstream
func main() {
	// Create server handler
	handler := http.NewServeMux()

	// Register our example endpoint
	handler.Handle("/example", httpgk.NewServer(
		MakeExampleEndpoint(),
		DecodeExampleRequest,
		httpbp.EncodeJSONResponse,
	))

	// Start the server
	log.Fatal(http.ListenAndServe(":8080", handler))
}
Output:

type HTTPError

type HTTPError struct {
	// Code is the status code to set on the HTTP response.  Defaults to 500 if
	// it is not set.
	Code int

	// Message is an optional message that can be returned to the
	// client.  Defaults to the native http.StatusText message for the
	// StatusCode() of the HTTPError.
	Message string

	// Cause is an optional error that can be used to retain the error that
	// led to us returning an HTTP error to the client.
	Cause error
}

HTTPError is a specialized error that is returned be the Err method specified in the ErrorResponse interface.

func (HTTPError) As

func (e HTTPError) As(v interface{}) bool

As implements helper interface for errors.As.

If v is pointer to either HTTPError or *HTTPError, *v will be set into this error.

func (HTTPError) Error

func (e HTTPError) Error() string

Error returns the standard error string, this is not returned to the client.

func (HTTPError) ResponseMessage

func (e HTTPError) ResponseMessage() string

ResponseMessage returns the error message to send to the client.

func (HTTPError) StatusCode

func (e HTTPError) StatusCode() int

StatusCode returns the HTTP status code to set on the response. Defaults to 500 if Code is not set on the HTTPError.

func (HTTPError) Unwrap

func (e HTTPError) Unwrap() error

Unwrap implements helper interface for errors.Unwrap. Returns the optional e.Cause error.

type HeaderContextKey

type HeaderContextKey int

HeaderContextKey is a key used to get HTTP headers from a context object.

const (
	// EdgeContextContextKey is the key for the raw edge request context
	EdgeContextContextKey HeaderContextKey = iota

	// TraceIDContextKey is the header for the trace ID passed by the caller
	TraceIDContextKey

	// ParentIDContextKey is the header for the parent ID passed by the caller
	ParentIDContextKey

	// SpanIDContextKey is the header for the span ID passed by the caller
	SpanIDContextKey

	// SpanFlagsContextKey is the header for the span flags passed by the caller
	SpanFlagsContextKey

	// SpanSampledContextKey is the header for the sampled flag passed by the caller
	SpanSampledContextKey
)

type HeaderTrustHandler

type HeaderTrustHandler interface {
	// TrustEdgeContext informs the function returned by PopulateBaseplateRequestContext
	// if it can trust the HTTP headers that can be used to create an edge
	// context.
	//
	// If it can trust those headers, then the headers will be copied into the
	// context object to be later used to initialize the edge context for the
	// request.
	TrustEdgeContext(r *http.Request) bool

	// TrustSpan informs the function returned by PopulateBaseplateRequestContext
	// if it can trust the HTTP headers that can be used to create a server
	// span.
	//
	// If it can trust those headers, then the headers will be copied into the
	// context object to later be used to initialize the server span for the
	// request.
	TrustSpan(r *http.Request) bool
}

HeaderTrustHandler provides an interface PopulateBaseplateRequestContext to verify that it should trust the HTTP headers it receives.

type Headers

type Headers interface {
	// AsMap returns the Headers struct as a map of header keys to header
	// values.
	AsMap() map[string]string
}

Headers is an interface to collect all of the HTTP headers for a particular baseplate resource (spans and edge contexts) into a struct that provides an easy way to convert them into HTTP headers.

This interface exists so we can avoid having to do runtime checks on maps to ensure that they have the right keys set when we are trying to sign or verify a set of HTTP headers.

type NeverTrustHeaders

type NeverTrustHeaders struct{}

NeverTrustHeaders implements the HeaderTrustHandler interface and always returns false.

This handler is appropriate when your service is exposed to the public internet and also do not expect to receive these headers anyways, or simply does not care to parse these headers.

func (NeverTrustHeaders) TrustEdgeContext

func (h NeverTrustHeaders) TrustEdgeContext(r *http.Request) bool

TrustEdgeContext always returns false. The edge context headers will never be added to the context.

func (NeverTrustHeaders) TrustSpan

func (h NeverTrustHeaders) TrustSpan(r *http.Request) bool

TrustSpan always returns false. The span headers will never be added to the context.

type ResponseCookies

type ResponseCookies interface {
	// Return a list of all cookies to set on the response.
	Cookies() []*http.Cookie
}

ResponseCookies is an interface that your Response objects can implement in order to have the httpbp.Encode methods automatically add cookies to the response.

type SpanHeaders

type SpanHeaders struct {
	TraceID  string
	ParentID string
	SpanID   string
	Flags    string
	Sampled  string
}

SpanHeaders implements the Headers interface for HTTP Span headers.

func NewSpanHeaders

func NewSpanHeaders(h http.Header) SpanHeaders

NewSpanHeaders returns a new SpanHeaders object from the given HTTP headers.

func (SpanHeaders) AsMap

func (s SpanHeaders) AsMap() map[string]string

AsMap returns the SpanHeaders as a map of header keys to header values.

type TrustHeaderSignature

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

TrustHeaderSignature implements the HeaderTrustHandler interface and checks the headers for a valid signature header. If the headers are signed, then they can be trusted and the Trust request returns true. If there is no signature or the signature is invalid, then the Trust request returns false.

For both the span and edge context headers, the trust handler expects the caller to provide the signature of a message in the following format:

"{header0}:{value0}|{header1}|{value1}|...|{headerN}:{valueN}"

where the headers are sorted lexicographically. Additionally, the signature should be generated using the baseplate provided `signing.Sign` function.

TrustHeaderSignature provides implementations for both signing and verifying edge context and span headers.

This handler is appropriate when your service wants to be able to trust headers that come from trusted sources, but also receives calls from un-trusted sources that you would not want to accept these headers from. One example would be an HTTP API that is exposed to clients over the public internet where you would not trust these headers but is also used internally where you want to accept these headers.

func NewTrustHeaderSignature

func NewTrustHeaderSignature(args TrustHeaderSignatureArgs) TrustHeaderSignature

NewTrustHeaderSignature returns a new HMACTrustHandler that uses the provided TrustHeaderSignatureArgs

func (TrustHeaderSignature) SignEdgeContextHeader

func (h TrustHeaderSignature) SignEdgeContextHeader(headers EdgeContextHeaders, expiresIn time.Duration) (string, error)

SignEdgeContextHeader signs the edge context header using signing.Sign.

The message that is signed has the following format:

"X-Edge-Request:{headerValue}

func (TrustHeaderSignature) SignSpanHeaders

func (h TrustHeaderSignature) SignSpanHeaders(headers SpanHeaders, expiresIn time.Duration) (string, error)

SignSpanHeaders signs the given span headers using signing.Sign.

The message that is signed has the following format:

"{header0}:{value0}|{header1}|{value1}|...|{headerN}:{valueN}"

where the headers are sorted lexicographically.

func (TrustHeaderSignature) TrustEdgeContext

func (h TrustHeaderSignature) TrustEdgeContext(r *http.Request) bool

TrustEdgeContext returns true if the request has the header "X-Edge-Request-Signature" set and is a valid signature of the header:

"X-Edge-Request"

The message that should be signed is:

"X-Edge-Request:{headerValue}"

func (TrustHeaderSignature) TrustSpan

func (h TrustHeaderSignature) TrustSpan(r *http.Request) bool

TrustSpan returns true if the request has the header "X-Span-Signature" set and is a valid signature of the headers:

"X-Flags"
"X-Parent"
"X-Sampled"
"X-Span"
"X-Trace"

The message that should be signed is:

"{header0}:{value0}|{header1}|{value1}|...|{headerN}:{valueN}"

where the headers are sorted lexicographically.

func (TrustHeaderSignature) VerifyEdgeContextHeader

func (h TrustHeaderSignature) VerifyEdgeContextHeader(headers EdgeContextHeaders, signature string) (bool, error)

VerifyEdgeContextHeader verifies the edge context header using signing.Verify.

func (TrustHeaderSignature) VerifySpanHeaders

func (h TrustHeaderSignature) VerifySpanHeaders(headers SpanHeaders, signature string) (bool, error)

VerifySpanHeaders verifies the edge context header using signing.Verify.

type TrustHeaderSignatureArgs

type TrustHeaderSignatureArgs struct {
	SecretsStore          *secrets.Store
	EdgeContextSecretPath string
	SpanSecretPath        string
}

TrustHeaderSignatureArgs is used as input to create a new TrustHeaderSignature.

Jump to

Keyboard shortcuts

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