O IETF HTTP Working Group especificou um novo método de requisição HTTP chamado QUERY (draft-ietf-httpbis-safe-method-w-body, a caminho de se tornar um RFC). O QUERY preenche uma lacuna que todo desenvolvedor REST já encontrou: é uma requisição segura e idempotente, como GET, mas que transporta um corpo de requisição, como POST. O sgcWebSockets agora oferece suporte completo ao QUERY, nos clientes HTTP/1.1, HTTP/2 e HTTP/3 e nos componentes de servidor, para Delphi e C++ Builder.
Por que um novo método HTTP?
Até agora havia duas formas de enviar uma consulta a um servidor, e ambas têm problemas. Você pode codificar a consulta na URL de uma requisição GET, mas as URLs têm limites práticos de comprimento, consultas complexas são difíceis de escapar e a URL completa costuma acabar nos logs de proxies e servidores, um péssimo lugar para valores de filtro sensíveis. Ou você pode enviar a consulta no corpo de uma requisição POST, mas o POST não é seguro, não é idempotente e não é armazenável em cache: um cliente HTTP não tem permissão para repeti-lo automaticamente após uma falha de conexão, e os caches não armazenam a resposta.
O QUERY combina o melhor dos dois. O conteúdo da requisição e seu Content-Type definem a consulta, que pode ser tão grande e tão estruturada quanto necessário (dados de formulário, JSON, SQL, JSONPath, o que o servidor aceitar). Ao mesmo tempo, a especificação declara o QUERY seguro e idempotente: ele nunca altera o estado do recurso, pode ser repetido com segurança e suas respostas são armazenáveis em cache, exatamente como uma resposta GET.
Como o QUERY funciona
Uma requisição QUERY se parece com um POST com semântica de GET. Este exemplo, adaptado da especificação, solicita a um recurso de contatos uma projeção filtrada:
QUERY /contacts HTTP/1.1
Host: example.org
Content-Type: application/x-www-form-urlencoded
Accept: application/json
select=surname,givenname,email&limit=10
HTTP/1.1 200 OK
Content-Type: application/json
[
{ "surname": "Smith", "givenname": "John", "email": "smith@example.org" },
{ "surname": "Jones", "givenname": "Sally", "email": "sally.jones@example.com" }
]
As regras mais importantes da especificação:
- Toda requisição QUERY deve incluir um
Content-Typeque corresponda ao seu corpo. Um servidor deve rejeitar a requisição com um status 4xx (400, 415 ou 422) quando o cabeçalho estiver ausente ou for inconsistente com o conteúdo. - As respostas são armazenáveis em cache. Um cache que as armazene deve incluir o corpo da requisição na chave de cache, para que duas consultas diferentes nunca colidam.
- Os redirecionamentos preservam o método: após um 301, 302, 307 ou 308, o cliente repete o QUERY na nova URI. A antiga exceção que reescreve um POST redirecionado em um GET não se aplica ao QUERY. Um 303 See Other é a forma explícita de um servidor dizer "busque o resultado com um GET a partir deste Location".
- Um servidor pode anunciar quais formatos de consulta um recurso entende com o novo cabeçalho de resposta
Accept-Query, e o QUERY aparece no cabeçalhoAllowde uma resposta OPTIONS quando há suporte. - Nos navegadores, o QUERY não é um método da lista segura do CORS, então chamadas entre origens disparam um preflight. Os padrões de CORS do servidor OpenAPI do sgcWebSockets já incluem o QUERY em
Access-Control-Allow-Methods.
QUERY a partir do cliente HTTP/1.1
O cliente HTTP/1.1 ganha os métodos Query e QueryAsync, que funcionam exatamente como seus equivalentes Post: passe a URL e um stream com o corpo da consulta, defina o Content-Type na requisição e leia o resultado.
uses
Classes, sgcHTTP;
var
oClient: TsgcHTTP1Client;
oQuery: TStringStream;
vResult: string;
begin
oClient := TsgcHTTP1Client.Create(nil);
try
oQuery := TStringStream.Create('select=surname,givenname,email&limit=10');
try
oClient.Request.ContentType := 'application/x-www-form-urlencoded';
oClient.Request.Accept := 'application/json';
vResult := oClient.Query('https://api.example.org/contacts', oQuery);
finally
oQuery.Free;
end;
finally
oClient.Free;
end;
end;
Como o QUERY é idempotente, ele também combina bem com as opções de retentativa do cliente: um QUERY que falha com um erro de conexão pode ser repetido automaticamente sem o risco de duplicar uma alteração de estado no servidor. Os mesmos métodos Query estão disponíveis no cliente base de API REST, então integrações de API personalizadas construídas sobre o sgcWebSockets podem adotar o verbo sem nenhum trabalho extra.
QUERY sobre HTTP/2 e HTTP/3
Os clientes HTTP/2 e HTTP/3 expõem o mesmo verbo. Sobre HTTP/2, a requisição é enviada com o pseudo-cabeçalho :method definido como QUERY e o corpo em frames DATA:
uses
Classes, sgcHTTP;
var
oClient: TsgcHTTP2Client;
oQuery: TStringStream;
vResult: string;
begin
oClient := TsgcHTTP2Client.Create(nil);
try
oQuery := TStringStream.Create('select=surname,givenname,email&limit=10');
try
vResult := oClient.Query('https://api.example.org/contacts', oQuery);
finally
oQuery.Free;
end;
finally
oClient.Free;
end;
end;
O cliente HTTP/3 oferece uma sobrecarga com string que usa o content type form-urlencoded como padrão, o que resolve consultas simples com uma única chamada:
uses
sgcHTTP;
var
oClient: TsgcHTTP3Client;
vResult: string;
begin
oClient := TsgcHTTP3Client.Create(nil);
try
vResult := oClient.Query('https://api.example.org/contacts',
'select=surname,givenname,email&limit=10');
finally
oClient.Free;
end;
end;
Tratando o QUERY no servidor
No lado do servidor, o TsgcWebSocketHTTPServer aceita requisições QUERY sobre HTTP/1.1 e HTTP/2 e as encaminha para o evento OnCommandOther, com o corpo da requisição já lido e disponível em PostStream. Um handler mínimo valida o Content-Type, executa a consulta e retorna o resultado:
procedure TForm1.ServerCommandOther(AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
var
vQuery: string;
begin
if SameText(ARequestInfo.Command, 'QUERY') then
begin
// the specification requires a Content-Type on every QUERY request
if (ARequestInfo.ContentType = '') or
not Assigned(ARequestInfo.PostStream) then
begin
AResponseInfo.ResponseNo := 400;
Exit;
end;
ARequestInfo.PostStream.Position := 0;
vQuery := ReadStringFromStream(ARequestInfo.PostStream);
AResponseInfo.ResponseNo := 200;
AResponseInfo.ContentType := 'application/json';
AResponseInfo.ContentText := RunContactsQuery(vQuery);
end;
end;
O servidor HTTP/3 lê corpos de QUERY da mesma forma, o servidor OpenAPI roteia operações query declaradas em uma especificação OpenAPI e anuncia o método em seus padrões de CORS, e o recurso de encaminhamento HTTP faz proxy de requisições QUERY com seu corpo para um servidor de backend.
Disponibilidade
O suporte ao QUERY está incluído no sgcWebSockets 2026.7 nos clientes HTTP/1.1, HTTP/2 e HTTP/3, no cliente base de API REST, nos componentes de servidor e no servidor OpenAPI, para Delphi 7 a Delphi 13 e C++ Builder. Como o formato de transmissão subjacente é HTTP puro, ele já interopera hoje com qualquer servidor ou cliente que entenda o método. Você pode baixar a versão mais recente na página de download do sgcWebSockets.
Dúvidas, comentários ou ajuda com a migração? Entre em contato, você receberá uma resposta das pessoas que escreveram o código.
