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" }
]
명세에서 가장 중요한 규칙은 다음과 같습니다:
- 모든 QUERY 요청은 본문과 일치하는
Content-Type을 포함해야 합니다. 헤더가 없거나 콘텐츠와 일치하지 않으면 서버는 4xx 상태(400, 415 또는 422)로 요청을 거부해야 합니다. - 응답은 캐시할 수 있습니다. 응답을 저장하는 캐시는 요청 본문을 캐시 키에 포함해야 하므로, 서로 다른 두 쿼리가 충돌하는 일이 없습니다.
- 리디렉션은 메서드를 유지합니다. 301, 302, 307 또는 308 이후 클라이언트는 새 URI에 대해 QUERY를 반복합니다. 리디렉션된 POST를 GET으로 바꿔 쓰는 기존 예외는 QUERY에 적용되지 않습니다. 303 See Other는 서버가 “이 Location에서 GET으로 결과를 가져오라”고 명시적으로 알리는 방법입니다.
- 서버는 새로운
Accept-Query응답 헤더로 리소스가 이해하는 쿼리 형식을 알릴 수 있으며, 지원되는 경우 QUERY는 OPTIONS 응답의Allow헤더에 나타납니다. - 브라우저에서 QUERY는 CORS 세이프리스트에 포함된 메서드가 아니므로, 교차 출처 호출은 프리플라이트를 발생시킵니다. sgcWebSockets OpenAPI 서버의 CORS 기본값에는 이미
Access-Control-Allow-Methods에 QUERY가 포함되어 있습니다.
HTTP/1.1 클라이언트에서 QUERY 사용
HTTP/1.1 클라이언트에는 Post와 완전히 동일한 방식으로 동작하는 Query 및 QueryAsync 메서드가 추가되었습니다. 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 다운로드 페이지에서 다운로드할 수 있습니다.
질문, 피드백 또는 마이그레이션 지원이 필요하신가요? 문의해 주세요. 코드를 직접 작성한 사람들이 답변해 드립니다.
