...
HTTP 메서드의 속성
주요 HTTP Method인 GET / POST / PUT / PATCH / DELETE 는 각 메서드의 동작 과정 뿐만 아니라, 메서드의 속성 또한 알 필요가 있다. 왜냐하면 어떠한 HTTP 메서드로 서버에 요청했느냐에 따라 API 설계나 복구 메커니즘 캐시 최적화 등, 설계 로직이 달라질 수 있기 때문이다.
HTTP 메서드의 속성으로는 크게 3 가지인 안전(Safe), 멱등(Idempotent), 캐시 가능(Cacheable)이 있다. 이들을 하나씩 살펴보는 시간을 가져보자.
안전성(Safe)
HTTP 메소드의 안정성이란 보안 취약성을 말하는 것이 아니라 호출해도 리소스가 변경되지 않는 성질을 말하는 것이다.
정말 쉽게 생각해서 GET 메서드는 단순히 데이터를 조회하는 기능을 수행하기 때문에 리소스를 변경 및 수정하지 않으니 안전한 HTTP 메소드이다.
반면에 POST PUT PATCH DELETE 와 같은 메서드들은 호출할 경우 데이터에 변경이 발생하거나, 서버에서 삭제되기 때문에 안전하지 않은 HTTP 메서드라고 볼 수 있다.
물론 트래픽이 몰려 수많은 GET 요청에 의해 서버가 터지게 되면 '안전성' 이라는 특성에 부합되지 않는다고 생각할 지도 모르겠지만, 여기서 말하는 안정성은, 리소스를 수정 / 삭제 하지 않으므로 데이터의 일관성 유지에 있어 안전하다는 의미이다.
즉, 메서드가 전체적인 시스템 장애로부터 안전하다는 의미를 가지지는 않는다.
멱등성(Idempotent)
멱등(冪等)이라는 단어의 뜻은 수학이나 전산학에서 연산을 여러 번 적용하더라도 결과가 달라지지 않는 성질을 의미한다.
위의 정의를 HTTP의 멱등성(Idempotent)에 대입해보자면, 멱등성이란 요청(Request)을 한 번을 호출하든 여러 번을 호출하든 그 결과가 같음을 의미한다. 즉, 동일한 요청을 한번 보내는 것과 여러번 연속으로 보내는 것이 같은 효과를 가지고, 서버의 상태도 동일하게 남을 때 해당 HTTP 메서드가 멱등성을 가진다고 말한다.
멱등의 개념을 안전(safe)과 혼동하기 쉬운데,
- HTTP 메소드의 안전이 한 번을 호출하든 여러 번을 호출하든 리소스에 수정이 발생하지 않는 속성이고,
- HTTP 메소드의 멱등은 리소스에 수정이 발생한다고 하더라도 메서드를 여러 번 실행한 결과가 한 번 실행한 결과와 같다면 만족하는 속성이라는 차이점이 있다.
또한 호출을 실행한 결과가 의미하는 것이 응답 상태 코드가 아닌 서버의 상태라는 점도 유의해야 한다. 예를들어 똑같은 요청을 했을 때 응답하는 상태코드가 다르더라도 서버의 상태가 같은 상태라면 멱등성이 있다고 판단하는 것이다
HTTP 스펙에 명시된 것에 의하면 GET, PUT, DELETE는 멱등성을 가지도록, POST와 PATCH는 멱등성을 가지지 않도록 구현해야 한다. 그 이유에 대해서 하나씩 살펴보자.
GET의 멱등 (O)
GET은 데이터를 한 번을 조회하든 여러 번을 조회하든 같은 결과가 조회되므로 안전과 멱등을 동시에 만족하는 메서드이다.
다음은 GET 메서드로 호출되는 API에 대한 간략한 예시이다.
- GET /post/1 요청
- 서버에서 id값이 1인 게시글을 조회
- 해당 게시글 데이터를 응답
위의 경우 같은 요청을 여러번 보내더라도 서버의 상태는 항상 같게 된다.
이처럼 GET 메서드가 멱등성을 가져야 할 이유는 직관적으로 알 수 있다.
멱등 적이지 않은 GET의 설계
하지만 만약 개발자가 조회수 기능을 추가하면서, 다음과 같이 게시글을 조회하면 동시에 조회수도 올리도록 구현하면 어떻게 될까?
- GET /post/1 요청
- 서버에서 id값이 1인 게시글을 조회
- 해당 게시글의 조회수 데이터를 1 증가
- 해당 게시글 데이터를 응답
이 경우, GET 요청을 여러번 보낼 경우 서버의 데이터 상태는 매번 바뀌게 될 것이다.
즉, 위의 GET 요청 로직은 멱등성을 가지지 않는 것이며, 개발자는 HTTP 스펙에 부합하지 않게 API를 구현했다고 볼 수 있다.
따라서 GET의 멱등성에 맞게 API를 설계하기 위해서는, 조회수 컬럼의 값을 증가시키는 요청을 PATCH 요청으로 따로 분리하는 것이 올바르다.
멱등하다고 해서 결과가 항상 같은것은 아님
GET이 멱등성을 만족하기 때문에, 어떤 상황에서도 여러 번 요청할 경우 항상 같은 결과물을 내놓는 다고 했지만, 이는 엄밀히 말하면 틀리다.
다음과 같은 상황을 생각해보자.
- A 가 GET /members/100 통해 리소스를 조회
- B 가 PUT /members/100 통해 리소스를 변경
- A 가 GET /members/100 통해 리소스를 다시 조회
위의 상황은 A 가 100번째 멤버에 대한 리소스 조회에 대해 재요청을 하기 직전, B 가 해당 리소스를 변경한 경우이다.
이때는 A 가 멱등성이 있는 GET 메서드를 사용하였는데도 불구하고, 중간에 B의 개입으로 인해 응답받는 결과값이 달라지게 된다.
그럼 멱등성이 깨진 잘못된 설계가 아니냐 라고 반문 할 수 있겠지만, 멱등성의 여부는 외부 요인으로 인해 중간에 리소스가 변경되는 것은 고려하지 않으며, 또한 서버의 상태 기준으로 판단하기 때문에 GET의 멱등성은 문제가 없는 것다.
서버의 상태를 바꾼건 PUT 메서드이지 GET 메서드는 충실히 이행했기 때문이다.
DELETE의 멱등 (O)
GET이 단순 조회라면, DELETE는 단순 삭제이다. 따라서 DELETE는 멱등성을 가진다.
DELETE를 처음 요청 하면, 서버에서 해당 리소스는 삭제가 된다. 이후 DELETE를 여러번 요청하더라도 해당 리소스는 삭제된 상태 그대로 일 것이니 서버의 상태는 변하지 않는다.
서버에서 삭제 동작이 되니 서버의 상태가 변경된 것이 아니냐라고 생각하겠지만, 다시한번 말하지만 멱등성은 한번 호출하든 두번 호출하든 결과 상태가 같다는 의미이지, 전혀 변경이 일어나지 않음을 의미하는 것은 아니다.
즉, 처음 요청하든 다시 여러번 요청을 하든 서버의 상태는 리소스가 삭제된 상태를 반환하고 추가적인 동작을 하지 않으니 멱등하다는 것이다.
멱등 적이지 않은 DELTE의 설계
추가적으로 이러한 DELTE의 멱등성 때문에, DELETE API를 설계할 때에는 정확한 식별자를 통해 리소스를 지정해야 한다.
예를들어 게시글을 삭제할때 정확한 게시글 ID 값이 아닌 last라는 구조로 좀 더 유연하게 서버에서 처리되도록 구현하였다고 가정하자.
그럴듯한 로직으로 보이겠지만, 이런 경우 해당 DELETE 요청을 여러번 보내게 되면 매번 마지막 게시글을 삭제하기 때문에 매번 서버의 상태가 변하게 된다.
즉, 멱등성을 가지지 않는 것이다. 따라서 이런 경우에는 DELETE로 구현하는 건 HTTP 스펙상 옳지 않고, 멱등성을 가지지 않는 POST를 쓰는게 스펙상으로 맞다.
POST의 멱등 (X)
반대로 POST 메서드는 멱등을 만족하지 않는다.
POST는 서버로 데이터를 전송하여 새로운 자원을 생성하는 역할을 한다. 따라서 요청을 여러번 보내는 경우 매번 새로운 자원이 생겨나는 것이며, 이는 서버의 상태가 변경되는 것을 의미한다.
복구 메커니즘에 따져야 하는 멱등성
클라이언트가 서버에게 HTTP 메세지를 전송하였는데, TIMEOUT 과 같은 문제들로 인해 정상적인 응답을 전달받지 못했다고 가정해보자.
정상적인 응답을 받지 못했다면 다시 요청하면 그만이라고 생각하겠지만 여기에 HTTP의 멱등성의 유무가 들어가게 된다.
일반적으로 GET 이나 DELETE 같은 메서드들은 여러 번 호출하여도 응답 결과는 변함이 없기 때문에, 통신 장애 시 똑같은 요청을 재전송하도록 설계해도 문제는 없을 것이다.
하지만 POST 와 같은 멱등하지 않게 설계된 메서드들은 똑같은 요청을 다시 전송할 경우 문제 발생 가능성 요지가 있다. 왜냐하면 통신 장애 이유가 서버 자체가 문제일수도 있고 아니면 인터넷 선이 문제일수도 있기 때문이다.
만일 서버에서는 정상 처리했는데 응답 과정에서 네트워크 문제로 정상적인 응답을 받지 않았다면, POST를 재요청 해버리면 서버 입장에선 처리를 한 번 더 하게 되는 결과를 낳게 된다.
만일 이것이 돈과 관련된 결제와 같은 서비스라면 결제가 중복으로 되는 큰 문제로 직결된다.
따라서 POST 요청 같은 경우 더 큰 문제가 발생할 수 있기 때문에 요청을 또 보내는 것이 아닌 다른 조치를 취할 수 있도록 설계하는 것이 적절할 것이다.
이렇게 복구 매커니즘의 사용 가능 여부에 있어서 HTTP의 멱등적인 속성은 설계의 중심이 되게 된다.
PUT의 멱등 (O)
PUT 메서드는 대상 리소스를 덮어씌워 변경하거나, 대상 리소스가 없다면 새로 추가한다.
그래서 만일 대상 리소스가 없다면 PUT이 POST와 같은 동작을 하게 되는데, 반면 POST는 매번 새로운 자원을 만드는 반면, PUT은 해당 자원이 이미 있다면 데이터만 덮어쓴다.
따라서 요청을 한번하든 여러번하든 결국 서버의 상태는 같아지니, PUT은 멱등하다.
멱등하다고 해서 결과가 항상 같은것은 아님
GET의 멱등 부분에서 소개했던 것 처럼 PUT도 예외 케이스가 있다.
PUT 요청을 보냈는데, 새로운 데이터를 생성한 경우 201(Created) 상태코드를 응답하며, 기존 데이터를 덮어쓴 경우 200(OK) 혹은 204(No Content)를 응답하게 된다.
상태코드 | 설명 |
200 OK | 요청이 올바르게 수행되었음 (GET, PUT) |
201 Created | 서버가 새로운 리소스를 생성했음 (POST, PUT) |
204 No Content | 응답할 데이터가 없음 (HTTP Body 가 없음). (DELETE, PUT) |
물론 다른 상태 코드를 응답한다고 해서 멱등 하지 않다는 것은 결코 아니다. 서버의 상태는 동일하므로 멱등성을 가진다고 할 수 있다.
다만 동일한 요청을 여러번 보내더라도 상태 코드라는 결과값이 다를수 있다는 점만 기억하면 된다.
PATCH의 멱등 (O or X)
PUT이 리소스 전체 교체 라면, PATCH는 리소스의 부분적인 수정을 할 때 사용된다.
PATCH의 특이한 점은 기본적으로 멱등성을 가지지 않는 메서드인데, 그 구현을 PUT과 동일한 방식으로 할 경우 멱등성을 가지게 되는 특성을 지니고 있다.
즉, PATCH는 멱등하게 설계할 수도 있고, 멱등하지 않도록 설계할 수도 있다는 말이다.
사실 PUT과 PATCH 메소드의 진짜 중요한 차이점은 전체 교체, 일부 교체 행위의 차이가 아니라, PUT 메소드는 반드시 멱등성을 보장하지만 PATCH 메소드는 멱등성을 보장하지 않을 수도 있다는 것이다.
PATCH의 멱등 적인 설계
PATCH 메소드에 수정할 리소스의 일부분만 담아서 보내는 경우에는 멱등성이 보장된다.
한번 요청을 보낸후 같은 요청을 여러번 하더라도 변경된 같은 결과가 나오기 때문이다.
// 기존의 리소스
{
id: 1,
name: "김철수",
age: 15
}
// 변경된 리소스
{
id: 1,
name: "김철수",
age: 20
}
PATCH의 멱등 적이지 않은 설계
PATCH의 또다른 특징으로는 HTTP 스펙상 구현 방법에 제한이 없다는 것이다.
그렇기 때문에 위처럼 꼭 데이터를 또다른 데이터로 '대체' 하도록 구성할 필요는 없다.
예를들어 PATCH의 동작을 '증가'를 통한 변경이라고 하면 상황이 달라진다.
아래 예시의 경우 동일한 요청을 여러번 보내면, 매 요청마다 age가 1씩 증가하도록 PATCH api를 설계 하였다. 이럴경우 동일한 요청을 여러번 하면 할수록 age의 값이 계속 달라질테니, 멱등성을 가지지 않는 것이다.
// 기존의 리소스
{
id: 1,
name: "김철수",
age: 15
}
// 변경된 리소스
{
id: 1,
name: "김철수",
age: 16
}
캐시 가능성(Cacheable)
캐시 가능성은 응답 결과 리소스를 캐싱해서 효율적으로 사용할 수 있는가 에 대한 여부이다.
캐시(Cache)가 꼭 운영체제나 서버에만 있는 것이 아니라, 브라우저 자체도 하나의 소프트웨어라 캐시 공간을 가지고 있는데, 클라이언트가 서버에 한 번 요청했던 데이터에 대해 매 요청마다 다시 전송할 필요가 없도록 브라우저가 임시적으로 데이터를 보관하고 있는 장소이다.
즉, 캐싱이 가능한 HTTP 메소드는 빠르게 결과값을 받을 수 있다는 소리이다.
1. 처음 웹사이트에 접속할때 서버로부터 리소스를 모두 받아온다
2. 웹사이트를 재방문 할경우 캐시에서 리소스를 가져오기 때문에 빠르게 로드 된다.
위의 표에서 볼수 있듯이, GET, POST, PATCH 메소드는 스펙 상으로 캐시가 가능하다.
하지만 실제로는 GET이나 HEAD 정도만 캐시로 이용이 가능하고 POST 와 PATCH 는 지원되지 않는 경우가 일반적이라고 한다.
왜냐하면 브라우저의 캐시를 이용하려면 원본 데이터가 변경되지 않고 유지되어야 하는데, POST, PUT, DELETE, PATCH 는 기본적으로 데이터 변경이 되는 메서드 이기 때문에, 만일 호출로 인해 데이터가 변경되게 되면 원본 데이터 또한 변경되기어 캐시 데이터 불일치 문제가 생기기 때문이다.
반면 GET은 딱 URI만 키로 잡고 캐시하면 되서 간단하다.
따라서, GET HEAD 메서드는 캐시가 가능하기 때문에 브라우저에서 리소스를 임시로 보관할 수 있고, 나머지 메서드들은 구현의 복잡성 혹은 유지의 어려움 때문에 캐시를 이용하지 않는다고 생각하면 된다.
# 참고자료
모든 개발자를 위한 HTTP 웹 기본 지식 - 김영한
https://velog.io/@cataiden/http-method-properties
https://www.section.io/engineering-education/understanding-browser-caching/
이 글이 좋으셨다면 구독 & 좋아요
여러분의 구독과 좋아요는
저자에게 큰 힘이 됩니다.