Подтвердить что ты не робот

Как я могу управлять контекстом среды App Engine Go, чтобы избежать блокировки App Engine?

Я пишу приложение Go для запуска во время выполнения App Engine Go.

Я замечаю, что практически любая операция, использующая сервис App Engine (например, Datastore, Mail или даже Capabilities), требует, чтобы вы передали ему экземпляр appengine.Context, который должен быть получен с помощью функции appengine.NewContext(req *http.Request) Context.

Пока я пишу это приложение для App Engine, я хочу легко и быстро перенести его на какую-либо другую платформу (возможно, такую, которая не поддерживает какой-либо из API App Engine), если я этого захочу.

Итак, я абстрагирую фактическое взаимодействие с сервисами App Engine и API, написав небольшие обертки вокруг любого взаимодействия с конкретным приложением (включая функции обработки запросов). При таком подходе, если я когда-либо захочу перейти на другую платформу, я просто переписал те конкретные модули, которые привязывают мое приложение к App Engine. Легко и просто.

Единственная проблема заключается в том, что объект appengine.Context. Я не могу передать это из моих обработчиков запросов через свои слои логики в модули, которые обрабатывают этот API, не привязывая почти весь мой код к App Engine. Я мог бы передать объект http.Request, из которого может быть выведен объект appengine.Context, но для этого потребуется связать вещи, которые, вероятно, не должны быть связаны. (Я считаю, что ни одна из моих приложений не может даже знать это веб-приложение, кроме тех частей, которые специально предназначены для обработки HTTP-запросов.)

Первое решение, появившееся на ум, состояло в том, чтобы просто создать постоянную переменную в каком-то модуле. Что-то вроде этого:

package context

import (
    "appengine"
)

var Context appengine.Context

Затем, в обработчиках запросов, я могу установить эту переменную с помощью context.Context = appengine.NewContext(r) и в модулях, которые непосредственно используют службы App Engine, я могу получить контекст, присоединившись к context.Context. Ни один из промежуточных кодов не должен знать о существовании объекта appengine.Context. Единственная проблема заключается в том, что "несколько запросов могут обрабатываться одновременно с данным экземпляром" , что может привести к условиям гонки и неожиданному поведению с этим планом. (Один запрос устанавливает его, другой устанавливает его, первый обращается к нему и получает неправильный объект appengine.Context.)

Я мог бы теоретически хранить appengine.Context в хранилище данных, но тогда мне пришлось бы передавать определенный идентификатор запроса по логическим слоям в модули, специфичные для службы, определяющие, какой объект appengine.Context в хранилище данных является тем, который текущий запрос, который снова соединит вещи, которые, как я думаю, не должен сочетаться. (И это увеличит использование хранилища данных приложения.)

Я мог бы также передать объект appengine.Context по всей логической цепочке с типом interface{} весь путь и иметь любой модуль, который не нуждается в объекте appengine.Context, игнорировать его. Это позволит избежать привязки большей части моего приложения к чему-либо конкретному. Однако это также кажется очень беспорядочным.

Итак, я немного пораньше, как для чистого обеспечения модулей App-Engine, которые нуждаются в объекте appengine.Context, можно получить его. Надеюсь, вы, ребята, можете дать мне решение, о котором я еще не подумал.

Спасибо заранее!

4b9b3361

Ответ 1

Это сложно, потому что ваше самонастраиваемое правило определения (что является разумным) означает не пропускать экземпляр Context, и нет ничего похожего на Java ThreadLocal для достижения тех же целей с помощью скрытых средств. На самом деле это хорошая вещь.

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

Есть одна вещь, которая может вам помочь - вы можете включить файл конфигурации с вашим приложением, который указывает, находится ли он в GAE или иначе, используя какой-либо флаг. Ваш глобальный логический объект должен хранить этот флаг (а не общий контекст). Затем ваши функции фасада могут проконсультироваться с этим флагом при принятии решения о том, следует ли использовать NewContext(r) для получения Context для доступа к службам GAE или использовать скрытую структуру для доступа к вашим собственным услугам-заменителям.

Изменить: как последнее замечание, когда вы решите это, я могу предложить вам поделиться тем, как вы это сделали, возможно, даже с проектом с открытым исходным кодом? Чувствую меня, чтобы спросить, но если вы не спросите...; -)

Ответ 2

Я (надеюсь) решил эту проблему, обернув мои обработчики запросов (в этом примере один из них называется "realHandler" ) следующим образом:

http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    ds := NewDataStore(r)
    realHandler(w, r, ds)
})

NewDataStore создает DataStore, который является простой оболочкой, которая абстрагирует хранилище данных GAE. Он имеет неэкспонированное поле, где он хранит контекст:

type DataStore struct {
    c appengine.Context
}

func NewDataStore(req *http.Request) *DataStore {
    return &DataStore{appengine.NewContext(req)}
}

Внутри обработчика запроса я могу просто получить доступ к абстрактному хранилищу данных, когда мне это нужно, не беспокоясь о контексте GAE, который уже установлен:

func realHandler(w http.ResponseWriter, req *http.Request, db *DataStore) {
    var s SomeStruct{}
    key, err := db.Add("Structs", &s)
    ...
}

Ответ 3

В частности, в случае Datastore вы должны иметь возможность повторно использовать один и тот же appengine.Context среди разных запросов. Я не пытался это сделать сам, но способ, которым работает альтернативный API для хранилища данных goon:

Goon отличается от пакета хранилища различными способами: он запоминает контекст appengine, который нужно указывать только один раз во время создания

То, что хранилище должно зависеть от HTTP-запроса, звучит смешно, кстати. Я не думаю, что Datastore зависит от конкретной просьбы в обычном смысле. Скорее всего, ему нужно было определить конкретное приложение Google App Engine, которое, очевидно, остается неизменным с запросом на запрос. Мои предположения основаны на быстром просмотре исходного кода google.golang.org/appengine.

Вы можете делать подобные наблюдения в отношении других API приложений Google App Engine. Причина в том, что все детали могут быть специфичными для реализации, и я провел более глубокие исследования, прежде чем фактически использовать эти наблюдения в реальном приложении.

Ответ 4

Стоит отметить, что команда Go представила пакет golang.org/x/net/context.

Позже контекст был доступен в управляемых виртуальных машинах, здесь здесь. В документации указано:

Этот репозиторий поддерживает текущую среду Go в App Engine, включая как классические App Engine, так и управляемые виртуальные машины. Он предоставляет API для взаимодействия с службами App Engine. Его путь канонического импорта google.golang.org/appengine.

Это означает, что вы можете легко написать еще один пакет из среды dev в зависимости от appengine.

В частности, очень легко обернуть пакеты, такие как appengine/log (тривиальная обертка журнала пример).

Но еще важнее это позволяет создавать обработчики в форме:

func CoolHandler(context.Context, http.ResponseWriter, *http.Request)

Здесь есть статья о пакете context в блоге Go здесь. Я написал об использовании контекста здесь. Если вы решите использовать обработчик с проходящим вокруг контекстом, хорошо создать контекст для всех реквистеров в одном месте. Вы можете сделать это, используя нестандартный requrest router, например github.com/orian/wctx.