• Stars
    star
    2,623
  • Rank 17,456 (Top 0.4 %)
  • Language
    Go
  • License
    Apache License 2.0
  • Created almost 7 years ago
  • Updated 6 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

An enhanced HTTP client for Go

Heimdall

Build Status

Description

Heimdall is an HTTP client that helps your application make a large number of requests, at scale. With Heimdall, you can:

  • Use a hystrix-like circuit breaker to control failing requests
  • Add synchronous in-memory retries to each request, with the option of setting your own retrier strategy
  • Create clients with different timeouts for every request

All HTTP methods are exposed as a fluent interface.

Installation

go get -u github.com/gojek/heimdall/v7

Usage

Importing the package

This package can be used by adding the following import statement to your .go files.

import "github.com/gojek/heimdall/v7/httpclient" 

Making a simple GET request

The below example will print the contents of the google home page:

// Create a new HTTP client with a default timeout
timeout := 1000 * time.Millisecond
client := httpclient.NewClient(httpclient.WithHTTPTimeout(timeout))

// Use the clients GET method to create and execute the request
res, err := client.Get("http://google.com", nil)
if err != nil{
	panic(err)
}

// Heimdall returns the standard *http.Response object
body, err := ioutil.ReadAll(res.Body)
fmt.Println(string(body))

You can also use the *http.Request object with the http.Do interface :

timeout := 1000 * time.Millisecond
client := httpclient.NewClient(httpclient.WithHTTPTimeout(timeout))

// Create an http.Request instance
req, _ := http.NewRequest(http.MethodGet, "http://google.com", nil)
// Call the `Do` method, which has a similar interface to the `http.Do` method
res, err := client.Do(req)
if err != nil {
	panic(err)
}

body, err := ioutil.ReadAll(res.Body)
fmt.Println(string(body))

Creating a hystrix-like circuit breaker

To import hystrix package of heimdall.

import "github.com/gojek/heimdall/v7/hystrix"

You can use the hystrix.NewClient function to create a client wrapped in a hystrix-like circuit breaker:

// Create a new hystrix-wrapped HTTP client with the command name, along with other required options
client := hystrix.NewClient(
	hystrix.WithHTTPTimeout(10 * time.Millisecond),
	hystrix.WithCommandName("google_get_request"),
	hystrix.WithHystrixTimeout(1000 * time.Millisecond),
	hystrix.WithMaxConcurrentRequests(30),
	hystrix.WithErrorPercentThreshold(20),
	hystrix.WithStatsDCollector("localhost:8125", "myapp.hystrix"),
)

// The rest is the same as the previous example

In the above example, there are two timeout values used: one for the hystrix configuration, and one for the HTTP client configuration. The former determines the time at which hystrix should register an error, while the latter determines when the client itself should return a timeout error. Unless you have any special requirements, both of these would have the same values.

You can choose to export hystrix metrics to a statsD collector with the hystrix.WithStatsDCollector(<statsd addr>, <metrics-prefix>) option when initializing the client as shown above.

Creating a hystrix-like circuit breaker with fallbacks

You can use the hystrix.NewClient function to create a client wrapped in a hystrix-like circuit breaker by passing in your own custom fallbacks:

The fallback function will trigger when your code returns an error, or whenever it is unable to complete based on a variety of health checks.

How your fallback function should look like you should pass in a function whose signature looks like following

func(err error) error {
    // your logic for handling the error/outage condition
    return err
}

Example

// Create a new fallback function
fallbackFn := func(err error) error {
    _, err := http.Post("post_to_channel_two")
    return err
}

timeout := 10 * time.Millisecond

// Create a new hystrix-wrapped HTTP client with the fallbackFunc as fall-back function
client := hystrix.NewClient(
	hystrix.WithHTTPTimeout(timeout),
	hystrix.WithCommandName("MyCommand"),
	hystrix.WithHystrixTimeout(1100 * time.Millisecond),
	hystrix.WithMaxConcurrentRequests(100),
	hystrix.WithErrorPercentThreshold(20),
	hystrix.WithSleepWindow(10),
	hystrix.WithRequestVolumeThreshold(10),
	hystrix.WithFallbackFunc(fallbackFn),
})

// The rest is the same as the previous example

In the above example, the fallbackFunc is a function which posts to channel two in case posting to channel one fails.

Creating an HTTP client with a retry mechanism

// First set a backoff mechanism. Constant backoff increases the backoff at a constant rate
backoffInterval := 2 * time.Millisecond
// Define a maximum jitter interval. It must be more than 1*time.Millisecond
maximumJitterInterval := 5 * time.Millisecond

backoff := heimdall.NewConstantBackoff(backoffInterval, maximumJitterInterval)

// Create a new retry mechanism with the backoff
retrier := heimdall.NewRetrier(backoff)

timeout := 1000 * time.Millisecond
// Create a new client, sets the retry mechanism, and the number of times you would like to retry
client := httpclient.NewClient(
	httpclient.WithHTTPTimeout(timeout),
	httpclient.WithRetrier(retrier),
	httpclient.WithRetryCount(4),
)

// The rest is the same as the first example

Or create client with exponential backoff

// First set a backoff mechanism. Exponential Backoff increases the backoff at a exponential rate

initalTimeout := 2*time.Millisecond            // Inital timeout
maxTimeout := 9*time.Millisecond               // Max time out
exponentFactor := 2                            // Multiplier
maximumJitterInterval := 2*time.Millisecond    // Max jitter interval. It must be more than 1*time.Millisecond

backoff := heimdall.NewExponentialBackoff(initalTimeout, maxTimeout, exponentFactor, maximumJitterInterval)

// Create a new retry mechanism with the backoff
retrier := heimdall.NewRetrier(backoff)

timeout := 1000 * time.Millisecond
// Create a new client, sets the retry mechanism, and the number of times you would like to retry
client := httpclient.NewClient(
	httpclient.WithHTTPTimeout(timeout),
	httpclient.WithRetrier(retrier),
	httpclient.WithRetryCount(4),
)

// The rest is the same as the first example

This will create an HTTP client which will retry every 500 milliseconds incase the request fails. The library also comes with an Exponential Backoff

Custom retry mechanisms

Heimdall supports custom retry strategies. To do this, you will have to implement the Backoff interface:

type Backoff interface {
	Next(retry int) time.Duration
}

Let's see an example of creating a client with a linearly increasing backoff time:

First, create the backoff mechanism:

type linearBackoff struct {
	backoffInterval int
}

func (lb *linearBackoff) Next(retry int) time.Duration{
	if retry <= 0 {
		return 0 * time.Millisecond
	}
	return time.Duration(retry * lb.backoffInterval) * time.Millisecond
}

This will create a backoff mechanism, where the retry time will increase linearly for each retry attempt. We can use this to create the client, just like the last example:

backoff := &linearBackoff{100}
retrier := heimdall.NewRetrier(backoff)

timeout := 1000 * time.Millisecond
// Create a new client, sets the retry mechanism, and the number of times you would like to retry
client := httpclient.NewClient(
	httpclient.WithHTTPTimeout(timeout),
	httpclient.WithRetrier(retrier),
	httpclient.WithRetryCount(4),
)

// The rest is the same as the first example

Heimdall also allows you to simply pass a function that returns the retry timeout. This can be used to create the client, like:

linearRetrier := NewRetrierFunc(func(retry int) time.Duration {
	if retry <= 0 {
		return 0 * time.Millisecond
	}
	return time.Duration(retry) * time.Millisecond
})

timeout := 1000 * time.Millisecond
client := httpclient.NewClient(
	httpclient.WithHTTPTimeout(timeout),
	httpclient.WithRetrier(linearRetrier),
	httpclient.WithRetryCount(4),
)

Custom HTTP clients

Heimdall supports custom HTTP clients. This is useful if you are using a client imported from another library and/or wish to implement custom logging, cookies, headers etc for each request that you make with your client.

Under the hood, the httpClient struct now accepts Doer, which is the standard interface implemented by HTTP clients (including the standard library's net/*http.Client)

Let's say we wish to add authorization headers to all our requests.

We can define our client myHTTPClient

type myHTTPClient struct {
	client http.Client
}

func (c *myHTTPClient) Do(request *http.Request) (*http.Response, error) {
	request.SetBasicAuth("username", "passwd")
	return c.client.Do(request)
}

And set this with httpclient.NewClient(httpclient.WithHTTPClient(&myHTTPClient{client: http.DefaultClient}))

Now, each sent request will have the Authorization header to use HTTP basic authentication with the provided username and password.

This can be done for the hystrix client as well

client := httpclient.NewClient(
	httpclient.WithHTTPClient(&myHTTPClient{
		client: http.Client{Timeout: 25 * time.Millisecond},
	}),
)

// The rest is the same as the first example

Plugins

To add a plugin to an existing client, use the AddPlugin method of the client.

An example, with the request logger plugin:

// import "github.com/gojek/heimdall/v7/plugins"

client := heimdall.NewHTTPClient(timeout)
requestLogger := plugins.NewRequestLogger(nil, nil)
client.AddPlugin(requestLogger)
// use the client as before

req, _ := http.NewRequest(http.MethodGet, "http://google.com", nil)
res, err := client.Do(req)
if err != nil {
	panic(err)
}
// This will log:
//23/Jun/2018 12:48:04 GET http://google.com 200 [412ms]
// to STDOUT

A plugin is an interface whose methods get called during key events in a requests lifecycle:

  • OnRequestStart is called just before the request is made
  • OnRequestEnd is called once the request has successfully executed
  • OnError is called is the request failed

Each method is called with the request object as an argument, with OnRequestEnd, and OnError additionally being called with the response and error instances respectively. For a simple example on how to write plugins, look at the request logger plugin.

Documentation

Further documentation can be found on pkg.go.dev

FAQ

Can I replace the standard Go HTTP client with Heimdall?

Yes, you can. Heimdall implements the standard HTTP Do method, along with useful wrapper methods that provide all the functionality that a regular Go HTTP client provides.


When should I use Heimdall?

If you are making a large number of HTTP requests, or if you make requests among multiple distributed nodes, and wish to make your systems more fault tolerant, then Heimdall was made for you.

Heimdall makes use of multiple mechanisms to make HTTP requests more fault tolerant:

  1. Retries - If a request fails, Heimdall retries behind the scenes, and returns the result if one of the retries are successful.
  2. Circuit breaking - If Heimdall detects that too many of your requests are failing, or that the number of requests sent are above a configured threshold, then it "opens the circuit" for a short period of time, which prevents any more requests from being made. This gives your downstream systems time to recover.

So does this mean that I shouldn't use Heimdall for small scale applications?

Although Heimdall was made keeping large scale systems in mind, it's interface is simple enough to be used for any type of systems. In fact, we use it for our pet projects as well. Even if you don't require retries or circuit breaking features, the simpler HTTP client provides sensible defaults with a simpler interface, and can be upgraded easily should the need arise.


Can I contribute to make Heimdall better?

Please do! We are looking for any kind of contribution to improve Heimdalls core funtionality and documentation. When in doubt, make a PR!

License

Copyright 2018-2020, GO-JEK Tech (http://gojek.tech)

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

More Repositories

1

awesome-distributed-systems

Awesome list of distributed systems resources
811
star
2

weaver

An Advanced HTTP Reverse Proxy with Dynamic Sharding Strategies
Go
601
star
3

ziggurat

A stream processing framework to build stateless applications on Kafka
Clojure
397
star
4

draftsman

Draftsman is an on device layout inspector which can be embedded in your android app.
Kotlin
255
star
5

wrest

A fluent, easy-to-use, object oriented Ruby HTTP/REST client library with support RFC2616 HTTP caching and async calls that runs on CRuby and JRuby.
Ruby
234
star
6

darkroom

Go
224
star
7

consul-envoy-xds

Envoy XDS backed by Consul
Go
163
star
8

courier-android

Kotlin library for creating long running connections using MQTT protocol
Kotlin
140
star
9

merlin

Kubernetes-friendly ML model management, deployment, and serving.
Go
128
star
10

proctor

A Developer-Friendly Automation Orchestrator
Go
123
star
11

clickstream-android

A Modern, Fast, and Lightweight Android Library Ingestion Platform.
Kotlin
70
star
12

CureIAM

Clean accounts over permissions in GCP infra at scale
Python
70
star
13

kingsly

Your own x.509 cert manager
Ruby
67
star
14

turing

Fast, scalable and extensible system to deploy and evaluate ML experiments in production
Go
61
star
15

valkyrie

Go wrapper for handling zero or more errors
Go
60
star
16

dollhouse

Python
60
star
17

xp

Extreme Programming made simple
Go
60
star
18

go-coverage

Drive higher confidence in making changes by detecting large blocks of untested functionality
Go
58
star
19

courier-go

courier-go
Go
44
star
20

mlp

A platform for developing and operating the machine learning systems at the various stages of machine learning life cycle.
Go
44
star
21

courier-flutter

Dart port of our popular courier library
Dart
43
star
22

kafqa

Quality tool for kafka, verifying kafka ops
Go
42
star
23

courier-iOS

Courier iOS
Swift
39
star
24

clickstream-ios

A Modern, Fast, and Lightweight iOS Library Ingestion Platform.
Swift
37
star
25

kat

Swiss Knife for Kafka admin operations
Go
32
star
26

charts

Kubernetes Helm Charts
Mustache
28
star
27

bulwark

Hystrix for Clojurists
Clojure
23
star
28

StorageToolKit-iOS

StorageToolKit aims to be a set of tools that works together to identify and optimize disk usage.
Swift
19
star
29

gojek.github.io

Gojek Technologies Website 👻 🕸 hosted with ❤️ by GitHub
HTML
16
star
30

courier

Swift
16
star
31

meniscus

Fire concurrent HTTP requests, return partial successes after a timeout
Go
15
star
32

gojek

GO-JEK Technologies Website Source 👻 🕸 💻
HTML
15
star
33

fiber

Library for building dynamic proxies, routers and traffic mixers from a set of composable abstract network components
Go
14
star
34

next.gojek

JavaScript
11
star
35

nsxt_exporter

Simple server that scrapes NSX-T stats and exports them via HTTP for Prometheus consumption
Go
10
star
36

WorkManager

Swift
9
star
37

sentry-clj.async

Async Processor for pushing events to sentry
Clojure
9
star
38

turing-experiments

ML Experimentation Platform
Go
8
star
39

clickstream-web

A Modern, Fast, and Lightweight Event Ingestion library for Web
JavaScript
7
star
40

optimus-extension-valor

Go
6
star
41

homebrew-tap

Homebrew Formulas for GO-JEK OSS Tools
Ruby
5
star
42

conventional-changelog-angular-asana

asana task references support for your conventional commits
JavaScript
5
star
43

twemproxy-docker

Alpine docker build for twemproxy
Shell
4
star
44

GopherCon

GO-JEK's Code Challenges + Quizzes at GopherCon 🐵 💻 🌮
4
star
45

statsd-docker

Dockerized version of StatsD with console backend.
JavaScript
4
star
46

ziggurat-web

Home of all things Ziggurat.
JavaScript
3
star
47

postcss-customprop-validate

PostCSS plugin to validate fallback values of CSS custom properties
JavaScript
3
star
48

vision

Ruby
3
star
49

lua-dev

Lua / Luajit / Luarocks image for dev
Dockerfile
2
star
50

old.gojek

next.gojek.io Website Source
JavaScript
2
star
51

docker-kong-plugin-dev

base docker image for Kong with Plugins dev, testing and setup
Python
1
star