Skip to content
mobius1qwe edited this page Nov 12, 2026 · 1 revision

Home > gRPC 🚧

gRPC

Important

Em desenvolvimento (2.0). Ainda não está numa versão lançada e pode mudar.

Vale para a 2.0 · Delphi XE em diante e Lazarus/FPC 3.2 em diante · Nível: avançado

Em uma frase: o TRALGrpcModule atende chamadas gRPC no seu servidor e o TRALGrpcClient (ou o cliente que o ralprotoc gera) as faz, com mensagens Protocol Buffers.

Você vai precisar: o pacote RALModuleGrpcPackage, que requer o RALModuleProtobufPackage, e o ralprotoc para gerar as units a partir do .proto. Para gRPC nativo, o motor NgHttp2.

Três formas de falar

Protocol o que é onde roda
gpWeb (padrão do cliente) application/grpc-web: os trailers vão no corpo qualquer motor, até em HTTP/1.1; serve a navegadores
gpWebText application/grpc-web-text: o mesmo em base64 idem, para clientes que não leem binário
gpNative application/grpc, HTTP/2 com trailers de verdade só com o motor NgHttp2 no servidor; no cliente, EngineType := 'NgHttp2'

No servidor, AcceptProtocols escolhe o que o módulo aceita (as três, por padrão); outro tipo de conteúdo gRPC volta 415.

O .proto e o código gerado

syntax = "proto3";
package saudacao;
message OlaPedido { string nome = 1; int32 vezes = 2; }
message OlaResposta { string texto = 1; }
service Saudador {
  rpc Dizer (OlaPedido) returns (OlaResposta);
  rpc DizerVarias (OlaPedido) returns (stream OlaResposta);
  rpc Juntar (stream OlaPedido) returns (OlaResposta);
  rpc Conversar (stream OlaPedido) returns (stream OlaResposta);
}
ralprotoc -out=gen saudacao.proto

Para cada serviço, a unit traz:

  • TSaudadorClient: um método tipado por rpc;
  • TSaudadorServiceBase: um método virtual; abstract por rpc, que você sobrescreve.

Servidor

uses RALGrpcModule, Saudacao;

type
  TSaudador = class(TSaudadorServiceBase)
  protected
    function Dizer(ARequest: TOlaPedido; ACall: TRALGrpcServerCall): TOlaResposta; override;
    procedure DizerVarias(ARequest: TOlaPedido; ACall: TRALGrpcServerCall); override;
    function Juntar(ACall: TRALGrpcServerCall): TOlaResposta; override;
    procedure Conversar(ACall: TRALGrpcServerCall); override;
  end;

function TSaudador.Dizer(ARequest: TOlaPedido; ACall: TRALGrpcServerCall): TOlaResposta;
begin
  if ARequest.Nome = '' then
    raise ERALGrpcError.CreateStatus(RAL_GRPC_INVALID_ARGUMENT, 'falta o nome');
  Result := TOlaResposta.Create;          // o serviço libera
  Result.Texto := 'Olá, ' + ARequest.Nome;
end;

Modulo := TRALGrpcModule.Create(Self);
Modulo.AddService(TSaudador.Create);      // o módulo libera o serviço
Modulo.Server := Servidor;

As chamadas chegam em POST /<pacote>.<Serviço>/<Método> sob o Domain do módulo.

Os quatro tipos de método

tipo no .proto no serviço
unário rpc A (X) returns (Y) devolve a resposta (novo objeto; o serviço o libera)
servidor em fluxo returns (stream Y) ACall.Send(resposta) para cada uma; dá False se o cliente saiu ou o prazo passou
cliente em fluxo rpc A (stream X) ACall.Receive até vir nil; devolve a resposta
os dois em fluxo (stream X) returns (stream Y) Receive lê, Send responde

O cliente e o bidirecional são half duplex: o cliente manda todos os pedidos e só então lê as respostas, que chegam ao vivo.

A chamada: TRALGrpcServerCall

membro
ReadRequest, Receive a primeira / a próxima mensagem (quem chama libera; Receive dá nil no fim)
Send, SendReply uma resposta agora (fluxo) / a resposta de uma chamada unária
Fail(status, mensagem) termina com esse status, sem exceção
Status, StatusMessage o status com que a chamada vai terminar
Header(nome) cabeçalho (metadado) do pedido; vazio se faltar
AddHeader, AddTrailer metadado de resposta (antes do primeiro Send) / enviado com o status
Deadline, Expired, TimeLeft o prazo que o cliente pôs (grpc-timeout)
Request, Response, Protocol, Method, ServiceName acesso ao pedido do núcleo e à chamada

Uma exceção qualquer vira RAL_GRPC_UNKNOWN; para escolher o status, levante ERALGrpcError.CreateStatus(código, mensagem). Os códigos são as constantes RAL_GRPC_* (OK, CANCELLED, INVALID_ARGUMENT, DEADLINE_EXCEEDED, NOT_FOUND, UNIMPLEMENTED, UNAUTHENTICATED... os 17 do gRPC).

Sem código gerado

A coleção Methods do módulo declara métodos tratados por evento, no Object Inspector (ServiceName, Method, Kind, OnCall); OnUnknownMethod atende o que não existe (sem ele, o status é UNIMPLEMENTED). Dentro do evento, use ReadRequest(TClasse)/Receive(TClasse) e SendReply/Send.

Cliente

uses RALClient, RALGrpc, RALGrpcClient, Saudacao;

Cliente := TRALClient.Create(Self);
Cliente.BaseURL.Text := 'http://localhost:8000';

Saudador := TSaudadorClient.Create(Self);
Saudador.Client := Cliente;
Saudador.Protocol := gpWeb;
Saudador.Timeout := 5000;                      // grpc-timeout, em ms

Pedido := TOlaPedido.Create;
try
  Pedido.Nome := 'Ana';
  Resposta := Saudador.Dizer(Pedido);          // quem chama libera
  try
    WriteLn(Resposta.Texto);
  finally
    Resposta.Free;
  end;
except
  on E: ERALGrpcError do
    WriteLn(E.Status, ' ', E.StatusMessage);
end;

Em fluxo do servidor, cada resposta vai a um evento (TRALGrpcMessageEvent), na sua thread, e ACancel := True encerra a chamada:

procedure TForm1.AoResponder(ASender: TObject; AMessage: TRALProtoMessage; var ACancel: Boolean);
begin
  Memo1.Lines.Add(TOlaResposta(AMessage).Texto);   // não libere: o cliente libera
end;

Saudador.DizerVarias(Pedido, {$IFDEF FPC}@{$ENDIF}AoResponder);
propriedade do cliente
Client o TRALClient que leva as chamadas (motor, URL, TLS, autenticação)
Protocol gpWeb (padrão), gpWebText ou gpNative
Prefix caminho posto antes de /pacote.Serviço/Método (o Domain do módulo)
Timeout prazo de cada chamada em ms (grpc-timeout); 0 = sem prazo
Metadata cabeçalhos mandados em toda chamada (nome=valor)
Compress comprime cada mensagem com gzip; precisa da unit RALCompressZLib
MaxMessageSize maior mensagem de resposta aceita (4 MB; 0 = sem limite)
LastStatus, LastStatusMessage, LastMetadata o que a última chamada deste objeto trouxe (não serve a várias threads)

Um status diferente de OK levanta ERALGrpcError.

Mais detalhes

  • Prazo: o cliente manda grpc-timeout; no servidor, ACall.Expired/TimeLeft dizem quanto resta. Passado o prazo, Send dá False e a chamada termina com DEADLINE_EXCEEDED.
  • Compressão: gzip por mensagem nos dois lados, com RALCompressZLib no uses.
  • Tamanho: MaxMessageSize do módulo (pedido) e do cliente (resposta); passou, o status é RESOURCE_EXHAUSTED.
  • Navegador: o gRPC-Web vindo de outra origem precisa do TRALCORSPlugin liberando a origem. O módulo já manda Access-Control-Expose-Headers: grpc-status, grpc-message, para o JavaScript ler o status de uma resposta sem mensagem.
  • Foi testado contra o @grpc/grpc-js (nativo) e com curl (gRPC-Web).

Erros comuns

O que acontece Causa O que fazer
415 o módulo não aceita aquele tipo de conteúdo confira AcceptProtocols e o Protocol do cliente
gpNative não funciona o motor do servidor não manda trailers use o NgHttp2, ou gpWeb
UNIMPLEMENTED serviço ou método não ligado ao módulo AddService, ou o nome do .proto mudou
o navegador recusa a chamada a origem da página não está liberada ligue o TRALCORSPlugin com a origem
gzip falha falta RALCompressZLib no uses acrescente a unit
RESOURCE_EXHAUSTED mensagem maior que MaxMessageSize aumente o limite dos dois lados

Veja também

Começar

Servidor

MQTT 🚧

E-mail 🚧

Cliente

Motores

Avançado

Ajuda

🚧 = em desenvolvimento, pode mudar

Clone this wiki locally