Дано:
Возможные варианты:
Ссылки для изучения возможностей nginx и envoy:
https://www.nginx.com/blog/nginx-1-13-10-grpc/
https://habr.com/ru/post/351994/
https://www.nginx.com/blog/deploying-nginx-plus-as-an-api-gateway-part-3-publishing-grpc-services/
https://dropbox.tech/infrastructure/how-we-migrated-dropbox-from-nginx-to-envoy
Чем больше я копал, тем больше фрустрация меня одолевала. Kubernetes, Ingress, Istio, service mesh, темный лес - аааа.
В итоге решено было реализовать самописное решение на языке go.
Сказано - сделано.
Далее создается функция-director для прокси сервера, возвращающая по имени вызываемого метода значение из хэш-таблицы.
для proxy и для grpc-отражения используются модули:
https://github.com/mwitkow/grpc-proxy
https://github.com/jhump/protoreflect
Создается gRPC-сервер, регистрируется прокси.
В завершение, регистрируется сервис gRPC-отражения со всеми собранными proto-определениями.
endpoints:
- dial: "localhost:50051"
- dial: "localhost:50052"
- dial: "localhost:50053"
ca_cert: "../certs/ca.crt"
server:
listen: ":42001"
blacklist:
- "/grpc.examples.echo.Echo/ServerStreamingEcho"
Секция endpoints определяет внутренние подключения. В приведенном выше примере определены три: два в незащищенном режиме, один с использованием TLS. Также возможно опциями my_cert, my_key указать на ключевую пару клиента для использования режима mTLS.
Сервер настраивается в секции server.
В приведенном примере указан только привязываемый порт 42001, возможно использовать также опции my_cert, my_key для указания ключевой пары сервера и ca_cert для аутентификации клиентов.
Документацию по данному протоколу можно найти здесь:
https://github.com/grpc/grpc/blob/master/doc/server-reflection.md
Оттуда можно узнать, что отражение предназначено для возможности получить proto-определения "на лету", без необходимости кодогенерации инструментом protoc.
Пожалуй, это самая полный документ, что мне удалось найти.
Содержание многих остальных включает описание и указание вызвать reflection.Register(s) для сервера. Поставленная же задача - обратная.
Из описания видно, что отражение - это gRPC-сервис с одним методом grpc.reflection.v1alpha.ServerReflection.ServerReflectionInfo.
Хоть метод и один, запрос определяется полем oneof message_request.
Оттуда нам потребуются запросы list_services и file_containing_symbol.
Для исследования запустим greeter_server из официального репозитория grpc-go/examples/helloworld и подключимся утилитой evans.
~$ evans --host localhost --port 50052 -r
______
| ____|
| |__ __ __ __ _ _ __ ___
| __| \ \ / / / _. | | '_ \ / __|
| |____ \ V / | (_| | | | | | \__ \
|______| \_/ \__,_| |_| |_| |___/
more expressive universal gRPC client
helloworld.Greeter@localhost:50052> package grpc.reflection.v1alpha
grpc.reflection.v1alpha@localhost:50052> service ServerReflection
grpc.reflection.v1alpha.ServerReflection@localhost:50052> call ServerReflectionInfo
host (TYPE_STRING) =>
✔ list_services
list_services (TYPE_STRING) => *
host (TYPE_STRING) => {
"listServicesResponse": {
"service": [
{
"name": "grpc.reflection.v1alpha.ServerReflection"
},
{
"name": "helloworld.Greeter"
}
]
},
"originalRequest": {
"listServices": "*"
}
}
host (TYPE_STRING) =>
✔ file_containing_symbol
file_containing_symbol (TYPE_STRING) => helloworld.Greeter
host (TYPE_STRING) => {
"fileDescriptorResponse": {
"fileDescriptorProto": [
"Ci9leGFtcGxlcy...base64encoding..=="
]
},
"originalRequest": {
"fileContainingSymbol": "helloworld.Greeter"
}
}
Находим в исходниках evans:
https://github.com/ktr0731/evans/blob/master/grpc/grpcreflection/reflection.go
Все просто: первым делом, получаем список сервисов, далее, получаем описания файлов, определяющих данный сервис.
Без зазрения совести(смотрим лицензии) копируем, добавляем обертку:
func DiscoverServices(conn *grpc.ClientConn) (error, []string, []*desc.FileDescriptor) {
stub := rpb.NewServerReflectionClient(conn)
rclient := grpcreflect.NewClient(context.Background(), stub)
// получить proto-файлы
fds, err := ListPackages(rclient)
if err != nil {
return err, []string{}, []*desc.FileDescriptor{}
}
// Да, далее дублирование кода(ListServices и ResolveService),
// в рамках исследования, допускаем
services, err := rclient.ListServices()
if err != nil {
return err, []string{}, []*desc.FileDescriptor{}
}
// получить список методов в виде строк /helloworld.Greeter/SayHello
var methods []string
for _, srv := range services {
sdes, err := rclient.ResolveService(srv)
if err == nil {
for _, m := range sdes.GetMethods() {
fullMethodName := "/" + srv + "/" + m.GetName()
methods = append(methods, fullMethodName)
}
}
}
return nil, methods, fds
}
В фунции main, при подключении к gRPC-сервису добавляем функцию для рекурсивного получения всех зависимостей и вызываем для сбора всех дескрипторов, исключая пакет grpc.reflection:
import "github.com/jhump/protoreflect/desc"
...
...
func main() {
....
// map of processed methods
mProcessed := map[string]struct{}{}
// mapping method => connection
mapping := make(map[string]*grpc.ClientConn)
// collected file descriptors
fdsCollected := make([]*desc.FileDescriptor, 0)
for _, cli := range appConfig.Endpoints {
...
err, methods, fds := DiscoverServices(conn)
if err != nil {
panic(err)
}
var processFds func(f *desc.FileDescriptor) []*desc.FileDescriptor
processFds = func(f *desc.FileDescriptor) []*desc.FileDescriptor {
var result []*desc.FileDescriptor
result = append(result, f)
for _, d := range f.GetDependencies() {
result = append(result, processFds(d)...)
}
for _, d := range f.GetWeakDependencies() {
result = append(result, processFds(d)...)
}
for _, d := range f.GetPublicDependencies() {
result = append(result, processFds(d)...)
}
return result
}
for _, f := range fds {
if strings.HasPrefix(f.GetPackage(), "grpc.reflection") {
continue
}
fdsCollected = append(fdsCollected, processFds(f)...)
}
Далее, в этом же цикле, собираем список методов и, если метод был найден для другого подключения ранее, плюемся:
for _, m := range methods {
log.Println("method discovered: ", m)
if strings.HasPrefix(m, "/grpc.reflection.") {
// ignore reflection.
continue
}
if _, ok := mProcessed[m]; ok {
panic("duplicate method discovered!: " + m)
}
mProcessed[m] = struct{}{}
mapping[m] = conn
}
На этом сбор proto-определений, gRPC-методов завершен.
Берем базовый пример и на его основе пишем функцию director:
director := func(ctx context.Context, fullMethodName string) (context.Context,
*grpc.ClientConn, error) {
log.Println("somebody calling: ", fullMethodName)
// blacklist
for _, bl := range appConfig.Blacklist {
if strings.HasPrefix(fullMethodName, bl) {
log.Println("method is blacklisted")
return ctx, nil, grpc.Errorf(codes.Unimplemented, "blacklisted")
}
}
if conn, ok := mapping[fullMethodName]; ok {
log.Println("method registered")
md, ok := metadata.FromIncomingContext(ctx)
if ok {
outCtx, _ := context.WithCancel(ctx)
outCtx = metadata.NewOutgoingContext(outCtx, md.Copy())
return outCtx, conn, nil
}
}
log.Println("unknown method")
return ctx, nil, nil
}
Все, что она делает - ищет по имени метода подключение из хэш-таблицы и возвращает это подключение.
Далее, регистрируем для сервера:
var opts []grpc.ServerOption
opts = append(opts, grpc.CustomCodec(proxy.Codec()))
opts = append(opts, grpc.UnknownServiceHandler(proxy.TransparentHandler(director)))
Здесь я столкнулся с недостатком документации: все сводится к вызову reflection.Register(s). Находим определение здесь:
https://github.com/grpc/grpc-go/blob/v1.43.0/reflection/serverreflection.go
Изучаем. Структура, определяющая gRPC-сервис отражения:
type serverReflectionServer struct {
rpb.UnimplementedServerReflectionServer
s GRPCServer
initSymbols sync.Once
serviceNames []string
symbols map[string]*dpb.FileDescriptorProto // map of fully-qualified names to files
}
Далее смотрим реализацию процедуры ServerReflectionInfoдля типа запроса ListServices. Видим, что вызывается в первую очередь метод getSymbols().
Вызове функции происходит следующим образом:
s.initSymbols.Do(func() {
serviceInfo := s.s.GetServiceInfo()
s.symbols = map[string]*dpb.FileDescriptorProto{}
s.serviceNames = make([]string, 0, len(serviceInfo))
....
...
})
return s.serviceNames, s.symbols
При первом вызове с gRPC сервера считывается сервисная информация, далее, оттуда выдергиваются имена сервисов и proto-дескрипторы.
По идее, надо всего-лишь заменить этот метод своей реализацией, чтобы вернуть символы и сервисы из уже собранных файлов.
Для этого:
Добавляем к структуре serverReflectionServer таблицу fds map[string]*desc.FileDescriptor. Ключом будет являться имя файла.
Вместо метода Register пишем RegisterCustomReflection, принимающем массив дескрипторов на вход:
func RegisterCustomReflection(s GRPCServer, fds []*desc.FileDescriptor) {
srs := &serverReflectionServer{
s: s,
fds: make(map[string]*desc.FileDescriptor),
}
for _, f := range fds {
if _, ok := srs.fds[f.GetName()]; !ok {
srs.fds[f.GetName()] = f
}
}
rpb.RegisterServerReflectionServer(s, srs)
}
переписываем функцию getSymbols следующим образом:
s.initSymbols.Do(func() {
s.symbols = map[string]*dpb.FileDescriptorProto{}
s.serviceNames = []string{}
fProcessed := map[string]struct{}{}
sProcessed := map[string]struct{}{}
for _, fd := range s.fds {
// for each file descriptor
// get services, push it's name to serviceNames
// done: processFile(fd)
ssvcs := fd.GetServices()
for _, svc := range ssvcs {
fqn := svc.GetFullyQualifiedName()
// if there is duplicated service fqn, don't add it to slice
// e.g. reflection used in multiple services
if _, ok := sProcessed[fqn]; !ok {
s.serviceNames = append(s.serviceNames, fqn)
sProcessed[fqn] = struct{}{}
}
}
s.processFile(fd.AsFileDescriptorProto(), fProcessed)
}
sort.Strings(s.serviceNames)
})
При подключении клиента evans отправляется запросListServices, ответ корректен. Но вот далее возникает ошибка "unknown file: ..". Поиск указывает на функцию fileDescEncodingByFilename. Для получения дескриптора файла вызывается proto.FileDescriptor(name), который как раз возвращает nil и это приводит к вышеупомянутой ошибке. Где proto.FileDescriptor() должен найти файл я разбираться не стал, поменял на следующее:
func (s *serverReflectionServer) fileDescEncodingByFilename(name string, sentFileDescriptors map[string]bool) ([][]byte, error) {
// use s.fds instead
fd, ok := s.fds[name]
if ok {
return fileDescWithDependencies(fd.AsFileDescriptorProto(), sentFileDescriptors)
}
return nil, fmt.Errorf("unknown file: %v", name)
}
В итоге заработало, evans считывает протокол отражения корректно.
bobalus@penguin:~/build/test$ evans --host localhost --port 42001 -r
______
| ____|
| |__ __ __ __ _ _ __ ___
| __| \ \ / / / _. | | '_ \ / __|
| |____ \ V / | (_| | | | | | \__ \
|______| \_/ \__,_| |_| |_| |___/
more expressive universal gRPC client
localhost:42001> show package
+--------------------+
| PACKAGE |
+--------------------+
| grpc.examples.echo |
| helloworld |
+--------------------+
localhost:42001> package helloworld
helloworld@localhost:42001> service Greeter
helloworld.Greeter@localhost:42001> call SayHello
name (TYPE_STRING) => friend
{
"message": "Hello friend"
}
helloworld.Greeter@localhost:42001>
Единственное, в списке пакетов отстутствует grpc.reflection.v1apha.
Как это починить - мне пока не известно, к тому же решено проект оставить как инструмент для отладки, а не как шлюз.
Репозиторий проекта:
https://github.com/shabunin/grpc-api-gw
Считаю так, поскольку, во-первых, протокол gRPC-отражения на данный момент имеет версию альфа, документации мало.
Во-вторых, потому-что код я написал достаточно страшный. =)
Остается исследовать nginx, envoy, traefik.
- несколько gRPC-сервисов, каждый слушает свой порт.
- сервисы могут доверенно подключаться друг к другу, для аутентификации используется Mutual TLS.
- некоторые процедуры предназначены только для внутреннего пользования, доступ извне к ним должен быть ограничен
- единую точку входа для API (API Gateway) для gRPC, HTTP/2.
Возможные варианты:
- nginx
- Envoy
- Traefik
- Istio
- самописное решение
Ссылки для изучения возможностей nginx и envoy:
https://www.nginx.com/blog/nginx-1-13-10-grpc/
https://habr.com/ru/post/351994/
https://www.nginx.com/blog/deploying-nginx-plus-as-an-api-gateway-part-3-publishing-grpc-services/
https://dropbox.tech/infrastructure/how-we-migrated-dropbox-from-nginx-to-envoy
Чем больше я копал, тем больше фрустрация меня одолевала. Kubernetes, Ingress, Istio, service mesh, темный лес - аааа.
В итоге решено было реализовать самописное решение на языке go.
Сказано - сделано.
Решение
Приложение подключается к определенным в файле конфигурации точкам, считывает информацию о предоставляемых ими сервисах и типах сообщений, используя gRPC-reflection (на русском назовем отражением), суммирует все полученные сервисы и создает хэш-таблицу с именами метода в качестве ключа и gRPC-подключением в качестве значения.Далее создается функция-director для прокси сервера, возвращающая по имени вызываемого метода значение из хэш-таблицы.
для proxy и для grpc-отражения используются модули:
https://github.com/mwitkow/grpc-proxy
https://github.com/jhump/protoreflect
Создается gRPC-сервер, регистрируется прокси.
В завершение, регистрируется сервис gRPC-отражения со всеми собранными proto-определениями.
Файл конфигурации
Файл конфигурации выглядит следующим образом:endpoints:
- dial: "localhost:50051"
- dial: "localhost:50052"
- dial: "localhost:50053"
ca_cert: "../certs/ca.crt"
server:
listen: ":42001"
blacklist:
- "/grpc.examples.echo.Echo/ServerStreamingEcho"
Секция endpoints определяет внутренние подключения. В приведенном выше примере определены три: два в незащищенном режиме, один с использованием TLS. Также возможно опциями my_cert, my_key указать на ключевую пару клиента для использования режима mTLS.
Сервер настраивается в секции server.
В приведенном примере указан только привязываемый порт 42001, возможно использовать также опции my_cert, my_key для указания ключевой пары сервера и ca_cert для аутентификации клиентов.
Отражение
Чтобы знать куда перенаправлять вызов по имени метода нам нужно получить список этих самых методов. Для этого используем протокол серверного отражения.Документацию по данному протоколу можно найти здесь:
https://github.com/grpc/grpc/blob/master/doc/server-reflection.md
Оттуда можно узнать, что отражение предназначено для возможности получить proto-определения "на лету", без необходимости кодогенерации инструментом protoc.
Пожалуй, это самая полный документ, что мне удалось найти.
Содержание многих остальных включает описание и указание вызвать reflection.Register(s) для сервера. Поставленная же задача - обратная.
Из описания видно, что отражение - это gRPC-сервис с одним методом grpc.reflection.v1alpha.ServerReflection.ServerReflectionInfo.
Хоть метод и один, запрос определяется полем oneof message_request.
Оттуда нам потребуются запросы list_services и file_containing_symbol.
Для исследования запустим greeter_server из официального репозитория grpc-go/examples/helloworld и подключимся утилитой evans.
~$ evans --host localhost --port 50052 -r
______
| ____|
| |__ __ __ __ _ _ __ ___
| __| \ \ / / / _. | | '_ \ / __|
| |____ \ V / | (_| | | | | | \__ \
|______| \_/ \__,_| |_| |_| |___/
more expressive universal gRPC client
helloworld.Greeter@localhost:50052> package grpc.reflection.v1alpha
grpc.reflection.v1alpha@localhost:50052> service ServerReflection
grpc.reflection.v1alpha.ServerReflection@localhost:50052> call ServerReflectionInfo
host (TYPE_STRING) =>
✔ list_services
list_services (TYPE_STRING) => *
host (TYPE_STRING) => {
"listServicesResponse": {
"service": [
{
"name": "grpc.reflection.v1alpha.ServerReflection"
},
{
"name": "helloworld.Greeter"
}
]
},
"originalRequest": {
"listServices": "*"
}
}
host (TYPE_STRING) =>
✔ file_containing_symbol
file_containing_symbol (TYPE_STRING) => helloworld.Greeter
host (TYPE_STRING) => {
"fileDescriptorResponse": {
"fileDescriptorProto": [
"Ci9leGFtcGxlcy...base64encoding..=="
]
},
"originalRequest": {
"fileContainingSymbol": "helloworld.Greeter"
}
}
Находим в исходниках evans:
https://github.com/ktr0731/evans/blob/master/grpc/grpcreflection/reflection.go
Все просто: первым делом, получаем список сервисов, далее, получаем описания файлов, определяющих данный сервис.
Без зазрения совести(смотрим лицензии) копируем, добавляем обертку:
func DiscoverServices(conn *grpc.ClientConn) (error, []string, []*desc.FileDescriptor) {
stub := rpb.NewServerReflectionClient(conn)
rclient := grpcreflect.NewClient(context.Background(), stub)
// получить proto-файлы
fds, err := ListPackages(rclient)
if err != nil {
return err, []string{}, []*desc.FileDescriptor{}
}
// Да, далее дублирование кода(ListServices и ResolveService),
// в рамках исследования, допускаем
services, err := rclient.ListServices()
if err != nil {
return err, []string{}, []*desc.FileDescriptor{}
}
// получить список методов в виде строк /helloworld.Greeter/SayHello
var methods []string
for _, srv := range services {
sdes, err := rclient.ResolveService(srv)
if err == nil {
for _, m := range sdes.GetMethods() {
fullMethodName := "/" + srv + "/" + m.GetName()
methods = append(methods, fullMethodName)
}
}
}
return nil, methods, fds
}
В фунции main, при подключении к gRPC-сервису добавляем функцию для рекурсивного получения всех зависимостей и вызываем для сбора всех дескрипторов, исключая пакет grpc.reflection:
import "github.com/jhump/protoreflect/desc"
...
...
func main() {
....
// map of processed methods
mProcessed := map[string]struct{}{}
// mapping method => connection
mapping := make(map[string]*grpc.ClientConn)
// collected file descriptors
fdsCollected := make([]*desc.FileDescriptor, 0)
for _, cli := range appConfig.Endpoints {
...
err, methods, fds := DiscoverServices(conn)
if err != nil {
panic(err)
}
var processFds func(f *desc.FileDescriptor) []*desc.FileDescriptor
processFds = func(f *desc.FileDescriptor) []*desc.FileDescriptor {
var result []*desc.FileDescriptor
result = append(result, f)
for _, d := range f.GetDependencies() {
result = append(result, processFds(d)...)
}
for _, d := range f.GetWeakDependencies() {
result = append(result, processFds(d)...)
}
for _, d := range f.GetPublicDependencies() {
result = append(result, processFds(d)...)
}
return result
}
for _, f := range fds {
if strings.HasPrefix(f.GetPackage(), "grpc.reflection") {
continue
}
fdsCollected = append(fdsCollected, processFds(f)...)
}
Далее, в этом же цикле, собираем список методов и, если метод был найден для другого подключения ранее, плюемся:
for _, m := range methods {
log.Println("method discovered: ", m)
if strings.HasPrefix(m, "/grpc.reflection.") {
// ignore reflection.
continue
}
if _, ok := mProcessed[m]; ok {
panic("duplicate method discovered!: " + m)
}
mProcessed[m] = struct{}{}
mapping[m] = conn
}
На этом сбор proto-определений, gRPC-методов завершен.
Прокси
Для реализации, как уже было написано выше, выбран пакет https://github.com/mwitkow/grpc-proxyБерем базовый пример и на его основе пишем функцию director:
director := func(ctx context.Context, fullMethodName string) (context.Context,
*grpc.ClientConn, error) {
log.Println("somebody calling: ", fullMethodName)
// blacklist
for _, bl := range appConfig.Blacklist {
if strings.HasPrefix(fullMethodName, bl) {
log.Println("method is blacklisted")
return ctx, nil, grpc.Errorf(codes.Unimplemented, "blacklisted")
}
}
if conn, ok := mapping[fullMethodName]; ok {
log.Println("method registered")
md, ok := metadata.FromIncomingContext(ctx)
if ok {
outCtx, _ := context.WithCancel(ctx)
outCtx = metadata.NewOutgoingContext(outCtx, md.Copy())
return outCtx, conn, nil
}
}
log.Println("unknown method")
return ctx, nil, nil
}
Все, что она делает - ищет по имени метода подключение из хэш-таблицы и возвращает это подключение.
Далее, регистрируем для сервера:
var opts []grpc.ServerOption
opts = append(opts, grpc.CustomCodec(proxy.Codec()))
opts = append(opts, grpc.UnknownServiceHandler(proxy.TransparentHandler(director)))
Еще раз отражение
Теперь уже надо подключить протокол отражения для прокси сервера: все собранные файлы есть, почему бы прокси-серверу не рассказать клиентам о всех доступных сервисах?Здесь я столкнулся с недостатком документации: все сводится к вызову reflection.Register(s). Находим определение здесь:
https://github.com/grpc/grpc-go/blob/v1.43.0/reflection/serverreflection.go
Изучаем. Структура, определяющая gRPC-сервис отражения:
type serverReflectionServer struct {
rpb.UnimplementedServerReflectionServer
s GRPCServer
initSymbols sync.Once
serviceNames []string
symbols map[string]*dpb.FileDescriptorProto // map of fully-qualified names to files
}
Далее смотрим реализацию процедуры ServerReflectionInfoдля типа запроса ListServices. Видим, что вызывается в первую очередь метод getSymbols().
Вызове функции происходит следующим образом:
s.initSymbols.Do(func() {
serviceInfo := s.s.GetServiceInfo()
s.symbols = map[string]*dpb.FileDescriptorProto{}
s.serviceNames = make([]string, 0, len(serviceInfo))
....
...
})
return s.serviceNames, s.symbols
При первом вызове с gRPC сервера считывается сервисная информация, далее, оттуда выдергиваются имена сервисов и proto-дескрипторы.
По идее, надо всего-лишь заменить этот метод своей реализацией, чтобы вернуть символы и сервисы из уже собранных файлов.
Для этого:
Добавляем к структуре serverReflectionServer таблицу fds map[string]*desc.FileDescriptor. Ключом будет являться имя файла.
Вместо метода Register пишем RegisterCustomReflection, принимающем массив дескрипторов на вход:
func RegisterCustomReflection(s GRPCServer, fds []*desc.FileDescriptor) {
srs := &serverReflectionServer{
s: s,
fds: make(map[string]*desc.FileDescriptor),
}
for _, f := range fds {
if _, ok := srs.fds[f.GetName()]; !ok {
srs.fds[f.GetName()] = f
}
}
rpb.RegisterServerReflectionServer(s, srs)
}
переписываем функцию getSymbols следующим образом:
s.initSymbols.Do(func() {
s.symbols = map[string]*dpb.FileDescriptorProto{}
s.serviceNames = []string{}
fProcessed := map[string]struct{}{}
sProcessed := map[string]struct{}{}
for _, fd := range s.fds {
// for each file descriptor
// get services, push it's name to serviceNames
// done: processFile(fd)
ssvcs := fd.GetServices()
for _, svc := range ssvcs {
fqn := svc.GetFullyQualifiedName()
// if there is duplicated service fqn, don't add it to slice
// e.g. reflection used in multiple services
if _, ok := sProcessed[fqn]; !ok {
s.serviceNames = append(s.serviceNames, fqn)
sProcessed[fqn] = struct{}{}
}
}
s.processFile(fd.AsFileDescriptorProto(), fProcessed)
}
sort.Strings(s.serviceNames)
})
При подключении клиента evans отправляется запросListServices, ответ корректен. Но вот далее возникает ошибка "unknown file: ..". Поиск указывает на функцию fileDescEncodingByFilename. Для получения дескриптора файла вызывается proto.FileDescriptor(name), который как раз возвращает nil и это приводит к вышеупомянутой ошибке. Где proto.FileDescriptor() должен найти файл я разбираться не стал, поменял на следующее:
func (s *serverReflectionServer) fileDescEncodingByFilename(name string, sentFileDescriptors map[string]bool) ([][]byte, error) {
// use s.fds instead
fd, ok := s.fds[name]
if ok {
return fileDescWithDependencies(fd.AsFileDescriptorProto(), sentFileDescriptors)
}
return nil, fmt.Errorf("unknown file: %v", name)
}
В итоге заработало, evans считывает протокол отражения корректно.
bobalus@penguin:~/build/test$ evans --host localhost --port 42001 -r
______
| ____|
| |__ __ __ __ _ _ __ ___
| __| \ \ / / / _. | | '_ \ / __|
| |____ \ V / | (_| | | | | | \__ \
|______| \_/ \__,_| |_| |_| |___/
more expressive universal gRPC client
localhost:42001> show package
+--------------------+
| PACKAGE |
+--------------------+
| grpc.examples.echo |
| helloworld |
+--------------------+
localhost:42001> package helloworld
helloworld@localhost:42001> service Greeter
helloworld.Greeter@localhost:42001> call SayHello
name (TYPE_STRING) => friend
{
"message": "Hello friend"
}
helloworld.Greeter@localhost:42001>
Единственное, в списке пакетов отстутствует grpc.reflection.v1apha.
Как это починить - мне пока не известно, к тому же решено проект оставить как инструмент для отладки, а не как шлюз.
Репозиторий проекта:
https://github.com/shabunin/grpc-api-gw
Заключение
Решение для API-шлюза мною найдено не было.Считаю так, поскольку, во-первых, протокол gRPC-отражения на данный момент имеет версию альфа, документации мало.
Во-вторых, потому-что код я написал достаточно страшный. =)
Остается исследовать nginx, envoy, traefik.
В поисках gRPC-шлюза
Дано: несколько gRPC-сервисов, каждый слушает свой порт. сервисы могут доверенно подключаться друг к другу, для аутентификации используется Mutual TLS. некоторые процедуры предназначены только для...
habr.com