Die neue HTTP-QUERY-Methode in Delphi | eSeGeCe Blog

Die neue HTTP-QUERY-Methode in Delphi

· Funktionen

Die IETF HTTP Working Group hat eine neue HTTP-Anfragemethode namens QUERY spezifiziert (draft-ietf-httpbis-safe-method-w-body, auf dem Weg zum RFC). QUERY schließt eine Lücke, auf die jeder REST-Entwickler schon gestoßen ist: Es ist eine sichere und idempotente Anfrage wie GET, trägt aber einen Request-Body wie POST. sgcWebSockets unterstützt QUERY jetzt durchgängig, in den HTTP/1.1-, HTTP/2- und HTTP/3-Clients sowie in den Serverkomponenten, für Delphi und C++ Builder.

Warum eine neue HTTP-Methode?

Bislang gab es zwei Möglichkeiten, eine Abfrage an einen Server zu senden, und beide haben Probleme. Man kann die Abfrage in die URL einer GET-Anfrage kodieren, aber URLs haben praktische Längenbeschränkungen, komplexe Abfragen sind mühsam zu maskieren, und die vollständige URL landet meist in Proxy- und Serverprotokollen, ein schlechter Ort für sensible Filterwerte. Oder man sendet die Abfrage im Body einer POST-Anfrage, aber POST ist weder sicher noch idempotent noch cachefähig: Ein HTTP-Client darf die Anfrage nach einem Verbindungsfehler nicht automatisch wiederholen, und Caches speichern die Antwort nicht.

QUERY kombiniert das Beste aus beiden Welten. Der Inhalt der Anfrage und ihr Content-Type definieren die Abfrage, sie kann also so groß und so strukturiert sein wie nötig (Formulardaten, JSON, SQL, JSONPath, was auch immer der Server akzeptiert). Gleichzeitig erklärt die Spezifikation QUERY für sicher und idempotent: Die Anfrage ändert nie den Zustand der Ressource, sie kann gefahrlos wiederholt werden, und ihre Antworten sind genau wie eine GET-Antwort cachefähig.

So funktioniert QUERY

Eine QUERY-Anfrage sieht aus wie ein POST mit GET-Semantik. Dieses Beispiel, angelehnt an die Spezifikation, fragt eine Kontakte-Ressource nach einer gefilterten Projektion:

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" }
]

Die wichtigsten Regeln der Spezifikation:

QUERY mit dem HTTP/1.1-Client

Der HTTP/1.1-Client erhält die Methoden Query und QueryAsync, die genau wie ihre Post-Gegenstücke funktionieren: URL und einen Stream mit dem Abfrage-Body übergeben, den Content-Type in der Anfrage setzen und das Ergebnis lesen.

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;

Da QUERY idempotent ist, harmoniert es auch gut mit den Retry-Optionen des Clients: Ein QUERY, das mit einem Verbindungsfehler fehlschlägt, kann automatisch wiederholt werden, ohne dass die Gefahr besteht, eine Zustandsänderung auf dem Server zu duplizieren. Dieselben Query-Methoden stehen im REST-API-Basisclient zur Verfügung, sodass eigene API-Integrationen auf Basis von sgcWebSockets das Verb ohne zusätzlichen Aufwand übernehmen können.

QUERY über HTTP/2 und HTTP/3

Die HTTP/2- und HTTP/3-Clients bieten dasselbe Verb. Über HTTP/2 wird die Anfrage mit dem auf QUERY gesetzten :method-Pseudo-Header gesendet, der Body geht in DATA-Frames:

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;

Der HTTP/3-Client bietet eine String-Überladung mit dem Form-urlencoded-Content-Type als Standard, damit bleiben einfache Abfragen ein einziger Aufruf:

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;

QUERY auf dem Server verarbeiten

Auf der Serverseite akzeptiert TsgcWebSocketHTTPServer QUERY-Anfragen über HTTP/1.1 und HTTP/2 und leitet sie an das Ereignis OnCommandOther weiter, wobei der Request-Body bereits gelesen wurde und in PostStream verfügbar ist. Ein minimaler Handler validiert den Content-Type, führt die Abfrage aus und liefert das Ergebnis zurück:

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;

Der HTTP/3-Server liest QUERY-Bodys auf dieselbe Weise, der OpenAPI-Server routet in einer OpenAPI-Spezifikation deklarierte query-Operationen und kündigt die Methode in seinen CORS-Voreinstellungen an, und die HTTP-Weiterleitungsfunktion reicht QUERY-Anfragen samt Body als Proxy an einen Backend-Server durch.

Verfügbarkeit

Die QUERY-Unterstützung ist in sgcWebSockets 2026.7 enthalten, in den HTTP/1.1-, HTTP/2- und HTTP/3-Clients, im REST-API-Basisclient, in den Serverkomponenten und im OpenAPI-Server, für Delphi 7 bis Delphi 13 und C++ Builder. Da das zugrunde liegende Wire-Format reines HTTP ist, interoperiert es schon heute mit jedem Server oder Client, der die Methode versteht. Die aktuelle Version können Sie von der sgcWebSockets-Downloadseite herunterladen.

Fragen, Feedback oder Hilfe bei der Migration? Kontaktieren Sie uns, Sie erhalten eine Antwort von den Leuten, die den Code geschrieben haben.