Когда я создавал перехватчики сервера gRPC для zerolog и Sentry, я повторял код как для перехватчиков Stream, так и для Unary. Всегда было какое-то действие до обработки вызова и действие после.

Логичным подходом было бы создание функций BeforeHandler и AfterHandler. Однако само по себе это не очень весело или вдохновляюще, поэтому я хотел сделать еще один шаг: я хотел обобщить оба перехватчика вместе.

Создание общего перехватчика

Определить интерфейс

Сначала я подумал о том, какие функции есть у перехватчика, и придумал два метода: BeforeHandler и AfterHandler. Естественным выводом было создание интерфейса Interceptor для обработки этого:

type Interceptor interface{
  BeforeHandler()
  AfterHandler()
}

Это означает, что любой перехватчик, реализующий этот интерфейс, будет выполнять код до и после вызова gRPC. Функции также могут быть пустыми, но всегда должны быть определены для реализации нашего интерфейса.

Реализовать интерфейс

Чтобы использовать наш общий перехватчик, мы должны реализовать интерфейс. Вот базовый перехватчик, который выведет в журнал «до» и «после»:

// Struct can contain values such as configs or shared variables
type FooInter struct {}
func (FooInter) BeforeHandler() {
  fmt.Println("before")
}
func (FooInter) AfterHandler() {
  fmt.Println("after")
}

Теперь, когда мы определили наш собственный общий перехватчик, как мы на самом деле его используем?

Использование общего перехватчика

Преобразование обратно в унарные/потоковые перехватчики

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

Для этого мы воспользуемся мощью функционального программирования, чтобы выполнить преобразование с помощью замыканий:

Эти функции берут общий перехватчик и преобразуют его либо в функцию StreamServerInterface, либо в функцию UnaryServerInterceptor. Теперь, когда у нас есть способ вернуть перехватчик gRPC, как мы можем эффективно передать его обратно на сервер?

Передайте преобразованный перехватчик на сервер gRPC

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

grpcServer := grpc.NewServer(
  grpc.UnaryInterceptor(
    UnaryInterceptor(FooInter{})
  ),
  grpc.StreamInterceptor(
    StreamInterceptor(FooInter{})
  )
)

Теперь для каждого вызова, сделанного на сервер, он будет распечатывать журнал до и после каждого вызова gRPC. Возможно, это было бы полезно для определения времени вызовов gRPC 😉.

Расширение перехватчика контекстом и ошибками

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

type Interceptor interface{
  BeforeHandler(ctx context.Context) context.Context
  AfterHandler(ctx context.Context, err error)
}

Нам также понадобятся новые функции-перехватчики, в которых мы добавим значение в контекст.

func (FooInter) BeforeHandler(ctx context.Context) context.Context {
  fmt.Println("before")
  ctx = context.WithValue(ctx, key{}, "value")
  return ctx
}
func (FooInter) AfterHandler(ctx context.Context, err error) {
  fmt.Println("after")
}

Это будет работать для унарных перехватчиков. Однако перехватчики потоков отличаются: контекст — это закрытая переменная внутриServerStream. Чтобы раскрыть это, нам нужно обернуть интерфейс ServerStream в локальную структуру, которая предоставляет изменяемый контекст, например:

type WrappedServerStream struct {
  grpc.ServerStream
  Ctx context.Context
}
// Override to return wrapper context rather than private context
func (w WrappedServerStream) Context() context.Context {
  return w.Ctx
}

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

Контекст, передаваемый функциям в обработчиках нашего сервера, теперь будет включать наше пользовательское значение. Это может быть полезно, если, например. вы хотите сохранить концентратор Sentry или регистратор в контексте.

Полный пакет теперь можно импортировать с GitHub! 🚀



Присоединяйтесь к Третьей форте

В Thirdfort мы стремимся сделать переезд проще для всех участников. Мы используем технологии, дизайн и данные, чтобы сделать работу с клиентами безопасной и удобной для юристов, специалистов по недвижимости и финансам. С момента запуска в 2017 году мы выросли из одного лондонского офиса в международную команду с площадками в Манчестере и Шри-Ланке. Нас поддерживают ведущие инвесторы, такие как Алекс Честерман (основатель Zoopla и Cazoo) и HM Land Registry.

Хотите помочь сформировать историю Thirdfort в 2022 году? Мы хотели бы услышать от вас. Найдите свою следующую должность в нашем Центре карьеры или свяжитесь с нами по адресу: careers@ Thirdfort.com.