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!
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.
An idiomatic net/http
client experience, but with additional benefits:
- seamless integration with
http.Client
andhttp.RoundTripper
- reduced boilerplate (configure common things once)
- zero dependencies (standard library-based)
- extensible middleware support and a collection of common utilities
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
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
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
For the Go code documentation reference - check pkg.go.dev.
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.
- Recover - recover from panics
- StripSlashes - clean the URL path
- Secure - https only
- Timeout - apply timeout to requests
- UserAgent - set the
User-Agent
header - Accept - set the
Accept
header - ContentType - set the
Content-Type
header - Authorization, AuthorizationBearer, AuthorizationBasic - set the
Authorization
header - SetHeader - set a header
Explore details about the middlewares in the reference docs at pkg.go.dev.
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.
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,
// ...
},
}
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))
Check out practical examples showcasing how to use this project in the examples directory:
- wtc - calls WhatTheCommit API and logs request/response info
- replace-transport - replace
http.Transport
- replace-doer - replaces a http
Doer
- redirects - tripper and client middlewares in action
Also check out the _test.go
files for testable examples.
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.
This project is licensed under the MIT License. See the LICENSE file for more information.