PATCH 메서드는 HTTP 표준 메서드가 아니었다? (feat. HTTP 메서드 히스토리 알아보기)
HTTP의 표준 메서드
HTTP 표준 메서드를 하나씩 떠올려보자.
아마 대부분의 사람들이 GET, POST, PUT, PATCH, DELETE 이렇게 5가지 메서드를 생각할 것이다. (그 외에도 HEAD, OPTIONS 등..)
위 5가지 메서드들은 마치 일종의 불변상수와도 같아서 HTTP라는 녀석이 처음 세상에 나올때부터 함께 존재했던 것과 같은 착각을 불러일으킨다.
하지만 HTTP는 1990년대 초에 처음으로 명세되어 거진 30년이 가까운 시간동안 끊임없이 발전해왔고, 특히 PATCH 메서드는 비교적 최근인 2010년에야 RFC5789를 통해 HTTP의 표준 메서드로 채택되었다.
HTTP 스펙 살펴보기
위 두 이미지는 순서대로 HTTP/1.0과 HTTP/1.1의 스펙에 명시된 Method Definition이다.
GET, POST, PUT, DELETE 뿐 아니라, HEAD, OPTIONS 등의 메서드도 표준으로 제정된 것을 확인할 수 있으며, PATCH 메서드는 포함되어있지 않다.
두 가지는 각각 1996년 11월과 1997년 1월에 등장했다는 점을 감안하면 PATCH 메서드가 표준으로 제정된 시기와 무려 10년 이상의 텀이 존재하는 것을 확인할 수 있다.
하지만 재미있는 점은 PATCH 메서드가 표준 메서드로 제정된 시기가 2010년이라는 것이지, PATCH 메서드 자체의 등장은 RFC 2068로 이는 HTTP/1.1의 공식 명세인 RFC 2616보다 앞선다.
위는 실제 RFC 2068의 Additional Request Methods로 명시된 PATCH 메서드에 대한 명세이다.
Partial Update에 대한 설명으로, 우리가 아는 그 PATCH 메서드가 되겠다.
그리고 위는 앞서 언급한 HTTP/1.1의 표준 명세인 RFC 2616 중 일부인데, 비록 PATCH 메서드를 표준 메서드로 정의하진 않았으나 언급은 하고 있는 것을 확인할 수 있었다.
해당 내용으로 미루어보아, PATCH 메서드에 대한 논의가 이루어지긴 했으나 제대로 구현되지 않아 정작 표준 메서드에선 제외된 것으로 예상해볼 수 있을 것 같다.
그렇다면 사실상 HTTP/1.1의 공식 명세 이전에 등장한 PATCH 메서드는 어떤 계기로 10년이 넘은 후에야 표준 메서드로 제정되었을까?
이는 위키피디아에 명시된 PATCH 메서드 히스토리 부분에서 실마리를 잡을 수 있다.
루비 온 레일즈 팀에서 리소스의 부분 업데이트 지원을 위한 HTTP 메서드 도입의 필요성을 주장했고, 이후 2010년에 정식 메서드로 채택되게 되었다.
이를 바탕으로 2012년에 루비 온 레일즈는 자신들의 공식 홈페이지에 업데이트 작업을 위한 PATCH 메서드 사용을 소개하기도 했다.
PATCH 메서드 표준화의 흔적 찾아보기
HttpServlet
이와 같은 PATCH 메서드 히스토리의 흔적은 루비뿐만 아니라 Web 및 HTTP 통신과 관련된 유명 라이브러리들에서도 확인할 수 있다.
가장 와닿을 수 있는 케이스 중 하나로, 자바의 서블릿 기술에서는 PATCH 요청을 지원하는 메서드가 존재하지 않는다.
package jakarta.servlet.http;
public abstract class HttpServlet extends GenericServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
...
}
protected void doHead(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
...
}
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
...
}
protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
...
}
protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
...
}
protected void doOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
...
}
protected void doTrace(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
...
}
}
HttpServlet
객체에서 지원하는 메서드는 GET, HEAD, POST, PUT, DELETE, OPTIONS, TRACE로, 모두 HTTP/1.1에서 표준 메서드로 정의된 것들 뿐이다.
이는 자바의 서블릿 기술이 처음 개발된 것이 1997년이고, 이는 PATCH 메서드가 표준이 된 2010년과 상당한 시간 차이가 나기 때문에 단순히 doPatch()
와 같은 메서드를 추가하는 형태로 지원하기는 매우 어려웠을 것으로 판단된다.
이에 따라서, 스프링 웹 기술에서는 자신들이 정의한 FrameworkServlet
을 통해 PATCH 메서드라면 자체적으로 처리를 해주고, 그 외의 자바 서블릿 기술이 지원하는 HTTP 메서드들에 대해선 서블릿이 처리하도록 구현해두었다.
위처럼 서블릿에서 지원 가능한 HTTP 메서드들을 HTTP_SERVLET_METHODS
로 정의해놓고, 요청 메서드가 그에 해당되면 HttpServlet
에게 처리를 위임하고, 그 외엔 자신들이 정의한 processRequest()
메서드를 호출하도록 구현되었다.
해당 부분은 역시 독스에 잘 정리되어있다.
FeignClient
이전에 FeignClient
를 사용 시 PATCH 메서드를 사용할 수 없다는 얘기를 들은 적이 있는데, 이 역시 FeignClient
가 PATCH 메서드를 지원하지 않는 구현체를 사용하고 있기 때문이다.
FeignClient
는 내부적으로 외부 API와의 통신을 위해 java.net.HttpURLConnection
을 사용한다.
그리고 해당 클래스를 살펴보면, HttpServlet
과 마찬가지로 PATCH 메서드를 지원하지 않는다.
역시나 RFC 5789이전 HTTP/1.1에서 공표한 표준 메서드만을 유효한 HTTP 메서드로 정의하고 있다.
따라서 해당 구현체를 그대로 사용하는 경우, PATCH 요청 시 오류가 발생하게 되는 것이다!
물론 스프링 웹 기술에서처럼 OkHttpClient와 같은 PATCH 메서드를 지원하는 HTTP Connection 구현체들이 여럿 존재하고, FeignClient
사용 시 HttpURLConnection
대신 해당 구현체들을 사용해주도록 변경해주면 PATCH 메서드도 사용할 수 있게된다.
해당 부분은 검색해보면 많은 솔루션이 나오니 참고하면 된다.
레퍼런스
https://en.wikipedia.org/wiki/PATCH_(HTTP)#History_of_PATCH
https://datatracker.ietf.org/doc/html/rfc5789
https://datatracker.ietf.org/doc/html/rfc2616
https://datatracker.ietf.org/doc/html/rfc2068
https://mangkyu.tistory.com/225