HTTP QUERY: GET과 POST 사이의 빠진 중간 지점
요약
RFC 10008에서 발표된 새로운 HTTP QUERY 메서드는 GET의 데이터 용량 제한과 POST의 비멱등성 문제를 동시에 해결합니다. QUERY 메서드는 요청 본문(body)을 허용하면서도 안전하고 멱등한 특성을 유지하여 효율적인 API 설계를 가능하게 합니다.
핵심 포인트
- GET의 URL 길이 제한 및 인코딩 비효율성 문제 해결
- POST 사용 시 발생하는 캐싱 및 재시도 로직의 어려움 극복
- QUERY 메서드는 안전(safe)하고 멱등(idempotent)한 특성 제공
- 요청 본문을 포함하면서도 리소스 상태를 변경하지 않는 쿼리 수행 가능
수년 동안 API 개발자들은 어색한 선택의 기로에 놓여 있었습니다. 클라이언트가 데이터를 가져오고 싶을 때 자연스러운 메서드는 GET입니다. 하지만 쿼리가 너무 커지거나, 너무 구조화되어 있거나, URL에 담기에 너무 민감한 정보를 포함하게 되면 대부분의 팀은 POST로 전환합니다. 작동은 하지만, 항상 약간 잘못된 느낌을 주었습니다.
2026년 6월에 발표된 RFC 10008은 드디어 HTTP QUERY 메서드를 통해 그 간극을 메웁니다. 이는 요청 본문 (request body)을 허용하는 안전하고 멱등한 (idempotent) 메서드입니다. 저는 쿼리 문자열 (query string)이 2,000자를 넘어가는 것을 처음 목격한 이후로 이런 것을 기다려 왔기에, RFC가 발표된 주에 바로 읽어보았습니다. 실제 내용은 다음과 같습니다.
GET의 문제점
전형적인 검색 엔드포인트 (endpoint)는 다음과 같습니다:
GET /feed?q=foo&limit=10&sort=-published HTTP/1.1
Host: example.com
단순한 쿼리에는 괜찮습니다. 하지만 실제 시스템은 좀처럼 단순하게 유지되지 않습니다. 다음을 생각해 보세요:
{
"filters": {
"country": "GH",
...
이를 URL로 인코딩 (encoding)하는 것은 빠르게 지저분해지며, 지저분함은 가장 작은 문제입니다. RFC 10008은 진짜 문제들을 나열합니다: 요청이 여러 시스템을 통과할 때 URI 길이 제한을 알 수 없다는 점, URI 인코딩이 비효율적이라는 점, URL은 로그에 기록되거나 북마크될 가능성이 더 높다는 점, 그리고 쿼리 파라미터 (query parameters)의 모든 조합이 사실상 별개의 리소스 (resource)가 된다는 점입니다. 그래서 개발자들은 실용적인 선택을 하여 POST를 사용합니다.
POST의 문제점
일반적인 해결책은 다음과 같습니다:
POST /feed/search HTTP/1.1
Host: example.com
Content-Type: application/json
...
저는 이런 방식들을 수없이 배포해 왔고 잘 작동했습니다. 하지만 POST가 클라이언트와 핸들러(handler) 사이의 모든 요소에게 전달하는 메시지, 즉 '무언가가 변경될 수 있다'는 점을 인지하기 전까지는 말이죠. HTTP 클라이언트, 프록시(proxies), 캐시(caches), API 게이트웨이(API gateways), 재시도 로직(retry logic), 그리고 보안 도구들은 모두 의도를 이해하기 위해 메서드 의미론(method semantics)을 읽습니다. 여러분의 POST /search가 완전히 읽기 전용(read-only)일지라도, 요청 내에 이를 명시하는 것은 아무것도 없습니다. 이는 어떤 중간 매개체(intermediary)도 이를 캐싱하지 않을 것이며, 어떤 클라이언트도 자동으로 재시도하지 않을 것임을 의미합니다. RFC 10008은 이를 직접적으로 지적합니다: POST가 안전한 쿼리(query) 작업에 사용될 때, 서버 특유의 지식 없이는 해당 요청이 안전하고 멱등(idempotent)한지 알 방법이 없습니다.
QUERY의 등장
QUERY /feed HTTP/1.1
Host: example.com
Content-Type: application/json
...
이 요청은 다음과 같이 말합니다: 이 쿼리 본문(body)을 처리하여 결과를 반환하되, 대상 리소스의 상태를 변경하지 마십시오. RFC 10008은 QUERY를 서버 측 쿼리를 시작하기 위한 메서드로 정의하며, 여기서 요청 콘텐츠와 그 미디어 타입(media type)이 쿼리를 정의하고 서버는 대상 리소스의 범위 내에서 이를 처리합니다. 이 메서드는 안전(safe)하고 멱등(idempotent)하기 때문에, 클라이언트와 중간 매개체는 부분적인 상태 변경을 걱정하지 않고 재시도할 수 있습니다.
개념 모델(Mental model):
GET → URI로 식별되는 리소스를 검색
POST → 상태를 생성하거나 변경할 수 있는 데이터를 제출
QUERY → 구조화된 요청 콘텐츠를 사용하여 읽기/쿼리 작업 수행
이것이 API 설계에서 중요한 이유
현대의 API는 단순한 CRUD인 경우가 드뭅니다. 검색 엔드포인트(Search endpoints), 분석 및 보고 API, GraphQL 스타일의 읽기, RAG 검색 서비스, 사기 및 리스크 점수 쿼리, BI 대시보드 등: 우리가 현재 구축하는 것 중 상당수는 입력값이 깔끔한 URL 파라미터(URL parameters)로 처리하기에는 너무 복잡한 읽기 전용 작업들입니다.
지금까지 여러분에게는 두 가지 불완전한 선택지뿐이었습니다. GET을 사용하여 URL을 남용하거나, POST를 사용하여 읽기 전용 의미론을 포기하는 것이었습니다. QUERY를 사용하면 실제로 원하는 HTTP 속성인 안전성(safety), 멱등성(idempotency), 재시도 가능성(retryability), 그리고 캐시 가능성(cacheability)을 유지하면서 본문에 구조화된 입력을 담을 수 있습니다.
Content-Type의 중요성
QUERY 요청은 "본문(body)이 있는 GET"이 아닙니다. 본문은 미디어 타입(media type)과 함께 있어야만 의미를 가지며, RFC 10008에 따르면 서버는 Content-Type이 누락되었거나 콘텐츠와 일치하지 않을 경우 요청을 실패 처리해야 합니다. 또한 RFC는 어떤 오류 코드를 사용해야 하는지도 명시하고 있습니다: 잘못되었거나 누락된 미디어 타입 정보의 경우 400, 지원되지 않는 미디어 타입의 경우 415, 콘텐츠가 파싱은 되지만 처리할 수 없는 경우 422, 요청된 응답 형식을 사용할 수 없는 경우 406을 사용합니다.
따라서 다음과 같은 방식은 바람직합니다:
QUERY /transactions HTTP/1.1
Content-Type: application/json
Accept: application/json
반면, 다음과 같은 방식은 불충분합니다:
QUERY /transactions HTTP/1.1
{ "status": "pending" }
서버는 추측하기를 거부해야 합니다. 왜냐하면 콘텐츠 스니핑(content sniffing)은 보안 및 상호 운용성(interoperability) 버그로 이어졌던 오랜 역사가 있기 때문입니다.
QUERY와 캐싱 (caching)
가장 큰 이점 중 하나는 QUERY 응답이 캐시 가능하다(cacheable)는 점입니다. RFC 10008은 캐시 키(cache key)에 요청 콘텐츠와 관련 메타데이터를 포함하도록 요구하는데, 그 이유는 다음과 같습니다:
QUERY /customers HTTP/1.1
Content-Type: application/json
...
은 다음의 쿼리와 동일하지 않습니다:
QUERY /customers HTTP/1.1
Content-Type: application/json
...
URI는 동일하지만 본문이 다르므로, 서로 다른 캐시 키가 필요합니다. 이로 인해 QUERY 캐싱은 GET 캐싱보다 까다롭습니다. 캐시는 키를 생성하기 위해 요청 콘텐츠를 충분히 읽고 이해해야 하며, 저는 바로 이 지점에서 초기 구현체들에 가장 많은 버그가 발생할 것이라고 생각합니다. RFC는 이를 완화할 수 있는 한 가지 방법을 제시합니다. 바로 Location을 사용하여 이후의 요청을 위해 동일한 GET 리소스를 노출하는 것입니다.
Location과 Content-Location
이는 제가 매우 좋아하는 패턴을 떠올리게 합니다. QUERY에 응답하는 서버는 다음과 같이 반환할 수 있습니다:
Content-Location: /reports/results/abc123
Location: /reports/queries/q456
두 헤더는 서로 다른 의미를 가집니다. Content-Location은 방금 반환된 결과를 나타내는 리소스를 식별합니다. Location은 클라이언트가 나중에 본문(body)을 다시 보내지 않고도 동일한 쿼리(query)를 재실행하기 위해 일반적인 GET 요청으로 호출할 수 있는 동등한 리소스를 식별합니다.
비용이 많이 드는 쿼리(expensive queries)의 경우 이는 진정으로 유용합니다. 분석 API (analytics API)가 QUERY를 수락하여 복잡한 필터(filter)를 한 번 실행한 다음, 저장된 쿼리를 일반적인 URI로 노출하는 방식입니다:
첫 번째 요청:
QUERY /analytics/events
Content-Type: application/json
이후 요청:
GET /analytics/stored-queries/q456
이제 비용이 많이 드는 작업은 한 번만 발생하며, 그 이후의 모든 하위 과정은 일반적이고 캐시 가능한 (cacheable) GET이 됩니다.
QUERY 지원 여부 확인
클라이언트는 OPTIONS 메서드를 통해 리소스가 QUERY를 지원하는지 확인할 수 있으며, 서버는 Allow 헤더에 QUERY를 포함합니다. RFC 10008은 또한 리소스가 쿼리 콘텐츠로 어떤 미디어 타입 (media types)을 수락하는지 광고할 수 있게 해주는 Accept-Query 응답 필드를 정의합니다.
마지막 부분이 중요한 이유는 QUERY가 의도적으로 하나의 쿼리 언어 (query language)에 묶여 있지 않기 때문입니다. 어떤 API는 application/json을 수락하고, 다른 API는 application/sql을, 세 번째 API는 특정 도메인 전용 형식을 수락할 수 있습니다. 메서드 (method)는 의미론 (semantics)을 전달하고, 미디어 타입 (media type)은 쿼리 언어를 정의합니다.
보안 고려 사항
쿼리 데이터를 URL에서 분리하는 것은 한 가지 측면에서 도움이 됩니다. URL은 서버, 프록시 (proxies), 브라우저, 모니터링 시스템, 그리고 분석 도구 등 모든 곳에 로그 (log)로 남기 때문입니다. RFC 10008은 요청 콘텐츠가 중간 매개체 (intermediaries)에 의해 로그에 기록되거나 처리될 가능성이 더 낮다고 언급하며, 따라서 민감한 쿼리 정보는 본문 (body)에 배치하는 것이 더 나을 수 있습니다.
하지만 이를 과신해서는 안 됩니다. 요청 본문 (request body) 또한 여전히 로그에 기록될 수 있습니다. 임시 결과 URI (Temporary result URIs)는 부주의하게 설계될 경우 정보를 유출할 수 있습니다. 캐시 (Caches)는 쿼리 내용을 잘못 정규화할 경우 잘못된 응답을 제공할 수 있습니다. 또한 브라우저에서 호출하는 경우, QUERY는 CORS-safelisted (CORS 안전 목록에 포함된) 메서드가 아니므로, 교차 출처 요청 (cross-origin requests) 시 프리플라이트 (preflight)가 발생합니다. 이는 부수적인 프라이버시 이점이 있는 의미론적 개선 (semantic improvement)일 뿐, 보안 제어 (security control) 수단은 아닙니다.
API를 즉시 마이그레이션해야 할까요?
아마 아닐 것입니다. 이 메서드는 지난달 기준으로 스펙 (spec)에 존재하지만, 스펙 자체를 배포할 수는 없습니다. 채택 여부는 브라우저, HTTP 클라이언트, 게이트웨이 (gateways), 리버스 프록시 (reverse proxies), 로드 밸런서 (load balancers), SDK 생성기 (SDK generators), OpenAPI 툴링 (OpenAPI tooling), 그리고 백엔드 프레임워크 (backend frameworks)가 모두 따라와 주느냐에 달려 있으며, 이 중 일부는 변화가 느립니다. 대부분의 프로덕션 시스템에서는 POST /search가 당분간 실질적인 선택지로 남을 것이며, 이는 전혀 부끄러운 일이 아닙니다.
실험을 시작해 볼 만한 곳은 다음과 같습니다: 새로운 내부 API, 데이터 플랫폼, AI 검색 서비스 (AI retrieval services), 그리고 클라이언트와 서버 사이의 모든 홉 (hop)을 직접 제어할 수 있는 게이트웨이 제어 환경입니다. 만약 귀하의 인프라가 전달 과정에서 해당 메서드를 변형시키지 않을 수 있다면, 오늘 바로 그 이점을 누릴 수 있습니다.
QUERY의 위치
QUERY는 GET이나 POST를 대체하는 것이 아니라, 그 사이의 공간을 채웁니다. URI가 리소스를 명확하게 식별하고 쿼리가 단순할 때는 GET을 사용하세요. 요청에 부수 효과 (side effects)가 있을 때는 POST를 사용하세요. 클라이언트가 구조화된 입력을 사용하여 안전하고 멱등한 (idempotent) 읽기를 원할 때는 QUERY를 사용하세요.
마지막 생각
수년 동안 우리는
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기