Получение и отправка заголовков на сервере и клиенте

Последнее обновление: 22.12.2022

В рамках приложения GRPC в ASP.NET Core мы можем получить заголовки запроса на сервере и заголовки ответа сервера на клиенте. Что на стороне сервера, что на стороне клиента заголовки представляют объект Grpc.Core.Metadata, который по сути является списком заголовоков - объектов Metadata.Entry.

У объекта Entry среди всего функционала следует выделить два свойства: Key (название заголовка) и Value (значение заголовка). Оба свойства представляют тип string.

Для получения нужного заголовка из объекта Metadata можно использовать ряд методов этого класса

  • IEnumerable<Entry> GetAll(string key): возвращает набор заголовков по определенному ключу (по одному ключу может быть несколько заголовков)

  • string? GetValue(string key): возвращает значение заголовка по определенному ключу. Если такого заголовка нет, то возвращается null

  • Entry? Get(string key): возвращает один заголовок по определенному ключу. Если такого заголовка нет, то возвращается null

Рассмотрим получение заголовков на примере.

Получение заголовков

Проект сервера

пусть в проекте сервера используется следующий файл metanit.proto:

syntax = "proto3";

package metanit;

message Request{}

message Response{ 
	string content=1;
}

service Messenger{
  rpc SendMessage (Request) returns (Response);
}

Здесь сервис Messenger определяет метод SendMessage, который получает от клиента пустое сообщение Request. А в ответ клиенту возвращает сообщение Response с одним свойством content.

Далее в проекте сервера в качестве сервиса определим следующий класс MessengerService:

using Grpc.Core;
using Metanit;  // пространство имен сервиса MessengerService

public class MessengerService : Messenger.MessengerBase
{
    public override Task<Response> SendMessage(Request request, ServerCallContext context)
    {
        // получаем все заголовки запроса
        foreach(var header in context.RequestHeaders)
        {
            Console.WriteLine($"{header.Key}: {header.Value}");    // получаем ключ и значение заголовка
        }
        // получаем один заголовок по названию - user-agent
        var userAgent = context.RequestHeaders.GetValue("user-agent");
        // отправляем ответ
        return Task.FromResult(new Response
        {
            Content = userAgent
        });
    }
}

На стороне клиента все заголовки помещаются в параметр типа ServerCallContext, который передается в метод сервиса и который представляет контекст вызова сервиса. Непосредственно все заголовки помещаются в свойство RequestHeaders, которое представляет выше рассмотренный класс Metadata. В данном случае с помощью свойства context.RequestHeaders получаем все заголовки и выводим их на консоль:

foreach(var header in context.RequestHeaders)
{            
    Console.WriteLine($"{header.Key}: {header.Value}");    // получаем ключ и значение заголовка
}

Далее получаем один заголовок - user-agent и для теста отправляем его клиенту в ответе:

var userAgent = context.RequestHeaders.GetValue("user-agent");
return Task.FromResult(new Response { Content = userAgent });

Определение клиента

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

using Grpc.Net.Client;
using Metanit;  // пространство имен класса Messenger.MessengerClient

// создаем канал для обмена сообщениями с сервером
// параметр - адрес сервера gRPC
using var channel = GrpcChannel.ForAddress("https://localhost:7229");

// создаем клиент
var client = new Messenger.MessengerClient(channel);

// отправляем сообщение серверу
using var call = client.SendMessageAsync(new Request());

// получаем ответ
Response response = await call.ResponseAsync;
Console.WriteLine($"Response: {response.Content}");
// получаем все заголовки и выводим их на консоль
var headers = await call.ResponseHeadersAsync;
foreach(var header in headers)
{
    Console.WriteLine($"{header.Key}: {header.Value}");
}

Для получения заголовков на стороне клиента сначала получаем результат метода отправки сообщения:

using var call = client.SendMessageAsync(new Request());

Здесь результат представляет объект AsyncUnaryCall. Для получения из него собственно заголовков, обращаемся к его свойству ResponseHeadersAsync, которое представляет объект Task<Metadata>:

var headers = await call.ResponseHeadersAsync;

То есть здесь headers опять же представляет объект Metadata.

Правда, если мы запустим проекты сервера и клиента, то увидим, что взаимодействие через grpc довольно бедно на заголовки в отличие от стандартных запросов по протоколу http. Например, вывод консоли сервера:

user-agent: grpc-dotnet/2.50.0 (.NET 7.0.1; CLR 7.0.1; net7.0; windows; x64)

То есть на сервер передается только один заголовок - "user-agent".

И консоль клиента

Response: grpc-dotnet/2.50.0 (.NET 7.0.1; CLR 7.0.1; net7.0; windows; x64)
date: Thu, 22 Dec 2022 17:36:34 GMT
server: Kestrel

На клиент передается два заголовка: date и server.

Отправка заголовков

Код клиента

Теперь изменим код клиента, чтобы он также отправлял свои кастомные заголовки:

using Grpc.Core;
using Grpc.Net.Client;
using Metanit;

using var channel = GrpcChannel.ForAddress("https://localhost:7229");

var client = new Messenger.MessengerClient(channel);

// формируем отправляемые заголовки
Metadata requestHeaders = new Metadata();
// добавляем один заголовок
requestHeaders.Add("username", "Tom");
// отправляем сообщение серверу
using var call = client.SendMessageAsync(new Request(), requestHeaders);

// получаем ответ
Response response = await call.ResponseAsync;
Console.WriteLine($"Response: {response.Content}");
// получаем все заголовки и выводим их на консоль
Metadata responseHeaders = await call.ResponseHeadersAsync;
foreach (var header in responseHeaders)
{
    Console.WriteLine($"{header.Key}: {header.Value}");
}

Для установки кастомных заголовков создаем объект Metadata и добавляем в него один заголовок с ключом "username" и значением "Tom".

Metadata requestHeaders = new Metadata();
requestHeaders.Add("username", "Tom");

Через второй, необязательный параметр метода отправки указываем отправляемые заголовки:

using var call = client.SendMessageAsync(new Request(), requestHeaders);

Таким образом, клиент отправляет заголовки на сервер.

Код сервера

Теперь изменим код сервиса, чтобы он получил кастомный заголовок "username" и отправил свой кастомный заголовок:

using Grpc.Core;
using Metanit;  // пространство имен сервиса MessengerService

public class MessengerService : Messenger.MessengerBase
{
    public override async Task<Response> SendMessage(Request request, ServerCallContext context)
    {
        // получаем все заголовки запроса
        foreach(var header in context.RequestHeaders)
        {
            Console.WriteLine($"{header.Key}: {header.Value}");    // получаем ключ и значение заголовка
        }
        // получаем один заголовок  username
        var username = context.RequestHeaders.GetValue("username");

        // формируем заголовки ответа
        Metadata responseHeaders = new Metadata();
        responseHeaders.Add("secret-code", "123445");
        // пишем заголовки в ответ
        await context.WriteResponseHeadersAsync(responseHeaders);
        // отправляем ответ
        return await Task.FromResult(new Response { Content =  $"Hello {username}!" });
    }
}

Здесь получаем от клиента кастомный заголовок "username":

var username = context.RequestHeaders.GetValue("username");

Затем используем его для отправки в ответе:

return await Task.FromResult(new Response { Content =  $"Hello {username}!" });

Кроме того, сервер также отпправляет свой кастомный заголовок - "secret-code":

Metadata responseHeaders = new Metadata();
responseHeaders.Add("secret-code", "123445");

Для отправки применяется метод context.WriteResponseHeadersAsync(), который принимает отправляемый объект Metadata:

await context.WriteResponseHeadersAsync(responseHeaders);

Запустим программы сервера и клиента. Консоль сервиса также выведет все полученные заголовки, в том числе заголовок "username":

user-agent: grpc-dotnet/2.50.0 (.NET 7.0.1; CLR 7.0.1; net7.0; windows; x64)
username: Tom

А консоль клиента отобразит ответ сервера и заголовки ответа, в том числе заголовок "secret-code":

Response: Hello Tom
date: Thu, 22 Dec 2022 17:36:34 GMT
server: Kestrel
secret-code: 123445
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850