Grupa robocza IETF HTTP opracowała specyfikację nowej metody żądania HTTP o nazwie QUERY (draft-ietf-httpbis-safe-method-w-body, na drodze do stania się RFC). QUERY wypełnia lukę, na którą natknął się każdy programista REST: jest to żądanie bezpieczne i idempotentne, jak GET, ale przenosi treść żądania, jak POST. sgcWebSockets obsługuje teraz QUERY od początku do końca, w klientach HTTP/1.1, HTTP/2 i HTTP/3 oraz w komponentach serwerowych, dla Delphi i C++ Builder.
Dlaczego nowa metoda HTTP?
Do tej pory istniały dwa sposoby wysłania zapytania do serwera i oba mają swoje problemy. Można zakodować zapytanie w adresie URL żądania GET, ale adresy URL mają praktyczne limity długości, złożone zapytania są uciążliwe w escapowaniu, a pełny adres URL zwykle trafia do logów proxy i serwera, co jest złym miejscem dla wrażliwych wartości filtrów. Można też wysłać zapytanie w treści żądania POST, ale POST nie jest bezpieczny, nie jest idempotentny i nie podlega buforowaniu: klient HTTP nie może automatycznie ponowić go po awarii połączenia, a pamięci podręczne nie zapiszą odpowiedzi.
QUERY łączy najlepsze cechy obu podejść. Treść żądania i jej Content-Type definiują zapytanie, więc może być tak duże i tak ustrukturyzowane, jak potrzeba (dane formularza, JSON, SQL, JSONPath, cokolwiek akceptuje serwer). Jednocześnie specyfikacja deklaruje QUERY jako bezpieczne i idempotentne: nigdy nie zmienia stanu zasobu, może być bezpiecznie ponawiane lub powtarzane, a jego odpowiedzi podlegają buforowaniu dokładnie tak, jak odpowiedź GET.
Jak działa QUERY
Żądanie QUERY wygląda jak POST z semantyką GET. Ten przykład, zaadaptowany ze specyfikacji, prosi zasób kontaktów o przefiltrowaną projekcję:
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" }
]
Najważniejsze reguły ze specyfikacji:
- Każde żądanie QUERY musi zawierać nagłówek
Content-Typezgodny z jego treścią. Serwer musi odrzucić żądanie ze statusem 4xx (400, 415 lub 422), gdy nagłówek jest nieobecny lub niespójny z treścią. - Odpowiedzi podlegają buforowaniu. Pamięć podręczna, która je przechowuje, musi uwzględnić treść żądania w kluczu bufora, aby dwa różne zapytania nigdy nie kolidowały.
- Przekierowania zachowują metodę: po 301, 302, 307 lub 308 klient powtarza QUERY wobec nowego URI. Dawny wyjątek, który przepisuje przekierowany POST na GET, nie ma zastosowania do QUERY. Odpowiedź 303 See Other to jawny sposób, w jaki serwer mówi "pobierz wynik żądaniem GET z tego nagłówka Location".
- Serwer może ogłaszać, jakie formaty zapytań rozumie zasób, za pomocą nowego nagłówka odpowiedzi
Accept-Query, a QUERY pojawia się w nagłówkuAllowodpowiedzi OPTIONS, gdy jest obsługiwane. - W przeglądarkach QUERY nie jest metodą z listy bezpiecznych metod CORS, więc wywołania między różnymi źródłami wyzwalają żądanie wstępne (preflight). Domyślne ustawienia CORS serwera OpenAPI w sgcWebSockets już zawierają QUERY w
Access-Control-Allow-Methods.
QUERY z klienta HTTP/1.1
Klient HTTP/1.1 zyskuje metody Query i QueryAsync, które działają dokładnie tak, jak ich odpowiedniki Post: przekaż adres URL i strumień z treścią zapytania, ustaw Content-Type w żądaniu i odczytaj wynik.
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;
Ponieważ QUERY jest idempotentne, dobrze współpracuje również z opcjami ponawiania klienta: żądanie QUERY, które nie powiodło się z powodu błędu połączenia, może zostać automatycznie ponowione bez ryzyka zduplikowania zmiany stanu na serwerze. Te same metody Query są dostępne w bazowym kliencie REST API, więc niestandardowe integracje API zbudowane na sgcWebSockets mogą przyjąć ten czasownik bez dodatkowej pracy.
QUERY przez HTTP/2 i HTTP/3
Klienci HTTP/2 i HTTP/3 udostępniają ten sam czasownik. Przez HTTP/2 żądanie jest wysyłane z pseudonagłówkiem :method ustawionym na QUERY i treścią w ramkach 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;
Klient HTTP/3 oferuje przeciążenie przyjmujące łańcuch znaków, z typem treści form-urlencoded jako domyślnym, co sprowadza proste zapytania do jednego wywołania:
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;
Obsługa QUERY na serwerze
Po stronie serwera TsgcWebSocketHTTPServer akceptuje żądania QUERY przez HTTP/1.1 i HTTP/2 i przekazuje je do zdarzenia OnCommandOther, z treścią żądania już odczytaną i dostępną w PostStream. Minimalna procedura obsługi weryfikuje Content-Type, uruchamia zapytanie i zwraca wynik:
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;
Serwer HTTP/3 odczytuje treści QUERY w ten sam sposób, serwer OpenAPI kieruje operacje query zadeklarowane w specyfikacji OpenAPI i ogłasza tę metodę w swoich domyślnych ustawieniach CORS, a funkcja przekazywania HTTP przekazuje żądania QUERY wraz z ich treścią do serwera zaplecza.
Dostępność
Obsługa QUERY jest zawarta w sgcWebSockets 2026.7 w klientach HTTP/1.1, HTTP/2 i HTTP/3, bazowym kliencie REST API, komponentach serwerowych i serwerze OpenAPI, dla Delphi 7 do Delphi 13 oraz C++ Builder. Ponieważ format przesyłany po sieci to zwykłe HTTP, metoda już dziś współpracuje z każdym serwerem lub klientem, który ją rozumie. Najnowszą wersję można pobrać ze strony pobierania sgcWebSockets.
Pytania, uwagi lub pomoc w migracji? Skontaktuj się z nami, odpowiedzą osoby, które napisały ten kod.
