Il gruppo di lavoro HTTP dell'IETF ha specificato un nuovo metodo di richiesta HTTP chiamato QUERY (draft-ietf-httpbis-safe-method-w-body, in procinto di diventare un RFC). QUERY colma una lacuna in cui ogni sviluppatore REST si è imbattuto: è una richiesta sicura e idempotente, come GET, ma trasporta un corpo di richiesta, come POST. sgcWebSockets ora supporta QUERY end to end, nei client HTTP/1.1, HTTP/2 e HTTP/3 e nei componenti server, per Delphi e C++ Builder.
Perché un nuovo metodo HTTP?
Fino ad ora c'erano due modi per inviare una query a un server, ed entrambi presentano problemi. Puoi codificare la query nell'URL di una richiesta GET, ma gli URL hanno limiti pratici di lunghezza, le query complesse sono difficili da gestire con l'escape e l'URL completo tende a finire nei log di proxy e server, che è un pessimo posto per valori di filtro sensibili. Oppure puoi inviare la query nel corpo di una richiesta POST, ma POST non è sicuro, non è idempotente e non è memorizzabile nella cache: un client HTTP non può ritentarlo automaticamente dopo un errore di connessione, e le cache non memorizzeranno la risposta.
QUERY combina il meglio di entrambi. Il contenuto della richiesta e il suo Content-Type definiscono la query, che può quindi essere grande e strutturata quanto necessario (dati di form, JSON, SQL, JSONPath, qualsiasi cosa il server accetti). Allo stesso tempo la specifica dichiara QUERY sicuro e idempotente: non modifica mai lo stato della risorsa, può essere ritentato o ripetuto in sicurezza e le sue risposte sono memorizzabili nella cache esattamente come una risposta GET.
Come funziona QUERY
Una richiesta QUERY assomiglia a una POST con la semantica di GET. Questo esempio, adattato dalla specifica, chiede a una risorsa di contatti una proiezione filtrata:
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" }
]
Le regole più importanti della specifica:
- Ogni richiesta QUERY deve includere un
Content-Typeche corrisponda al suo corpo. Un server deve rifiutare la richiesta con uno stato 4xx (400, 415 o 422) quando l'header è assente o incoerente con il contenuto. - Le risposte sono memorizzabili nella cache. Una cache che le memorizza deve includere il corpo della richiesta nella chiave di cache, così due query diverse non collidono mai.
- I redirect preservano il metodo: dopo un 301, 302, 307 o 308 il client ripete la QUERY verso il nuovo URI. La vecchia eccezione che riscrive una POST reindirizzata in una GET non si applica a QUERY. Un 303 See Other è il modo esplicito con cui un server dice "recupera il risultato con una GET da questa Location".
- Un server può annunciare quali formati di query una risorsa comprende con il nuovo header di risposta
Accept-Query, e QUERY compare nell'headerAllowdi una risposta OPTIONS quando è supportato. - Nei browser, QUERY non è un metodo incluso nella safelist CORS, quindi le chiamate cross-origin attivano un preflight. Le impostazioni CORS predefinite del server OpenAPI di sgcWebSockets includono già QUERY in
Access-Control-Allow-Methods.
QUERY dal client HTTP/1.1
Il client HTTP/1.1 acquisisce i metodi Query e QueryAsync, che funzionano esattamente come le loro controparti Post: passa l'URL e uno stream con il corpo della query, imposta il Content-Type nella richiesta e leggi il risultato.
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;
Poiché QUERY è idempotente, si integra bene anche con le opzioni di retry del client: una QUERY che fallisce con un errore di connessione può essere ritentata automaticamente senza il rischio di duplicare una modifica di stato sul server. Gli stessi metodi Query sono disponibili nel client base delle REST API, quindi le integrazioni API personalizzate costruite su sgcWebSockets possono adottare il verbo senza alcun lavoro aggiuntivo.
QUERY su HTTP/2 e HTTP/3
I client HTTP/2 e HTTP/3 espongono lo stesso verbo. Su HTTP/2 la richiesta viene inviata con lo pseudo-header :method impostato a QUERY e il corpo in frame 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;
Il client HTTP/3 offre un overload con stringa che usa come predefinito il content type form-urlencoded, il che riduce le query semplici a una singola chiamata:
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;
Gestire QUERY sul server
Sul lato server, TsgcWebSocketHTTPServer accetta richieste QUERY su HTTP/1.1 e HTTP/2 e le inoltra all'evento OnCommandOther, con il corpo della richiesta già letto e disponibile in PostStream. Un handler minimale valida il Content-Type, esegue la query e restituisce il risultato:
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;
Il server HTTP/3 legge i corpi delle richieste QUERY allo stesso modo, il server OpenAPI instrada le operazioni query dichiarate in una specifica OpenAPI e annuncia il metodo nelle sue impostazioni CORS predefinite, e la funzionalità di forwarding HTTP inoltra le richieste QUERY con il loro corpo a un server backend.
Disponibilità
Il supporto per QUERY è incluso in sgcWebSockets 2026.7 nei client HTTP/1.1, HTTP/2 e HTTP/3, nel client base delle REST API, nei componenti server e nel server OpenAPI, per Delphi da 7 a 13 e C++ Builder. Poiché il formato di trasmissione sottostante è semplice HTTP, interopera già oggi con qualsiasi server o client che comprenda il metodo. Puoi scaricare l'ultima versione dalla pagina di download di sgcWebSockets.
Domande, feedback o aiuto con la migrazione? Contattaci, riceverai una risposta dalle persone che hanno scritto il codice.
