Добавьте заголовки для каждого HTTP-запроса с помощью клиента

Я знаю, что могу добавить заголовки к каждому HTTP-запросу вручную, используя

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)

но я хочу добавить этот заголовок автоматически для каждого HTTP-запроса в моем приложении.

Каков наилучший способ сделать это?

2 ответа

Решение

Я знаю о трех возможных решениях этого. В (моем) порядке предпочтения:

  1. Заворачивать http.NewRequest с пользовательским кодом, который добавляет желаемые заголовки:

    func MyRequest(method, path url, 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
    }
    

    Этот подход имеет преимущество в том, что он прост, не волшебен и переносим. Он будет работать с любым сторонним программным обеспечением, которое добавляет свои собственные заголовки или устанавливает пользовательские транспорты.

    Единственный случай, когда это не сработает, это если вы используете сторонние библиотеки для создания своих HTTP-запросов. Я ожидаю, что это редко (я не помню, чтобы когда-либо сталкивался с этим в моем собственном опыте). И даже в таком случае, возможно, вы можете вместо этого обернуть этот вызов.

  2. Оберните звонки client.Do добавить заголовки и, возможно, любую другую общую логику.

    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
    }
    

    Этот подход также прост и имеет дополнительное преимущество (по сравнению с № 1), позволяющее легко сократить другие шаблоны. Этот общий метод также может очень хорошо работать в сочетании с #1. Одним из возможных недостатков является то, что вы должны всегда MyDo метод, то есть вы не можете полагаться на стороннее программное обеспечение, которое вызывает http.Do сам.

  3. Используйте кастом 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)
    }
    

    Затем используйте это так:

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

    Этот подход имеет преимущество работы "за кулисами" практически с любым другим программным обеспечением, поэтому, если вы полагаетесь на стороннюю библиотеку для создания своего http.Request объекты и позвонить http.Do, это может быть вашим единственным вариантом.

    Тем не менее, это имеет потенциальный недостаток, заключающийся в том, что он неочевиден и, возможно, ломается, если вы используете любое стороннее программное обеспечение, которое также устанавливает пользовательский транспорт (не заботясь о существующем пользовательском транспорте).

В конечном итоге, какой метод вы используете, будет зависеть от того, какой тип переносимости вам нужен со сторонним программным обеспечением. Но если это не проблема, я предлагаю использовать наиболее очевидное решение, которое, по моей оценке, является приведенным выше порядком.

Можно настроить http.Client использовать пользовательский транспорт, который может обрабатывать каждый запрос клиента (эту реализацию можно найти в библиотеке https://godo c.org/golang.org/x/oauth2). Этот пример добавляет заголовки к каждому http-запросу:

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)
    }
}
Другие вопросы по тегам