templar

package module
v0.0.0-...-a16f1e0 Latest Latest
Warning

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

Go to latest
Published: Dec 12, 2019 License: BSD-3-Clause Imports: 15 Imported by: 0

README

templar

Build Status

HTTP APIs, they're everywhere. But they have a serious problem: their sychronous nature means that code using them stalls while waiting for a reply.

This means that your apps uptime and reliability are intertwined with whatever HTTP APIs, especially SaaS ones, you use.

templar helps you control the problem.

It is a an HTTP proxy that provides advanced features to help you make better use of and tame HTTP APIs.

Installation

Directly via go: go get github.com/vektra/templar/cmd/templar

Linux
Darwin
Windows

Usage

templar functions like an HTTP proxy, allowing you use your favorite HTTP client to easily send requests through it. Various languages have different HTTP clients but many respect the http_proxy environment variable that you can set to the address templar is running on.

Most HTTP clients in various programming languages have some configuration to configure the proxy directly as well. Nearly all of them do, just check the docs.

HTTPS

Many HTTP APIs located in SaaS products are available only via HTTPS. This is a good thing though it makes templar's job a little harder. We don't want to a client to use CONNECT because then we can't provide any value. So to interact with these APIs, use the X-Templar-Upgrade header. Configure your client to talk to the API as normal http but include X-Templar-Upgrade: https and templar will be able manage your requests and still talk to the https service!

Examples

Do a request through templar, no timeout, no caching:

curl -x http://localhost:9224 http://api.openweathermap.org/data/2.5/weather?q=Los+Angeles,CA

Now add some caching in, caching the value for a minute at a time:

curl -x http://localhost:9224 -H "X-Templar-Cache: eager" -H "X-Templar-CacheFor: 1m" 'http://api.openweathermap.org/data/2.5/weather?q=Los+Angeles,CA'

Features

Timeouts

It's important that timeouts are used when accessing a synchronous API like an HTTP endpoint. It's not uncommon for upstream APIs to have no timeouts to fulfill a request so that typically needs to be done on the client side. Effect use of timeouts on these APIs will improve the robustness of your own system.

For great discussion on this, check out Jeff Hodges thoughts on the topic:

At present, templar does not enforce a default timeout, it needs to be set per request via the X-Templar-Timeout header. The format is documented below under Duration format.

Request collapsing

Say that your app hits an HTTP endpoint at http://isitawesomeyet.com. When you send those HTTP requests through templar, it will reduce the number of requests to the external service to the bare minimum by combining requests together. So if a request comes in while we're waiting on another request to the same endpoint, we combine those requests together and serve the same data to both. This improves response times and reduces load on upstream servers.

Caching

Templar can, if requested, cache upstream requests. By setting the X-Templar-Cache header to either fallback or eager, templar will cache responses to the endpoint and serve them back.

fallback will only use the cache if accessing the endpoint times out. eager will use the cache if it exists first and always repopulate from the endpoint when needed.

The X-Templar-CacheFor header time is used to control how long a cached value will be used for. See Duration format below for how to specify the time.

There are 4 caches available presently:

  • Memory (the default)
  • Memcache
  • Redis
  • Groupcache

The later 3 are used only if configure on the command line.

In the future, the plan is to name the caches and allow requests to say which caching backend they'd like to use. Currently they all use the same one.

Stats generation

Tracking what APIs are used and how well they're performing is critical to understanding. When requests flow through templar, it can generate metrics about those requests and send them to statsd.

Just specify a statsd host via -statsd and templar will start sending them.

We'll support more metrics backends in the future.

Request categorization

Not all requests should use some of these features, for instance, request collapsing. So templar includes a categorizer to identify requests that it should apply additional handling to. It identifies a request as stateless or not. If it is stateless, then things like request collapsing and caching can be used.

By default, only GET requests are treated as stateless. The X-Templar-Category header allows the user to explicitly specify the category. The 2 valid values are stateful and stateless.

Again, a stateless request is subject to the following additional handling:

  • Request collapsing
  • Caching

Duration format

A number of headers take time durations, for instances, 30 seconds. These use the simple "(number)(unit)" parser, so for 1 second, use 1s and 5 minutes use 5m. Units supported are: ns, us, ms, s, m, and h.

Control Headers

Templar uses a number of headers to control how the requests are processed.

X-Templar-Cache

Possible values:

  • eager: Return a value from the cache before checking upstream
  • fallback: Return a value from the cache only if the upstream has issues
X-Templar-CacheFor

When caching, how long to cache the value for. If caching and this isn't set, the default is used.

X-Templar-Cached

Set on responses that are served from the cache.

X-Templar-Category

Possible values:

  • stateless: Process the request as stateless
  • stateful: Process the request as stateful
X-Templar-Timeout

Specifies how long to wait for the response before giving up.

X-Templar-TimedOut

Set to true on a response when the request timed out.

X-Templar-Upgrade

Possible values:

  • https: When connecting to the upstream, switch to https

Future features

  • Automatic caching based on HTTP Expire headers
  • Request throttling
  • Multiple active caching backends
  • Request stream inspection
  • Fire-and-forget requests
  • Return response via AMQP

Documentation

Index

Constants

View Source
const (
	CacheHeader       = "X-Templar-Cache"
	CacheTimeHeader   = "X-Templar-CacheFor"
	CacheCachedHeader = "X-Templar-Cached"
)
View Source
const CTimeoutHeader = "X-Templar-Timeout"
View Source
const CategoryHeader = "X-Templar-Category"
View Source
const TemplarPrefix = "X-Templar-"
View Source
const UpgradeHeader = "X-Templar-Upgrade"

Variables

View Source
var ErrTimeout = errors.New("request timed out")

Functions

func CopyBody

func CopyBody(dst io.Writer, src io.Reader)

func CopyResponse

func CopyResponse(res http.ResponseWriter, upstream *http.Response)

func NewGroupCacheCache

func NewGroupCacheCache(thisPeerURL string, otherPeersURLs string, defaultExpiration time.Duration, memoryLimit int64, transport Transport) *cache.GroupCacheCache

Types

type Cache

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

func NewMemcacheCache

func NewMemcacheCache(hostlist []string, expire time.Duration) *Cache

func NewMemoryCache

func NewMemoryCache(expire time.Duration) *Cache

func NewRedisCache

func NewRedisCache(host string, password string, expire time.Duration) *Cache

func (*Cache) Get

func (m *Cache) Get(req *http.Request) (*http.Response, bool)

func (*Cache) Set

func (m *Cache) Set(req *http.Request, resp *http.Response)

type CacheBackend

type CacheBackend interface {
	Set(req *http.Request, resp *http.Response)
	Get(req *http.Request) (*http.Response, bool)
}

type Categorizer

type Categorizer struct{}

func NewCategorizer

func NewCategorizer() *Categorizer

func (*Categorizer) Stateless

func (c *Categorizer) Stateless(req *http.Request) bool

type Client

type Client interface {
	Forward(res Responder, req *http.Request) error
}

type Collapser

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

func NewCollapser

func NewCollapser(client Client, categorizer *Categorizer) *Collapser

func (*Collapser) Forward

func (c *Collapser) Forward(res Responder, req *http.Request) error

type DebugStats

type DebugStats struct{}

func (*DebugStats) Emit

func (d *DebugStats) Emit(req *http.Request, dur time.Duration)

func (*DebugStats) RequestTimeout

func (d *DebugStats) RequestTimeout(req *http.Request, timeout time.Duration)

func (*DebugStats) StartRequest

func (d *DebugStats) StartRequest(req *http.Request)

type EagerCacher

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

func NewEagerCacher

func NewEagerCacher(backend CacheBackend, transport Transport, categorizer *Categorizer) *EagerCacher

func (*EagerCacher) CancelRequest

func (c *EagerCacher) CancelRequest(req *http.Request)

func (*EagerCacher) RoundTrip

func (c *EagerCacher) RoundTrip(req *http.Request) (*http.Response, error)

type Fallback

type Fallback interface {
	Fallback(*http.Request) (*http.Response, error)
}

type FallbackCacher

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

func NewFallbackCacher

func NewFallbackCacher(backend CacheBackend, transport Transport, categorizer *Categorizer) *FallbackCacher

func (*FallbackCacher) CancelRequest

func (c *FallbackCacher) CancelRequest(req *http.Request)

func (*FallbackCacher) Fallback

func (c *FallbackCacher) Fallback(req *http.Request) (*http.Response, error)

func (*FallbackCacher) RoundTrip

func (c *FallbackCacher) RoundTrip(req *http.Request) (*http.Response, error)

type Finisher

type Finisher interface {
	Finish()
}

type HTTPTransport

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

func (*HTTPTransport) CancelRequest

func (h *HTTPTransport) CancelRequest(req *http.Request)

func (*HTTPTransport) RoundTrip

func (h *HTTPTransport) RoundTrip(req *http.Request) (*http.Response, error)

type MockCacheBackend

type MockCacheBackend struct {
	mock.Mock
}

func (*MockCacheBackend) Get

func (m *MockCacheBackend) Get(req *http.Request) (*http.Response, bool)

func (*MockCacheBackend) Set

func (m *MockCacheBackend) Set(req *http.Request, resp *http.Response)

type MockClient

type MockClient struct {
	mock.Mock
}

func (*MockClient) Forward

func (m *MockClient) Forward(res Responder, req *http.Request) error

type MockResponder

type MockResponder struct {
	mock.Mock
}

func (*MockResponder) Send

func (m *MockResponder) Send(resp *http.Response) io.Writer

type MockRiemannClient

type MockRiemannClient struct {
	mock.Mock
}

func (*MockRiemannClient) Send

func (r *MockRiemannClient) Send(e *raidman.Event) error

type MockStats

type MockStats struct {
	mock.Mock
}

func (*MockStats) Emit

func (m *MockStats) Emit(req *http.Request, dur time.Duration)

func (*MockStats) RequestTimeout

func (m *MockStats) RequestTimeout(req *http.Request, timeout time.Duration)

func (*MockStats) StartRequest

func (m *MockStats) StartRequest(req *http.Request)

type MockStatsdClient

type MockStatsdClient struct {
	mock.Mock
}

func (*MockStatsdClient) GaugeDelta

func (m *MockStatsdClient) GaugeDelta(name string, delta int64) error

func (*MockStatsdClient) Incr

func (m *MockStatsdClient) Incr(name string, count int64) error

func (*MockStatsdClient) PrecisionTiming

func (m *MockStatsdClient) PrecisionTiming(name string, t time.Duration) error

type MockTransport

type MockTransport struct {
	mock.Mock
}

func (*MockTransport) CancelRequest

func (m *MockTransport) CancelRequest(req *http.Request)

func (*MockTransport) RoundTrip

func (m *MockTransport) RoundTrip(_a0 *http.Request) (*http.Response, error)

type MultiStats

type MultiStats []Stats

func (MultiStats) Emit

func (m MultiStats) Emit(req *http.Request, t time.Duration)

func (MultiStats) RequestTimeout

func (m MultiStats) RequestTimeout(req *http.Request, timeout time.Duration)

func (MultiStats) StartRequest

func (m MultiStats) StartRequest(req *http.Request)

type Proxy

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

func NewProxy

func NewProxy(cl Client, stats Stats) *Proxy

func (*Proxy) ServeHTTP

func (p *Proxy) ServeHTTP(res http.ResponseWriter, req *http.Request)

type Responder

type Responder interface {
	Send(resp *http.Response) io.Writer
}

type RiemannClient

type RiemannClient interface {
	Send(*raidman.Event) error
}

type RiemannOutput

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

func NewRiemannOutput

func NewRiemannOutput(client RiemannClient) *RiemannOutput

func (*RiemannOutput) Emit

func (r *RiemannOutput) Emit(req *http.Request, delta time.Duration)

func (*RiemannOutput) RequestTimeout

func (r *RiemannOutput) RequestTimeout(req *http.Request, timeout time.Duration)

func (*RiemannOutput) StartRequest

func (r *RiemannOutput) StartRequest(req *http.Request)

type RunningRequest

type RunningRequest struct {
	Request *http.Request
	// contains filtered or unexported fields
}

type Stats

type Stats interface {
	StartRequest(req *http.Request)
	Emit(req *http.Request, dur time.Duration)
	RequestTimeout(req *http.Request, timeout time.Duration)
}

type StatsdClient

type StatsdClient interface {
	Incr(name string, count int64) error
	GaugeDelta(name string, delta int64) error
	PrecisionTiming(name string, t time.Duration) error
}

type StatsdOutput

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

func NewStatsdOutput

func NewStatsdOutput(client StatsdClient) *StatsdOutput

func (*StatsdOutput) Emit

func (s *StatsdOutput) Emit(req *http.Request, delta time.Duration)

func (*StatsdOutput) RequestTimeout

func (s *StatsdOutput) RequestTimeout(req *http.Request, timeout time.Duration)

func (*StatsdOutput) StartRequest

func (s *StatsdOutput) StartRequest(req *http.Request)

type Transport

type Transport interface {
	RoundTrip(*http.Request) (*http.Response, error)
	CancelRequest(req *http.Request)
}

func NewHTTPTransport

func NewHTTPTransport() Transport

type Upstream

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

func NewUpstream

func NewUpstream(client Transport, stats Stats) *Upstream

func (*Upstream) Forward

func (t *Upstream) Forward(res Responder, req *http.Request) error

Directories

Path Synopsis
cmd

Jump to

Keyboard shortcuts

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