Контекст входящего запроса

Время от времени я сталкиваюсь с концепцией "Контекст" , которая, как правило, создается для всех входящих запросов. Недавно я прочитал статью Go blog, в которой описывается использование пакета golang.org/x/net/context. Однако после игры с кодом и попытки воспроизвести логику статьи я все еще не понимаю, как использовать ее для каждого входящего запроса и даже почему это полезно для этого.

Как мне организовать мой код для создания контекста (и что он должен содержать, в общем) для каждого входящего запроса с помощью пакета golang.org/x/net/context? Может ли кто-нибудь дать небольшой пример и объяснить, что так полезно и почему так часто используется?

Ответ 1

Одной из наиболее распространенных потребностей в передаче контекста является корреляция исходящих запросов к входящим запросам. Я использовал это для различных целей, например:

  • Я хочу, чтобы журналы ошибок для моего компонента базы данных включали полный URL-адрес из запроса http, это результат.
  • Входящие HTTP-запросы содержат набор заголовков, которые мне нужно сохранить и передать другим службам http, которые я вызываю downstream (возможно, для отслеживания причин).
  • Я хочу проверить входящий HTTP-запрос в каком-либо другом компоненте, чтобы выполнить контроль доступа или аутентификацию пользователя или что-то еще. Это может быть на уровне обработчика http или какой-либо другой части моего приложения.

Многие языки и платформы имеют удобные/магические способы получения текущего запроса Http. С# имеет HttpRequest.Current, который доступен по всему миру (через локальное хранилище потоков) всем, кто хочет узнать контекст текущего HTTP-запроса. Вы можете установить на нем произвольные данные для передачи различных контекстных данных. Другие платформы имеют аналогичные возможности.

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

Один из самых простых способов сделать это - сделать объект контекста с текущим HTTP-запросом и передать его:

func someHandler(w http.ResponseWriter, r * http.Request){
   ctx := context.WithValue(context.Background(),"request",r)
   myDatabase.doSomething(ctx,....)
}

Конечно, вы можете ограничить его более целенаправленным набором данных, которые вам нужно передать, а не весь запрос.

Другое, что помогает пакет контекста (и я думаю, что блог делает хорошую работу по указанию), является общей основой для тайм-аутов или сроков.

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

edit - on timeouts

Очень полезно иметь возможность управлять тайм-аутами извне компонента. Если у меня есть модуль базы данных, мне не нужно фиксировать значения тайм-аута, просто уметь обрабатывать тайм-аут, запускаемый извне.

Один из способов, которым я это сделал, - это служба, которая делает несколько вызовов db/service на входящий запрос. Если общее время превышает 1 секунду, я хочу прервать все исходящие операции и вернуть результат частичного или ошибки. Инициализация контекста с тайм-аутом на верхнем уровне и передача его во все зависимости - это действительно простой способ управления этим.

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