Delphi의 새로운 HTTP QUERY 메서드 | eSeGeCe Blog

Delphi의 새로운 HTTP QUERY 메서드

· 기능

IETF HTTP 워킹 그룹이 QUERY라는 새로운 HTTP 요청 메서드를 명세했습니다(draft-ietf-httpbis-safe-method-w-body, RFC가 되는 과정에 있습니다). QUERY는 모든 REST 개발자가 겪어온 공백을 메웁니다. GET처럼 안전하고 멱등한 요청이면서도, POST처럼 요청 본문을 포함합니다. sgcWebSockets는 이제 Delphi와 C++ Builder를 위해 HTTP/1.1, HTTP/2, HTTP/3 클라이언트와 서버 컴포넌트에서 QUERY를 처음부터 끝까지 지원합니다.

왜 새로운 HTTP 메서드가 필요할까요?

지금까지 서버에 쿼리를 보내는 방법은 두 가지였고, 둘 다 문제가 있습니다. 쿼리를 GET 요청의 URL에 인코딩할 수 있지만, URL에는 실질적인 길이 제한이 있고, 복잡한 쿼리는 이스케이프하기 번거로우며, 전체 URL이 프록시와 서버 로그에 남는 경향이 있어 민감한 필터 값을 두기에 좋지 않은 위치입니다. 또는 POST 요청의 본문에 쿼리를 담아 보낼 수 있지만, POST는 안전하지도, 멱등하지도, 캐시 가능하지도 않습니다. HTTP 클라이언트는 연결 실패 후 이를 자동으로 재시도할 수 없고, 캐시는 응답을 저장하지 않습니다.

QUERY는 두 방식의 장점을 결합합니다. 요청 콘텐츠와 그 Content-Type이 쿼리를 정의하므로, 필요한 만큼 크고 구조화된 쿼리를 사용할 수 있습니다(폼 데이터, JSON, SQL, JSONPath 등 서버가 받아들이는 모든 형식). 동시에 명세는 QUERY를 안전하고 멱등한 것으로 선언합니다. 리소스의 상태를 절대 변경하지 않고, 안전하게 재시도하거나 반복할 수 있으며, 응답은 GET 응답과 마찬가지로 캐시할 수 있습니다.

QUERY의 작동 방식

QUERY 요청은 GET 의미론을 가진 POST처럼 보입니다. 명세에서 가져와 각색한 다음 예제는 연락처 리소스에 필터링된 프로젝션을 요청합니다:

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

명세에서 가장 중요한 규칙은 다음과 같습니다:

HTTP/1.1 클라이언트에서 QUERY 사용

HTTP/1.1 클라이언트에는 Post와 완전히 동일한 방식으로 동작하는 QueryQueryAsync 메서드가 추가되었습니다. URL과 쿼리 본문이 담긴 스트림을 전달하고, 요청에 Content-Type을 설정한 다음, 결과를 읽으면 됩니다.

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;

QUERY는 멱등하므로 클라이언트 재시도 옵션과도 잘 어울립니다. 연결 오류로 실패한 QUERY는 서버에서 상태 변경이 중복될 위험 없이 자동으로 재시도할 수 있습니다. 동일한 Query 메서드가 REST API 기본 클라이언트에서도 제공되므로, sgcWebSockets 기반으로 구축된 커스텀 API 통합에서도 추가 작업 없이 이 동사를 도입할 수 있습니다.

HTTP/2 및 HTTP/3에서의 QUERY

HTTP/2와 HTTP/3 클라이언트도 동일한 동사를 제공합니다. HTTP/2에서는 :method 의사 헤더를 QUERY로 설정하고 본문을 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;

HTTP/3 클라이언트는 form-urlencoded 콘텐츠 타입을 기본값으로 하는 문자열 오버로드를 제공하므로, 간단한 쿼리는 한 번의 호출로 처리할 수 있습니다:

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 처리하기

서버 측에서는 TsgcWebSocketHTTPServer가 HTTP/1.1과 HTTP/2를 통해 QUERY 요청을 수락하고, 요청 본문이 이미 읽혀 PostStream에서 사용할 수 있는 상태로 OnCommandOther 이벤트에 전달합니다. 최소한의 핸들러는 Content-Type을 검증하고, 쿼리를 실행한 후 결과를 반환합니다:

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;

HTTP/3 서버도 같은 방식으로 QUERY 본문을 읽으며, OpenAPI 서버는 OpenAPI 명세에 선언된 query 오퍼레이션을 라우팅하고 CORS 기본값에서 이 메서드를 알리며, HTTP 포워딩 기능은 QUERY 요청을 본문과 함께 백엔드 서버로 프록시합니다.

지원 버전

QUERY 지원은 sgcWebSockets 2026.7에 포함되어 있으며, HTTP/1.1, HTTP/2, HTTP/3 클라이언트, REST API 기본 클라이언트, 서버 컴포넌트, OpenAPI 서버에 걸쳐 Delphi 7부터 Delphi 13까지와 C++ Builder를 지원합니다. 기반 와이어 형식이 일반 HTTP이므로, 이 메서드를 이해하는 모든 서버나 클라이언트와 지금 바로 상호 운용됩니다. 최신 버전은 sgcWebSockets 다운로드 페이지에서 다운로드할 수 있습니다.

질문, 피드백 또는 마이그레이션 지원이 필요하신가요? 문의해 주세요. 코드를 직접 작성한 사람들이 답변해 드립니다.