PUT
고친 사람 github-actions[bot]
PUT 은 주소 하나에 내용을 올려 두라고 서버에 요청하는 방법입니다. 그 주소에 이미 무언가 있으면 보낸 내용으로 바꿉니다. 없으면 보낸 내용으로 새로 만듭니다. 같은 요청을 여러 번 보내도 한 번 보낸 것과 결과가 같습니다.
쉽고 빠른 이해
PUT 은 "이 주소의 내용을 지금 보내는 것으로 맞춰 달라"는 요청입니다. PUT /users/42 에 사용자 정보를 실어 보내면 42번 사용자가 그 내용이 됩니다.
같은 요청을 몇 번 보내도 결과가 같습니다. 그래서 응답을 못 받고 연결이 끊겨도 안심하고 다시 보낼 수 있습니다.
돌아가는 방식은 이렇습니다.
- 클라이언트가 주소와 새 내용 전체를 보냅니다
- 서버는 그 주소에 없던 것이면 새로 만듭니다. 있던 것이면 바꿉니다
- 만들었는지 바꿨는지를 응답 코드로 알려 줍니다
대가도 있습니다. 한 칸만 고치고 싶어도 내용 전체를 보내야 합니다. 빠뜨린 칸은 지워질 수 있습니다. 두 사람이 같은 주소를 동시에 고치면 나중에 보낸 쪽이 앞의 수정을 덮어씁니다.
일부만 고치고 싶을 때는 PUT 대신 PATCH 라는 다른 메서드를 씁니다.
상세
PUT 은 HTTP(HyperText Transfer Protocol, 하이퍼텍스트 전송 프로토콜)의 요청 메서드 가운데 하나입니다. 요청 메서드는 요청 첫 줄 맨 앞에 적는 낱말로, 이 요청이 무엇을 하려는지 서버에 알립니다. PUT 은 요청에 실어 보낸 내용으로 대상의 상태를 만들거나 바꿔 달라는 뜻입니다.
대상은 주소가 가리키는 리소스입니다. 리소스는 /users/42 같은 URI(Uniform Resource Identifier, 통합 자원 식별자) 하나로 이름 붙인 데이터입니다. 사용자 한 명, 파일 하나, 설정 묶음 하나가 각각 리소스가 될 수 있습니다.
요청의 모양
PUT 요청은 첫 줄에 메서드와 주소를 적습니다. 새 내용은 본문에 싣습니다. 본문이 어떤 형식인지는 헤더로 알립니다. 아래는 42번 사용자를 새 내용으로 맞추는 요청입니다.
PUT /users/42 HTTP/1.1
Host: api.example.com
Content-Type: application/json
{"name": "A", "mail": "a@x"}
Content-Type 은 본문이 어떤 형식인지 알리는 헤더입니다. 위 요청에서는 본문이 JSON(JavaScript Object Notation) 형식이라고 알립니다. 서버는 이 헤더를 보고 본문을 어떻게 읽을지 정합니다.
본문에 실린 것은 리소스의 한 순간 모습입니다. 이것을 표현이라고 부릅니다. 리소스는 서버 안에 있습니다. 클라이언트와 서버가 주고받는 것은 늘 그 표현입니다. PUT 은 "이 표현이 앞으로 이 주소의 모습이다"라고 서버에 건네는 셈입니다.
내용 전체를 바꾼다
PUT 은 보낸 표현으로 리소스를 바꿉니다. 일부만 섞어 넣는 것이 아닙니다. 그래서 보내지 않은 칸은 서버가 비워 버릴 수 있습니다.
아래는 같은 주소에 PUT 을 두 번 보내고 매번 GET 으로 읽어 본 것입니다. 오른쪽 주석이 GET 이 돌려준 값입니다.
PUT /users/42 {"name":"A","mail":"a@x"}
GET /users/42 // {"name":"A","mail":"a@x"}
PUT /users/42 {"name":"B"}
GET /users/42 // {"name":"B"}
두 번째 PUT 은 이름만 바꾸려던 것이었습니다. 그런데 mail 을 빼고 보냈더니 읽어 온 값에서도 mail 이 사라졌습니다. 이름 하나만 고치려면 나머지 칸도 전부 같이 보내야 합니다.
일부만 고치는 요청은 PATCH 가 따로 맡습니다. PATCH 는 "무엇을 어떻게 바꿔라"라는 변경 내역을 보냅니다. PUT 은 "결과가 이것이어야 한다"라는 완성본을 보냅니다.
만들기와 바꾸기
서버는 그 주소에 리소스가 이미 있었는지를 보고 응답을 가릅니다. 응답의 첫 줄에 붙는 세 자리 숫자가 상태 코드입니다. 서버는 이 숫자로 무슨 일이 있었는지 알립니다.
flowchart TD
A["PUT /users/42 도착"] --> B{"그 주소에 리소스가 있었나"}
B -- 없었다 --> C["새로 만든다"]
C --> D["201 Created"]
B -- 있었다 --> E["보낸 내용으로 바꾼다"]
E --> F{"응답에 본문을 싣나"}
F -- 싣는다 --> G["200 OK"]
F -- 안 싣는다 --> H["204 No Content"]
없던 것을 만들었으면 201 Created 로 답합니다. 있던 것을 바꿨으면 200 OK 나 204 No Content 로 답합니다. 둘은 응답 본문이 있느냐로 갈립니다.
모든 서버가 PUT 으로 새로 만들기를 허락하지는 않습니다. API(Application Programming Interface)는 프로그램끼리 요청을 주고받는 창구입니다. 웹 API 가운데 많은 곳은 이미 있는 리소스를 바꾸는 데만 PUT 을 받습니다. 없는 주소에 보내면 404 Not Found 를 돌려줍니다.
여러 번 보내도 결과가 같다
PUT 에는 멱등성이 있습니다. 멱등성은 같은 요청을 한 번 보내든 열 번 보내든 서버에 남는 결과가 같다는 성질입니다. {"name":"B"} 로 맞추는 요청을 열 번 보내도 42번 사용자는 여전히 {"name":"B"} 입니다.
이 성질이 쓸모 있는 때는 네트워크가 끊겼을 때입니다. 요청을 보냈는데 응답이 안 왔으면 클라이언트는 서버가 요청을 처리했는지 알 수 없습니다. PUT 이면 그냥 다시 보내면 됩니다. 이미 처리됐어도 같은 결과로 한 번 더 맞출 뿐입니다.
sequenceDiagram
participant 클라이언트
participant 서버
클라이언트->>서버: PUT /users/42 {"name":"B"}
Note over 서버: 처리했지만 응답이 도중에 끊긴다
클라이언트->>서버: 같은 PUT 을 다시 보낸다
Note over 서버: 이미 B 라서 결과가 그대로다
서버-->>클라이언트: 200 OK
멱등성은 서버에 남는 결과에 대한 약속입니다. 응답까지 같다는 뜻은 아닙니다. 첫 요청이 201 Created 를 받고 다시 보낸 요청이 200 OK 를 받아도 멱등성은 지켜진 것입니다.
POST 와 가르는 기준
POST 도 서버에 데이터를 보내 무언가를 만듭니다. 둘을 가르는 첫 기준은 주소를 누가 정하나입니다. PUT 은 클라이언트가 주소를 정해 보냅니다. POST 는 /users 같은 모음 주소에 보냅니다. 새 리소스의 주소는 서버가 정합니다.
두 번째 기준은 멱등성입니다. POST 로 "사용자를 하나 만들어라"를 두 번 보내면 사용자가 둘 생길 수 있습니다. 그래서 응답이 끊겼을 때 POST 는 함부로 다시 보낼 수 없습니다.
| PUT | POST | PATCH | |
|---|---|---|---|
| 보내는 주소 | 대상 리소스의 주소 | 모음 주소 | 대상 리소스의 주소 |
| 새 주소를 정하는 쪽 | 클라이언트 | 서버 | 새로 만들지 않는 것이 보통 |
| 본문 | 완성된 전체 내용 | 서버가 처리할 데이터 | 바꿀 부분만 |
| 멱등성 | 있다 | 없다 | 보장하지 않는다 |
표에서 보듯 PUT 은 "주소도 알고 완성본도 가진" 클라이언트의 메서드입니다. 파일을 정해진 경로에 올리는 일이나 설정값 하나를 통째로 덮는 일이 이런 경우입니다.
캐시에 미치는 영향
PUT 은 서버의 상태를 바꾸므로 안전하지 않은 메서드입니다. 안전한 메서드는 GET 처럼 읽기만 하는 메서드를 말합니다. 이 성질이 캐시와 얽힙니다. 캐시는 클라이언트와 서버 사이에서 응답을 저장해 두는 것입니다. 같은 주소를 다시 읽으면 서버 대신 저장해 둔 응답을 돌려줍니다. 브라우저가 한 번 받은 응답을 저장해 두는 것이 한 예입니다.
상태를 바꾸는 요청이 지나가면 그 주소에 대해 캐시가 저장해 둔 응답은 낡은 것이 됩니다. 그래서 캐시는 자기를 거쳐 간 PUT 요청이 성공하면 그 주소로 저장해 둔 응답을 버립니다. 이 일을 무효화라고 부릅니다. 무효화가 없으면 방금 바꾼 사용자 정보를 읽어도 캐시가 예전 값을 돌려줍니다.
PUT 에 대한 응답 자체는 캐시에 저장하지 않습니다. 이 응답은 "바꿨다"는 알림일 뿐 그 주소를 읽은 결과가 아니기 때문입니다. 바뀐 내용이 필요하면 GET 으로 다시 읽습니다.
동시에 고칠 때의 덮어쓰기
두 클라이언트가 같은 리소스를 읽고 각자 고친 뒤 PUT 을 보내면 문제가 생깁니다. 나중에 도착한 PUT 이 먼저 도착한 수정을 지웁니다. 먼저 보낸 쪽은 자기 수정이 사라진 줄 모릅니다. 이 문제를 갱신 손실이라고 부릅니다.
sequenceDiagram
participant 갑
participant 서버
participant 을
갑->>서버: GET · 이름 A, 메일 a@x
을->>서버: GET · 이름 A, 메일 a@x
갑->>서버: PUT · 이름 B, 메일 a@x
을->>서버: PUT · 이름 A, 메일 c@x
Note over 서버: 갑이 바꾼 이름 B 가 사라진다
막는 방법은 "내가 읽은 판이 아직 그대로일 때만 바꿔라"라는 조건을 붙이는 것입니다. 여기서 판은 리소스 내용이 바뀔 때마다 새로 생기는 한 벌을 말합니다.
그러려면 판을 알아볼 표시가 있어야 합니다. 서버는 리소스의 판마다 엔티티 태그(ETag)라는 꼬리표 문자열을 붙여 GET 응답에 실어 줍니다. 내용이 바뀌면 이 꼬리표도 바뀝니다.
클라이언트는 PUT 을 보낼 때 읽어 둔 꼬리표를 If-Match 헤더에 담습니다. 이 헤더는 "서버의 꼬리표가 이것과 같을 때만 수행하라"는 조건입니다. 이렇게 조건을 붙인 요청을 조건부 요청이라고 합니다.
서버의 꼬리표가 그새 바뀌었으면 서버는 PUT 을 수행하지 않습니다. 대신 412 Precondition Failed 로 답합니다. 조건이 맞지 않았다는 뜻입니다.
위 그림의 상황에 이 조건을 붙였다고 해 봅시다. 갑의 PUT 이 먼저 반영되면서 꼬리표가 바뀝니다. 을이 들고 있던 꼬리표는 낡은 것이 되므로 을의 PUT 은 412 로 거절됩니다. 을은 다시 읽어서 갑의 수정 위에 고칩니다.
실패 응답
PUT 이 거절될 때 자주 보는 상태 코드는 아래와 같습니다. 각 코드는 "무엇이 맞지 않았나"를 가리킵니다.
| 상태 코드 | 무엇이 맞지 않았나 |
|---|---|
| 400 Bad Request | 본문의 모양이 틀렸다. 빠진 필수 칸이나 깨진 JSON |
| 404 Not Found | 없는 주소인데 서버가 PUT 으로 새로 만들기를 허락하지 않는다 |
| 405 Method Not Allowed | 이 주소가 PUT 자체를 받지 않는다. 받는 메서드 목록이 Allow 헤더로 온다 |
| 409 Conflict | 보낸 내용이 리소스의 지금 상태와 맞지 않는다. 예: 서버에 있는 판보다 오래된 판을 고쳐 보냈다 |
| 412 Precondition Failed | If-Match 로 붙인 조건이 맞지 않았다 |
| 415 Unsupported Media Type | Content-Type 에 적은 본문 형식을 서버가 다루지 못한다 |
405 는 메서드가 틀린 것입니다. 404 는 주소가 틀린 것입니다.
400 과 415 는 본문이 문제입니다. 400 은 형식은 알아듣는데 내용이 틀렸을 때 옵니다. 415 는 형식부터 모를 때 옵니다.
409 와 412 는 둘 다 리소스의 지금 상태와 어긋났다는 뜻입니다. 412 는 클라이언트가 If-Match 로 붙인 조건이 틀린 것입니다. 409 는 그런 조건이 없어도 서버가 보낸 내용을 직접 보고 지금 상태와 맞지 않는다고 판단한 것입니다.
파일을 올리는 PUT
PUT 은 API 말고 파일 저장소에서도 널리 씁니다. 오브젝트 스토리지는 파일 하나를 올릴 때 그 파일이 놓일 경로에 PUT 을 보냅니다. 본문이 파일 내용입니다. 경로가 곧 파일 이름입니다.
이때 파일에 딸린 메타데이터는 요청 헤더에 담아 함께 보냅니다. 메타데이터는 파일 내용이 아니라 파일에 대한 정보입니다. 형식, 만든 사람, 사용자가 직접 정한 이름과 값 같은 것이 들어갑니다. 앞에서 본 ETag 와는 다른 것입니다. 같은 경로에 다시 PUT 을 보내면 파일과 메타데이터가 함께 새것으로 바뀝니다.
관련 항목
PUT 과 나란히 쓰는 요청 메서드
GET · POST · PATCH · DELETE · HEAD · OPTIONS
PUT 이 속하는 상위 분류
HTTP · 요청 메서드 · REST · RFC 9110 · API 설계
PUT 이 지키거나 어기는 성질
멱등성 · 안전한 메서드 · 안전하지 않은 메서드 · 캐시 가능성
PUT 요청을 이루는 구성 요소
리소스 · URI · 표현 · 요청 본문 · Content-Type · 미디어 타입 · 메타데이터
PUT 뒤에 캐시가 거치는 처리 단계
캐시 · 무효화 · HTTP 캐싱 · Cache-Control
PUT 의 덮어쓰기를 막는 수단
조건부 요청 · 엔티티 태그 · If-Match · 낙관적 잠금 · 갱신 손실
PUT 에서 자주 나는 오류
상태 코드 · HTTP 400 · HTTP 404 · HTTP 405 · HTTP 409 · HTTP 412 · HTTP 415
PUT 으로 파일을 올리는 저장소
오브젝트 스토리지 · Amazon S3 · 멀티파트 업로드 · WebDAV
다른 이름: PUT 메서드 · PUT 요청