Skip to content

Commit

Permalink
add license
Browse files Browse the repository at this point in the history
  • Loading branch information
dyaksa committed Aug 25, 2024
1 parent 779f1a0 commit 963fc94
Show file tree
Hide file tree
Showing 3 changed files with 210 additions and 11 deletions.
201 changes: 201 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
# Barbarian - Simple HTTP Client For Go

Barbarian is an simple HTTP client that helps your application with circuit breaker

All HTTP methods are exposed as a fluent interface.

## Installation

```
go get -u github.com/dyaksa/barbarian
```

## Usage

### Importing the package

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

```go
import "github.com/dyaksa/barbarian/httpclient"
```

### Making a simple `GET` request

The below example will print the contents:

```go
// Create a new HTTP client with a default timeout
client := httpclient.NewClient(&httpclient.Config{
BaseUrl: "http://localhost:3001", // baseurl
HTTPTimeout: 30 * time.Second, // http timeout
})

// Use the clients GET method to create and execute the request
res, err := client.Get(context.Background(), "/test")
if err != nil {
panic(err)
}
// Barbarian 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 :

```go
client := httpclient.NewClient(&httpclient.Config{
HTTPTimeout: 30 * time.Second, // http 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))
```

You can configure `CircuitBreaker` by the struct `Settings`:

```go
client := httpclient.NewClient(&httpclient.Config{
Name: "test",
ConsiderServerErrorAsFailure: true,
ServerErrorThreshold: 500,
Timeout: 30 * time.Second,
OnStateChange: func(name string, to, from httpclient.State) {
fmt.Printf("State change from %s to %s\n", from, to)
},
ReadyToTrip: func(cunts httpclient.Counts) bool {
return cunts.TotalFailures >= 2
},
})

req, _ := http.NewRequest(http.MethodGet, "http://google.com", nil)

if err != nil {
panic(err)
}

// 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))
```

- `Name` is the name of the `CircuitBreaker`.

- `ConsiderServerErrorAsFailure` Determines whether server errors should trigger the circuit breaker.

- `ServerErrorThreshold` Specifies the HTTP status code threshold at which the circuit breaker should open.

- `MaxRequests` is the maximum number of requests allowed to pass through
when the `CircuitBreaker` is half-open.
If `MaxRequests` is 0, `CircuitBreaker` allows only 1 request.

- `Interval` is the cyclic period of the closed state
for `CircuitBreaker` to clear the internal `Counts`, described later in this section.
If `Interval` is 0, `CircuitBreaker` doesn't clear the internal `Counts` during the closed state.

- `Timeout` is the period of the open state,
after which the state of `CircuitBreaker` becomes half-open.
If `Timeout` is 0, the timeout value of `CircuitBreaker` is set to 60 seconds.

- `ReadyToTrip` is called with a copy of `Counts` whenever a request fails in the closed state.
If `ReadyToTrip` returns true, `CircuitBreaker` will be placed into the open state.
If `ReadyToTrip` is `nil`, default `ReadyToTrip` is used.
Default `ReadyToTrip` returns true when the number of consecutive failures is more than 5.

- `OnStateChange` is called whenever the state of `CircuitBreaker` changes.

- `IsSuccessful` is called with the error returned from a request.
If `IsSuccessful` returns true, the error is counted as a success.
Otherwise the error is counted as a failure.
If `IsSuccessful` is nil, default `IsSuccessful` is used, which returns false for all non-nil errors.

You can call options `GET` by the method `Options`:

```go
client := httpclient.NewClient(&httpclient.Config{
Name: "http client",
BaseUrl: "http://localhost:3001",
})

headers := map[string]string{
"Content-Type": "application/json",
"Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c",
}

res, err := client.Get(context.Background(), "/test",
httpclient.WithHeaders(headers),
httpclient.BodyJSON(map[string]interface{}{"name": "John Doe"}),
)

if err != nil {
panic(err)
}

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

## Plugins

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

An example, with the [logger plugin](/plugins/logger.go):

```go
// import "github.com/dyaksa/barbarian/plugins"
client := httpclient.NewClient(&httpclient.Config{
Name: "http client",
BaseUrl: "http://localhost:3001",
HTTPTimout: 30 * time.Second
})

logger := plugins.NewLogger(nil, nil)
client.AddPlugin(logger)

res, err := client.Get(context.Background(), "/test", nil)
if err != nil {
panic(err)
}

// This will log:
//23/Aug/2024 12:48:04 GET http://localhost:3001 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 [logger plugin](/plugins/logger.go).

## License

Copyright 2024

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.
14 changes: 3 additions & 11 deletions examples/test_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,9 @@ import (

func main() {
client := httpclient.NewClient(&httpclient.Config{
Name: "test",
BaseUrl: "http://localhost:3001",
ConsiderServerErrorAsFailure: true,
ServerErrorThreshold: 500,
Timeout: 30 * time.Second,
OnStateChange: func(name string, to, from httpclient.State) {
fmt.Printf("State change from %s to %s\n", from, to)
},
ReadyToTrip: func(cunts httpclient.Counts) bool {
return cunts.TotalFailures >= 2
},
Name: "test",
BaseUrl: "http://localhost:3001",
Timeout: 30 * time.Second,
})

logger := plugins.NewLogger(nil, nil)
Expand Down
6 changes: 6 additions & 0 deletions httpclient/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import (
type Config struct {
BaseUrl string

HTTPTimeout time.Duration

Name string
MaxRequests uint32
Interval time.Duration
Expand Down Expand Up @@ -43,6 +45,10 @@ func NewClient(config *Config) (c *Client) {
serverErrorThreshold: config.ServerErrorThreshold,
}

if config.HTTPTimeout != 0 {
c.httpClient.Timeout = config.HTTPTimeout
}

c.breaker = NewCircuitBreaker(Settings{
Name: config.Name,
MaxRequests: config.MaxRequests,
Expand Down

0 comments on commit 963fc94

Please sign in to comment.