В рамках приложения 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