- #Etc
HTTP Method가 뭔데요 (4)
Prolip
2024-05-17
메서드 정의 마저 알아보기... 근데 일단 PUT이랑 DELETE까지만..
시작..
지난 포스팅에선 GET, HEAD, POST에 대해 정리해봤습니다..
이번 포스팅에서 나머지 메서드 정리하고 끝내보려고 합니다.
PUT
PUT이 뭔가요?
PUT은 클라이언트가 서버에 특정 리소스를 생성하거나 업데이트하도록 요청하는 메서드입니다!
클라이언트가 서버에 데이터를 보낼 때, 해당 데이터로 서버에 있는 특정한 리소스를 완전히 대체하거나 새로운 리소스를 생성하게 됩니다.
여기서 대상 리소스가 서버에 존재하지 않는다면 새로운 리소스를 생성하게 되고, 대상 리소스가 서버에 이미 존재한다면 새로운 데이터로 기존 리소스를 대체(업데이트)하게 됩니다.
서버의 응답은 만약 리소스가 생성되었다면 201 Created, 리소스가 새로운 데이터로 대체되었다면 200 OK, 204 No Content를 반환하게 됩니다.
PUT 요청이 성공적으로 이루어진다면 GET 요청을 통해 PUT 요청에 사용된 데이터와 동일한 데이터를 응답으로 반환하게 됩니다.
이게 무슨 말이냐구요??
- 클라이언트가 ‘PUT /users/내아이디’ 해당 요청을 보낸다고 가정해봅시다.
{ "name": "jimin", "age": 26 }
- 서버는 해당 리소스를 생성하거나 혹은 기존 리소스를 대체하게 됩니다.
- 이후에 클라이언트가 다시 ‘GET /users/내아이디’ 요청을 보냅니다.
{ "name": "jimin", "age": 26 }
- 이렇게 서버는 클라이언트가 PUT으로 보낸 데이터를 반환하는 것입니다!
However, there is no guarantee that such a state change will be observable, since the target resource might be acted upon by other user agents in parallel, or might be subject to dynamic processing by the origin server, before any subsequent GET is received.
하지만 문서에선 다른 사용자에 의해 동일한 리소스를 병렬로 변경하거나, 서버에서 동적으로 처리될 수 있어 항상 같은 상태를 반환한다는 보장은 없다고 말합니다. 뭐 서버가 해당 리소스에 id를 추가한다거나 하면 동일한 상태는 아니겠죠??
서버의 일관성 검증이 필요해요.
An origin server SHOULD verify that the PUT representation is consistent with its configured constraints for the target resource.
서버는 PUT 요청이 대상 리소스에 대해 구성된 제약 조건과 일치하는지 확인해야 한다는데 또… 또… 이렇게만 설명하죠?
구성된 제약 조건은 서버가 특정 리소스에 대해 설정한 규칙이나 제한 사항을 의미합니다. 리소스의 타입이나 형식, 데이터 구조, 허용된 값 등이 있겠죠??
이렇게 일관성 검증이 필요한 이유는 데이터의 무결성과 시스템의 안정성을 보장하기 위함이라고 합니다.
이제 제약 조건의 예시를 확인해봅시다!
- Content-Type
- 예를 들어 우리는 특정 리소스가 항상 ‘application/json’ 타입의 데이터를 가져야한다고 조건을 걸었다고 생각해봅시다.
PUT /profile HTTP/1.1
Host: www.gojimin.com
Content-Type: application/json
{
"name": "jimin",
"age": 26
}
- 이제 서버는 데이터의 타입이 JSON 타입인 것을 확인하고 요청을 받아들입니다. 여기서 만약 ‘application/xml’ 이런 형식이면 서버는 415 Unsupported Media Type 상태 코드와 함께 요청을 거부할 수 있게 됩니다.
- 데이터 구조
- 이번엔 리소스가 특정 필드**(‘name’, ‘age’, ‘email’)**를 포함해야된다고 가정해봅시다.
{ "name": "jimin", "age": 26, "phone": "010-7777-7777" }
- 저런! 서버는 ‘email’ 필드가 누락되어 400 Bad Request 상태 코드를 보내며 요청을 거부하게 됩니다!
- 허용된 값
- 이번엔 특정 필드의 값을 정해진 범위 내에서만 받도록 조건을 걸어봅시다**. ‘age’** 필드가 10이상이면서 30이하인 경우만 허용한다고 가정해봅시다.
{ "name": "jimin", "age": 26 }
- 이 경우 서버는 ‘age’ 필드가 허용된 범위 내에 있어 요청을 받아들이게 됩니다. **‘age’**가 42였다면 마찬가지로 서버는 400 Bad Request와 함께 요청을 거부할 수 있습니다.
이렇게 서버는 제약 조건을 검증함으로써 리소스의 일관성과 무결성을 유지할 수 있게 됩니다. 전에 정리한 내용인데 멱등성을 보장하기 위함이겠죠??
만약 데이터가 제약 조건을 충족하지 않는다면 서버는 다음과 같은 조치를 취해야한다고 합니다.
- 수신된 데이터를 리소스의 제약 조건에 맞게 변환시킨다.
- 이 말은 ‘application/xml’ 이러한 형식으로 온 데이터를 ‘application/json’ 이렇게 변환 시키라는 의미인듯합니다. 근데 그럼 그게 제약 조건이 맞나?
- 리소스의 제약 조건을 수정해 수신된 데이터를 포함하도록 변경하라고 합니다.
- 이 말은 ‘application/xml’ 이러한 형식으로 온 데이터가 존재하면, 이 형식을 제약 조건에 추가하라는 의미인듯합니다. 근데 그럼 이것도 제약 조건이 맞나..?
- 마지막으로 적절한 상태코드와 함께 오류 메세지를 반환하라고 합니다.
- 409 Conflict: 데이터가 리소스의 현재 상태와 충돌하는 경우 사용합니다.
- 415 Unsupported Media Type: 데이터 형식이 허용되지 않는 경우 사용됩니다.
HTTP/1.1 415 Unsupported Media Type Content-Type: application/json { "message": "그 형식은 안돼요!!! application/json으로 보내주세요!!!" }
이렇게 문서에선 검증 후 제약 조건이 일치하지 않을 때 이론상으로 할 수 있는 모든 방법을 제시해주고 있는듯 합니다.
PUT 요청의 처리 방식
HTTP does not define exactly how a PUT method affects the state of an origin server beyond what can be expressed by the intent of the user agent request and the semantics of the origin server response.
혼란하다 혼란해..
문서에서 말하고자 하는 의미는 HTTP 프로토콜 자체가 서버와 클라이언트와 서버 간의 데이터 전송에 대한 규칙만을 정의할뿐, 데이터가 서버에서 어떻게 처리되고 저장되는지에 대한 구체적인 사항은 서버의 구현에 맡긴다는 의미입니다.
클라이언트가 서버에 PUT 요청을 보낼 때, 보내진 데이터가 서버에서 어떤 방식으로 처리되는지는 클라이언트가 알 수 없습니다. 서버는 클라이언트에게 내부에서 어떤 방식으로 처리되는지에 대해서는 숨기고, 단지 요청이 성공했는지 상태 코드를 통해서 알려줄 뿐입니다.
당연하게도 클라이언트가 PUT 요청을 통해 서버에 데이터를 업로드하거나 갱신할 때 서버에서 해당 데이터를 로컬 상에 저장할지, DB에 저장할지, 혹은 또 다른 방식으로 처리할 수도 있습니다. 하지만 클라이언트는 그렇게 처리되는 방식에 대해서는 알 수 없고 단순히 서버가 200 OK, 201 Created 등 상태 코드를 반환하면 그제서야 요청이 성공했다는 것을 알 수 있는 것입니다.
또, 클라이언트가 PUT 요청을 보낼 때, 다양한 헤더와 트레일러 필드를 포함할 수 있습니다.
이 때 서버는 body 외에도 이 헤더와 트레일러 필드를 처리해야 되는데, 서버는 인식되지 않는 헤더와 트레일러 필드를 무시할 수 있다고 합니다. 따라서 PUT 요청에 포함된 데이터 중 일부는 서버가 무시할 수 있으며, 클라이언트가 보낸 데이터가 그대로 저장되지 않을 수 있음을 의미합니다.
PUT /posts/411 HTTP/1.1
Host: www.gojimin.com
Content-Type: application/json
Jimin-Custom-Header: 내가만들었지롱
{
"title": "뭐라는 거야",
"description": "이해하기 어려워요."
}
이렇게 클라이언트가 요상한 사용자 정의 헤더를 만들었다고 가정해봅시다. 당연히 서버는 이따위 헤더는 필요도 없을 뿐더러 인식도 못할 것입니다.
그럼 서버는 이 헤더를 무시하고 body에 담긴 JSON 객체만을 처리하게 됩니다. 결과적으로 서버는 “title”과 “description” 데이터를 사용해 리소스를 업데이트하거나 새로 생성하지만, ‘Jimin-Custom-Header’는 무시합니다.
GET /posts/411 HTTP/1.1
Host: www.gojimin.com
이제 서버는 이후 동일한 URI에 대한 GET 요청에 대해 이렇게 응답할 것입니다.
HTTP/1.1 200 OK
Content-Type: application/json
{
"title": "뭐라는 거야",
"description": "이해하기 어려워요."
}
이렇게 일반적으로 Content-Type과 같은 헤더 필드는 저장되지만, 서버에서 인식할 수 없는 헤더 및 트레일러 필드는 무시될 수 있음을 확인했습니다.
검증자 필드
An origin server MUST NOT send a validator field (Section 8.8), such as an ETag or Last-Modified field, in a successful response to PUT unless the request's representation data was saved without any transformation applied to the content ~~
문서에선 원본 서버는 성공적인 PUT 응답에 검증자 필드인 Etag, Last-Modified를 포함해서는 안된다고 합니다.
하지만 예외적으로 포함 가능한 경우가 존재합니다.
- 클라이언트가 요청으로 보낸 데이터를 변형 없이 저장한 경우
- 검증자 필드 값이 새로운 표현을 정확히 반영한 경우
그럼 위의 경우가 아닐 때 검증자 필드를 포함해서는 안되는 이유는 무엇일까요??
우선 검증자 필드는 제 첫 포스팅의 조건부 요청 부분에서 다뤘는데 리소스의 현재 상태를 나타내는 값입니다.
Etag의 경우 리소스의 특정 버전을 식별하는 고유한 태그로 리소스가 변경되면 갱신됩니다. 그럼 서버가 만약 클라이언트가 보낸 데이터를 어떠한 방법으로 변경해 저장했을 때 Etag가 바뀌지 않겠습니까?
이 경우에 위에서 설명했듯 검증자 필드 값이 새로운 표현을 정확히 반영한다면 포함할 수 있지만 그렇지 않을 경우엔 검증자 필드를 포함해선 안됩니다.
만약 이렇게 반영되지 못한 잘못된 검증자 필드를 반환하면 클라이언트는 이를 신뢰하고 조건부 요청에 사용하게 됩니다. 그럼 데이터 무결성이 깨지게 되겠죠!? 그럼 여기서 또 변형된 데이터를 다시 가져오면서 불필요한 데이터 전송 과정이 또 생기게 되겠군요.
Last-Modified도 리소스가 마지막으로 수정된 시간을 나타내니 마찬가지겠군요.
문서에선 이런 상황에 대해 포함하지 말라고는 하는데 검증자 필드를 통해 우발적인 덮어쓰기를 방지할 수 있습니다.
예를 들자면 하나의 리소스를 클라이언트 A와 B가 거의 동시에 수정한다고 가정해봅시다.
이 때 A가 데이터를 수정한 뒤 PUT 요청을 보내면 서버의 리소스는 A가 수정한 데이터로 변경됩니다. 그런데 B는 이를 인지하지 못한 채 이전의 데이터를 기반으로 리소스를 수정하게 되고 이후에 B가 PUT 요청을 보내면 A의 수정 내용은 사라지게 되고 B가 수정한 내용으로 덮어쓰여집니다.
그럼 검증자 필드를 통해 어떻게 방지하게 되는걸까요??
- A가 리소스 가져옴
GET /products/412 HTTP/1.1
ETag: "버전1"
- B도 동시에 리소스 가져옴
GET /products/412 HTTP/1.1
ETag: "버전1"
- A가 수정하고 PUT
PUT /products/412 HTTP/1.1
If-Match: "버전1"
{
"description": "정말 멋진 청바지"
}
HTTP/1.1 200 OK
ETag: "버전2"
- B가 수정하고 PUT
PUT /products/412 HTTP/1.1
If-Match: "버전1"
{
description: "아 증말 멋진 청바지"
}
HTTP/1.1 412 Precondition Failed
이렇게 B는 리소스가 변경된 것을 인지할 수 있게 됩니다. 그럼 데이터가 덮어써지는 일을 방지할 수 있겠죠??
그래서 POST랑 뭐가 다른데요?
다음으로 문서에선 POST와 근본적으로 무엇이 다른지에 대해 설명하고 있습니다.
아니 POST도 리소스 없으면 생성하고, 리소스 있으면 수정하고 PUT이랑 뭐 똑같은 거 아닙니까?
제가 문서를 보고 정리한 내용은 다음과 같습니다.
PUT
- 대상 URI가 고정되어 있다.
- PUT 요청은 클라이언트가 리소스가 어디에 위치할지 URI를 직접 정확히 지정합니다.
- 예를 들자면 클라이언트에서 특정 상품의 정보를 업데이트하기 위해 PUT 요청을 보낼 때 ‘products/411’ 이런 엔드포인트로 요청을 보낼 수 있습니다.
- 클라이언트가 보낸 데이터로 리소스를 대체한다.
- 클라이언트가 보낸 데이터로 지정된 URI에 기존 리소스가 있다면 대체, 없으면 새로운 리소스를 생성합니다.
- 이 동작은 리소스를 완전히 대체하고 이후에 GET 요청으로 동일한 URI에 요청하면 클라이언트는 자신이 제공한 데이터를 포함한 리소스를 응답으로 받아볼 수 있습니다.
- 멱등성을 가진다.
- PUT 요청은 동일한 요청을 여러 번 보내더라도 서버의 리소스 상태는 첫 요청 이후에 항상 동일하게 유지됩니다.
- 위에서 들었던 예시로 상품의 정보를 업데이트하기 위해 해당 URI로 동일한 데이터로 총 5번의 요청을 보내더라도 처음에 변경된 후 서버 상태는 변하지 않습니다.
- 클라이언트가 보내는 데이터가 리소스의 최종 상태를 결정한다.
- PUT 요청은 클라이언트가 보낸 데이터로 서버의 해당 URI에 있는 리소스를 완전히 ‘교체’하는 요청입니다.
- 이는 클라이언트가 서버에 전달할 데이터의 전체적인 상태를 알고 있는 것이며 이 데이터가 서버에 저장될 최종 상태를 결정하는 것입니다.
POST
- 대상 URI가 고정되지 않는다.
- POST 요청은 리소스가 생성될 위치를 지정하지 않습니다. 이 위치는 서버가 결정합니다.
- 예를 들어 클라이언트가 새로운 상품을 등록하기 위해 POST 요청을 **‘/products’**라는 엔드포인트로 보냅니다. PUT과 다르게 명확한 URI가 아니죠??
- 서버에서 새로운 리소스 URI를 생성한다.
- 클라이언트에서 보내진 데이터를 서버에서 처리해 새로운 리소스를 생성하게 됩니다.
- 이 때 서버는 생성된 리소스의 URI를 결정하고, 이를 클라이언트에게 반환하게 됩니다.
- 위에서 들었던 예시로 상품이 성공적으로 생성된다면 서버는 ‘products/411’ 이라는 URI를 반환할 수 있게 됩니다. 예시를 직접 볼까요??
POST /products HTTP/1.1
Host: www.gojimin.com
Content-Type: application/json
{
"title": "신상 셔츠",
"description": "아 증말 멋진 셔츠"
}
HTTP/1.1 201 Created
Location: /products/411
- 멱등성을 가지지 않는다.
- POST 요청은 동일한 요청을 여러 번 보냈을 때 서버의 상태가 계속해서 변할 수 있습니다.
- 만약 동일한 데이터로 5번 요청하면 서버에선 총 5번의 모든 요청을 계속해서 처리하고 여러 리소스가 생성될 수 있습니다.
- 리소스의 상태가 고정되지 않는다.
- POST 요청은 클라이언트가 서버에 데이터를 보내면 서버가 이를 처리해 새로운 리소스를 생성하거나 기존 리소스를 변경합니다.
- 이전 포스팅에서 정리했듯 POST 요청은 리소스 대상 리소스가 요청에 포함된 표현을 리소스 고유의 특정 시맨틱스에 따라 처리합니다. 그럼 이 데이터들은 서버의 논리에 따라 다양한 방식으로 처리될 수 있는 것이고 당연히 최종 상태는 서버에 의해 결정됩니다.
이렇게 제가 정리한 PUT과 POST의 차이점입니다. 짧게 요약해보자면!
PUT은 대상 URI가 고정되어 있으며 클라이언트가 보낸 데이터로 리소스를 완전히 교체합니다. 멱등성을 가지며 클라이언트가 보낸 데이터가 리소스의 최종 상태를 결정합니다.
POST는 대상 URI가 고정되지 않으며 서버가 새로운 리소스 URI를 생성합니다. 멱등성을 가지지 않고 리소스의 최종 상태는 서버에서 결정합니다.
PUT 요청 처리 가이드라인
다음 문단은 PUT 요청의 처리 가이드라인?을 설명하는 문단으로 보입니다.
PUT 요청의 적절한 해석
Proper interpretation of a PUT request presumes that the user agent knows which target resource is desired.
PUT 요청의 적절한 해석은 사용자 에이전트가 어떤 대상 리소스를 원하는지 알고 있다는 전제를 가진다고 합니다.
이 뜻은 클라이언트에서 PUT 요청을 보낼 때 서버에서 정확히 어떤 리소스를 업데이트하거나 생성해야 하는지 명확히 알고 있어야 한다는 것을 의미합니다.
쉽게 예를 들어보자면 우리가 특정한 상품의 정보를 업데이트할 때 PUT 요청을 보낼텐데 여기서 그 상품의 정확한 URI를 알고 있어야 된다는 뜻입니다! 이렇게 정확하게 URI를 지정해줘야 서버는 해당 URI에 있는 리소스를 업데이트하거나 생성할 수 있습니다.
URI를 선택하는 서비스
A service that selects a proper URI on behalf of the client, after receiving a state-changing request, SHOULD be implemented using the POST method rather than PUT.
클라이언트 대신 적절한 URI를 선택하는 서비스는 PUT 대신 POST를 사용해야한다고 합니다.
만약 서버가 클라이언트의 요청을 받고 데이터를 처리한 뒤 리소스의 URI를 결정한다면 이 때 PUT 대신 POST를 사용하라는 의미입니다!
위에서 비교할 때 살펴봤듯 서버가 새로 생성된 리소스의 URI를 결정한다면 POST를 사용하면 됩니다.
리다이렉션
클라이언트가 보낸 PUT 요청으로 상태를 변경할 때 대상 리소스(클라이언트가 지정한 URI)가 아닌 다른 리소스에 적용하려면 클라이언트에게 적절한 3XX(리다이렉션) 응답을 보내라고 문서에서 말하고 있습니다.
이유는 리소스 이동에 대한 리다이렉션 응답으로 클라이언트에게 원본 리소스가 새로운 위치로 이동되었음을 알리기 위함입니다.
이를 통해 클라이언트가 만약 원래 URI로 PUT 요청을 보내면 서버는 해당 요청을 새로운 URI로 리디렉션해 클라이언트가 요청을 제대로 보낼 수 있도록 해야만 합니다.
3XX 리다이렉션 코드는 다음과 같습니다!
- 301 Moved Permanently: 리소스가 영구적으로 이동되었음을 뜻합니다. 구글 서치 콘솔 사용해보시면 아시겠지만 사이트 이동 처리할 때 301로 처리해서 검색엔진이 알아서 새로운 위치로 접근합니다…
- 302 Found: 리소스가 일시적으로 이동했음을 알릴 때 사용합니다.
- 307 Temporary Redirect: 307 코드도 302 코드와 비슷하게 일시적으로 다른 URI로 이동됨을 나타냅니다.
- 302와 307 코드의 다른 점은 302 상태 코드는 요청한 리소스가 일시적으로 다른 위치에 있다고 알려줍니다. 이 때 클라이언트가 요청 메서드를 변경할 수 있습니다.
- 307 코드는 요청한 리소스가 일시적으로 다른 위치에 있다고 알리며 클라이언트는 동일한 메서드와 데이터를 사용해 요청을 다시 시도하라고 명확히 명시하는 것입니다.
- 두 코드의 차이는 클라이언트가 어떻게 요청을 다시 시도해야 하는지를 명확히 규정하는가 즉, 클라이언트의 동작 방식을 제어하기 위한 규정의 차이로 볼 수 있습니다.
- 308 Permanent Redirect: 리소스가 영구적으로 새로운 위치로 이동되었음을 알립니다.
- 301 코드와 다른 점은 302와 307의 차이점과 동일합니다.
- 308 코드의 경우도 307과 동일하게 원래의 요청 메서드와 데이터를 유지해 요청을 유지해야 한다고 명시합니다.
다른 리소스에 대한 부작용
A PUT request applied to the target resource can have side effects on other resources.
PUT 요청이 다른 리소스에 부작용을 미칠 수 있다고 문서에서 말하는데 이건 무슨 내용일까요,,
먼저 PUT 요청은 단일 리소스의 상태를 변경하는데 사용됩니다. 그런데 문서에선 이 변경이 다른 관련 리소스에도 영향을 미칠 수 있다고 합니다.
이렇게 한 리소스에 대한 PUT 요청이 예상치 못한 방식으로 다른 리소스에 영향을 미치는 경우를 다른 리소스에 대한 부작용이라고 문서에선 표현하고 있습니다.
이 때 이러한 부작용은 서버의 구조, 리소스의 관계에 따라 발생할 수 있다고 합니다.
예시로 알아봅시다. 어떤 리소스가 여러 버전으로 관리되는 경우를 생각해보겠습니다.
제가 운영하는 뉴스 사이트에서 어떤 기사의 최신 버전을 가리키는 URI가 있다고 가정해보겠습니다. 이 URI는 https:/www,gojimin.com/article/current 와 같습니다.
다음으로 이 기사의 각 버전이 고유한 URI를 가진다고 가정해보겠습니다. https:/www,gojimin.com/article/v1, https:/www,gojimin.com/article/v2’ 이와 같습니다.
- 클라이언트에서 기사의 최신 버전에 PUT 요청을 보냅니다. 이 때 요청에 새로운 데이터를 포함해 요정합니다.
- 서버는 PUT 요청을 받아 ‘current’에 해당하는 URI의 내용을 업데이트하게 됩니다.
- 위에서 설명했듯 기사의 각 버전이 고유한 URI를 가지기 때문에 서버는 기존 버전 간의 링크를 재구성해야 합니다. ‘current’에 해당하는 버전이 바뀌면서 이전 버전과의 링크 관계가 영향을 받을 수 있기 때문입니다.
- 만약 이 때 다른 리소스가 ‘current’에 해당하는 URI를 참조하고 있다면 이 참조가 예상치 못한 방식으로 변경이 될 수 있습니다. 만약에 제 블로그에서 상단에 노출 시키는 기사가 있는데 이를 ‘current’ 버전을 기반으로 동작하도록 구현했다면 이 동작이 제가 의도하지 않은 방식으로 변하겠죠??
- 또 여러 클라이언트가 동시에 ‘current’ URI에 PUT 요청을 보낼 때 동시성 문제가 발생할 수도 있습니다.
이렇게 문서에선 PUT 요청이 다른 리소스에 부작용을 미칠 수 있다고 설명하고 있습니다..
부분적인 PUT 요청
Some origin servers support use of the Content-Range header field (Section 14.4) as a request modifier to perform a partial PUT, as described in Section 14.5.
일부 서버는 ‘Content-Range’ 헤더 필드를 사용해 클라이언트가 리소스의 특정 부분만 업데이트할 수 있도록 지원한다고 합니다.
일부만 변경하고 싶은데 큰 리소스를 전체적으로 전송할 필요가 없어 좋을 거 같습니다.
Content-Range 헤더 필드란 요청이나 응답이 리소스의 특정 범위에 해당하는지 나타내는 헤더로 일반적으로 GET 요청의 응답에 사용되지만 PUT 요청에서도 사용 가능하다고 합니다.
예시로 알아봅시다.
- 일반적으로 전체 리소스에 대해 PUT 요청을 보내는 경우입니다. 이 경우는 전체 리소스를 교체하며 클라이언트가 전체 파일을 서버로 전송해 교체하는 방식입니다.
PUT /example.txt HTTP/1.1
Host: www.gojimin.com
Content-Type: text/plain
Content-Length: 30
그만하고 싶다.
- 이번엔 ‘Content-Range’ 헤더를 사용해 리소스의 특정 부분만을 업데이트하는 방법입니다. 파일의 일부만 변경하려는 경우를 보겠습니다.
PUT /example.txt HTTP/1.1
Host: www.gojimin.com
Content-Type: text/plain
Content-Range: bytes 0-3/26
Conent-Length: 6
이거
- 클라이언트가 ‘example.txt’의 첫 4바이트를 ‘이거’로 업데이트하기 위해 요청을 보냅니다.
- ‘Content-Range’ 헤더 필드의 bytes 0-3/26은 이 요청이 해당 파일의 첫 4바이트만 변경할 것임을 나타냅니다.
- 이 ‘Content-Range’ 헤더의 구조는 다음과 같습니다.
- Content-Range: bytes start-end/total
- ‘bytes’: 바이트 단위의 범위를 나타냄을 뜻합니다. 여기서 단위는 frames 등이 올 수도 있다고는 하는데 HTTP 표준이랑 호환성 고려하면 바이트 단위가 가장 많이 쓰인다고 합니다.
- ‘start’: 전송할 데이터의 시작 바이트 위치를 의미합니다.
- ‘end’: 전송할 데이터의 끝 바이트 위치를 의미합니다.
- ‘total’: 전체 리소스 크기를 의미합니다.
- 그럼 이후에 서버는 기존의 ‘example.txt’ 파일의 내용을 읽습니다.
- 클라이언트가 명시한 0번째 바이트부터 3번째 바이트까지 클라이언트가 제공한 ‘이거’로 대체합니다.
- ‘Content-Range’ 헤더에 포함되지 않은 나머지 부분은 변경되지 않고 그대로 유지됩니다.
캐시 불가
Responses to the PUT method are not cacheable. If a successful PUT request passes through a cache that has one or more stored responses for the target URI, those stored responses will be invalidated (see Section 4.4 of [CACHING]).
PUT 메서드에 대한 응답은 캐시될 수 없습니다!
PUT 요청은 일반적으로 서버의 리소스를 업데이트하거나 혹은 생성하는데 사용됩니다. 이로 인해 리소스의 현재 상태가 변경되기 때문에 변경된 상태가 반영되지 않은 이전의 응답을 캐싱하고 있다? 의미가 없습니다.
또한 PUT 요청이 성공하면 해당 URI에 대해 기존에 캐시되어있던 모든 응답이 무효화된다고 합니다.
이유는 위와 비슷하게 PUT 요청이 리소스의 상태를 변경하기 때문에 기존에 캐시되어있는 데이터가 최신 상태를 반영하지 않기 때문입니다.
요약하자면..
- PUT 메서드는 클라이언트가 서버에 특정 리소스를 생성하거나 업데이트하도록 요청하는 메서드입니다. (리소스 있으면 교체, 없으면 생성)
- 서버는 PUT 요청에 들어온 데이터가 구성된 제약 조건과 일치하는지 확인해야 합니다. (Content-Type, 데이터 구조, 허용된 값 등)
- HTTP는 데이터 전송에 관한 규칙만 정할뿐 어떻게 처리되는지는 정의하지 않습니다. (서버에서 알아서 함) 이후에 상태코드를 이용해 성공 여부만 알립니다.
- 클라이언트가 보낸 데이터가 변형 없이 저장된다면 검증자 필드를 포함할 수 있습니다. 이는 데이터 무결성 보장 및 우발적 덮어쓰기를 방지합니다.
- POST와 PUT의 차이는 다음과 같습니다.
- PUT: 대상 URI가 고정되어 있으며 클라이언트가 보낸 데이터로 리소스를 완전히 교체합니다. 멱등성을 가지며 클라이언트가 보낸 데이터가 리소스의 최종 상태를 결정합니다.
- POST: 대상 URI가 고정되지 않으며 서버가 새로운 리소스 URI를 생성합니다. 멱등성을 가지지 않고 리소스의 최종 상태는 서버에서 결정합니다.
- PUT 요청은 클라이언트가 어떤 리소스를 업데이트하는지 명확히 알고 있어야 합니다. 또한 서버가 URI를 결정한다면 POST를 사용하고, 다른 리소스에 요청을 적용한다면 3xx 응답으로 클라이언트에게 알립니다.
- PUT 요청은 관련된 다른 리소스에도 영향을 미칠 수 있습니다.
- 일부 서버는 ‘Content-Range’ 헤더를 사용해 리소스의 특정한 부분만을 업데이트할 수 있습니다.
- PUT 요청의 응답은 캐시될 수 있습니다.
DELETE
DELETE 메서드는 클라이언트가 서버에 특정 리소스와 현재 기능간의 연관성을 제거하도록 요청하는 메서드입니다. 문서에선 UNIX의 ‘rm’ 명령어와 비슷하게 서버의 URI 매핑에서 리소스를 삭제하는 작업을 의미한다고 합니다.
여기서 실제로 리소스의 데이터를 삭제하는지의 여부는 서버의 구현에 따라 다르다고 합니다.
삭제를 할 수도 있는데 아닐 수도 있습니다.
If the target resource has one or more current representations, they might or might not be destroyed by the origin server.
대상 리소스에 하나 이상의 현재 표현이 있는 경우 원본 서버는 이를 삭제할 수도 있고, 삭제하지 않을 수도 있다고 합니다.
음.. 조금 난해하게 설명되어있는데 찾아보고 제가 이해한 내용을 정리해보자면 하나의 리소스는 여러 형태로 존재할 수 있습니다.
예를 들어보자면 하나의 기사가 존재한다고 가정해봅시다. ‘/article/123’ 이렇게요!
그런데 이 기사는 ‘/article/123.pdf’ 이렇게 pdf 버전이 존재할 수도 있고 ‘/article/123.html’, ‘/article/123.txt’ 이렇게 html 파일과 txt 파일 버전도 존재할 수 있습니다.
그럼 클라이언트가 DELETE 요청을 보냈을 때 서버는 이 리소스의 모든 표현을 삭제할지, 일부만 삭제할지 결정하게 됩니다.
이는 서버의 구현 방식에 따라 다르며 일부 서버는 물리적으로 삭제하지 않고 단순하게 해당 리소스에 접근하지 못하게 마크하는 경우도 있다고 합니다.
그래서 저는 아니 어차피 클라이언트는 이거 필요 없다고 생각해서 DELETE 요청하는 거 아닌가? 근데 왜 삭제할 수도 있습니다…! 근데 아닐 수도 있구용 ~ 이러는게 이해가 가지 않아서 알아본 결과!
- 데이터의 중요도 및 보존 정책으로 인해
- 만약 서버에서 해당 기사가 중요한 기사였다고 가정해봅시다. 그럼 법적으로 또는 규정 준수를 위해 특정한 형식의 데이터를 보존해야할 수도 있는 것입니다.
- 위의 예시중 pdf, html, txt 중에서 pdf 파일은 법적 기록으로 남겨야한다면 서버는 이 중에서 선택해 html 버전과 txt 버전만 삭제할 수도 있습니다.
- 서버의 백업 및 복구 정책으로 인해
- 혹시라도 서버에서 데이터를 삭제하기 전에 백업본을 만들 수도 있습니다. 그럼 이 과정에서 일부 표현이 바로 삭제되지 않고 백업이 완료될 동안 기다린 후 삭제될 수 있는 것입니다!
- 혹시 모를 DB 또는 서버 시스템의 제한으로 인해
- 특정한 시스템 혹은 DB에서는 모든 표현을 동시에 삭제하는 작업이 오래 걸릴 수 있다고 합니다. 그럼 우선적으로 일부분만 삭제하고 비동기적으로 나머지를 삭제할 수 있다고 합니다. 즉 나머지 작업을 백그라운드로 처리하며 사용자 경험을 증가시키기 위함인듯 합니다.
and the associated storage might or might not be reclaimed,
또한 관련된 저장소도 서버의 구현 사항에 따라 회수할 수도 있고 회수하지 않을 수도 있다고 합니다.
이 말은 클라이언트가 DELETE 요청으로 해당 리소스의 표현을 삭제하더라도 서버에서 해당 리소스에 할당하고 있던 저장소 공간을 즉시 회수할지 혹은 나중에 회수할지는 서버의 구현 사항에 따라 다르다는 것입니다.
이는 서버의 최적화 전략 혹은 시스템 자원 관리 방식에 따라 달라질 수 있다고 합니다. 문서에선 뭐 데이터베이스 행에서 해당 리소스는 지우지만 바로 방을 빼지는 않고 **‘너 나가’**하고 마크만 해두고 나중에 가비지 컬렉터가 회수하는 방식이나 이런 경우도 있다고 합니다.
다른 구현 측면
문서에선 DELETE 요청은 단순히 리소스를 삭제하는 행동 외에도 여러 부수적인 효과를 가져올 수 있다고 합니다. 리소스의 삭제가 해당 리소스가 의존하고 있는 다른 시스템이나 연결 등에 영향을 미칠 수 있다고 합니다.
1. 데이터베이스의 경우
- 삭제 혹은 비활성화
- 클라이언트가 DELETE 요청을 통해 특정한 리소스를 제거하려고 합니다. 이 때 리소스가 DB의 행으로 구현되어있다면 이 요청은 해당 행을 삭제하거나 비활성화할 수 있습니다.
- 예를 들어보자면 사용자의 계정을 하나 삭제한다고 가정했을 때 DELETE 요청이 들어오면 해당 사용자의 레코드를 삭제하거나, 혹은 계정을 비활성화 상태로 표시할 수 있습니다. 여기서 비활성화는 데이터 복구, 기록 보관의 목적으로 사용될 수 있습니다.
- 데이터 보관
- DELETE 요청이 들어올 때 데이터베이스에서 해당 데이터가 보관될 수 있습니다. 데이터를 물리적으로 삭제하는 것이 아닌 삭제된 것으로 표시하고 보관할 수 있습니다. 법적으로 혹은 규정 준수 요건 등 삭제된 데이터를 일정 기간 동안 보관해야만 할 때 데이터는 논리저긍로 삭제되지만 실제로 DB에서 보관 후 일정 기간 후에 삭제할 수 있습니다.
2. 게이트웨이의 경우
- 외부 시스템과 연결을 비활성화
- 리소스가 외부 시스템과의 연결에 사용 중이라면 DELETE 요청으로 인해 이 연결이 비활성화 될 수 있습니다.
- 예를 들어 참조하고 있는 클라우드 서비스와의 연결에서 인증 정보를 삭제 요청할 경우 클라우드 서비스에 엑세스할 수 없게 됩니다.
- 연결 보관
- DB와 마찬가지로 게이트웨이 연결 사이를 완전히 삭제하지 않고 보관할 수 있습니다. 보관 후 나중에 연결이 필요할 때 재활성화하기 위함입니다.
제한된 사용 및 주요 용도
1. 제한된 사용
DELETE 메서드는 일반적으로 모든 리소스에 대해 허용되지 않는다고 합니다. 상대적으로 적은 수의 리소스에만 사용이 허가된다고 하는데 이유는 당연하게도 DELETE 요청은 리소스를 완전히 제거하는 동작을 하기 때문입니다.
2. 원격 저작(리소스 작성, 수정, 삭제) 환경에서의 주요 사용
its primary use is for remote authoring environments, where the user has some direction regarding its effect.
DELETE 메서드는 주로 원격으로 사용자가 리소스를 작성하거나 수정, 삭제할 수 있는 시스템에서 사용된다고 하며 이러한 환경에선 사용자가 DELETE 요청이 어떤 효과를 가지는지 이해하고 있다고 합니다…… … … .?
음 사실 이렇게만 말하면 좀 어렵고 쉽게 CMS를 생각하면 좋을듯 합니다.
블로그 같은 콘텐츠 관리 시스템에선 사용자가 해당 웹의 인터페이스를 통해서 글을 쓰거나 수정하고, 삭제할 수 있습니다.
만약 사용자가 작성한 글이 마음에 들지 않아서 해당 글을 삭제하고자 삭제 버튼을 누르면 서버에 DELETE 요청을 보냅니다. 그럼 서버는 DB에서 해당 글을 삭제하거나 비활성화하겠죠??
이 때 사용자는 DELETE 요청이 글을 삭제할 것임을 알고 요청을 하게 됩니다. 즉 위에서 말한 사용자가 DELETE 요청이 어떤 효과를 가지는지 이해하고 있다는 뜻입니다!
3. PUT, POST 요청으로 생성된 리소스 삭제
문서에선 클라이언트가 PUT 요청, POST 요청으로 리소스를 생성했을 때, 해당하는 리소스를 나중에 삭제할 수 있다고 합니다. … 아니 DELETE 요청으로 당연히 리소스 삭제하는 거 당연한 거 아닌가? 싶은데
- PUT 요청으로 생성된 리소스의 경우
- PUT 요청의 경우 클라이언트가 특정한 URI에 리소스를 새로 생성하거나 업데이트할 때 사용됩니다. 이 때 클라이언트는 해당 URI의 리소스에 대한 직접적인 접근 권한을 가지고 이 권한을 통해 후에 DELETE 요청으로 해당 리소스를 삭제할 수 있습니다.
- 예를 들어 제가 ‘PUT /article/1234’ 요청으로 문서를 생성했다고 가정해봅시다. 그럼 당연히 ‘DELETE /article/1234’ 요청을 통해 그 문서를 삭제할 수도 있습니다. 이는 클라이언트가 직접적으로 생성한 리소스에 대한 삭제 권한을 가질 수 있음을 나타냅니다.
- POST 요청으로 리소스가 생성되어(201 Created) Location 헤더 필드로 식별된 경우
- POST 요청의 경우 클라이언트가 서버에 데이터를 보내 새로운 리소스를 생성할 때 사용되는 메서드입니다. 이전 포스팅에서 다뤘듯 서버는 생성된 리소스에 대한 URI를 Location 헤더에 포함해 클라이언트에게 응답하게 됩니다. 그럼 클라이언트는 이 URI를 통해 생성된 리소스를 식별할 수 있고 DELETE 요청을 통해 삭제할 수 있습니다.
- 예를 들어 제가 ‘POST /article’ 요청을 통해 새로운 리소스를 생성한다고 가정해보겠습니다. 서버는 새로운 리소스를 생성한 ‘Location: /article/321’ 헤더를 포함해 응답을 보내고 저는 해당 URI를 식별해 ‘DELETE /article/321’ 요청을 통해 리소스를 삭제할 수 있습니다. 그렇다면 이 또한 서버가 생성한 리소스에 대해 클라이언트가 명시적으로 삭제 요청이 가능함을 나타냅니다.
문서에서 해당 내용을 언급한 내용은 2번에서 정리한 내용과 비슷한데 클라이언트가 명확히 알고 있는 리소스에 대해 DELETE 메서드를 사용할 수 있다는 상황을 강조하기 위함으로 보입니다. 즉 DELETE 메서드가 제한적으로 사용되고, 주로 클라이언트가 직접 생성한 리소스에 대해 사용된다는 점을 알려주고자 나온 내용 같습니다..
4. 사용자 에이전트?
Similarly, custom user agent implementations that implement an authoring function, such as revision control clients using HTTP for remote operations, might use DELETE based on an assumption that the server's URI space has been crafted to correspond to a version repository.
사용자가 구현한 커스텀 사용자 에이전트도 DELETE 메서드를 사용할 수 있다구요?
- Custom User Agent:
사용자 에이전트는 전에 작성했던 포스트에 나와있는데 사용자를 대신해 일을 수행하는 소프트웨어로 생각하면 됩니다.
웹에선 흔히 브라우저를 의미하고 있습니다.
문서에서 말하는 Custom User Agent는 사용자가 특정 기능을 구현하기 위해 직접 만든 소프트웨어로 문서의 맥락상 데이터를 생성하고 수정하거나 삭제하는 기능을 포함한 에이전트를 말하고 있는 것 같습니다..
예를 들어보자면 버전 관리 시스템으로 Git이 있습니다.
버전 관리 시스템은 원격 저장소에 데이터를 작성하고 수정하거나 삭제하는 기능을 가지고 있습니다. Git도 ‘commit’, **‘push’**로 데이터 추가하고 다 쓴 브랜치는 **‘branch -d name’**으로 삭제하는 등 명령을 통해 데이터를 관리하지 않습니까?
그럼 여기서 버전 관리 시스템 서버의 URI 공간이 저장소 구조와 일치하도록 구성되어 있다고 가정해봅시다. 쉽게 특정 파일의 버전이 ‘/repo/project/file/version’ 과 같은 URI로 식별되겠죠??
그럼 클라이언트가 이 구조를 이해하고 있으니 특정 URI에 DELETE 요청을 보내면 해당 버전의 파일이 삭제될 것이라고 예상할 수 있게 됩니다.
5. 그래서 요약은..
- DELETE 메서드는 상대적으로 적은 리소스에서 허용된다. 또한 사용자는 DELETE 요청의 효과를 이해하고 사용한다.
- PUT 및 POST 요청으로 생성된 리소스는 DELETE 요청을 통해 삭제할 수 있다.
- 커스텀 사용자 에이전트도 DELETE 메서드를 사용할 수 있다.
DELETE 요청에 대한 상태 코드
서버는 DELETE 요청이 성공적으로 처리되면 알맞은 상태코드로 응답해야된다고 합니다.
- 202 Accepted
- 작업이 성공할 가능성이 높지만 아직 완료되지 않았음을 의미합니다.
- 서버가 리소스를 백그라운드에서 삭제하고 있는 경우 작업이 성공할 가능성은 높은데 아직 완료되지 않은 상태 아닙니까? 이럴 때 클라이언트에게 이를 알리기 위해 202 상태 코드를 보냅니다.
- 204 No Content
- 작업이 끝났으며 추가적인 정보가 제공되지 않음을 의미합니다.
- 200 OK
- 작업이 완료되었고 204 코드와 반대로 추가적인 정보를 포함한 응답 메세지가 있을 때 사용됩니다.
- 예를 들자면 200 코드의 경우 해당 리소스가 삭제되었으며 클라이언트에게 삭제 상태를 설명하는 정보를 제공한다면 200 코드를 보냅니다.
DELETE 요청의 콘텐츠 처리
다음 문단에선 DELETE 요청에 포함된 body에 대한 처리에 대한 내용을 다루고 있습니다. 정리해보자면..
Although request message framing is independent of the method used, content received in a DELETE request has no generally defined semantics, cannot alter the meaning or target of the request, and might lead some implementations to reject the request and close the connection because of its potential as a request smuggling attack (Section 11.2 of [HTTP/1.1]).
그런데,,,,!! 이거 이거 이전 포스팅의 HEAD를 정리할 때 나온 부분이군요!! 해당 내용은 HEAD에 대한 정리 중 ‘보안적인 고려 사항?’에서 다루고 있습니다. 다시 정리해보자면
- 요청 메세지 프레이밍은 사용된 메서드와 독립적입니다.
- 우리가 어떤 메서드를 사용하던 HTTP 요청 메세지의 구조는 항상 동일한 구조를 가집니다. GET을 써도 요청 메세지의 구조는 요청 라인, 헤더, 본문을 포함하고, DELETE를 써도 동일한 구조를 사용됩니다. 하지만 일부 메서드에서는 사용되지 않을 수 있습니다. 이전에서 정리했듯 HEAD 요청에선 body가 필요 없습니다.
- DELETE 요청에서 보내는 내용은 정의된 의미론이 없고, 요청의 의미, 대상을 변경할 수도 없습니다.
- DELETE는 본문을 포함하지 않습니다. 여기에 본문을 포함한다고 해서 이게 특별한 의미를 가지지 않습니다. 또한 이로 인해 DELETE 요청의 의미나 목적이 변경되지도 않습니다. 당연히 DELETE 요청으로 리소스 지우는데 본문 포함한다고 해서 ‘지우는척 추가해줘’라고 의미를 바꿀 수 없으니까요.
- 때문에 본문이 포함된다면 스머글링 공격의 우려로 요청 거부 및 연결이 종료될 수 있다.
A client SHOULD NOT generate content in a DELETE request unless it is made directly to an origin server that has previously indicated, in or out of band, that such a request has a purpose and will be adequately supported. An origin server SHOULD NOT rely on private agreements to receive content, since participants in HTTP communication are often unaware of intermediaries along the request chain.
문서에선 DELETE 요청에 본문을 포함하는 행위를 아주 강력하게 권장하지 않고 있습니다.
하지만 아주 예외적으로 매우 제한적인 조건을 통해 본문을 포함할 수 있는듯 합니다.
- 서버가 명시적으로 본문을 포함하는 행위에 어떠한 목적이 있고 적절하게 지원될 것이라고 표시한 경우.
- 중간 기기(프록시 서버 등)가 없는 상태에서 오리진 서버와 직접적으로 연결된 경우. 즉 서버와 클라이언트 사이에 어떠한 기기가 없고 직접 다이렉트로 통신하는 경우.
필요하다면 위의 예외적 상황으로 DELETE 요청에 본문을 포함할 수는 있지만 문서에선 포함하지 않는 것을 강력히 권장하고 있습니다.
중간 기기인 프록시 서버, 캐시 서버는 DELETE 요청에 본문이 포함되는 것을 예상하지 못할 수 있습니다. 그 이유로 예상치 못한 동작, 혹은 요청을 거부할 수 있기 때문에 클라이언트와 서버 간의 사적 협약에 의해 본문을 포함하는 등의 행동은 하지 않는 것이 좋겠습니다..
캐싱
Responses to the DELETE method are not cacheable. If a successful DELETE request passes through a cache that has one or more stored responses for the target URI, those stored responses will be invalidated (see Section 4.4 of [CACHING]).
문서에 따르면 삭제된 리소스에 대한 정보가 유효하지 않기 때문에 DELETE 요청에 대한 응답은 캐싱되지 않는다고 합니다.
당연히 클라이언트가 DELETE 요청을 보낸다는 것은 해당 리소스를 삭제하고자 하는 의미를 가지고 있고, 이 요청에 대한 응답은 삭제된 리소스와 관련된 정보를 포함하니 캐싱하는 것이 의미가 없습니다.
다음으로 DELETE 요청이 성공한다면 캐시는 무효화된다고 합니다.
예를 들어 제가 ‘/files/777’ 리소스를 삭제하고 싶어 DELETE 요청을 보낸다고 가정해봅시다. 그럼 이 DELETE 요청은 캐시 서버를 거쳐 오리진 서버에 도착합니다.
그럼 서버는 ‘/files/777’ 리소스를 삭제하고 성공했다는 응답을 반환하게 됩니다. 이후에 캐시 서버는 DELETE 요청이 성공적으로 처리되었음을 인식하고 해당 URI에 해당하는 모든 캐시되어있는 응답을 무효화합니다.
이후에 **‘/files/777’**에 새로운 요청이 들어오면 캐시 서버는 더 이상 데이터를 제공하지 않고 오리진 서버로부터 응답을 받아오게 됩니다.
마치며..
아니 이번 포스팅에서 제발 그만 끝내고 싶었는데 진짜 밑도 끝도 없이 길어져서 또 끊어야 될 거 같습니다… 제가 저번에 말했듯 저도 제가 정리한 내용을 보고 다시 공부하곤 하는데 이거 너무 길면 보기가 싫어져서..
이정도에서 끊고! 나머지 메서드는 다음 포스팅에서 정리해보려 합니다…
.
.
.
뿅..