Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ jobs:
- "config"
- "generate"
- "healthcheck"
- "httpclient"
- "log"
- "trace"

Expand Down
31 changes: 31 additions & 0 deletions .github/workflows/httpclient-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: "httpclient-ci"

on:
push:
branches:
- "feat**"
- "fix**"
- "hotfix**"
- "chore**"
paths:
- "httpclient/**.go"
- "httpclient/go.mod"
- "httpclient/go.sum"
pull_request:
types:
- opened
- synchronize
- reopened
branches:
- main
paths:
- "httpclient/**.go"
- "httpclient/go.mod"
- "httpclient/go.sum"

jobs:
ci:
uses: ./.github/workflows/common-ci.yml
secrets: inherit
with:
module: "httpclient"
66 changes: 66 additions & 0 deletions httpclient/.golangci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
run:
timeout: 5m
concurrency: 8

linters:
enable:
- asasalint
- asciicheck
- bidichk
- bodyclose
- containedctx
- contextcheck
- cyclop
- decorder
- dogsled
- dupl
- durationcheck
- errcheck
- errchkjson
- errname
- errorlint
- exhaustive
- forbidigo
- forcetypeassert
- gocognit
- goconst
- gocritic
- gocyclo
- godot
- godox
- gofmt
- goheader
- gomoddirectives
- gomodguard
- goprintffuncname
- gosec
- gosimple
- govet
- grouper
- importas
- ineffassign
- interfacebloat
- logrlint
- maintidx
- makezero
- misspell
- nestif
- nilerr
- nilnil
- nlreturn
- nolintlint
- nosprintfhostport
- prealloc
- predeclared
- promlinter
- reassign
- staticcheck
- tenv
- thelper
- tparallel
- typecheck
- unconvert
- unparam
- unused
- usestdlibvars
- whitespace
167 changes: 167 additions & 0 deletions httpclient/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
# Http Client Module

[![ci](https://github.com/ankorstore/yokai/actions/workflows/httpclient-ci.yml/badge.svg)](https://github.com/ankorstore/yokai/actions/workflows/httpclient-ci.yml)
[![go report](https://goreportcard.com/badge/github.com/ankorstore/yokai/httpclient)](https://goreportcard.com/report/github.com/ankorstore/yokai/httpclient)
[![codecov](https://codecov.io/gh/ankorstore/yokai/graph/badge.svg?token=5s0g5WyseS&flag=httpclient)](https://app.codecov.io/gh/ankorstore/yokai/tree/main/httpclient)
[![PkgGoDev](https://pkg.go.dev/badge/github.com/ankorstore/yokai/httpclient)](https://pkg.go.dev/github.com/ankorstore/yokai/httpclient)

> Http client module based on [net/http](https://pkg.go.dev/net/http).

<!-- TOC -->

* [Installation](#installation)
* [Documentation](#documentation)
* [Requests](#requests)
* [Transports](#transports)
* [BaseTransport](#basetransport)
* [LoggerTransport](#loggertransport)

<!-- TOC -->

## Installation

```shell
go get github.com/ankorstore/yokai/httpclient
```

## Documentation

To create a `http.Client`:

```go
package main

import (
"time"

"github.com/ankorstore/yokai/httpclient"
"github.com/ankorstore/yokai/httpclient/transport"
)

var client, _ = httpclient.NewDefaultHttpClientFactory().Create()

// equivalent to:
var client, _ = httpclient.NewDefaultHttpClientFactory().Create(
httpclient.WithTransport(transport.NewBaseTransport()), // base http transport (optimized)
httpclient.WithTimeout(30*time.Second), // 30 seconds timeout
httpclient.WithCheckRedirect(nil), // default redirection checks
httpclient.WithCookieJar(nil), // default cookie jar
)
```

### Requests

This module provide some [request helpers](request.go) to ease client requests headers propagation from an incoming
request:

- `CopyObservabilityRequestHeaders` to copy `x-request-id` and `traceparent` headers
- `CopyRequestHeaders` to choose a list of headers to copy

For example:

```go
package main

import (
"net/http"

"github.com/ankorstore/yokai/httpclient"
)

func exampleHandler(w http.ResponseWriter, r *http.Request) {
// create http client
client, _ := httpclient.NewDefaultHttpClientFactory().Create()

// build a request to send with the client
rc, _ := http.NewRequest(http.MethodGet, "https://example.com", nil)

// propagate observability headers: x-request-id and traceparent
httpclient.CopyObservabilityRequestHeaders(r, rc)

// client call
resp, _ := client.Do(rc)

// propagate response code
w.WriteHeader(resp.StatusCode)
}

func main() {
http.HandleFunc("/", exampleHandler)
http.ListenAndServe(":8080", nil)
}
```

### Transports

#### BaseTransport

This module provide a [BaseTransport](transport/base.go), optimized regarding max connections handling.

To use it:

```go
package main

import (
"github.com/ankorstore/yokai/httpclient"
"github.com/ankorstore/yokai/httpclient/transport"
)

var client, _ = httpclient.NewDefaultHttpClientFactory().Create(
httpclient.WithTransport(transport.NewBaseTransport()),
)

// equivalent to:
var client, _ = httpclient.NewDefaultHttpClientFactory().Create(
httpclient.WithTransport(
transport.NewBaseTransportWithConfig(&transport.BaseTransportConfig{
MaxIdleConnections: 100,
MaxConnectionsPerHost: 100,
MaxIdleConnectionsPerHost: 100,
}),
),
)
```

#### LoggerTransport

This module provide a [LoggerTransport](transport/logger.go), able to decorate any `http.RoundTripper` to add logging:

- with requests and response details (and optionally body)
- with configurable log level for each

To use it:

```go
package main

import (
"github.com/ankorstore/yokai/httpclient"
"github.com/ankorstore/yokai/httpclient/transport"
"github.com/rs/zerolog"
)

var client, _ = httpclient.NewDefaultHttpClientFactory().Create(
httpclient.WithTransport(transport.NewLoggerTransport(nil)),
)

// equivalent to:
var client, _ = httpclient.NewDefaultHttpClientFactory().Create(
httpclient.WithTransport(
transport.NewLoggerTransportWithConfig(
transport.NewBaseTransport(),
&transport.LoggerTransportConfig{
LogRequest: false, // to log request details
LogResponse: false, // to log response details
LogRequestBody: false, // to log request body (if request details logging enabled)
LogResponseBody: false, // to log response body (if response details logging enabled)
LogRequestLevel: zerolog.InfoLevel, // log level for request log
LogResponseLevel: zerolog.InfoLevel, // log level for response log
LogResponseLevelFromResponseCode: false, // to use response code for response log level
},
),
),
)
```

Note: if no transport is provided for decoration in `transport.NewLoggerTransport(nil)`, the [BaseTransport](transport/base.go) will be used as base transport.
62 changes: 62 additions & 0 deletions httpclient/coverage.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
mode: atomic
github.com/ankorstore/yokai/httpclient/factory.go:16.54,18.2 1 2
github.com/ankorstore/yokai/httpclient/factory.go:32.94,34.35 2 1
github.com/ankorstore/yokai/httpclient/factory.go:34.35,36.3 1 3
github.com/ankorstore/yokai/httpclient/factory.go:38.2,43.8 1 1
github.com/ankorstore/yokai/httpclient/option.go:19.41,26.2 1 5
github.com/ankorstore/yokai/httpclient/option.go:32.58,33.26 1 1
github.com/ankorstore/yokai/httpclient/option.go:33.26,35.3 1 1
github.com/ankorstore/yokai/httpclient/option.go:39.95,40.26 1 2
github.com/ankorstore/yokai/httpclient/option.go:40.26,42.3 1 2
github.com/ankorstore/yokai/httpclient/option.go:46.55,47.26 1 2
github.com/ankorstore/yokai/httpclient/option.go:47.26,49.3 1 2
github.com/ankorstore/yokai/httpclient/option.go:53.52,54.26 1 2
github.com/ankorstore/yokai/httpclient/option.go:54.26,56.3 1 2
github.com/ankorstore/yokai/httpclient/request.go:11.86,12.33 1 2
github.com/ankorstore/yokai/httpclient/request.go:12.33,15.63 2 4
github.com/ankorstore/yokai/httpclient/request.go:15.63,17.4 1 5
github.com/ankorstore/yokai/httpclient/request.go:22.80,24.2 1 1
github.com/ankorstore/yokai/httpclient/transport/base.go:21.40,29.2 1 5
github.com/ankorstore/yokai/httpclient/transport/base.go:32.77,44.2 5 5
github.com/ankorstore/yokai/httpclient/transport/base.go:47.48,49.2 1 0
github.com/ankorstore/yokai/httpclient/transport/base.go:52.78,54.2 1 0
github.com/ankorstore/yokai/httpclient/transport/logger.go:30.66,43.2 1 0
github.com/ankorstore/yokai/httpclient/transport/logger.go:46.107,47.17 1 0
github.com/ankorstore/yokai/httpclient/transport/logger.go:47.17,49.3 1 0
github.com/ankorstore/yokai/httpclient/transport/logger.go:51.2,54.3 1 0
github.com/ankorstore/yokai/httpclient/transport/logger.go:58.52,60.2 1 0
github.com/ankorstore/yokai/httpclient/transport/logger.go:63.80,68.25 3 0
github.com/ankorstore/yokai/httpclient/transport/logger.go:68.25,70.17 2 0
github.com/ankorstore/yokai/httpclient/transport/logger.go:70.17,72.4 1 0
github.com/ankorstore/yokai/httpclient/transport/logger.go:75.2,85.47 6 0
github.com/ankorstore/yokai/httpclient/transport/logger.go:85.47,86.10 1 0
github.com/ankorstore/yokai/httpclient/transport/logger.go:87.101,88.27 1 0
github.com/ankorstore/yokai/httpclient/transport/logger.go:89.58,90.28 1 0
github.com/ankorstore/yokai/httpclient/transport/logger.go:91.11,92.57 1 0
github.com/ankorstore/yokai/httpclient/transport/logger.go:94.8,96.3 1 0
github.com/ankorstore/yokai/httpclient/transport/logger.go:98.2,98.26 1 0
github.com/ankorstore/yokai/httpclient/transport/logger.go:98.26,100.17 2 0
github.com/ankorstore/yokai/httpclient/transport/logger.go:100.17,102.4 1 0
github.com/ankorstore/yokai/httpclient/transport/logger.go:105.2,112.18 2 0
github.com/ankorstore/yokai/httpclient/transport/base.go:21.40,29.2 1 5
github.com/ankorstore/yokai/httpclient/transport/base.go:32.77,44.2 5 6
github.com/ankorstore/yokai/httpclient/transport/base.go:47.48,49.2 1 3
github.com/ankorstore/yokai/httpclient/transport/base.go:52.78,54.2 1 5
github.com/ankorstore/yokai/httpclient/transport/logger.go:30.66,43.2 1 3
github.com/ankorstore/yokai/httpclient/transport/logger.go:46.107,47.17 1 4
github.com/ankorstore/yokai/httpclient/transport/logger.go:47.17,49.3 1 3
github.com/ankorstore/yokai/httpclient/transport/logger.go:51.2,54.3 1 4
github.com/ankorstore/yokai/httpclient/transport/logger.go:58.52,60.2 1 1
github.com/ankorstore/yokai/httpclient/transport/logger.go:63.80,68.25 3 4
github.com/ankorstore/yokai/httpclient/transport/logger.go:68.25,70.17 2 3
github.com/ankorstore/yokai/httpclient/transport/logger.go:70.17,72.4 1 3
github.com/ankorstore/yokai/httpclient/transport/logger.go:75.2,85.47 6 4
github.com/ankorstore/yokai/httpclient/transport/logger.go:85.47,86.10 1 3
github.com/ankorstore/yokai/httpclient/transport/logger.go:87.101,88.27 1 1
github.com/ankorstore/yokai/httpclient/transport/logger.go:89.58,90.28 1 1
github.com/ankorstore/yokai/httpclient/transport/logger.go:91.11,92.57 1 1
github.com/ankorstore/yokai/httpclient/transport/logger.go:94.8,96.3 1 1
github.com/ankorstore/yokai/httpclient/transport/logger.go:98.2,98.26 1 4
github.com/ankorstore/yokai/httpclient/transport/logger.go:98.26,100.17 2 3
github.com/ankorstore/yokai/httpclient/transport/logger.go:100.17,102.4 1 3
github.com/ankorstore/yokai/httpclient/transport/logger.go:105.2,112.18 2 4
44 changes: 44 additions & 0 deletions httpclient/factory.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package httpclient

import (
"net/http"
)

// HttpClientFactory is the interface for [http.Client] factories.
type HttpClientFactory interface {
Create(opts ...HttpClientOption) (*http.Client, error)
}

// DefaultHttpClientFactory is the default [HttpClientFactory] implementation.
type DefaultHttpClientFactory struct{}

// NewDefaultHttpClientFactory returns a [DefaultHttpClientFactory], implementing [HttpClientFactory].
func NewDefaultHttpClientFactory() HttpClientFactory {
return &DefaultHttpClientFactory{}
}

// Create returns a new [http.Client], and accepts a list of [HttpClientOption].
// For example:
//
// var client, _ = httpclient.NewDefaultHttpClientFactory().Create()
//
// // equivalent to:
// var client, _ = httpclient.NewDefaultHttpClientFactory().Create(
// httpclient.WithTransport(transport.NewBaseTransport()), // base http transport (optimized)
// httpclient.WithTimeout(30*time.Second), // 30 seconds timeout
// httpclient.WithCheckRedirect(nil), // default redirection checks
// httpclient.WithCookieJar(nil), // default cookie jar
// )
func (f *DefaultHttpClientFactory) Create(options ...HttpClientOption) (*http.Client, error) {
appliedOpts := DefaultHttpClientOptions()
for _, applyOpt := range options {
applyOpt(&appliedOpts)
}

return &http.Client{
Transport: appliedOpts.Transport,
CheckRedirect: appliedOpts.CheckRedirect,
Jar: appliedOpts.Jar,
Timeout: appliedOpts.Timeout,
}, nil
}
Loading