Skip to content

kraciasty/httpc

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

httpc

Go Reference Go Report Card GitHub go.mod Go version GitHub codecov

Enhance your HTTP clients with middleware support.
A Go package that simplifies HTTP client setup the Go way.
Enjoy simplicity and elegance with zero dependencies.

Dress the Gopher in whatever layers it needs!

Rationale

Becoming fed up with the lack of built-in HTTP client middleware support in the standard Go library, I created this package to reduce the need for repetitive wrappers in each project and organize things a bit with reusability in mind.

Features

An idiomatic net/http client experience, but with additional benefits:

  • seamless integration with http.Client and http.RoundTripper
  • reduced boilerplate (configure common things once)
  • zero dependencies (standard library-based)
  • extensible middleware support and a collection of common utilities

Quickstart

Get the package:

go get -u github.com/kraciasty/httpc

Import the package into your Go project:

import "github.com/kraciasty/httpc"
Example client and tripper usage

Usage with the client

Create a client instance with the desired middlewares.

// Create a new client with the desired middlewares.
client := httpc.NewClient(http.DefaultClient, middlewares...)

// Make requests just like with the standard http client.
resp, err := client.Do(req)

Use the clients' With method to create a sub-client with additional middlewares.

client := httpc.NewClient(http.DefaultClient, Foo, Bar) // uses Foo, Bar
subclient := client.With(Baz) // uses Foo, Bar, Baz

Usage with the transport

Use the RoundTripper middleware when you can't swap the http.Client or want to have the middleware specifically on the transport.

// Prepare a custom transport.
tr := httpc.NewRoundTripper(http.DefaultTransport, mws...)

// Use it in a http client or something that accepts a round-tripper.
client := &http.Client{Transport: tr}

// Make requests with the middleware-wrapped transport.
resp, err := client.Do(req)

Use the transports' With method to create a sub-transport with additional middlewares:

base := http.DefaultTransport
tripper := httpc.NewRoundTripper(base, Foo, Bar) // uses Foo, Bar
subtripper := client.With(Baz) // uses Foo, Bar, Baz

Documentation

For the Go code documentation reference - check pkg.go.dev.

Middlewares

Middlewares sit between HTTP requests and responses, enabling to intercept and modify them.

Each middleware function should have the following signature:

type MiddlewareFunc func(DoerFunc) DoerFunc

This package offers some basic built-in middlewares to tailor client behavior according to your needs.

Explore details about the middlewares in the reference docs at pkg.go.dev.

Creating middlewares

Creating a middleware is as simple as:

func Foo(next DoerFunc) DoerFunc {
	return DoerFunc(func(r *http.Request) (*http.Response, error) {
		// do stuff with the request or response
		return next(r)
	})
}

Such a middleware is ready to use with httpc.Client or httpc.RoundTripper.

Note

When using a middleware with an httpc.Client, it should be triggered only once.
If the middleware should be triggered on each round-trip, provide it to the http.Client.Transport instead.

tr := httpc.NewRoundTripper(http.DefaultTransport, Foo)
c := httpc.NewClient(
	&http.Client{Transport: tr},
	Bar,
)

In the example above, requests made with the httpc.Client will trigger Bar only once, but may invoke Foo multiple times in conditions such as redirects.

Setting a custom base client

The httpc.Client can be provided with a custom base client.

client := httpc.NewClient(customClient)

Tip

The http.Client can be provided a base client with better defaults.
The timeouts should be configured appropriately for production environments.

Example http client with better defaults
c := &http.Client{
	Transport: &http.Transport{
		DialContext: (&net.Dialer{
			Timeout:   30 * time.Second,
			KeepAlive: 90 * time.Second,
		}).DialContext,
		TLSHandshakeTimeout:   10 * time.Second,
		ResponseHeaderTimeout: 10 * time.Second,
		ExpectContinueTimeout: time.Second,
		Proxy:                 http.ProxyFromEnvironment,
		// ...
	},
}

Handling OAuth2/OIDC

Retrieve an access token from a token source and add the Authorization header with the access token to outgoing HTTP requests.

Example oauth2 middleware
// OAuth2 is a middleware that handles OAuth2/OpenID Connect (OIDC)
// authentication. It retrieves an access token from the provided token
// source and adds the "Authorization" header with the access token to
// the outgoing HTTP requests.
//
// The token source should implement the `oauth2.TokenSource` interface,
// which provides a method to obtain an access token.
func OAuth2(ts oauth2.TokenSource) MiddlewareFunc {
	return func(next DoerFunc) DoerFunc {
		return DoerFunc(func(r *http.Request) (*http.Response, error) {
			token, err := ts.Token()
			if err != nil {
				return nil, err
			}

			token.SetAuthHeader(r)
			return next(r)
		})
	}
}

It should be pretty straightforward to use:

at := "your-personal-access-token"
ts := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: at})
c := httpc.NewClient(http.DefaultClient, OAuth2(ts))

Examples

Check out practical examples showcasing how to use this project in the examples directory:

Also check out the _test.go files for testable examples.

Contributing

Contributions are welcome!
If you find any issues or want to enhance the project, please submit a pull request.

Middlewares that are somewhat complex or require dependencies are expected to be maintained as a third-party package in a separate repository.

Example httpc middleware module
package foo

import (
	"net/http"

	"github.com/kraciasty/httpc"
)

type Plugin struct {
	opts Options
}

type Options struct{}

func NewPlugin(opts Options) *Plugin {
	return &Plugin{opts: opts}
}

func (p *Plugin) Middleware(next httpc.DoerFunc) httpc.DoerFunc {
	return func(next httpc.DoerFunc) httpc.DoerFunc {
		return func(r *http.Request) (*http.Response, error) {
			// do your stuff
			return next(r)
		}
	}
}
Example third-party middleware
package thirdparty

import (
	"net/http"

	"github.com/kraciasty/httpc"
)

type Plugin struct {
	tr http.RoundTripper
	opts Options
}

type Options struct{}

func New(tr http.RoundTripper, opts Options) *Plugin {
	return &Plugin{
		tr: tr,
		opts: opts,
	}
}

func (p *Plugin) RoundTrip(r *http.Request) (*http.Response, error) {
	// do stuff before request
	resp, err := p.tr.RoundTrip(r)
	// do stuff after request
	return resp, err
}
package yours

func NewMiddleware(opts thirdparty.Options) httpc.MiddlewareFunc {
	return func(next httpc.DoerFunc) httpc.DoerFunc {
		rt := thirdparty.New(next, opts)
		return func(r *http.Request) (*http.Response, error) {
			return rt.RoundTrip(r)
		}
	}
}

Even if your're not able to modify a third-party package to return such a middleware, it should be fairly easy to wrap it yourself - like it's done in the NewMiddleware func above.

License

This project is licensed under the MIT License. See the LICENSE file for more information.