메뉴
HN
Hacker News 36일 전

새로운 HTTP QUERY 메서드 도입, 왜 필요할까?

IMP
8/10
핵심 요약

HTTP 통신 규약인 RFC에 복잡한 쿼리를 안전하게 처리하기 위한 새로운 'QUERY' 메서드가 도입되었습니다. 기존에 GET 요청의 URL 길이 제한 문제나 POST 요청의 부작용(캐싱 불가, 안전성 문제 등)을 해결하여, 데이터 조회 시 요청 본문(body)을 안전하고 명확하게 전달할 수 있게 되었습니다. RESTful API 설계에 있어 매우 실용적이고 중요한 업데이트입니다.

번역된 본문

RESTful API의 세계에서 우리는 오랫동안 엄격한 (스스로 부여한) 규칙에 따라 살아왔습니다. 데이터를 가져올 때 GET을 사용하든, POST로 엔티티를 생성하든, PUT으로 리소스를 업데이트하든 간에, HTTP 메서드는 서버에게 클라이언트의 의도가 무엇인지 알려줍니다. 아주 최근에 HTTP용 새로운 QUERY 메서드를 정의한 RFC 10008이 게시되었습니다. 이미 다른 HTTP 메서드가 있는데 왜 이것이 필요할까요? 알아봅시다.

순전히 기술적 관점에서 볼 때, HTTP 메서드는 단순히 문자열일 뿐입니다. 이론적으로는 GET /api/v1/users를 보내는 대신 FETCH /api/v1/users를 사용할 수도 있습니다. 하지만 실제로는 GET 및 POST와 같이 잘 알려진 HTTP 메서드를 둘러싼 수많은 RFC와 암묵적, 문서화되지 않은 동작들이 존재합니다. 예를 들어, 브라우저는 주소를 입력하거나 북마크를 클릭할 때 GET 요청을 보냅니다. 표준 HTML 폼은 메서드로 GET과 POST만 허용합니다. 또한 대부분의 프록시, 방화벽 및 웹 서버는 '표준' HTTP 메서드만 허용합니다. 그렇다면 수십 년 동안 잘 작동해 온 기존 메서드 세트가 이미 있는데 왜 새로운 HTTP 메서드를 도입해야 할까요?

GET을 사용한 쿼리

전통적으로 리소스를 필터링하려면 GET 요청의 쿼리 매개변수를 사용했습니다 (예: /api/v1/users?role=admin&status=active&sort=desc). 이 방식은 단순한 필터에는 잘 작동합니다. 하지만 복잡한 관계형 쿼리, 깊은 중첩 또는 고급 논리를 수행해야 할 때 URL이 너무 길어져 읽기 어렵고, 때로는 브라우저나 서버의 문자 제한에 걸리기도 합니다.

기타 잠재적인 문제는 다음과 같습니다:

  • 비 ASCII 또는 특수 문자를 매개변수로 보내려면 인코딩해야 하므로 요청 크기가 증가합니다.
  • 서버 및 기타 미들웨어가 요청 매개변수를 로그에 남기기 십상이며, 이는 특정 상황에서 문제가 될 수 있습니다.
  • 배열과 같은 일부 데이터 구조를 표현하는 방식이 명확하게 정의되지 않고 구현마다 다릅니다 (예: ?roles[0]=admin&roles[1]=reporter vs ?roles=admin&roles=reporter vs ?roles[]=admin&roles[]=reporter 식의 차이).
  • 깊게 중첩된 구조를 표현할 때도 마찬가지입니다.

이러한 모든 단점이 데이터를 쿼리 매개변수로 보낼 때 발생하는 것이라면, 단순히 JSON 요청 본문을 포함한 GET 요청을 보내면 되지 않을까요? 다시 말하지만, 이론적인 관점에서는 이 방법이 작동해야 합니다. HTTP RFC 중 어느 것도 HTTP GET 요청을 수행할 때 요청 본문을 사용하는 것을 명시적으로 금지하지는 않지만, 이를 수행하지 않아야 한다고 지적하고 있습니다. 결과적으로 다양한 클라이언트, 프록시 및 웹 서버 구현이 본문이 있는 GET 요청을 다르게 처리합니다. 일부는 즉시 거부하고, 일부는 단순히 본문을 버리며 다른 일부는 이를 해석하려고 시도합니다. 따라서 기업 방화벽이나 다른 브라우저 뒤에 있는 사용자가 웹사이트를 사용할 수 없게 될 수 있으므로 요청 본문과 함께 HTTP GET을 사용하는 것은 좋지 않은 생각입니다. 이것이 또한 GET 요청이 이제 요청 본문을 지원해야 한다고 지정하는 새로운 RFC가 없는 이유이기도 합니다. 그렇게 하면 기존의 많은 구현이 손상될 것이기 때문입니다.

해결 방법: POST를 사용한 쿼리

GET을 사용하여 요청 본문을 보내는 것이 문제를 일으킬 수 있으므로, 이를 우회하는 방법으로 POST를 사용하는 것이 일반적입니다. POST는 요청 본문을 허용하지만 상당한 의미론적 문제를 야기합니다. POST는 멱등성이 없는 것(non-idempotent)으로 정의되며 리소스 생성 또는 처리를 위한 것입니다. 이것이 큰 문제처럼 들리지 않을 수도 있지만, 예를 들어 실패 시 자동 재시도를 구현할 때 성가실 수 있습니다. GET 메서드는 안전하고 멱등성이 있는 것으로 정의되어 있어 서버 구현이 올바르기만 하면 부작용에 대한 걱정 없이 실패한 요청을 재시도할 수 있습니다. 또한 POST를 사용하면 프록시나 다른 미들웨어가 해당 작업이 읽기 전용이라는 것을 자동으로 파악하는 것이 불가능해집니다. 예를 들어, 미들웨어는 일정 시간 동안 GET 요청을 자동으로 캐시할 수 있지만 POST 요청에서는 작동하지 않습니다.

QUERY 메서드

위의 모든 이유로 인해 수년간의 논의 끝에 QUERY 메서드가 명세에 포함되었습니다. QUERY 메서드는 특별할 것이 없으며, RFC는 대략적으로 GET 메서드와 비슷하지만 요청 본문이 있다고 설명합니다. 이 메서드는 안전하고 멱등성을 가질 목적으로 만들어졌습니다. QUERY 요청은 캐시될 수 있지만, 이를 구현할 때는 주의를 기울여야 합니다.

원문 보기
원문 보기 (영어)
In the world of RESTful APIs, we have long lived by a strict set of (self-imposed) rules. Whether you are fetching data with GET, creating an entity with POST, or updating a resource with PUT, the HTTP method tells the server what your intention is. Quite recently, RFC 10008 got published, which defines the new QUERY method for HTTP. Why is this needed when we already have other HTTP methods? Let's find out. From a purely technical point of view, the HTTP method is just a string. Instead of sending GET /api/v1/users you could also use FETCH /api/v1/users in theory. In practice, there are lots of RFCs and implicit, undocumented behaviour around the well-known HTTP methods, such as GET and POST. As an example, browsers send a GET request when you enter an address or click on a bookmark. Standard HTTP forms only allow GET and POST as methods. Most proxies, firewalls and webservers only allow the "standard" HTTP methods. So why introduce a new HTTP method when we already have a set of existing ones that worked well for decades? Queries using GET ​ Traditionally, if you wanted to filter a resource, you used query parameters in a GET request (e.g., /api/v1/users?role=admin&status=active&sort=desc ). This works well for simple filters. However, when you need to perform complex relational queries, deep nesting, or advanced logic, the URL becomes massive, hard to read, and sometimes hits browser or server character limits. Other potential problems include: Sending non-ASCII or special characters as parameters requires encoding them, increasing the request size Servers and other middlewares probably log the request parameters, which may be problematic in certain circumstances Expressing some data structures, such as arrays, is not well-defined and implementation specific (e.g. ?roles[0]=admin&roles[1]=reporter vs ?roles=admin&roles=reporter vs ?roles[]=admin&roles[]=reporter ) Same for expressing deeply nested structures Since these are all drawbacks of sending the data as query parameters, why not simply send a GET request with a JSON request body? Again, from a theoretical point of view, this should work. None of the HTTP RFCs explicitly forbid the usage of a request body when performing a HTTP GET request, but indicate that it should not be done. As a result, various client, proxy and webserver implementation handle GET requests with a body differently. Some reject them outright, some simply drop the body while others interpret it. Due to this, using HTTP GET with a request body is a bad idea, as for example users behind a corporate firewall or a different browser may be unable to use your website. This is also the reason why there is no new RFC which specifies that GET requests should now support request bodies, as that would break lots of existing implementations. The workaround: Querying using POST ​ Since sending request bodies using GET could introduce problems, the workaround is to use POST. While POST allows for a request body, it introduces significant semantic issues. POST is defined as non-idempotent and is intended for resource creation or processing. While this may not sound like a huge problem, it can be annoying when implementing e.g. automatic retries on failures. As the GET method is defined as safe and idempotent, as long as the server implementation is correct, we can retry failed requests without worrying about side effects. It also makes it impossible for proxies or other middleware to automatically understand that the operation is read-only. For example, a middleware may automatically cache GET requests for some time, which does not work with POST requests. The QUERY method ​ All of the above reasons resulted in the QUERY method being specified, after many years of discussion. The QUERY method is nothing special, the RFC roughly states that it is similar to the GET method, but with a request body. It is meant to be safe and idempotent. QUERY request can be cached, but implementation must be careful to incorporate the request content into the cache key. All in all, it finally offers a fitting HTTP method for complicated search queries. QUERY gotchas ​ It may be tempting to immediately switch all search related endpoints to use QUERY. Before doing that, there are a few things that you need to consider. Support for HTTP QUERY is still very limited and may be for some time. It may take years for it to be fully supported everywhere. As an example, Kreya has added out-of-the-box support for HTTP QUERY with the recent 1.20 release (though it was possible to send custom HTTP methods before already). Other clients, proxies and webservers may still reject it. Standard GET queries with data in the URL parameters are still perfectly fine. If there is no immediate need to change those to the QUERY method, leave them be. If your users should be able to share or bookmark links of the filtered data, continue using GET requests. Sharing links as QUERY requests does not work. Implementing custom caching for QUERY requests is more difficult than for GET requests, since you need to consider the request body. Conclusion ​ In short, HTTP QUERY replaces POST for read-only requests. It may take some time until it is fully supported everywhere, but you should still consider (and test!) it should normal GET requests not suffice for your use case.