Правильное использование go context.Context

Я только что прочитал статью: Создайте собственную веб-инфраструктуру на Go и обмен ценностями между обработчики я выбрал context.Context и использую его следующим образом для обмена значениями между обработчики и промежуточное ПО:

type appContext struct {
    db     *sql.DB
    ctx    context.Context
    cancel context.CancelFunc
 }


func (c *appContext)authHandler(next http.Handler) http.Handler {
    fn := func(w http.ResponseWriter, r *http.Request {
        defer c.cancel() //this feels weird
        authToken := r.Header.Get("Authorization") // this fakes a form
        c.ctx = getUser(c.ctx, c.db, authToken) // this also feels weird
        next.ServeHTTP(w, r)
    }

    return http.HandlerFunc(fn)
}

func (c *appContext)adminHandler(w http.ResponseWriter, r *http.Request) {
    defer c.cancel()
    user := c.ctx.Value(0).(user)
    json.NewEncoder(w).Encode(user)
}

func getUser(ctx context.Context, db *sql.DB, token string) context.Context{
    //this function mimics a database access
    return context.WithValue(ctx, 0, user{Nome:"Default user"})
}

func main() {
    db, err := sql.Open("my-driver", "my.db")
    if err != nil {
        panic(err)
    }
    ctx, cancel := context.WithCancel(context.Background())
    appC := appContext{db, ctx, cancel}
    //....
}

Все работает и обработчики загружаются быстрее, чем при использовании gorilla/context Итак, мои вопросы:

  1. Безопасен ли этот подход?
  2. Действительно ли необходимо откладывать функцию c.cancel() так, как я это делаю?
  3. Могу ли я использовать его для реализации пользовательской веб-инфраструктуры с использованием таких контроллеров, как struct, для обмена значениями с моделями?

person Bruno Vieira    schedule 19.06.2015    source источник


Ответы (3)


У вас проблема с вашим кодом, потому что вы сохраняете пользователя в контексте приложения. С несколькими пользователями одновременно это не работает. Контекст должен быть связан с запросом, чтобы он не был перезаписан другими запросами. Пользователь должен храниться в контексте запроса. В своих статьях я использую следующую функцию-гориллу: context.Set(r, "user", user). r это запрос.

Если вы хотите использовать context.Context в своем приложении, вам следует использовать их оболочку gorilla (вы можете найти ее в конце этой статьи: https://blog.golang.org/context).

Кроме того, вам не нужен контекст с отменой. context.Background() подходит для корневого контекста.

person Nicolas Merouze    schedule 19.06.2015
comment
Хорошо, теперь я понимаю, насколько неправильно я понял концепцию контекста. Однако я настаиваю на использовании context.Context. Я копну еще немного об этом. Спасибо за статью и помощь - person Bruno Vieira; 19.06.2015

Примечание: go 1.7.0-rc2 немного поясняет как освобождать ресурсы, связанные с контекстами (Самир Аджмани):

Некоторые пользователи не понимают, что создание Context с CancelFunc прикрепляет поддерево к родительскому элементу и что это поддерево не освобождается до тех пор, пока не будет вызван CancelFunc или родитель не будет отменен.

Уточните это в начале документации пакета, чтобы у людей, изучающих этот пакет, была правильная концептуальная модель.

документация теперь включает:

Входящие запросы к серверу должны создавать Context, а исходящие вызовы к серверам должны принимать Context.
Цепочка вызовов функций между ними должна распространять Context, при необходимости заменяя его производным Context, созданным с использованием WithCancel, WithDeadline, WithTimeout, или WithValue.
Эти значения Context образуют дерево: когда Context отменяется, все производные от него Contexts также отменяются.

Функции WithCancel, WithDeadline и WithTimeout возвращают производные Context и CancelFunc.
Вызов CancelFunc отменяет новый Context и любые производные от него контексты, удаляет Context из родительского дерева и останавливает все связанные таймеры.
Неспособность вызвать CancelFunc приводит к утечке связанных ресурсов до тех пор, пока родительский Context не будет отменен или не сработает таймер.

person VonC    schedule 17.06.2016

Два основных варианта использования пакета Context:

  1. Для хранения значений области запроса — использование context.WithValue()
  2. Для отмены - использование context.WithCancel(), context.WithTimeout(), context.WithDeadline()

Использование context формирует контекстное дерево с context.Background() в качестве корня. WithValue() ,context.WithCancel(), WithTimeout(), WithDeadline() являются производным контекстом от корневого контекста, который может быть дополнительно разделен. Пример каждого из них может прояснить, что является правильным контекстом использования. Наткнулся на это руководство, которое обеспечивает использование всего, что обсуждалось выше, с правильными примерами.

Источник: https://golangbyexample.com/using-context-in-golang-complete-guide/

person user27111987    schedule 23.12.2019