앞에서는 HTTP 메서드가 각각 어떤 역할을 하는지 공부했다.
GET
→ 조회
POST
→ 요청 데이터 처리
PUT
→ 리소스 대체
PATCH
→ 리소스 부분 변경
DELETE
→ 리소스 삭제
그런데 HTTP 메서드의 역할을 알고 나니 다음 질문이 생겼다.
그래서 클라이언트가 서버에 실제 데이터를 보내려면 어디에 넣어야 할까?
회원 목록을 조회하면서 검색 조건을 전달할 수도 있고, 회원가입을 하면서 이름과 나이를 전달할 수도 있다.
파일을 업로드해야 할 수도 있다.
이번에는 클라이언트에서 서버로 데이터를 전달하는 방법부터 시작해서, 실제 HTTP API를 어떻게 설계하는지까지 이어서 정리해봤다.
먼저 데이터 전달 방식은 크게 두 가지로 나눌 수 있다.
1. Query Parameter
2. Message Body
Query Parameter는 주로 GET에서 사용한다.
GET
→ 조회
→ 검색, 필터, 정렬 조건 전달
반면 Message Body는 주로 다음 메서드에서 사용한다.
POST
PUT
PATCH
회원가입, 상품 주문, 리소스 등록이나 변경처럼 서버에 데이터를 전달해야 하는 경우다.
처음에는 그냥
"데이터를 보내려면 Body에 넣으면 되는 것 아닌가?"
라고 생각했다.
그런데 HTTP에서는 요청의 목적에 따라 데이터를 전달하는 위치도 달라졌다.
수업에서는 클라이언트에서 서버로 데이터를 전송하는 상황을 크게 네 가지로 나눴다.
클라이언트 → 서버
├── 정적 데이터 조회
├── 동적 데이터 조회
├── HTML Form 데이터 전송
└── HTTP API 데이터 전송
하나씩 살펴보자.
가장 단순한 경우다.
이미지나 정적 텍스트 문서를 조회한다고 해보자.
GET /static/star.jpg HTTP/1.1
Host: localhost:8080
여기서는 별도의 Query Parameter가 없다.
/static/star.jpg라는 URI 자체만으로 어떤 리소스를 원하는지 알 수 있기 때문이다.
Client
│
│ GET /static/star.jpg
▼
Server
│
│ star.jpg
▼
Client
따라서 이미지나 정적 텍스트 문서는 일반적으로 리소스 경로만으로 단순하게 조회할 수 있다.
그런데 조회를 하면서 서버에게 추가적인 조건까지 전달하고 싶다면 어떻게 해야 할까?
예를 들어 Google에서 hello라는 단어를 검색한다고 해보자.
수업 자료에서는 다음 URL을 예시로 들었다.
https://www.google.com/search?q=hello&hl=ko
HTTP 요청으로 보면 다음과 같다.
GET /search?q=hello&hl=ko HTTP/1.1
Host: www.google.com
여기서
?q=hello&hl=ko
부분이 Query Parameter다.
URL을 조금 나눠보면 이해하기 쉽다.
/search?q=hello&hl=ko
│ │
│ └─ Query Parameter
│
└─ Resource Path
? 뒤부터 Query Parameter가 시작된다.
그리고 여러 값을 전달할 때는 &를 이용해서 연결한다.
q=hello
&
hl=ko
즉
key=value
형태로 서버에게 추가적인 정보를 전달한다.
예를 들어 회원을 검색한다면
GET /members?username=kim&age=20
처럼 만들 수 있다.
username=kim
→ 이름이 kim
age=20
→ 나이가 20
서버는 이 조건을 이용해서 조회 결과를 동적으로 만들어낸다.
따라서 Query Parameter는 주로
검색
필터링
정렬
같은 조회 조건을 전달할 때 사용한다.
강의 자료에서도 동적 데이터 조회는 GET과 Query Parameter를 사용하고, 이를 기반으로 결과를 필터링하거나 정렬한다고 설명한다. 5.http-method-use.pdf
이번에는 회원가입 화면을 생각해보자.
사용자가 다음 정보를 입력한다.
username = kim
age = 20
HTML에서는 <form>을 이용해서 데이터를 서버로 전송할 수 있다.
<form action="/save" method="post">
<input type="text" name="username">
<input type="text" name="age">
<button type="submit">전송</button>
</form>
action은 데이터를 어디로 보낼지를 의미하고,
action="/save"
method는 어떤 HTTP 메서드로 보낼지를 의미한다.
method="post"
Form을 제출하면 브라우저가 HTTP 메시지를 만들어준다.
POST /save HTTP/1.1
Host: localhost:8080
Content-Type: application/x-www-form-urlencoded
username=kim&age=20
여기서 처음 눈에 들어온 것은 이 부분이었다.
username=kim&age=20
"이거 아까 Query Parameter랑 모양이 거의 똑같은데?"
맞다.
application/x-www-form-urlencoded에서는 Form 데이터를
key=value&key=value
형태로 인코딩해서 Message Body에 넣는다.
수업 자료에서도 HTML Form의 POST 요청은 application/x-www-form-urlencoded를 사용하고, Form의 내용을 Message Body에 key=value 형식으로 전달한다고 설명한다. 5.http-method-use.pdf
차이는 어디에 들어가느냐다.
GET + Query Parameter
/members?username=kim&age=20
↑
URL
반면
POST + HTML Form
POST /save
username=kim&age=20
↑
Message Body
이다.
HTML Form에서는 method를 GET으로 지정할 수도 있다.
<form action="/members" method="get">
그러면 브라우저는 Form 데이터를 Message Body가 아니라 URL의 Query Parameter로 넣는다.
GET /members?username=kim&age=20 HTTP/1.1
즉
HTML Form + POST
→ Form 데이터를 Message Body에 전달
HTML Form + GET
→ Form 데이터를 Query Parameter로 전달
이라고 이해할 수 있다.
기술적으로 다음과 같이 작성하는 것 자체는 가능하다.
<form action="/save" method="get">
그러면
GET /save?username=kim&age=20
이라는 요청이 만들어진다.
그런데 여기서 중요한 문제가 생긴다.
GET은 조회에 사용해야 한다.
회원 저장처럼 서버의 리소스를 변경하는 작업을 GET으로 처리해서는 안 된다.
GET
→ 조회
POST
→ 데이터 처리 / 저장 등에 사용
따라서
GET /save?username=kim&age=20
처럼 GET으로 리소스를 변경하는 API를 설계하면 안 된다.
강의 자료에서도 이 부분을 명확하게 강조하고 있다.
GET은 조회에만 사용하고, 리소스 변경이 발생하는 곳에 사용하면 안 된다. 5.http-method-use.pdf
처음에는 단순히
"GET으로도 데이터가 서버까지 가는데 왜 쓰면 안 되지?"
라는 생각이 들 수 있다.
그런데 중요한 것은 데이터를 전달할 수 있느냐가 아니라 HTTP 메서드가 가진 의미를 지키느냐였다.
그런데 Form으로 문자열만 보내는 것은 아니다.
회원 프로필 사진처럼 파일을 함께 보내야 할 수도 있다.
username = kim
age = 20
file = intro.png
이때 사용할 수 있는 것이
multipart/form-data
다.
HTML에서는 다음처럼 작성할 수 있다.
<form action="/save"
method="post"
enctype="multipart/form-data">
<input type="text" name="username">
<input type="text" name="age">
<input type="file" name="file1">
<button type="submit">전송</button>
</form>
HTTP 메시지는 대략 다음과 같은 형태가 된다.
POST /save HTTP/1.1
Content-Type: multipart/form-data; boundary=-----XXX
여기서 boundary가 등장한다.
처음에는
multipart/form-data
라는 이름 자체가 조금 이해가 안 됐다.
HTTP 메시지 안에
username
age
image/png
처럼 서로 다른 데이터를 함께 넣어야 한다고 생각해보자.
이들을 하나의 덩어리로 붙여버리면 어디서 데이터가 끝나고 다음 데이터가 시작되는지 구분하기 어렵다.
그래서 boundary를 이용해서 각각의 Part를 나눈다.
------XXX
username
kim
------XXX
age
20
------XXX
file1
intro.png
Content-Type: image/png
(binary data)
------XXX--
즉 하나의 Message Body 안에 여러 Part가 들어간다.
그래서 이름도
Multi + Part
이다.
특히 파일 업로드처럼 바이너리 데이터를 Form 데이터와 함께 전송할 때 사용할 수 있다. 강의 자료의 11페이지 예시도 username, age, intro.png를 각각 boundary로 구분해 하나의 요청으로 보내는 구조를 보여준다. 5.http-method-use.pdf
HTML Form에서는 기본적으로 GET과 POST를 지원한다.
POST
Content-Type:
application/x-www-form-urlencoded
→ 일반적인 Form 데이터
파일을 함께 전송해야 한다면
POST
Content-Type:
multipart/form-data
→ 파일 + 여러 Form 데이터
그리고 GET Form이라면
GET
→ Query Parameter
형태로 데이터가 전달된다.
여기서 중요한 제약도 있다.
순수 HTML Form은 GET과 POST만 지원한다.
이 제약은 뒤에서 API를 설계할 때 다시 등장한다.
마지막은 내가 백엔드 개발을 하면서 가장 많이 보게 될 HTTP API 방식이다.
HTTP API는 다양한 환경에서 사용된다.
Server ↔ Server
→ 백엔드 시스템 간 통신
Mobile App ↔ Server
→ iPhone, Android
Web Client ↔ Server
→ JavaScript, AJAX
→ React, Vue 등
HTML Form처럼 브라우저가 알아서 Form 데이터를 만들어 보내는 것이 아니라 클라이언트가 직접 HTTP 요청을 구성한다.
예를 들어 회원을 등록한다고 해보자.
POST /members HTTP/1.1
Content-Type: application/json
{
"username": "young",
"age": 20
}
여기서는
Content-Type: application/json
을 사용했다.
즉 Message Body에 들어 있는 데이터가 JSON이라는 것을 서버에게 알려주는 것이다.
HTTP API에서는 JSON을 주로 사용하며, 강의 자료에서는 POST·PUT·PATCH는 Message Body로 데이터를 보내고 GET은 조회 시 Query Parameter를 사용한다고 정리한다. 5.http-method-use.pdf
처음에는 Query Parameter, Form, JSON이 각각 따로 떨어진 개념처럼 느껴졌다.
그런데 결국 질문은 하나였다.
클라이언트가 서버에 어떤 데이터를 어떤 방식으로 전달할 것인가?
정적 데이터 조회
GET /static/star.jpg
→ URI로 리소스 식별
동적 데이터 조회
GET /members?age=20
→ Query Parameter로 조회 조건 전달
HTML Form
POST /save
Content-Type: application/x-www-form-urlencoded
username=kim&age=20
파일 Form
POST /save
Content-Type: multipart/form-data
→ Form + Binary File
HTTP API
POST /members
Content-Type: application/json
{
"username": "kim",
"age": 20
}
이렇게 놓고 보니 차이가 훨씬 명확해졌다.
데이터를 전달하는 방법을 알아봤으니 이제 조금 더 실제적인 문제로 넘어가보자.
회원 관리 시스템의 API를 어떻게 설계할까?
수업에서는 크게 세 가지 상황을 비교했다.
HTTP API - Collection
→ POST 기반 등록
HTTP API - Store
→ PUT 기반 등록
HTML Form
→ GET / POST 기반
강의 자료에서도 회원 관리 API는 POST 기반 Collection, 파일 관리는 PUT 기반 Store의 예로 구분한다. 5.http-method-use.pdf
회원 관리 시스템을 만든다고 해보자.
필요한 기능은 다음과 같다.
회원 목록 조회
회원 등록
회원 조회
회원 수정
회원 삭제
앞에서 배운 리소스 중심 URI 설계를 적용하면 다음처럼 만들 수 있다.
GET /members
→ 회원 목록 조회
POST /members
→ 회원 등록
GET /members/{id}
→ 회원 조회
PATCH /members/{id}
PUT /members/{id}
POST /members/{id}
→ 회원 수정
DELETE /members/{id}
→ 회원 삭제
여기서 회원 등록을 조금 더 자세히 보자.
POST /members
클라이언트가 회원을 새로 등록한다.
그런데 클라이언트는 아직 새 회원의 ID를 모른다.
100?
101?
102?
이 ID를 누가 결정할까?
서버가 결정한다.
Client
POST /members
↓
Server
새 회원 생성
ID = 100
↓
/members/100
서버는 다음과 같이 응답할 수 있다.
HTTP/1.1 201 Created
Location: /members/100
즉 서버가 새롭게 생성된 리소스의 URI를 만들어준다.
강의 자료에서도 POST 기반 회원 등록은 클라이언트가 등록될 리소스의 URI를 모르고, 서버가 /members/100 같은 URI를 생성하는 방식으로 설명한다. 5.http-method-use.pdf
이런 형태를 Collection이라고 한다.
Collection
→ 서버가 관리하는 리소스 디렉터리
→ 서버가 새로운 리소스 URI를 생성하고 관리
회원 관리에서는
/members
가 Collection이다.
/members
│
├── /members/100
├── /members/101
└── /members/102
여기서 중요한 것은 서버가 URI를 결정한다는 것이다.
이번에는 회원이 아니라 파일 관리 시스템을 생각해보자.
필요한 기능은 다음과 같다.
파일 목록 조회
파일 조회
파일 등록
파일 삭제
파일 대량 등록
API는 다음처럼 설계할 수 있다.
GET /files
→ 파일 목록
GET /files/{filename}
→ 파일 조회
PUT /files/{filename}
→ 파일 등록
DELETE /files/{filename}
→ 파일 삭제
POST /files
→ 파일 대량 등록
여기서 중요한 것은 PUT이다.
PUT /files/star.jpg
POST와 뭔가 다르다.
POST에서는
POST /members
라고 요청했다.
클라이언트는 새로운 회원이
/members/100
이 될지 알지 못했다.
서버가 결정했다.
그런데 PUT은
PUT /files/star.jpg
라고 요청한다.
이미 클라이언트가
/files/star.jpg
라는 정확한 URI를 지정했다.
즉
POST
Client
→ /members
Server
→ /members/100 결정
반면
PUT
Client
→ /files/star.jpg 직접 지정
이다.
이 차이가 생각보다 중요했다.
PUT 기반 등록에서 등장하는 개념이 Store다.
Store
→ 클라이언트가 관리하는 리소스 저장소
→ 클라이언트가 리소스 URI를 알고 직접 관리
파일 관리 시스템에서는
/files
가 Store가 된다.
강의 자료에서도 PUT 기반 파일 등록은 클라이언트가 /files/star.jpg처럼 리소스 URI를 직접 지정하며, 이를 Store라고 설명한다. 5.http-method-use.pdf
그래서 둘을 비교하면 다음과 같다.
Collection
POST 기반
Client
│
│ POST /members
▼
Server
│
│ URI 생성
▼
/members/100
Store
PUT 기반
Client
│
│ PUT /files/star.jpg
▼
Server
Client가 URI를 이미 결정
수업에서는 실무에서 대부분 POST 기반 등록을 많이 사용한다고 설명했다.
여기서 다시 HTML Form으로 돌아온다.
앞에서 중요한 제약을 하나 봤다.
HTML Form
GET
POST
두 Method만 지원
그러면 문제가 생긴다.
HTTP API에서는
PATCH /members/100
DELETE /members/100
처럼 표현할 수 있었다.
그런데 순수 HTML Form에서는 PATCH나 DELETE를 직접 사용할 수 없다.
그럼 회원 수정과 삭제를 어떻게 표현하지?
강의 자료에서는 다음과 같이 설계한다.
회원 목록
GET /members
회원 등록 Form
GET /members/new
회원 등록
POST /members/new
또는
POST /members
회원 조회
GET /members/{id}
회원 수정 Form
GET /members/{id}/edit
회원 수정
POST /members/{id}/edit
또는
POST /members/{id}
회원 삭제
POST /members/{id}/delete
여기서 재미있는 부분은 Form 자체도 GET으로 조회한다는 것이다.
예를 들어 회원을 새로 등록하려면 먼저 회원 등록 화면이 필요하다.
GET /members/new
↓
회원 등록 HTML
사용자가 정보를 입력하고 저장하면
POST /members
↓
회원 생성
이 된다.
회원 수정도 비슷하다.
GET /members/100
↓
회원 상세 화면
여기서 수정 버튼을 누른다.
GET /members/100/edit
↓
회원 수정 Form
정보를 수정한 뒤 저장한다.
POST /members/100/edit
↓
회원 수정
강의 자료의 회원 관리 Form 예시도 이 흐름으로 URI를 구성하고 있다. 5.http-method-use.pdf
여기서 앞에서 배운 내용과 충돌하는 것처럼 보였다.
우리는 분명 URI에는
/members/100
처럼 리소스를 표현하고,
GET
POST
PATCH
DELETE
같은 HTTP Method로 행위를 표현한다고 배웠다.
그런데 갑자기
/new
/edit
/delete
같은 동사가 URI에 들어왔다.
"URI에는 리소스만 넣으라고 했는데 이건 뭐지?"
이때 등장하는 것이 Control URI다.
순수 HTML Form에서는 GET과 POST만 지원한다.
따라서 HTTP Method만으로 모든 행위를 표현하기 어려운 경우가 있다.
이런 제약을 해결하기 위해
/members/new
/members/100/edit
/members/100/delete
처럼 동사가 포함된 URI를 사용할 수 있다.
이것을 Control URI라고 한다.
강의 자료에서도 GET과 POST만 지원하는 HTML Form의 제약 때문에 /new, /edit, /delete 같은 Control URI를 사용할 수 있다고 설명한다. 5.http-method-use.pdf
다만 여기서 중요한 것은
처음부터 모든 API를 Control URI로 만들자는 이야기가 아니다.
가능하면
Resource
+
HTTP Method
로 먼저 표현한다.
그렇게 설계하기 어려운 경우에 Control URI를 사용하는 것이다.
내 식으로 정리하면
일단 리소스와 HTTP Method로 최대한 설계해보고, 정말 표현하기 애매할 때 사용하는 최후의 수단에 가깝다.
정도로 기억하면 될 것 같다.
여기까지 배우고 나니 POST와 PUT을 단순히
POST = 등록
PUT = 수정
이라고만 외우면 부족하다는 생각이 들었다.
등록 방식 자체에서도 차이가 있다.
HTTP API - Collection
POST 기반 등록
→ 서버가 Resource URI 결정
HTTP API - Store
PUT 기반 등록
→ 클라이언트가 Resource URI 결정
그리고 순수 HTML Form은
GET
POST
만 지원하기 때문에 필요하다면 Control URI를 사용한다.
강의 마지막에서도 이 세 가지를 POST 기반 Collection, PUT 기반 Store, GET/POST만 지원하는 HTML Form으로 정리한다. 5.http-method-use.pdf
이번 수업 마지막에는 URI를 설계할 때 참고할 만한 네 가지 개념이 등장했다.
Document
Collection
Store
Controller
처음 보면 이름이 비슷해서 헷갈리는데 결국 누가 무엇을 식별하고 관리하느냐를 보면 조금 쉽게 구분할 수 있었다.
Document는 하나의 개별 리소스다.
예를 들어
/members/100
은 100번 회원 하나를 나타낸다.
/files/star.jpg
는 star.jpg 파일 하나를 나타낸다.
즉
Document
→ 단일 개념
→ 객체 하나
→ DB Row 하나
→ 파일 하나
정도로 생각할 수 있다.
Collection은 서버가 관리하는 리소스 디렉터리다.
/members
를 생각하면 된다.
POST /members
↓
Server
↓
/members/100 생성
핵심은
서버가 새로운 Resource URI를 결정한다.
Store는 클라이언트가 관리하는 리소스 저장소다.
/files
를 생각하면 된다.
PUT /files/star.jpg
처럼 클라이언트가 직접 URI를 알고 지정한다.
핵심은
클라이언트가 Resource URI를 결정한다.
마지막은 Controller다.
Document, Collection, Store만으로 표현하기 어려운 추가적인 프로세스를 실행할 때 사용할 수 있다.
이 경우에는 URI에 동사를 직접 사용할 수 있다.
/members/100/delete
즉 앞에서 봤던 Control URI와 연결된다.
강의의 마지막 페이지도 Document를 단일 리소스, Collection을 서버 관리 디렉터리, Store를 클라이언트 관리 저장소, Controller를 추가 프로세스 실행을 위한 동사 URI로 구분한다. 5.http-method-use.pdf
처음에는 이번 내용이 조금 따로 노는 것처럼 느껴졌다.
Query Parameter
HTML Form
multipart/form-data
JSON
Collection
Store
Control URI
용어가 계속 새로 나왔기 때문이다.
그런데 하나의 흐름으로 다시 정리해보니 결국 두 가지 질문으로 연결됐다.
클라이언트가 서버에 데이터를 어떻게 전달할 것인가?
그리고
서버의 리소스를 어떤 URI와 HTTP Method로 표현할 것인가?
첫 번째 질문에서는 데이터 전달 방법이 나온다.
조회 조건
→ Query Parameter
HTML Form
→ application/x-www-form-urlencoded
파일 업로드
→ multipart/form-data
HTTP API
→ application/json
두 번째 질문에서는 API 설계 방식이 나온다.
Collection
→ POST 기반
→ 서버가 URI 결정
Store
→ PUT 기반
→ 클라이언트가 URI 결정
HTML Form
→ GET / POST 제약
Controller
→ HTTP Method만으로 표현하기 어려운 추가 동작
특히 이번에 가장 기억에 남은 부분은 POST와 PUT의 차이였다.
전에는
POST = 생성
PUT = 수정
정도로만 생각했다.
그런데 API 설계 관점에서 보면
POST /members
→ "회원 하나 만들어줘."
→ 정확한 URI는 서버가 결정
와
PUT /files/star.jpg
→ "바로 이 URI에 저장할 거야."
→ URI를 클라이언트가 결정
라는 차이도 있었다.
그리고 HTML Form을 보면서 앞에서 배운 HTTP Method의 의미가 왜 중요한지도 다시 연결됐다.
GET으로도 Form 데이터를 서버까지 전달할 수 있다.
하지만
전송할 수 있다
≠
그렇게 설계해도 된다
였다.
GET은 조회라는 의미를 가지고 있기 때문에 리소스를 변경하는 용도로 사용해서는 안 된다.
결국 HTTP를 공부하면서 계속 느끼는 것은 단순히
"요청이 서버까지 가는가?"
만 생각해서는 안 된다는 것이다.
이 요청의 의미가 무엇인지
리소스는 무엇인지
어떤 Method가 그 의미에 맞는지
데이터는 어디에 담아야 하는지
까지 함께 생각해야 한다.
처음에는 HTTP 요청이 그냥
URL 하나 정하고
JSON 보내면 되는 것
정도라고 생각했는데, 하나씩 뜯어보니 그 안에도 꽤 많은 설계 기준이 들어 있었다.
여기까지 정리하고 나니 앞에서 배운 URI 설계와 HTTP Method가 이제야 하나로 연결되는 느낌이 들었다.
앞에서는 URI를 설계할 때 가장 중요한 것은 리소스를 식별하는 것이라고 배웠다.
예를 들어 회원 관리 시스템에서 리소스는
회원 조회
회원 등록
회원 수정
회원 삭제
가 아니다.
이것들은 모두 행위다.
실제 리소스는
회원(Member)
이다.
그래서 URI에는 회원이라는 리소스를 표현한다.
/members
/members/100
그리고 그 리소스를 가지고 무엇을 할 것인지는 HTTP Method가 담당한다.
GET /members/100
POST /members
PATCH /members/100
DELETE /members/100
이렇게 보니까 처음 HTTP Method를 배울 때 했던
"URI에
/getMember,/createMember,/deleteMember라고 쓰면 더 알아보기 쉽지 않나?"
라는 생각에 대한 답도 조금 명확해졌다.
URI
→ 무엇을 대상으로 하는가?
HTTP Method
→ 그것을 가지고 무엇을 하는가?
역할을 나눠놓은 것이다.
여기서 또 하나 헷갈릴 수 있는 것이 있다.
/members/100
과
/members?age=20
은 무엇이 다른 걸까?
둘 다 회원을 조회하는 것처럼 보인다.
그런데 앞에서 배운 내용을 적용해보면 차이를 이해할 수 있다.
GET /members/100
은 100번이라는 특정 회원 리소스를 식별한다.
반면
GET /members?age=20
은 회원 Collection을 조회하면서
age = 20
이라는 조회 조건을 추가한 것이다.
즉 내 식으로 구분하면 다음과 같다.
Path
→ 어떤 리소스인가?
Query Parameter
→ 어떤 조건으로 조회할 것인가?
예를 들어
GET /members/100
이라면
100번 회원 주세요.
이고,
GET /members?age=20
이라면
회원들 중에서
age가 20인 회원을 찾아주세요.
라고 볼 수 있다.
동적 데이터 조회에서 Query Parameter가 주로 검색, 필터, 정렬 조건으로 사용된다는 것도 이 관점에서 이해할 수 있었다. 5.http-method-use.pdf
데이터 전송 방식을 정리하면서 계속 등장하는 HTTP Header가 하나 있었다.
Content-Type
HTML Form에서는
Content-Type: application/x-www-form-urlencoded
파일을 전송할 때는
Content-Type: multipart/form-data
HTTP API에서는
Content-Type: application/json
을 사용했다.
처음에는 그냥
"보내는 데이터 형식에 따라서 정해놓은 문자열인가?"
정도로 생각했다.
그런데 서버 입장에서 생각해보니 왜 필요한지 이해하기 쉬웠다.
클라이언트가 다음 데이터를 보냈다고 해보자.
username=kim&age=20
서버는 이 데이터를 어떤 형식으로 해석해야 할지 알아야 한다.
그래서
Content-Type: application/x-www-form-urlencoded
이라고 알려준다.
이번에는 다음과 같은 데이터가 들어온다.
{
"username": "kim",
"age": 20
}
그러면
Content-Type: application/json
을 통해
"내가 지금 보내는 Message Body는 JSON이야."
라고 서버에게 알려주는 것이다.
즉 Content-Type은 Message Body에 담긴 데이터의 표현 형식(Media Type)을 알려준다.
Client
Content-Type: application/json
↓
Message Body
{
"username": "kim"
}
↓
Server
"JSON으로 해석하면 되겠구나."
HTML Form에서는 application/x-www-form-urlencoded를 사용하고 파일과 Form 데이터를 함께 보낼 때는 multipart/form-data를 사용한다. 반면 HTTP API에서는 JSON을 주로 사용한다. oai_citation:1‡5.http-method-use.pdf
Collection 부분을 처음 봤을 때는
서버가 URI를 생성한다.
라는 표현이 조금 애매하게 느껴졌다.
회원가입을 예로 들어보자.
현재 서버에 다음 회원들이 있다고 하자.
/members/1
/members/2
/members/3
새로운 회원을 등록한다.
클라이언트는 다음 요청을 보낸다.
POST /members HTTP/1.1
Content-Type: application/json
{
"username": "kim",
"age": 20
}
여기서 클라이언트는
/members/4
를 요청하지 않았다.
그냥
/members
라는 Collection에 새로운 회원을 등록해달라고 요청했다.
Client
"회원 한 명 등록해주세요."
POST /members
↓
Server
새로운 ID 결정
id = 4
↓
/members/4
그리고 서버가 응답한다.
HTTP/1.1 201 Created
Location: /members/4
즉 Location을 통해 새로 생성된 리소스가 어디에 있는지를 알려줄 수 있다.
이것이
Collection에서는 서버가 리소스의 URI를 생성하고 관리한다.
는 의미였다. 강의에서도 POST로 /members에 등록한 뒤 서버가 /members/100과 같은 새 URI를 만들고 201 Created와 Location으로 응답하는 예시를 사용한다. 5.http-method-use.pdf
이번에는 파일을 저장한다고 해보자.
클라이언트가 star.jpg라는 파일을 정확히 이 위치에 저장하고 싶다.
/files/star.jpg
그러면 요청부터 URI가 완성되어 있다.
PUT /files/star.jpg
즉 클라이언트가 서버에게
"
/files/star.jpg라는 리소스를 여기에 둘 거야."
라고 위치까지 지정한 셈이다.
POST /members
→ 어디에 생성될지는 서버가 결정
PUT /files/star.jpg
→ 어디에 저장할지 클라이언트가 이미 결정
그래서 Store에서는 클라이언트가 리소스 URI를 알고 관리한다. 5.http-method-use.pdf
여기서 앞에서 배웠던 PUT의 특징도 다시 연결됐다.
PUT은 클라이언트가 대상 리소스를 정확하게 알고 요청한다.
PUT /files/star.jpg
그리고 해당 리소스가 있다면 대체하고, 없다면 생성할 수 있다.
결국 PUT을 이해할 때
"수정할 때 쓰는 Method"
라고 외우는 것보다
"클라이언트가 대상 리소스 URI를 알고 있고,
그 위치의 리소스를 대체한다."
라고 이해하는 편이 더 정확했다.
처음에는 둘 다
"리소스 여러 개가 들어 있는 곳 아닌가?"
라는 생각이 들었다.
실제로 /members, /files 모두 여러 리소스를 관리한다.
하지만 차이는 새로운 리소스의 URI를 누가 결정하는가에 있었다.
| 구분 | Collection | Store |
|---|---|---|
| 대표 Method | POST | PUT |
| URI 결정 | 서버 | 클라이언트 |
| 예시 | /members | /files |
| 등록 요청 | POST /members | PUT /files/star.jpg |
| 새 리소스 | 서버가 /members/100 생성 | 클라이언트가 /files/star.jpg 지정 |
이 기준 하나를 잡으니 두 개념을 외우기가 훨씬 쉬웠다.
Collection
→ 서버가 관리
Store
→ 클라이언트가 관리
Collection과 Store를 이해하고 나면 Document는 비교적 단순하다.
/members/100
에서 /members가 Collection이라면
/members/100
은 특정 회원 하나를 나타낸다.
이것이 Document다.
파일도 마찬가지다.
/files/star.jpg
은 파일 하나를 나타낸다.
강의에서는 Document를 파일 하나, 객체 인스턴스 하나, 데이터베이스의 Row 하나와 같은 단일 개념으로 설명한다. 5.http-method-use.pdf
그래서 다음처럼 생각해볼 수 있다.
/members
│
├── /members/100
│ ↑
│ Document
│
├── /members/101
│
└── /members/102
↑
Collection
마지막으로 Controller가 있다.
앞에서 URI 설계의 가장 중요한 원칙을
리소스를 식별한다.
라고 배웠다.
그래서 가능하면 URI에는 명사를 사용하고 행위는 HTTP Method에 맡긴다.
GET /members/100
DELETE /members/100
그런데 모든 비즈니스 동작을 HTTP Method만으로 자연스럽게 표현할 수 있는 것은 아니다.
특히 순수 HTML Form처럼 GET과 POST만 사용할 수 있는 환경에서는 제약이 생긴다.
그래서 다음과 같은 URI가 등장할 수 있다.
/members/100/delete
여기서는 delete라는 동사가 URI에 직접 들어갔다.
이것이 Controller 또는 Control URI다.
강의에서는 Document, Collection, Store로 해결하기 어려운 추가 프로세스를 실행할 때 동사를 직접 사용하는 방식으로 설명한다. 5.http-method-use.pdf
하지만 이 부분은 순서를 기억하는 것이 중요할 것 같다.
1. 먼저 리소스를 식별한다.
2. HTTP Method로 행위를 표현한다.
3. 그래도 자연스럽게 표현하기 어렵다면
4. Control URI를 고려한다.
즉 처음부터
/createMember
/updateMember
/deleteMember
처럼 모든 동작을 URI에 넣는 것이 아니라는 것이다.
HTTP를 처음 공부할 때는 각각 따로 외웠다.
URI
GET
POST
PUT
PATCH
DELETE
Query Parameter
Message Body
Content-Type
그런데 HTTP API 설계까지 오니 이 개념들이 서로 역할을 나눠 가지고 있다는 것이 보이기 시작했다.
예를 들어 회원을 조회한다고 해보자.
GET /members/100
여기에는 세 가지 정보가 이미 들어 있다.
GET
→ 무엇을 할 것인가?
→ 조회
/members/100
→ 무엇을 대상으로 할 것인가?
→ 100번 회원
검색 조건까지 필요하다면
GET /members?age=20
처럼 Query Parameter를 사용한다.
?age=20
→ 어떤 조건으로 조회할 것인가?
이번에는 회원을 등록한다.
POST /members
Content-Type: application/json
{
"username": "kim",
"age": 20
}
각 부분을 다시 나누면
POST
→ 요청 데이터를 처리한다.
/members
→ 대상 리소스는 회원 Collection이다.
Content-Type: application/json
→ Message Body는 JSON 형식이다.
Message Body
→ 실제로 전달할 회원 데이터다.
이렇게 하나의 HTTP 요청 안에서도 각각의 요소가 맡고 있는 역할이 다르다.
여기까지 공부한 내용을 가지고 내가 API를 설계한다고 생각해봤다.
예를 들어 회원 관리 API가 필요하다고 하자.
처음부터
GET을 쓸까?
POST를 쓸까?
부터 생각하는 것이 아니라 먼저 리소스가 무엇인지 찾는다.
회원 등록
회원 조회
회원 수정
회원 삭제
↓
행위를 제거
↓
회원
리소스는 회원이다.
그러면 URI를 만든다.
/members
/members/{id}
그다음 행위를 HTTP Method로 분리한다.
GET /members
→ 회원 목록
POST /members
→ 회원 등록
GET /members/{id}
→ 회원 조회
PATCH /members/{id}
→ 회원 일부 수정
DELETE /members/{id}
→ 회원 삭제
조회 조건이 필요하면 Query Parameter를 추가한다.
GET /members?age=20
서버에 데이터를 전달해야 한다면 Message Body를 사용한다.
POST /members
Content-Type: application/json
{
"username": "kim",
"age": 20
}
그리고 HTTP Method만으로 표현하기 애매한 특별한 프로세스가 있다면 마지막으로 Control URI를 생각해본다.
리소스 식별
↓
URI 설계
↓
HTTP Method 선택
↓
필요한 데이터 전달 방식 결정
↓
그래도 표현하기 어려운 동작
↓
Control URI 고려
처음 HTTP API를 볼 때는 URL을 예쁘게 짓는 것이 API 설계라고 생각했던 것 같다.
그런데 지금까지 배운 내용을 연결해보면 API 설계는 단순히 URL 이름을 정하는 작업이 아니었다.
어떤 것을 리소스로 볼 것인지 결정하고, 그 리소스에 대한 행위를 HTTP의 의미에 맞게 표현하는 과정이었다.
이번 수업에서 가장 크게 바뀐 생각은 HTTP 요청을 하나의 문자열처럼 보지 않게 됐다는 것이다.
예전에는
POST /members
를 보면 그냥
"회원 API 호출하는구나."
정도로 생각했다.
지금은 조금 다르게 보인다.
POST
→ 이 리소스에 데이터를 보내 처리해달라는 의미
/members
→ 회원이라는 리소스 Collection
Content-Type
→ 전달하는 데이터의 표현 형식
Message Body
→ 실제 전달 데이터
그리고 같은 데이터를 서버에 전달하더라도 상황에 따라 방법이 달라진다.
단순한 리소스 조회
→ URI
검색 / 필터 / 정렬
→ Query Parameter
HTML Form
→ application/x-www-form-urlencoded
파일 + Form
→ multipart/form-data
HTTP API
→ application/json
API를 설계할 때도 마찬가지였다.
Document
→ 하나의 리소스
Collection
→ 서버가 URI를 관리하는 리소스 집합
Store
→ 클라이언트가 URI를 관리하는 저장소
Controller
→ 일반적인 리소스 구조로 표현하기 어려운 추가 동작
결국 이번 내용도 앞에서 배운 "URI는 리소스를 식별한다"라는 원칙으로 다시 돌아온다.
무엇을 대상으로 하는가?
→ URI
무엇을 할 것인가?
→ HTTP Method
어떤 조건인가?
→ Query Parameter
어떤 데이터를 보낼 것인가?
→ Message Body
그 데이터는 어떤 형식인가?
→ Content-Type
이렇게 역할을 하나씩 분리해서 보니까 HTTP 메시지가 전보다 훨씬 읽기 쉬워졌다.
그리고 백엔드에서 API를 만들 때도 단순히
"일단 요청이 들어오게 만들자."
가 아니라
"이 요청을 처음 보는 사람도 URI와 HTTP Method만 보고 어떤 의미인지 이해할 수 있을까?"
를 한 번 더 생각해보는 것이 중요할 것 같다.