typhon

package module
Version: v0.0.0-...-5dd50fc Latest Latest
Warning

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

Go to latest
Published: Oct 19, 2021 License: MIT Imports: 26 Imported by: 48

README

Typhon 🐲

Build Status GoDoc

Typhon is a wrapper around Go's net/http library that we use at Monzo to build RPC servers and clients in our microservices platform.

It provides a number of conveniences and tries to promote safety wherever possible. Here's a short list of interesting features in Typhon:

  • No need to close body.Close() in clients
    Forgetting to body.Close() in a client when the body has been dealt with is a common source of resource leaks in Go programs in our experience. Typhon ensures that – unless you're doing something really weird with the body – it will be closed automatically.

  • Middleware "filters"
    Filters are decorators around Services; in Typhon servers and clients share common functionality by composing it functionally.

  • Body encoding and decoding
    Marshalling and unmarshalling request bodies to structs is such a common operation that our Request and Response objects support them directly. If the operations fail, the errors are propagated automatically since that's nearly always what a server will want.

  • Propagation of cancellation
    When a server has done handling a request, the request's context is automatically cancelled, and these cancellations are propagated through the distributed call stack. This lets downstream servers conserve work producing responses that are no longer needed.

  • Error propagation
    Responses have an inbuilt Error attribute, and serialisation/deserialisation of these errors into HTTP errors is taken care of automatically. We recommend using this in conjunction with monzo/terrors.

  • Full HTTP/1.1 and HTTP/2.0 support
    Applications implemented using Typhon can communicate over HTTP/1.1 or HTTP/2.0. Typhon has support for full duplex communication under HTTP/2.0, and h2c (HTTP/2.0 over TCP, ie. without TLS) is also supported if required.

Documentation

Overview

Package typhon wraps net/http to provide a simple yet powerful programming model for RPC servers and clients. At Monzo, it is used to implement the majority of our Go microservices.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func AppendMetadataToContext

func AppendMetadataToContext(ctx context.Context, md Metadata) context.Context

AppendMetadataToContext sets the metadata on the context.

func ErrorStatusCode

func ErrorStatusCode(err error) int

ErrorStatusCode returns a HTTP status code for the given error.

If the error is not a terror, this will always be 500 (Internal Server Error).

func HttpHandler

func HttpHandler(svc Service) http.Handler

HttpHandler transforms the given Service into a standard library HTTP handler. It is one of the main "bridges" between Typhon and net/http.

Types

type Filter

type Filter func(Request, Service) Response

Filter functions compose with Services to modify their behaviour. They might change a service's input or output, or elect not to call the underlying service at all.

These are typically useful to encapsulate common logic that is shared among multiple Services. Authentication, authorisation, rate limiting, and tracing are good examples.

type Metadata

type Metadata map[string][]string

Metadata provides a transport agnostic way to pass metadata with Typhon. It aligns to the interface of Go's default HTTP header type for convenience.

func MetadataFromContext

func MetadataFromContext(ctx context.Context) Metadata

MetadataFromContext retrieves the metadata from the context.

func NewMetadata

func NewMetadata(data map[string]string) Metadata

NewMetadata creates a metadata struct from a map of strings.

type Request

type Request struct {
	http.Request
	context.Context
	// contains filtered or unexported fields
}

A Request is Typhon's wrapper around http.Request, used by both clients and servers.

Note that Typhon makes no guarantees that a Request is safe to access or mutate concurrently. If a single Request object is to be used by multiple goroutines concurrently, callers must make sure to properly synchronise accesses.

func NewRequest

func NewRequest(ctx context.Context, method, url string, body interface{}) Request

NewRequest constructs a new Request with the given parameters, and if non-nil, encodes the given body into it.

func (*Request) BodyBytes

func (r *Request) BodyBytes(consume bool) ([]byte, error)

BodyBytes fully reads the request body and returns the bytes read.

If consume is true, this is equivalent to ioutil.ReadAll; if false, the caller will observe the body to be in the same state that it was before (ie. any remaining unread body can be read again).

func (Request) Decode

func (r Request) Decode(v interface{}) error

Decode de-serialises the JSON body into the passed object.

func (*Request) Encode

func (r *Request) Encode(v interface{})

Encode serialises the passed object as JSON into the body (and sets appropriate headers).

func (Request) Response

func (r Request) Response(body interface{}) Response

Response constructs a new Response to the request, and if non-nil, encodes the given body into it.

func (Request) ResponseWithCode

func (r Request) ResponseWithCode(body interface{}, statusCode int) Response

ResponseWithCode constructs a new Response with the given status code to the request, and if non-nil, encodes the given body into it.

func (Request) Send

func (r Request) Send() *ResponseFuture

Send round-trips the request via the default Client. It does not block, instead returning a ResponseFuture representing the asynchronous operation to produce the response. It is equivalent to:

r.SendVia(Client)

func (Request) SendVia

func (r Request) SendVia(svc Service) *ResponseFuture

SendVia round-trips the request via the passed Service. It does not block, instead returning a ResponseFuture representing the asynchronous operation to produce the response.

func (Request) String

func (r Request) String() string

func (*Request) Write

func (r *Request) Write(b []byte) (n int, err error)

Write writes the passed bytes to the request's body.

type Response

type Response struct {
	*http.Response
	Error   error
	Request *Request // The Request that we are responding to
	// contains filtered or unexported fields
}

A Response is Typhon's wrapper around http.Response, used by both clients and servers.

Note that Typhon makes no guarantees that a Response is safe to access or mutate concurrently. If a single Response object is to be used by multiple goroutines concurrently, callers must make sure to properly synchronise accesses.

func BareClient

func BareClient(req Request) Response

BareClient is the most basic way to send a request, using the default http RoundTripper

func ErrorFilter

func ErrorFilter(req Request, svc Service) Response

ErrorFilter serialises and deserialises response errors. Without this filter, errors may not be passed across the network properly so it is recommended to use this in most/all cases. It tries to do everything it can to give you all the information that it can about why your request might have failed. Because of this, it has some weird behavior.

func ExpirationFilter

func ExpirationFilter(req Request, svc Service) Response

ExpirationFilter provides admission control; it rejects requests which are cancelled

func H2cFilter

func H2cFilter(req Request, svc Service) Response

H2cFilter adds HTTP/2 h2c upgrade support to the wrapped Service (as defined in RFC 7540 §3.2, §3.4).

func NewResponse

func NewResponse(req Request) Response

NewResponse constructs a Response with status code 200.

func NewResponseWithCode

func NewResponseWithCode(req Request, statusCode int) Response

NewResponseWithCode constructs a Response with the given status code.

func (*Response) BodyBytes

func (r *Response) BodyBytes(consume bool) ([]byte, error)

BodyBytes fully reads the response body and returns the bytes read. If consume is false, the body is copied into a new buffer such that it may be read again.

func (*Response) Decode

func (r *Response) Decode(v interface{}) error

Decode de-serialises the JSON body into the passed object.

func (*Response) Encode

func (r *Response) Encode(v interface{})

Encode serialises the passed object as JSON into the body (and sets appropriate headers).

func (Response) String

func (r Response) String() string

func (*Response) Write

func (r *Response) Write(b []byte) (n int, err error)

Write writes the passed bytes to the response's body.

func (*Response) Writer

func (r *Response) Writer() ResponseWriter

Writer returns a ResponseWriter which can be used to populate the response.

This is useful when you want to use another HTTP library that is used to wrapping net/http directly. For example, it allows a Typhon Service to use a http.Handler internally.

type ResponseFuture

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

A ResponseFuture is a container for a Response which will materialise at some point.

func Send

func Send(req Request) *ResponseFuture

Send round-trips the request via the default Client. It does not block, instead returning a ResponseFuture representing the asynchronous operation to produce the response. It is equivalent to:

SendVia(req, Client)

func SendVia

func SendVia(req Request, svc Service) *ResponseFuture

SendVia round-trips the request via the passed Service. It does not block, instead returning a ResponseFuture representing the asynchronous operation to produce the response.

func (*ResponseFuture) Response

func (f *ResponseFuture) Response() Response

Response provides access to the response object, blocking until it is available

func (*ResponseFuture) WaitC

func (f *ResponseFuture) WaitC() <-chan struct{}

WaitC returns a channel which can be waited upon until the response is available

type ResponseWriter

type ResponseWriter interface {
	http.ResponseWriter
	// WriteJSON writes the given data as JSON to the Response. The passed value must (perhaps obviously) be
	// serialisable to JSON.
	WriteJSON(interface{})
	// WriteError writes the given error to the Response.
	WriteError(err error)
}

type Router

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

A Router multiplexes requests to a set of Services by pattern matching on method and path, and can also extract parameters from paths.

func RouterForRequest

func RouterForRequest(r Request) *Router

RouterForRequest returns a pointer to the Router that successfully dispatched the request, or nil.

func (*Router) CONNECT

func (r *Router) CONNECT(pattern string, svc Service)

CONNECT is shorthand for:

r.Register("CONNECT", pattern, svc)

func (*Router) DELETE

func (r *Router) DELETE(pattern string, svc Service)

DELETE is shorthand for:

r.Register("DELETE", pattern, svc)

func (*Router) GET

func (r *Router) GET(pattern string, svc Service)

GET is shorthand for:

r.Register("GET", pattern, svc)

func (*Router) HEAD

func (r *Router) HEAD(pattern string, svc Service)

HEAD is shorthand for:

r.Register("HEAD", pattern, svc)

func (Router) Lookup

func (r Router) Lookup(method, path string) (Service, string, map[string]string, bool)

Lookup returns the Service, pattern, and extracted path parameters for the HTTP method and path.

func (*Router) OPTIONS

func (r *Router) OPTIONS(pattern string, svc Service)

OPTIONS is shorthand for:

r.Register("OPTIONS", pattern, svc)

func (*Router) PATCH

func (r *Router) PATCH(pattern string, svc Service)

PATCH is shorthand for:

r.Register("PATCH", pattern, svc)

func (*Router) POST

func (r *Router) POST(pattern string, svc Service)

POST is shorthand for:

r.Register("POST", pattern, svc)

func (*Router) PUT

func (r *Router) PUT(pattern string, svc Service)

PUT is shorthand for:

r.Register("PUT", pattern, svc)

func (Router) Params

func (r Router) Params(req Request) map[string]string

Params returns extracted path parameters, assuming the request has been routed and has captured parameters.

func (Router) Pattern

func (r Router) Pattern(req Request) string

Pattern returns the registered pattern which matches the given request.

func (*Router) Register

func (r *Router) Register(method, pattern string, svc Service)

Register associates a Service with a method and path.

Method is a HTTP method name, or "*" to match any method.

Patterns are strings of the format: /foo/:name/baz/*residual As well as being literal paths, they can contain named parameters like :name whose value is dynamic and only known at runtime, or *residual components which match (potentially) multiple path components.

In the case that patterns are ambiguous, the last route to be registered will take precedence.

func (Router) Serve

func (r Router) Serve() Service

Serve returns a Service which will route inbound requests to the enclosed routes.

func (*Router) TRACE

func (r *Router) TRACE(pattern string, svc Service)

TRACE is shorthand for:

r.Register("TRACE", pattern, svc)

type Server

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

func Listen

func Listen(svc Service, addr string) (*Server, error)

func Serve

func Serve(svc Service, l net.Listener) (*Server, error)

Serve starts a HTTP server, binding the passed Service to the passed listener.

func (*Server) Done

func (s *Server) Done() <-chan struct{}

Done returns a channel that will be closed when the server begins to shutdown. The server may still be draining its connections at the time the channel is closed.

func (*Server) Listener

func (s *Server) Listener() net.Listener

Listener returns the network listener that this server is active on.

func (*Server) Stop

func (s *Server) Stop(ctx context.Context)

Stop shuts down the server, returning when there are no more connections still open. Graceful shutdown will be attempted until the passed context expires, at which time all connections will be forcibly terminated.

type Service

type Service func(req Request) Response

A Service is a function that takes a request and produces a response. Services are used symmetrically in both clients and servers.

var (
	// Client is used to send all requests by default. It can be overridden globally but MUST only be done before use
	// takes place; access is not synchronised.
	Client Service = BareClient
	// RoundTripper is used by default in Typhon clients
	RoundTripper http.RoundTripper = &http.Transport{
		Proxy:               http.ProxyFromEnvironment,
		DisableKeepAlives:   false,
		DisableCompression:  false,
		IdleConnTimeout:     10 * time.Minute,
		MaxIdleConnsPerHost: 10}
)

func HttpService

func HttpService(rt http.RoundTripper) Service

HttpService returns a Service which sends requests via the given net/http RoundTripper. Only use this if you need to do something custom at the transport level.

func (Service) Filter

func (svc Service) Filter(f Filter) Service

Filter vends a new service wrapped in the passed filter.

type StreamerWriter

type StreamerWriter interface {
	io.ReadWriteCloser
	CloseWithError(error) error
}

func Streamer

func Streamer() StreamerWriter

Streamer returns a reader/writer/closer that can be used to stream responses. A simple use of this is:

func streamingService(req typhon.Request) typhon.Response {
    body := typhon.Streamer()
    go func() {
        // do something to asynchronously produce output into body
        if err != nil {
            body.CloseWithError(err)
            return
        }
        body.Close()
    }()
    return req.Response(body)
}

Note that a Streamer may not perform any internal buffering, so callers should take care not to depend on writes being non-blocking. If buffering is needed, Streamer can be wrapped in a bufio.Writer.

type WrapDownstreamErrors

type WrapDownstreamErrors struct{}

WrapDownstreamErrors is a context key that can be used to enable wrapping of downstream response errors on a per-request basis.

This is implemented as a context key to allow us to migrate individual services from the old behaviour to the new behaviour without adding a dependency on config to Typhon.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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