Tripperwares: http.Client Middleware - chaining RoundTrippers
Tags: go, http, client
Published: 2020-07-22 00:36:30

---
title: Tripperwares: http.Client Middleware - chaining RoundTrippers
published: true
description: 
tags: go, http, client
//cover_image: https://direct_url_to_image.jpg
---

`http.Client` Middleware or "Tripperwares" adhere to `func (http.RoundTripper) http.RoundTripper` signature, hence the name. As such they are used as a Transport for `http.Client`, and can wrap other transports.

This means that the composition is purely net/http-based. 

While **server** middleware is pretty commonly discussed (and there's lots to pick from), I found the documentation a little sparse on how (and why) to do this for clients. Luckily the amazing @PeterBourgon helped me on the `#gophers` slack.

A couple good use cases for TripperWare are request logging, caching, and certain kinds of auth (e.g. bearer token renewal).

However, instead of making one complicated Tripperware to do all this, it's nice to compose several more tightly focussed ones.

```Go
package main

import (
	"encoding/base64"
	"fmt"
	"io"
	"net/http"
	"os"
	"time"
)

// thanks to peter bourgon on Gophers slack

func main() {
	jiraUserID := os.ExpandEnv("${JIRA_USERID}")
	jiraPassword := os.ExpandEnv("${JIRA_API_TOKEN}")
	jiraBaseURL := "https://example.atlassian.net/rest/agile/1.0/board/"

	var rt http.RoundTripper
	rt = http.DefaultTransport
	rt = NewDelayRoundTripper(rt, 123*time.Millisecond)
	rt = NewLoggingRoundTripper(rt, os.Stderr)
	header := make(http.Header)
	header.Set("Accept", "application/json")
	header.Set("Content-Type", "application/json")
	hrt := NewHeaderRoundTripper(rt, header)
	hrt.BasicAuth(jiraUserID, jiraPassword)

	client := &http.Client{
		Transport: hrt,
	}

	_, err := client.Get(jiraBaseURL)
	fmt.Printf("err=%v\n", err)
}

//
//
//

type DelayRoundTripper struct {
	next  http.RoundTripper
	delay time.Duration
}

func NewDelayRoundTripper(next http.RoundTripper, delay time.Duration) *DelayRoundTripper {
	return &DelayRoundTripper{
		next:  next,
		delay: delay,
	}
}

func (rt *DelayRoundTripper) RoundTrip(req *http.Request) (resp *http.Response, err error) {
	time.Sleep(rt.delay)
	return rt.next.RoundTrip(req)
}

//
//
//

type LoggingRoundTripper struct {
	next http.RoundTripper
	dst  io.Writer
}

func NewLoggingRoundTripper(next http.RoundTripper, dst io.Writer) *LoggingRoundTripper {
	return &LoggingRoundTripper{
		next: next,
		dst:  dst,
	}
}

func (rt *LoggingRoundTripper) RoundTrip(req *http.Request) (resp *http.Response, err error) {
	defer func(begin time.Time) {
		fmt.Fprintf(rt.dst,
			"method=%s host=%s status_code=%d err=%v took=%s\n",
			req.Method, req.URL.Host, resp.StatusCode, err, time.Since(begin),
		)
	}(time.Now())

	return rt.next.RoundTrip(req)
}

//
//
//

type HeaderRoundTripper struct {
	next http.RoundTripper
	Header http.Header
}

func NewHeaderRoundTripper(next http.RoundTripper, Header http.Header) *HeaderRoundTripper {
	if next == nil {
		next = http.DefaultTransport
	}
	return &HeaderRoundTripper{
		next:   next,
		Header: Header,
	}
}

func (rt *HeaderRoundTripper) RoundTrip(req *http.Request) (resp *http.Response, err error) {
	if rt.Header != nil {
		for k, v := range rt.Header {
			req.Header[k] = v
		}
	}
	fmt.Println("HeaderRoundTrip")
	return rt.next.RoundTrip(req)
}

func (rt *HeaderRoundTripper) BasicAuth(username, password string) {
	if rt.Header == nil {
		rt.Header = make(http.Header)
	}

	auth := username + ":" + password
	base64Auth := base64.StdEncoding.EncodeToString([]byte(auth))
	rt.Header.Set("Authorization", "Basic "+ base64Auth)
}

func (rt *HeaderRoundTripper) SetHeader(key, value string) {
	if rt.Header == nil {
		rt.Header = make(http.Header)
	}
	rt.Header.Set(key, value)
}
```

This works fine, but if you're less into receivers and prefer functional composition, here's an functional alternative:

```go
// NewRoundTripper returns an http.RoundTripper that is tooled for use in the app

func NewRoundTripper(original http.RoundTripper) http.RoundTripper {
	if original == nil {
		original = http.DefaultTransport
	}

	return roundTripperFunc(func(request *http.Request) (*http.Response, error) {
		response, err := original.RoundTrip(request)
		return response, err
	})
}

type roundTripperFunc func(*http.Request) (*http.Response, error)

func (f roundTripperFunc) RoundTrip(r *http.Request) (*http.Response, error) { return f(r) }

```
I found that code in [evepraisal/go-evepraisal](https://github.com/evepraisal/go-evepraisal/blob/master/evepraisal/app.go#L320-L340) where @sudorandom used it to instrument external calls with New Relic monitoring.

Also, I found that there's a several tripperwares and some helper libraries here: [improbable-eng/go-httpwares](https://github.com/improbable-eng/go-httpwares)

It's worth mentioning that changing the request is a bit of a no-no. Cloning the request, modifying that, and then passing that on is a workaround

Here's [an article about an alternative](https://developer20.com/add-header-to-every-request-in-go/) for header manipulation.