Что такое метаданные (metadata) в gRPC, для чего они нужны и как с ними работать?

Ответ

Метаданные в gRPC — это набор пар "ключ-значение", которые передаются вместе с RPC-вызовом, но не являются частью бизнес-логики сообщения (Protobuf). Они очень похожи на HTTP-заголовки и служат для передачи служебной информации.

Основные сценарии использования:

  • Аутентификация и авторизация: Передача токенов (JWT, API-ключи) для проверки прав доступа.
  • Трассировка и мониторинг: Передача ID трассировки (trace ID) и ID запроса (request ID) для сквозного отслеживания вызовов в микросервисной архитектуре.
  • Отладка: Передача отладочной информации, например, имени клиента или версии.
  • Управление потоком: Передача информации о сжатии или других параметрах вызова.

Как работать с метаданными в Go:

Метаданные передаются через объект context.Context.

Пример отправки метаданных клиентом:

import "google.golang.org/grpc/metadata"

// 1. Создаем метаданные
md := metadata.Pairs(
    "authorization", "Bearer my-secret-token",
    "request-id", "some-unique-id",
)

// 2. Прикрепляем их к контексту
ctx := metadata.NewOutgoingContext(context.Background(), md)

// 3. Выполняем RPC-вызов с этим контекстом
response, err := client.SomeRPC(ctx, request)

Пример чтения метаданных на сервере:

import "google.golang.org/grpc/metadata"

func (s *server) SomeRPC(ctx context.Context, req *pb.Request) (*pb.Response, error) {
    // 1. Извлекаем метаданные из входящего контекста
    md, ok := metadata.FromIncomingContext(ctx)
    if !ok {
        return nil, status.Errorf(codes.InvalidArgument, "Missing metadata")
    }

    // 2. Читаем значения по ключу (ключи автоматически приводятся к нижнему регистру)
    tokens := md.Get("authorization")
    if len(tokens) > 0 {
        fmt.Println("Auth Token:", tokens[0])
    }

    // ... остальная логика
    return &pb.Response{}, nil
}

Ключевые особенности:

  • Ключи метаданных нечувствительны к регистру и автоматически приводятся к lowercase.
  • Для передачи бинарных данных ключ должен иметь суффикс -bin.
  • Существуют также трейлеры (trailers) — это метаданные, которые сервер отправляет в самом конце обработки запроса, вместе со статусом. Они полезны для передачи информации, которая стала известна только по завершении вызова (например, итоговое время обработки).

Ответ 18+ 🔞

А, ну вот, смотри, опять эти ваши метаданные, блядь! Это ж как заголовки в HTTP, только для гРПЦшных дел. Представь себе, ты везешь посылку — это твоё Protobuf-сообщение, бизнес-логика, всё такое. А метаданные — это, сука, наклейки на коробке: «Хрупкое», «Срочно», «Токен внутри, ёпта». Саму посылку не трогают, но всем курьерам понятно, что с ней делать.

Зачем это, блядь, нужно?

  • Кто ты такой, сука? Токены всякие, ключики — запихнул в метаданные, и сервер тебя опознает. Авторизация, аутентификация, в рот меня чих-пых.
  • Где ты, засранец, пропадаешь? В микросервисах, когда один запрос по десяти сервисам скачет, нужно его отследить. Кидаешь trace-id в метаданные — и вот он, маршрут, как на ладони. Мониторинг, трассировка, мать их.
  • Чё за хуйня происходит? Для отладки. Версию клиента запилил, или флаг «debug» — и сервер тебе в ответ может подробнее сопли размазать.
  • Как тебя жрать? Настройки всякие: «сожми меня покрепче» или «не тормози».

Как с этим в Go ебаться?

Клиент, когда шлёт запрос, должен прилепить эти наклейки к контексту. Всё через библиотеку metadata.

Клиент, прикинь, клеит наклейки:

import "google.golang.org/grpc/metadata"

// 1. Делаем метаданные. Прям пары «ключ-значение», как в комиссионке.
md := metadata.Pairs(
    "authorization", "Bearer my-secret-token",
    "request-id", "some-unique-id",
)

// 2. Лепим этот пакет наклеек в контекст, который полетит на сервер.
ctx := metadata.NewOutgoingContext(context.Background(), md)

// 3. Вызываешь удалённую процедуру с этим контекстом — и всё, понеслась.
response, err := client.SomeRPC(ctx, request)

Сервер, сука, принимает и читает:

import "google.golang.org/grpc/metadata"

func (s *server) SomeRPC(ctx context.Context, req *pb.Request) (*pb.Response, error) {
    // 1. Выковыриваем метаданные из контекста, который прилетел.
    md, ok := metadata.FromIncomingContext(ctx)
    if !ok {
        // Во, приплыли. Контекст пустой, метаданных нет. Отказ, блядь.
        return nil, status.Errorf(codes.InvalidArgument, "Missing metadata")
    }

    // 2. Достаём по ключу. Запомни, ключи тут автоматом в нижний регистр идут!
    tokens := md.Get("authorization")
    if len(tokens) > 0 {
        fmt.Println("Auth Token:", tokens[0]) // Вот он, родной, токен.
    }

    // ... дальше твоя бизнес-логика, ебать её в сраку.
    return &pb.Response{}, nil
}

Важные нюансы, чтоб не обосраться:

  • Регистр, блядь, не важен. Написал Authorization, AUTHORIZATION или AuThOrIzAtIoN — всё равно станет authorization. Не выёбывайся.
  • Бинарные данные — если шлёшь какую-нибудь хуйню не текстовую, а бинарную, в ключе должен быть суффикс -bin. Правила, блядь.
  • Трейлеры — это, сука, метаданные на выхлопе. Сервер их шлёт в самом конце, когда уже всё сделал. Типа «итоговое время обработки — 5 секунд, ёпта». Удобно для пост-анализа, когда результат уже известен.

Вот и вся магия. Не такая и сложная хуйня, если разобраться. Главное — не забывай контекст правильный прокидывать, а то запрос улетит голый, и сервер тебя пошлёт нахуй.