[БЕЗ_ЗВУКА] В этом видео мы
продолжим рассматривать те возможности, которые нам предоставляет grpc.
Как мы видели ранее, мы можем просто дернуть удаленную функцию,
удаленный метод, и все.
Однако там не было ни проверок авторизации, ничего.
Это хотелось бы иметь, это в общем-то надо,
но и еще хочется логировать это, делать access log, timing и все такое.
Что мы можем с этим делать, что нам grpc может предложить?
Для этого у grpc есть
возможность делать UnaryInterceptor.
UnaryInterceptor — это перехватчик одиночных запросов,
по сути это как раз таки MiddleWare, который будет выполняться,
перед тем как запрос уйдет на удаленный сервер.
Также в grpc есть возможность передачи метадаты,
метаданных, как с запросом на сервер, так и со стороны сервера.
То есть это какие-то данные, которые приходят помимо самого запроса,
помимо самого результата удаленной функции.
Зачем это нужно?
В частности, например, чтобы передать токен,
то есть какие-то общесистемные вещи, которые относятся не непосредственно к
бизнес-логике самого удаленного сервиса, а скорее к движку.
В этом видео я как раз покажу, как это делать.
Для начала UnaryInterceptor.
При подключении к grpc я говорю, что можно указать n-е количество опций.
Я указываю UnaryInterceptor и в скобках указываю свою функцию.
Как она работает?
Вот код этой функции, сюда передается куча всяких параметров: контекст,
метод, запрос, ответ и все такое, коннект,
invoker — это как раз та функцию, которую мне нужно вызвать, и много всякого.
То есть что я тут делаю?
Я просто обернул это все в тайминг.
Я засек время начала, после этого я вывел всю сопутствующую информацию,
которая там есть, и все.
И вернул ошибку.
То есть у нас удаленный метод может вернуть ошибку.
Посмотрим, как это работает.
Ага.
Хорошо.
Вот мой запрос.
Я вызвал метод session.AuthChecker/Create,
и я туда передал запрос, потом мне вернулся результат.
Результат записывается в ту переменную по адресу, время, и ошибки не было.
Время много, потому что первое соединение слов и коннектов, все такое.
У последующей функции вызовов ничего нет фактически по времени.
Очень быстро, локальный хост.
То есть интерсептор вроде бы очень простой на стороне клиента.
Туда приходят все параметры,
и мы потом должны вызвать какую-то функцию с этими параметрами.
Это очень похоже на то, что мы рассматривали в MiddleWare.
Теперь метаданные.
Метаданные можно передавать несколькими способами.
Первый способ передать общие метаданные ко всем запросам
— это установить PerRPCCredentials.
PerRPCCredentials принимает в себя интерфейс, который можно реализовать.
В данном случае у меня есть тип авторизация по токену — то есть я создаю
структуру, не использую токен.
Когда эта штука будет вызываться, будет запрашиваться GetRequestMetadata,
я возвращаю там map, где как раз указываю токен.
И еще один метод, который запрашивает
информацию: нужна ли для этого способа авторизация с шифрованием.
Должно ли она работать по TLS/SSL.
Я говорю, что нет.
То есть этот метод будет вызываться перед тем,
как я пошлю запрос, и те данные, которые у меня есть,
в данном случае access-token, будут каждый раз добавляться к каждому запросу.
То есть чтобы даже не делать интерсептор, а указать.
То есть для каждого запроса понятно: если у нас есть какая-то система, у которой я
хочу указать токен или какие-то еще данные, я могу сделать это таким образом.
А если я хочу указывать запросы для каждого вызова?
Например, у меня есть request ID,
по которому я хочу отследить вызов моих процедур, где оно пошло и что было не так.
Для этого тоже есть способ: метаданные передаются через контекст.
Вот метадата, и это пакет от grpc.
Я сначала создаю контекст, потом создаю метаданные.
В данном случае pairs просто принимает четное число строк: первая
строка — это ключ, вторая — значение.
Но там есть и способ преобразовать это из map.
Потом я создаю контекст, NewOutgoingContext, уже с этими
метаданными, то есть метаданные туда присваиваются через WithValue.
И я вызываю свою функцию с этим контекстом.
То есть метаданные я могу добавить к любому своему вызову,
тоже довольно просто.
Как получить метаданные, которые мне вернулись со стороны запроса?
Я создам две переменные header, trailer: header — это то,
что придет в начале запроса, trailer — то, что придет в конце запроса.
Там можно даже вот так ограничить.
Теперь раньше у меня был вызов вот такой: то есть я туда передавал только аргумент
функции, однако вызовы grpc принимают еще дополнительные опции, и вот эти опции.
То есть я указал, что я хочу получать header вот в эту переменную,
и trailer — вот в эту переменную.
Ранее я печатал sessionID, и тут я напечатаю еще header и trailer.
Как это выглядит?
А вот так это выглядит.
Вот sessionID, который мне вернулся, то,
что я печатал в предыдущем примере, и вот header и trailer,
то есть какая-то map с ключами, в которых есть какое-то значение.
Опять-таки не используйте
header и trailer как параллели API для передачи параметров в удаленной функции.
Туда должны передаваться только какие-то общесистемные вещи,
типа requestID или токена авторизации.
На стороне клиента вроде бы все несложно.
Передали пару опций, указали функцию,
указали, в какую переменную записать результат.
Можно разобраться.
Теперь, каким образом это выглядит на сервере?
Давайте смотреть.
Вот наш сервер.
Значит, у сервера тоже
есть UnaryInterceptor, то есть перехватчик одиночных запросов.
Он тоже принимает функцию,
что он внутри этой функции делает?
Да фактически то же самое.
Очень похоже на перехватчик в клиенте.
Я тут тоже вывожу всякое, вызываю handler, вот
так даже будет лучше, то есть ту функцию, которая проделает реальную работу.
Я могу уже обернуть ее так, как мне надо.
В данном случае я получаю метаданные из запроса и вывожу их вместе со
всем остальным.
Вот в этом месте, например, можно вкрутить уже авторизацию по токену.
То есть я могу проверять в метаданных, есть ли там access-token,
правильный ли он, и разрешен ли для этого access-token вызов данного метода.
Смотрим, как это выглядит.
Вот, например, Check.
Сначала вызвался check ratelimit — это следующий пункт,
про который я буду говорить.
Вот вызвался call — это то, что я пишу руками.
А вот вызвался мой уже интерсептор.
Вот функция, которая у меня выводится, вот то, что у меня пришло, вот то, что ушло.
Время опять-таки нисколько.
И метаданные: то есть с какого сервера пришел запрос,
с какого юзер-агента он пришел, access-token, request ID и subsystem.
На основе этого уже можно делать какие-то проверки, что-то считать,
что-то оттуда получать.
То есть все более-менее просто.
Теперь, каким образом добавить метаданные к ответам?
Рассмотрим session.
Тут все тоже просто.
Создаем метаданные, структуру с метаданными,
и используя функцию SendHeader из grpc, я указываю уже сами эти метаданные.
С trailer аналогично: я просто указываю те значения, которые я хочу отправить.
То есть оно в таком виде никак не документировано,
это все будет чисто runtime проверятся.
Это параллельно API тому, что проверяется через compare time.
Используйте, пожалуйста, это с осторожностью,
ни в коем случае не как параллельно API для вашей бизнес-логики.
И есть еще одна функция, про которую я расскажу.
Это InTapHandle.
InTapHandle — это функция, которая выполняется при
приходе запроса, любого запроса на соединение.
В случае, если у нас UnaryInterceptor я уже все распаковал,
у меня уже есть метаданные, у меня уже есть параметры запроса, у меня есть метод,
куда я хочу это отправить, то InTapHandle, tapper, в данном случае у меня
эта функция rateLimiter называется, хотя реальной проверки rateLimiter там нет,
она вызывается, как только пришло само соединение.
То есть там еще ничего не распаршено, там еще нет ничего.
В этом месте она не выполняется в отдельной горутине,
в отличие от того же UnaryInterceptor.
Она нужна, для того чтобы вы могли проверить rateLimiter
или посчитать какую-то жесткую статистику до того, как все начнет паршится.
Потому что если у нас сервер перезагружается, нам не нужно
парсить дополнительные данные, чтобы потом выяснить, что мы не справляемся в работой.
Мы должны в самом-самом начале соединения,
на самом-самом приходе запроса это определить.
Определить это мы можем по методу, ну и если что,
мы можем докопаться до метаданных и прочего.
Но тут не стоит сажать какие-то блокирующие операции,
потому что этот перехватчик, этот TapHandle выполняется
в одном потоке на соединении.
Это те средства, которые предоставляет вам grpc для того,
чтобы вставить какие-то перехватчики и передать какие-то значения.
Оно используется в реальной работе, оно удобно, это, конечно, не все,
что предоставляет grpc, но это одно из самых основных.