Git Product home page Git Product logo

heimdall's Introduction

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.

heimdall's People

Contributors

apostov avatar ariefrahmansyah avatar darshanime avatar dcu avatar devdinu avatar dlahoza avatar flof avatar gwthm-in avatar italolelis avatar jamesdube avatar johanavril avatar kgrz avatar matias-barrios avatar mattias-lundell avatar medakk avatar mfrw avatar mwf avatar nydan avatar palash25 avatar plaguecoder avatar pravj avatar rshetty avatar shaaza avatar shubham7saxena avatar shubhamsaxena7 avatar sohamkamani avatar timusg avatar ukriish avatar vthiery avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

heimdall's Issues

Using gock with hystrix

Problem:
When I use hystix client, I want an ability to use a framework like https://github.com/h2non/gock to mock traffic for my testing purposes.

gock provides a means of doing roughly

hystrix := NewHystrixClient()
gock.InterceptClient(hystrix)
    gock.New("http://foo.com").
        Get("/bar").
        Reply(http.StatusOK).
        JSON(map[string]string{"foo": "bar"})

HystrixTimeout vs HTTPTimeout

What is the reason to have two timeouts: HystrixTimeout and HTTPTimeout for hystrix.Client ? It seems that two points are valid for current implementation:

  1. timeout field in hystrix.Client is set but never used (maybe bug ?)
  2. If any of the two events (HystrixTimeout or HTTPTimeout) occurs first then request will fail immediately. So if HTTPTimeout < HystrixTimeout, then HystrixTimeout is never used. And if HystrixTimeout < HTTPTimeout then hystrix will return error but goroutine with httpclient.Do will be still in blocking state waiting for response or http timeout (problem in hystrix code).

[Enhancement] Get Response Body Directly

Usecase:
After using the HTTP client to Do the request. We want to directly unmarshal the response body into a certain struct. If it fails, we just want to know the status code of the API and returns accordingly.

The snippet of working code will be like:

responseBody := YourProgramResponseBodyStruct{}
httpStatusCode, err := heimdall.ResponseBody(req, &responseBody)

What good it will do:
It reduces the need to write again and again for decoding the response body. Saving lines of code with this abstraction

Data race found

adding following test into hystrix_client_test.gowill cause data race using go test -race

func TestHystrixHTTPClientGetReturnedURLTimeout(t *testing.T) {
	client := NewClient(
		WithHTTPTimeout(5*time.Millisecond),
		WithCommandName("some_command_name"),
		WithHystrixTimeout(20*time.Millisecond),
		WithMaxConcurrentRequests(2000),
		WithErrorPercentThreshold(10),
		WithSleepWindow(100),
		WithRequestVolumeThreshold(10000),
	)

	dummyHandler := func(w http.ResponseWriter, r *http.Request) {
		assert.Equal(t, http.MethodGet, r.Method)
		assert.Equal(t, "application/json", r.Header.Get("Content-Type"))
		assert.Equal(t, "en", r.Header.Get("Accept-Language"))

		w.WriteHeader(http.StatusOK)
		_, _ = w.Write([]byte(`{ "response": "ok" }`))
		time.Sleep(10 * time.Millisecond)
	}

	server := httptest.NewServer(http.HandlerFunc(dummyHandler))
	defer server.Close()

	headers := http.Header{}
	headers.Set("Content-Type", "application/json")
	headers.Set("Accept-Language", "en")

	wg := sync.WaitGroup{}

	for n := 0; n < 100; n++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			_, err := client.Get(server.URL, headers)
			require.Error(t, err, "should having Client.Timeout exceeded while awaiting headers error")
			assert.IsType(t, &url.Error{}, err)
		}()
	}
	wg.Wait()
}

Log:

WARNING: DATA RACE
Write at 0x00c00020a5c0 by goroutine 99:
  github.com/gojektech/heimdall/hystrix.(*Client).Do()
      /Users/ardhi/gopath/src/github.com/gojektech/heimdall/hystrix/hystrix_client.go:181 +0x3e1
  github.com/gojektech/heimdall/hystrix.(*Client).Get()
      /Users/ardhi/gopath/src/github.com/gojektech/heimdall/hystrix/hystrix_client.go:105 +0xca
  github.com/gojektech/heimdall/hystrix.TestHystrixHTTPClientGetReturnedURLTimeout.func2()
      /Users/ardhi/gopath/src/github.com/gojektech/heimdall/hystrix/hystrix_client_test.go:566 +0xab

Previous write at 0x00c00020a5c0 by goroutine 138:
  github.com/gojektech/heimdall/hystrix.(*Client).Do.func1()
      /Users/ardhi/gopath/src/github.com/gojektech/heimdall/hystrix/hystrix_client.go:182 +0xe4
  github.com/afex/hystrix-go/hystrix.Do.func1()
      /Users/ardhi/gopath/src/github.com/afex/hystrix-go/hystrix/hystrix.go:179 +0x4a
  github.com/afex/hystrix-go/hystrix.Go.func3()
      /Users/ardhi/gopath/src/github.com/afex/hystrix-go/hystrix/hystrix.go:140 +0x1d3

Goroutine 99 (running) created at:
  github.com/gojektech/heimdall/hystrix.TestHystrixHTTPClientGetReturnedURLTimeout()
      /Users/ardhi/gopath/src/github.com/gojektech/heimdall/hystrix/hystrix_client_test.go:564 +0x370
  testing.tRunner()
      /usr/local/Cellar/go/1.11.5/libexec/src/testing/testing.go:827 +0x162

Goroutine 138 (running) created at:
  github.com/afex/hystrix-go/hystrix.Go()
      /Users/ardhi/gopath/src/github.com/afex/hystrix-go/hystrix/hystrix.go:96 +0x551
  github.com/afex/hystrix-go/hystrix.Do()
      /Users/ardhi/gopath/src/github.com/afex/hystrix-go/hystrix/hystrix.go:200 +0x32d
  github.com/gojektech/heimdall/hystrix.(*Client).Do()
      /Users/ardhi/gopath/src/github.com/gojektech/heimdall/hystrix/hystrix_client.go:181 +0x3b9
  github.com/gojektech/heimdall/hystrix.(*Client).Get()
      /Users/ardhi/gopath/src/github.com/gojektech/heimdall/hystrix/hystrix_client.go:105 +0xca
  github.com/gojektech/heimdall/hystrix.TestHystrixHTTPClientGetReturnedURLTimeout.func2()
      /Users/ardhi/gopath/src/github.com/gojektech/heimdall/hystrix/hystrix_client_test.go:566 +0xab

Support for Asynchronous HTTP Client

  • Should support async http client with various methods already supported for HTTP and Hystrix client.
  • Something on these lines: https://godoc.org/golang.org/x/net/context/ctxhttp

Missing . in an example

screen shot 2018-11-21 at 11 45 24 am

Minor typo In Example (Creating a hystrix-like circuit breaker with fallbacks): hystrixWithRequestVolumeThreshold should be hystrix.WithRequestVolumeThreshold

Response body is not closed after retries, and possibly after a 5xx error

  1. For the client as well as the hystrix client, the response body is not closed before the next retry.
  2. If the HTTP status code is >= 500, the client returns an error. This may lead developers to not close the response body on their end.

(1) is pretty straightforward to fix (close the body before the next retry)

For (2) we need to decide whether to send an error for 5xx http status codes. If we decide to omit the error, this would lead to a change in contract, and we would potentially have to bump the major version

Hystrix no longer maintained

Hello ๐Ÿ‘‹

I have a question regarding the Hystrix implementation.

As most of you probably already know Hystrix is no longer supported by Netflix and they are moving towards a more adaptive concurrency limits. They are only improving in the resilience4j.

I do understand that Hystrix is more a pattern used for resilience than a lib, but if there is no further development in improving it maybe we should rethink if it makes sense to keep using it?
Is there any plans for this lib to shift to the same idea?

Let me know your thoughts.

Thanks!

Support for golang context

  • Support for golang context across both http and hystrix clients
    Something on these lines:
    https://godoc.org/golang.org/x/net/context/ctxhttp

Misleading example in README.md

In one of the examples in the README.md the client gets configured with hystrix.WithHystrixTimeout(1100). This is misleading because it would be interpreted as 1100 nanoseconds and not milliseconds. And since heimdall converts this duration at some point to milliseconds, the duration drops to 0 because of rounding. Which causes a default duration of 1000ms to take effect.
So everybody who uses this example gets a timeout of 1000ms and not 1100ms.
I suggest changing this to hystrix.WithHystrixTimeout(1100 * time.Millisecond).

Performance issue while uploading large files using heimdall client

if request.Body != nil {
reqData, err := ioutil.ReadAll(request.Body)
if err != nil {
return nil, err
}
bodyReader = bytes.NewReader(reqData)
request.Body = ioutil.NopCloser(bodyReader) // prevents closing the body between retries
}
The above piece of code form client.go is using ioutil.ReadAll to read all data from reqeust body. Doesn't that process have performance issue while uploading large files?

usage in concurrent application

Hi guys, I don't see any example of concurrent usage. Is it safe to use the same the same client object from multiple go-routine?

Incorrect formula for exponential backoff

Need to change the first plus sign here to a multiplication:

return time.Duration(math.Min(eb.initialTimeout+math.Pow(eb.exponentFactor, float64(retry)), eb.maxTimeout)+float64(rand.Int63n(eb.maximumJitterInterval))) * time.Millisecond

Exponential backoff with exponent factor 2 should be initialBackoff * 2^n.

Hystrix timeout calculation is wrong (nanosecond instead of millisecond)

Currently the timeout being set is by type casting time.Duration to an int; this is not how you should be converting time.

	hystrix.ConfigureCommand(client.hystrixCommandName, hystrix.CommandConfig{
		Timeout:                int(client.hystrixTimeout),
		MaxConcurrentRequests:  client.maxConcurrentRequests,
		RequestVolumeThreshold: client.requestVolumeThreshold,
		SleepWindow:            client.sleepWindow,
		ErrorPercentThreshold:  client.errorPercentThreshold,
	})

https://github.com/gojektech/heimdall/blob/master/hystrix/hystrix_client.go#L70

github.com/afex/hystrix-go/hystrix expects the timeout to be in milliseconds but an int on time.Duration would convert this to nanoseconds and not milliseconds.

P.S: Also interestingly with default values this conversion to panic with integer overflow.

Add Do method in Client

Do(* http.Requestt) (http.Response, error)

Sticking to http contract would help us to use this library in existing codebase, also using the *http.Response wouldn't hide information the default functionality.

Retry wrapped around hystrix call?

Hi.

Does it really make sense, that the http retry mechanism is outside the CB call?

For me this seems very unintuitive:

When the CB is open, there will still be retries and also time.Sleep() backoffs. Isn't the intent of an opened CB also for the calling system to get a fast feedback and don't waste time?

Even on a completely new request there is no check if the cb is currently open. Why would one do retries and spend time waiting on an open CB?

For me it would make more sense to do the retries inside the cb. So you define the following behavior:

  • remote systems may fail, but the CB should only count a failure after the retries did not help
  • if the remote system fails and the cb is open, do not even try to send requests, but immediately return an error

Or maybe I do get the idea wrong.

Please help clarify.

[ENHANCEMENT] Add "retryDoer/hystrixDoer" implementation

When comparing https://github.com/gojek/heimdall/blob/master/httpclient/client.go and https://github.com/gojek/heimdall/blob/master/hystrix/hystrix_client.go it seems that they only differ fundamentally in their respective Do methods. One could factor that part out into seperate Doer implementations and only have one httpclient implementation that delegates to a configurable Doer.

I would be willing to file a PR if that is acceptable.

Split out interfaces for Retrier vs HTTP Client

We have a common big interface for retrier related methods vs those for making http requests.
We could have Client interface for HTTP related methods and Retriable interface for retrier related methods, segregating the interfaces

Functional Options support

Hello,

Thank you a lot for this amazing library, it's really useful and well thought.
How would you feel about supporting function options? Something like this:

hystrixClient := heimdall.NewHystrixHTTPClient(
	timeout,
	heimdall.WithCommandName("MyCommand"),
	heimdall.WithHystrixTimeout(1100),
	heimdall.WithMaxConcurrentRequests(100),
	heimdall.WithErrorPercentThreshold(25),
	heimdall.WithSleepWindow(10),
	heimdall.WithRequestVolumeThreshold(10),
    heimdall.WithHystrixRetryCount(2),
	heimdall.WithHystrixRetrier(heimdall.NewRetrier(heimdall.NewConstantBackoff(10, 5))),
)

Is this something you'd like to see in the lib? If yes I can send a PR with it.

Thanks again!

Using hystrix for SSE connections

I want an SSE connection to be established for reading messages from the server, with predefined controls for circuit breaking. Do you have something that can work?

Doer & Client Interface Relationship

Hi, when a type implement Client
it automatically implement Doer
what do you think if Client embed Doer interface to explicitly state every type that implement Client also implement Doer ?
Happy to hear any thought

Not respecting tcp keep alive

This is the result from using normal http client vs heimdall client

( direct http client )
Time: 613.24249ms
Time: 73.776966ms
Time: 56.926805ms
Time: 76.188755ms
Time: 52.602725ms
Time: 181.545667ms
Time: 68.099089ms
Time: 80.868807ms
Time: 57.184804ms
Time: 61.28775ms

( heimdall)
Time: 440.696523ms
Time: 217.184449ms
Time: 263.74181ms
Time: 216.604801ms
Time: 380.274223ms
Time: 193.812942ms
Time: 227.070945ms
Time: 248.829814ms
Time: 239.477054ms
Time: 273.660091ms

not able to pass token in headers

my http get method call using heimdall

timeout := 1000 * time.Second
	client := httpclient.NewClient(httpclient.WithHTTPTimeout(timeout)
	req, err := client.Get(api, r.Header)

--
If i set the header its working else its an unmarshal error
heimdall code

// Get makes a HTTP GET request to provided URL
func (c *Client) Get(url string, headers http.Header) (*http.Response, error) {
	var response *http.Response
	request, _ := http.NewRequest(http.MethodGet, url, nil)
	
	request.Header.Set("Authorization", headers["Authorization"][0])
	request.Header = headers
//if i add the below line code my api returns success response
request.Header.Set("Authorization", request.Header["Authorization"][0])
	return c.Do(request)
}

heimdall code ref

Add Jitter in retry time

  • Multiple instances retrying at the same time could be a problem, rather add random jitters when we retry a request
    Retrying at the same introduces Thundering herd problem

Retrier called even with 0 retry count

  server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(500)
	}))
	defer server.Close()

	retryCalled := 0
	httpclient := httpclient.NewClient(
		httpclient.WithRetrier(heimdall.NewRetrierFunc(func(_ int) time.Duration {
			retryCalled++
			return 1 * time.Second
		})),
		httpclient.WithRetryCount(0),
	)
	req, err := http.NewRequest(http.MethodGet, server.URL, nil)
	assert.NoError(t, err)
	res, err := httpclient.Do(req)
	assert.NoError(t, err)
	_ = res
	assert.Equal(t, 0, retryCalled)

retrier still called with 0 retry count, is this expected?

Cannot import v5.1.0 due to "non optimal" go.mod

I am using go1.14.
I tried to import v5.1.0 with this command:

go get -u github.com/gojek/[email protected]

and it gave me this error:

go get github.com/gojek/[email protected]: github.com/gojek/[email protected]: invalid version: module contains a go.mod file, so major version must be compatible: should be v0 or v1, not v5

As stated in go mod wiki page, any package with major version larger than v0 and v1 must include its major version in the module name.

So the statement here module github.com/gojektech/heimdall should be appended with /v5 so that it would become module github.com/gojektech/heimdall/v5.

I don't know whether this change would broke backward compatibility for imports.
Example: will import "github.com/gojektech/heimdall/httpclient" be changed to import "github.com/gojektech/heimdall/v5/httpclient" and all will be ok?

If all is OK I can make a PR to this.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.