Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Add headers for each HTTP request using client

I know that I can add headers to each HTTP request manually using

cli := &http.Client{}
req, err := http.NewRequest("GET", "https://myhost", nil)
req.Header.Add("X-Test", "true")
if err != nil {
    panic(err)
}
rsp, err := cli.Do(req)

but I want to add this header automatically for each HTTP request in my app.

What is the best way to do it?

like image 525
Kirill Avatar asked Dec 13 '22 13:12

Kirill


2 Answers

I'm aware of three possible solutions to this. In (my) order of preference:

  1. Wrap http.NewRequest with custom code that adds desired headers:

     func MyRequest(method, path string, body io.Reader) (*http.Request, error) {
         req, err := http.NewRequest(method, path, body)
         if err != nil {
             return nil, err
         }
         req.Header.Add("X-Test", "true")
         return req, nil
     }
    

    This approach has the advantage of being straight-forward, non-magical, and portable. It will work with any third-party software, that adds its own headers, or sets custom transports.

    The only case where this won't work is if you depend on a third-party library to create your HTTP requests. I expect this is rare (I don't recall ever running into this in my own experience). And even in such a case, perhaps you can wrap that call instead.

  2. Wrap calls to client.Do to add headers, and possibly any other shared logic.

     func MyDo(client *http.Client, req *http.Request) (*http.Response, error) {
         req.Header.Add("X-Test", "true")
         // Any other common handling of the request
         res, err := client.Do(req)
         if err != nil {
             return nil, err
         }
         // Any common handling of response
         return res, nil
     }
    

    This approach is also straight-forward, and has the added advantage (over #1) of making it easy to reduce other boilerplate. This general method can also work very well in conjunction with #1. One possible draw-back is that you must always call your MyDo method directly, meaning you cannot rely on third party software which calls http.Do itself.

  3. Use a custom http.Transport

     type myTransport struct{}
    
     func (t *myTransport) RoundTrip(req *http.Request) (*http.Response, error) {
         req.Header.Add("X-Test", "true")
         return http.DefaultTransport.RoundTrip(req)
     }
    

    Then use it like this:

     client := &Client{Transport: &myTransport{}}
     req := http.NewRequest("GET", "/foo", nil)
     res, err := client.Do(req)
    

    This approach has the advantage of working "behind the scenes" with just about any other software, so if you rely on a third-party library to create your http.Request objects, and to call http.Do, this may be your only option.

    However, this has the potential disadvantage of being non-obvious, and possibly breaking if you're using any third-party software which also sets a custom transport (without bothering to honor an existing custom transport).

Ultimately, which method you use will depend on what type of portability you need with third-party software. But if that's not a concern, I suggest using the most obvious solution, which, by my estimation, is the order provided above.

like image 64
Flimzy Avatar answered Dec 16 '22 01:12

Flimzy


It's possible to configure http.Client to use custom transport, which can handle each request in the client (found this implementation in golang.org/x/oauth2 library). This example appends headers to each http request:

type transport struct {
    headers map[string]string
    base    http.RoundTripper
}

func (t *transport) RoundTrip(req *http.Request) (*http.Response, error) {
    for k, v := range t.headers {
        req.Header.Add(k, v)
    }
    base := t.base
    if base == nil {
        base = http.DefaultTransport
    }
    return base.RoundTrip(req)
}

func main() {
    cli := &http.Client{
        Transport: &transport{
            headers: map[string]string{
                "X-Test": "true",
            },
        },
    }
    rsp, err := cli.Get("http://localhost:8080")
    defer rsp.Body.Close()
    if err != nil {
        panic(err)
    }
}
like image 24
Kirill Avatar answered Dec 16 '22 01:12

Kirill