Тайм-ауты HTTP-запросов в Go для начинающих
 
        Тайм-ауты - это одна из примитивных концепций надежности в распределенных системах, которая смягчает последствия неизбежных отказов распределенных систем, как упоминалось в этом твите.
Проблема
Как условно смоделировать ответ 504 http.StatusGatewayTimeout?
При попытке реализовать проверку токена OAuth в zalando/skipper мне пришлось понять и реализовать тест, имитирующий 504 http.StatusGatewayTimeout используя httptest при превышении времени ожидания сервера, но только при превышении времени ожидания клиента из-за задержки на сервере. Будучи новичком в языке, я делал то, что делает большинство из нас; создайте стандартный HTTP-клиент и добавьте время ожидания, как показано ниже:
client := http.Client{Timeout: 5 * time.Second}
Вышеуказанное кажется очень простым и интуитивно понятным, если вы хотите создать клиент для выполнения http-запроса. Но под ним скрываются многие детали низкого уровня, включая время ожидания клиента, время ожидания сервера и время ожидания для балансировщиков нагрузки.
Тайм-ауты на стороне клиента
Тайм-аут HTTP-запроса может быть определен на стороне клиента несколькими способами, в зависимости от тайм-аута, намеченного в цикле запроса. Цикл запрос-ответ состоит из Dialer, TLS Handshake, Request Header, Request Body, Response Header и Response Body. В зависимости от вышеуказанных частей запроса-ответа Go предоставляет следующие способы создания запроса с тайм-аутами
- http.client
- context
- http.Transport
http.client
http.client является собой высокоуровневый тайм - аут , который включает в себя весь цикл запроса от Dial до Response Body. Мудрая реализация http.client - это тип структуры, который принимает необязательное свойство Timeout типа time.Duration, которое определяет ограничение на время начала запроса до сброса тела ответа.
client := http.Client{Timeout: 5 * time.Second}
context
Пакет Go  context содержит полезные инструменты для настройки тайм - аута, срок и расторжение запросов через методы WithTimeout, WithDeadline и WithCancel. Используя WithTimeout, вы можете добавить тайм-аут к методу http.Request использования req.WithContext
ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
defer cancel()
req, err := http.NewRequest("GET", url, nil)
if err != nil {
    t.Error("Request error", err)
}
resp, err := http.DefaultClient.Do(req.WithContext(ctx))
http.Transport
Вы также можете указать время ожидания, используя низкоуровневую реализацию http.Transport создания пользовательского интерфейса, DialContext и использовать его для создания http.client
transport := &http.Transport{
    DialContext: (&net.Dialer{   
        Timeout: timeout,
    }).DialContext,
}
client := http.Client{Transport: transport}
Решение
Так что с вышеупомянутой проблемой и вариантами под рукой я создал http.request с context.WithTimeout(). Но это все равно выдало ошибку ниже
client_test.go:40: Response error Get http://127.0.0.1:49597: context deadline exceeded
Тайм-ауты на стороне сервера
Проблема с подходом context.WithTimeout() состоит в том, что он все еще только симулирует клиентскую сторону запроса. И в случае, если заголовок или тело запроса занимает больше времени, чем предусмотренное в бюджете время ожидания, запрос не выполняется на стороне клиента, а не на стороне сервера с кодом состояния 504 http.StatusGatewayTimeout.
Один из способов создания сервера httptest, время ожидания которого истекает каждый раз:
httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request){
    w.WriteHeader(http.StatusGatewayTimeout)
}))
Но я хотел, чтобы время ожидания было только на основе значения времени ожидания клиента. Чтобы сервер возвращал 504 на основе тайм-аута клиента, вы можете заключить обработчик в функцию-обработчик http.TimeoutHandler() для тайм-аута запроса на сервере. Ниже приводится рабочий тест, который применяет этот сценарий
func TestClientTimeout(t *testing.T) {
    handlerFunc := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        d := map[string]interface{}{
            "id":    "12",
            "scope": "test-scope",
        }
        time.Sleep(100 * time.Millisecond) //<- Any value > 20ms
        b, err:= json.Marshal(d)
        if err != nil {
            t.Error(err)
        }
        io.WriteString(w, string(b))
        w.WriteHeader(http.StatusOK)
    })
    backend := httptest.NewServer(http.TimeoutHandler(handlerFunc, 20*time.Millisecond, "server timeout"))
    url := backend.URL
    req, err := http.NewRequest("GET", url, nil)
    if err != nil {
        t.Error("Request error", err)
        return
    }
    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        t.Error("Response error", err)
        return
    }
    defer resp.Body.Close()
}
Детали вышеупомянутой проблемы используются в реализации tokeninfo_test.go/TestOAuth2TokenTimeout в zalando/skipper
Вероятно, начинающий суслик считает полезным понимать работу тайм-аутов http высокого уровня! Если вы хотите узнать более подробную информацию о http timeouts , эту статью из Cloudflare необходимо прочитать.