포스트

[Network] HTTP (3) - HTTP의 3가지 속성

Computer Science / Network

HTTP의 3가지 속성인 안정성(Safe)∙멱등성(Idempotent)∙캐시 가능성(Cacheable)에 대한 정리

HTTP의 메서드에 대한 정리는 이전 게시글인, #HTTP (2) - HTTP 메서드 종류 게시글을 참고

HTTP 메서드의 속성

주요 HTTP Method인 GETPOSTPUTPATCHDELETE는 각 메서드의 동작 과정 뿐만 아니라, 메서드의 속성에 대해서도 알아야 한다.

왜냐하면, HTTP 메서드로 서버에 요청했는냐에 따라서 API 설계나 복구 메커니즘, 캐시 최적화 등의 설계 로직이 달라질 수 있기 때문이다.

HTTP 메서드의 속성으로는

  • 안전 (Safe)
  • 멱등 (Idempotent)
  • 캐시 가능 (Cacheable)

이렇게 크게 3가지가 있다.

각 메서드의 속성 비교

HTTP MethodBody (요청)Body (응답)안전멱등캐시 가능
GETXOOOO
HEADXXOOO
POSTOOXXO
PUTOOXOX
DELETEXOXOX
CONNECTOOXXX
OPTIONSOptionOOOX
TRACEXOOOX
PATCHOOXXO

안전성 (Safe)

HTTP Method안전
GETO
POSTX
PUTX
PATCHX
DELETEX

HTTP 메소드의 안전성이란, 흔히 생각하는 보안 취약성을 말하는 것이 아닌 호출해도 리소스가 변경되지 않는 성질을 말하는 것이다.

쉽게 생각해서, GET 메서드는 단순히 데이터를 조회하는 기능을 수행하기 때문에 리소스를 변경 및 수정하지 않으니 안전한 HTTP 메소드인 것이다.

반면에 GET 메서드를 제외한 나머지 4개의 메서드들은 호출할 경우에 데이터에 변경이 발생하거나, 서버에서 삭제되기 때문에 안전하지 않은 HTTP 메서드라고 볼 수 있다.

물론, 트래픽이 몰려서 수많은 GET 요청에 의해 서버가 터지게 된다면, “안전성”이라는 특성에 부합되지 않는다고 할 수도 있지만, 여기서 말하는 안전성은 리소소를 수정 또는 삭제하지 않으므로, 데이터의 일관성 유지에 있어서 안전하다는 의미이다.

정리하자면, 여기서 말하는 안전성은 메서드가 전체적인 시스템 장애로부터 안전하다는 의미를 가지는 것이 아니다.

멱등(冪等)성 (Idempotent)

HTTP Method멱등
GETO
POSTX
PUTO
PATCHO or X
DELETEO

멱등(冪等)이라는 단어의 의미는 수학이나 전산학에서 연산을 여러 번 적용하더라도 결과가 달라지지 않는 성질을 의미한다.

image_2 네이버 국어 사전에 검색한 결과

위의 정의를 HTTP의 멱등성(Idenpotent)에 대입해보면, 멱등성이란 한 번이든, 여러 번이든 몇 번을 요첨(Request)해도 그 결과가 같음을 의미한다.

즉, 동일한 요청을 한 번 보내는 것과 여러 번 연속으로 보내는 것이 같은 효과를 가지고, 서버의 상태도 동일하게 남을 때, 해당 HTTP 메서득 멱등성을 가진다고 말한다.

멱등과 안전의 개념이 다소 헷갈릴 수 있는데,

  • HTTP 메소드의 안전은 호출을 통한 리소스에 수정이 발생하지 않는 속성이다.
  • HTTP 메소드의 멱등은 리소스에 수정이 발생한다 하더라도 호출 결과가 항상 같아야하는 속성이다.

또한, 호출을 실행한 결과가 의미하는 것은 응답 상태 코드가 아닌 서버의 상태하는 점도 유의해야 한다.

예를 들어, 똑같은 요청을 했을 때 응답하는 상태 코드가 다르더라도 서버의 상태가 같은 상태라면 멱등성이 있다고 판단한다.

HTTP 스펙에 명시된 것에 의하면 GET, PUT, DELETE 이 3가지 메서드는 멱등성을 가지도록, POSTPATCH는 멱등성을 가지지 않도록 구현해야 한다.

그 이유는 다음과 같다.

GET의 멱등 (O)

GET은 데이터를 몇 번을 조회하든 같은 결과가 조회되므로 안전과 멱등을 동시에 만족하는 메서드이다.

  • GET 메서드로 호출되는 API에 대한 간략한 예시
    1. GET 요청
      1
      
      GET /post/1 HTTP/1.1
      
    2. 서버에서 id 값이 1인 게시글 조회
    3. 해당 게시글 데이터를 응답

윙의 경우, 요청을 여러 번 보내더라도 서버의 상태는 항상 같다.

이처럼 GET 메서드가 멱등성을 가져야 할 이유는 직관적으로 알 수 있다.

멱등적이지 않은 GET의 설계

하지만, 만약 개발자가 조회수 기능을 추가하는 과정에서, 아래와 같이 게시글을 조회하면 동시에 조회수도 올리도록 구현하다면?

  1. GET 요청
    1
    
    GET /post/1 HTTP/1.1
    
  2. 서버에서 id 값이 1인 게시글을 조회
  3. 해당 게시글의 조회수 데이터를 +1
  4. 해당 게시글 데이터를 응답

이 경우에는 GET 요청을 여러 번 보낼 경우, 서버의 데이터 상태는 매번 바뀔 것이다.

즉, 위의 GET 요청 로직은 멱등성을 가지지 않는 것이며, 개발자는 API를 HTTP 스펙에 부합하지 않게 구현했다고 볼 수 있다.

따라서 GET의 멱등성에 맞게 API를 설계하기 위해서는 조회수 컬럼의 값을 증가시키는 요청을 PATCH 요청으로 따로 분리하는 것이 맞다.

멱등하다고 결과가 항상 같은 것은 아니다.

GET이 멱등성을 만족하기 때문에, 어떤 상황에서도 여러 번 요청할 경우, 항상 같은 결과물을 내놓는다고 했지만, 이는 엄밀히 따지자면 아니다.

아래와 같은 상황을 예로 들자면,

  1. A 측에서 리소스 조회
    1
    
    GET /members/100 HTTP/1.1
    
  2. B 측에서 리소스를 변경
    1
    
    PUT /members/100 HTTP/1.1
    
  3. A 측에서 리소스를 다시 조회
    1
    
    GET /members/100 HTTP/1.1
    

위의 상황은 A가 100번째 멤버에 대한 리소스 조회에 대해 재요청을 하기 직전, B가 해당 리소스를 변경한 경우이다.

이때는 A가 멱등성이 있는 GET 메서드를 사용했는데도 불구하고, 중간에 B의 개입으로 인해 응답받는 결과값이 달라지게 된다.

그럼 멱등성이 깨진 잘못된 설계로 보이지만, 멱등성의 여부는 외부의 요인으로 인해 중간에 리소스가 변경되는 것은 고려하지 않는다.

또한 서버의 상태 기준으로 판단하기 때문에, GET의 멱등성은 문제가 없다.

서버의 상태를 바꾼 것은 PUT 메서드이지, GET 메서드는 자신의 역할을 충실히 이행했을 뿐이다.

POST의 멱등 (X)

POST 메서드는 멱등을 만족하지 않는다.

POST는 서버로 데이터를 전송하여 새로운 자원을 생성하는 역할을 한다.

따라서 요청을 여러 번 보내는 경우, 매번 새로운 자원이 생겨나는 것이며, 이는 서버의 상태가 변경되는 것을 의미한다.

복구 메커니즘에 따져야 하는 멱등성

클라이언트가 서버에게 HTTP 메시지를 전송했는데, TIMEOUT과 같은 문제들로 인해 정상적인 응답을 전달받지 못했다고 가정해보면, 정상적인 응답을 받지 못했기 떄문에 다시 요청하면 그만이야이라고 생각하겠지만, 여기에 HTTP의 멱등성의 유무가 들어가게 된다.

일반적으로 GET이나 DELETE같은 메서드들은 여러 번 호출해도 응답 결과는 변함이 없기 때문에 통신 장애 시 똑같은 요청을 재전송하도록 설계해도 문제가 없을 것이다.

하지만 POST와 같은 멱등하지 않게 설계된 메서드들은 똑같은 요청을 다시 전송할 경우에 문제가 발생할 가능성이 있다.

왜냐하면 통신 장애 이유가 서버 자체 문제일 수도, 인터넷 선이 문제일수도 있기 때문이다.

image_3

만일 서버에서는 정상 처리했는데 응답 과정에서 네트워크 문제로 정상적인 응답을 받지 못해서 POST를 통한 재요청을 해버리면, 서버 입장에서는 처리를 한 번 더 하게 되는 결과를 낳게 된다.

이러한 상황이 돈과 맞물리게 된다면, 결제 중복이라는 아주 큰 문제로 직결될 수도 있다.

따라서 POST 요청과 같은 경우에는 더 큰 문제가 발생할 수 있기 때문에 요청을 또 보내는 것이 아닌, 다른 조치를 취할 수 있도록 설계하는 것이 적절하다.

이렇게 복구 메커니즘의 사용 가능 여부에 있어서 HTTP의 멱등적인 속성은 설계의 중심이 되게 된다.

PUT의 멱등 (O)

PUT 메서드는 대상 리소스를 덮어씌워서 변경하거나, 대상 리소스가 없다면 새로 추가한다.

그래서 만약에 대상 리소스가 없다면 PUTPOST와 같은 동작을 하게 되는데, 반면 POST는 매번 새로운 자원을 만들지만 PUT은 해당 자원이 이미 있다면 데이터만 덮어쓴다.

따라서 몇 번을 요청하든 경국 서버의 상태는 같기 때문에, PUT은 멱등하다.

멱등하다고 해서 결과가 항상 같은 것은 아니다.

GET의 멱등과 같이 PUT도 예외 케이스가 존재한다.

StatusDescription
200 OK요청이 올바르게 수행됨 (GET, PUT)
201 Created서버가 새로운 리소스를 생성함 (POST, PUT)
204 No Content응답할 데이터가 없음 - HTTP Body가 없음 (DELETE, PUT)

PUT 요청을 보냈는데 새로운 데이터를 생성한 경우 201 Created 상태 코드로 응답하며, 기존 데이터를 덮어 쓴 경우에는 200 OK 혹은 204 No Content로 응답한다.

다만 동일한 요청을 여러 번 보내더라도 상태 코드라는 결과값이 다를 수 있다는 점만 기억하자.

PATCH의 멱등 (O or X)

PUT이 리소스 전체에 대한 데이터 교체라면, PATCH는 리소스의 부분적인 수정을 할 때 사용한다.

PATCH의 경우, 기본적으로 멱등성을 가지지 않는 메서드이지만, 그 구현을 PUT과 동일한 방식으로 할 경우 멱등성을 가지게 되는 특성을 가지고 있다.

설계에 따라서 PATCH가 멱등할 수도, 그렇지 않을 수도 있다는 뜻이다.

여기서 중요한 것은 두 메서드의 차이점인 전체 교체와 일부 교체 부분이 아닌, PUT 메서드와 다르게 PATCH는 반드시 멱등성을 보장하지 않는다는 것이다.

PATCH의 멱등적인 설계

PATCH 메서드에 수정할 리소스의 일부분만을 담아서 보내는 경우에는 멱등성이 보장된다.

  1. 기존 데이터
    1
    2
    3
    4
    5
    
    {
      "id": 1,
      "name": "Hyung-Jin Han",
      "age": 28
    }
    
  2. PATCH 요청
    1
    2
    3
    4
    
    PATCH /users/1 HTTP/1.1
    {
      "age": 20
    }
    
  3. 변경된 데이터
    1
    2
    3
    4
    5
    
    {
      "id": 1,
      "name": "Hyung-Jin Han",
      "age": 20
    }
    

PATCH의 멱등적이지 않은 설계

PATCH의 또 다른 특징으로는 HTTP 스펙 상, 구현 방법에 제한이 없다는 것이다.

그렇기 때문에 위처럼 꼭 데이터를 또 다른 데이터로 “대체”하도록 설계할 필요가 없다.

예를 들어, PATCH의 동작을 “증가”로 인한 값 변경이라고 하면 얘기가 달라진다.

  1. 기존 데이터
    1
    2
    3
    4
    5
    
    {
      "id": 1,
      "name": "Hyung-Jin Han",
      "age": 20
    }
    
  2. PATCH 요청
    1
    2
    3
    4
    5
    6
    7
    
    PATCH /users/1 HTTP/1.1
    {
      "age": {
       type: $increase,
       value: 1
      }
    }
    

    예시로 작성한 코드로 HTTP API 문법이 아님

  3. 변경된 데이터
    1
    2
    3
    4
    5
    
    {
      "id": 1,
      "name": "Hyung-Jin Han",
      "age": 21
    }
    

DELETE의 멱등 (O)

GET이 단순 조회라면, DELETE는 단순 삭제이다.

따라서 DELETE는 멱등성을 가진다.

DELETE를 처음 요청하면, 서버에서 해당 리소스는 삭제가 된다.

이후, DELETE를 여러 번 요청하더라도 해당 리소스는 삭제된 상태 그대로일 것이기 때문에, 서버의 상태는 변하지 않는다.

서버에서 삭제 행위가 작동하기 때문에 서버의 상태가 변경된 것이라고 할 수 있지만, 멱등성은 해당 요청을 몇 번이든 호출하더라도 결과 상태가 같다는 것을 의미하난 것이다.

전혀 변경이 일어나지 않음을 의미하는 것이 아니다.

즉, 처음 요청하든 다시 여러 번 요청을 하든, 서버의 상태는 리소스가 삭제된 상태를 반환하고 추가적인 동작을 하지 않으니 멱등하다는 것이다.

처음 한 번의 경우, 삭제 성공에 대한 200 OK를 반환

이후, 같은 요청을 계속해서 보내더라도 존재하지 않는 값의 삭제 요청이기에 404 NOT FOUND를 계속해서 반환

멱등적이지 않은 DELETE의 설계

추가적으로 이러한 DELETE의 멱등성 떄문에 DELETE API를 설계할 때에는 정확한 식별자를 통해 리소스를 지정해야 한다.

예를 들어, 게시글을 삭제할 때, 정확한 게시글 ID 값이 아닌 last라는 구조로 좀 더 유연하게 서버에서 처리되도록 구현했다면,

last

1
2
3
4
const last: number;
const posts: string[];

last = posts[posts.length - 1];
1
DELETE /posts/last HTTP/1.1

위의 로직은 꽤나 그럴 듯하게 보인다.

하지만 이런 경우, 해당 DELETE 요청을 여러 번 보내게 되면 매번 마지막 게시글을 삭제하기 때문에 매번 서버의 상태가 변하게 된다.

즉, 멱등성을 가지지 않게 되는 것이다.

1
POST /posts/last HTTP/1.1

따라서 이런 경우에 위와 같이 DELETE로 구현하는 것이 아닌, 멱등성을 가지지 않는 POST를 쓰는 것이 HTTP 스펙에 맞게 설계했다고 볼 수 있다.

캐시 가능성 (Cacheable)

HTTP Method캐시 가능
GETO
POSTO (구현 X)
PUTX
PATCHO (구현 X)
DELETEX

캐시 가능성(Cacheable)이란, 응답 결과 리소스를 캐싱해서 효율적으로 사용할 수 있는가에 대한 여부이다.

캐시(Cache)가 꼭 운영 체제나 서버에만 있는 것이 아닌, 브라우저 자체도 하나의 소프트웨어라서 캐시 공간을 가지고 있다.

클라이언트가 서버에 한 번 요청했던 데이터에 대해 매 요청마다 다시 전송하지 않고 데이터를 불러올 수 있도록 브라우저가 임시적으로 데이터를 보관하고 있는 것이다.

  1. 처음 웹 사이트에 접속할 때, 서버로부터 리소스를 모두 받아온다. image_4
  2. 웹 사이트를 재방문할 경우, 캐시에서 리소스를 가져오기 때문에 빠르게 로드된다. image_5

즉, 위와 같이 캐싱이 가능한 HTTP 메서드는 빠르게 결과값을 받을 수 있다는 의미이다.

추가적으로, 위의 표에 의하면 GET, POST, PATCH 메서드는 HTTP 스펙 상으로는 캐시가 가능하다고 나와있다.

하지만 실제로는 GET이나 HEAD 메서드 정도면 캐시로 이용이 가능하고 POSTPATCH는 지원되지 않는 경우가 일반적이다.

그 이유는 브라우저의 캐시를 이용하려면 원본 데이터가 변경되지 않고 유지되어야 이용이 가능한데, POST, PUT, DELETE, PATCH는 기본적으로 데이터를 변경하는 메서드이기 때문에, 만일 호출로 인해 데이터가 변경되면 원본 데이터 또한 변경되기 때문에 캐시 데이터 불일치 문제가 생기는 것 때문이다.

반면, GET이나 HEAD는 URI만 키로 잡고 캐시하면 되기 때문에 원본 데이터의 변경을 걱정할 필요가 없다.

따라서 GETHEAD 메서드는 캐시가 가능하기 때문에 브라우저에서 리소스를 임시로 보관할 수 있고, 나머지 메서드들은 구현의 복잡성 혹은 유지의 어려움 때문에 캐시를 이용하지 않는다고 생각하면 된다.

참고 사이트

Inpa Dev - 🌐 HTTP의 멱등성 · 안정성 · 캐시성 💯 완벽 이해하기

Semantics - HTTP (4) - 메서드 속성 (안전 / 멱등 / 캐시가능)

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.