사전 PUT
인터페이스

PUT

gabury1고친 사람 github-actions[bot]

PUT 은 주소 하나에 내용을 올려 두라고 서버에 요청하는 방법입니다. 그 주소에 이미 무언가 있으면 보낸 내용으로 바꿉니다. 없으면 보낸 내용으로 새로 만듭니다. 같은 요청을 여러 번 보내도 한 번 보낸 것과 결과가 같습니다.

쉽고 빠른 이해

PUT 은 "이 주소의 내용을 지금 보내는 것으로 맞춰 달라"는 요청입니다. PUT /users/42 에 사용자 정보를 실어 보내면 42번 사용자가 그 내용이 됩니다.

같은 요청을 몇 번 보내도 결과가 같습니다. 그래서 응답을 못 받고 연결이 끊겨도 안심하고 다시 보낼 수 있습니다.

돌아가는 방식은 이렇습니다.

  1. 클라이언트가 주소와 새 내용 전체를 보냅니다
  2. 서버는 그 주소에 없던 것이면 새로 만듭니다. 있던 것이면 바꿉니다
  3. 만들었는지 바꿨는지를 응답 코드로 알려 줍니다

대가도 있습니다. 한 칸만 고치고 싶어도 내용 전체를 보내야 합니다. 빠뜨린 칸은 지워질 수 있습니다. 두 사람이 같은 주소를 동시에 고치면 나중에 보낸 쪽이 앞의 수정을 덮어씁니다.

일부만 고치고 싶을 때는 PUT 대신 PATCH 라는 다른 메서드를 씁니다.

상세

PUT 은 HTTP(HyperText Transfer Protocol, 하이퍼텍스트 전송 프로토콜)의 요청 메서드 가운데 하나입니다. 요청 메서드는 요청 첫 줄 맨 앞에 적는 낱말로, 이 요청이 무엇을 하려는지 서버에 알립니다. PUT 은 요청에 실어 보낸 내용으로 대상의 상태를 만들거나 바꿔 달라는 뜻입니다.

대상은 주소가 가리키는 리소스입니다. 리소스는 /users/42 같은 URI(Uniform Resource Identifier, 통합 자원 식별자) 하나로 이름 붙인 데이터입니다. 사용자 한 명, 파일 하나, 설정 묶음 하나가 각각 리소스가 될 수 있습니다.

요청의 모양

PUT 요청은 첫 줄에 메서드와 주소를 적습니다. 새 내용은 본문에 싣습니다. 본문이 어떤 형식인지는 헤더로 알립니다. 아래는 42번 사용자를 새 내용으로 맞추는 요청입니다.

http
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 요청