클라이언트는 서버에 데이터를 어떻게 전달할까? | HTTP 데이터 전송부터 API 설계까지

대현·2일 전

네트워크

목록 보기
5/5
post-thumbnail

클라이언트는 서버에 데이터를 어떻게 전달할까? | HTTP 데이터 전송부터 API 설계까지

앞에서는 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에서는 요청의 목적에 따라 데이터를 전달하는 위치도 달라졌다.


데이터 전송 상황은 4가지로 나눌 수 있다

수업에서는 클라이언트에서 서버로 데이터를 전송하는 상황을 크게 네 가지로 나눴다.

클라이언트 → 서버

├── 정적 데이터 조회
├── 동적 데이터 조회
├── HTML Form 데이터 전송
└── HTTP API 데이터 전송

하나씩 살펴보자.


1. 정적 데이터 조회

가장 단순한 경우다.

이미지나 정적 텍스트 문서를 조회한다고 해보자.

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

따라서 이미지나 정적 텍스트 문서는 일반적으로 리소스 경로만으로 단순하게 조회할 수 있다.

그런데 조회를 하면서 서버에게 추가적인 조건까지 전달하고 싶다면 어떻게 해야 할까?


2. 동적 데이터 조회

예를 들어 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다.

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


3. HTML Form으로 데이터를 전송하면?

이번에는 회원가입 화면을 생각해보자.

사용자가 다음 정보를 입력한다.

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에서 GET을 쓰면 어떻게 될까?

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도 GET으로 보내면 되지 않을까?

기술적으로 다음과 같이 작성하는 것 자체는 가능하다.

<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는 왜 이름이 multipart일까?

처음에는

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을 정리하면

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를 설계할 때 다시 등장한다.


4. HTTP 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
}

이렇게 놓고 보니 차이가 훨씬 명확해졌다.


이제 실제 HTTP API를 설계해보자

데이터를 전달하는 방법을 알아봤으니 이제 조금 더 실제적인 문제로 넘어가보자.

회원 관리 시스템의 API를 어떻게 설계할까?

수업에서는 크게 세 가지 상황을 비교했다.

HTTP API - Collection
→ POST 기반 등록

HTTP API - Store
→ PUT 기반 등록

HTML Form
→ GET / POST 기반

강의 자료에서도 회원 관리 API는 POST 기반 Collection, 파일 관리는 PUT 기반 Store의 예로 구분한다. 5.http-method-use.pdf


POST 기반 회원 관리 API

회원 관리 시스템을 만든다고 해보자.

필요한 기능은 다음과 같다.

회원 목록 조회
회원 등록
회원 조회
회원 수정
회원 삭제

앞에서 배운 리소스 중심 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이라고 한다.

Collection

→ 서버가 관리하는 리소스 디렉터리
→ 서버가 새로운 리소스 URI를 생성하고 관리

회원 관리에서는

/members

가 Collection이다.

/members
   │
   ├── /members/100
   ├── /members/101
   └── /members/102

여기서 중요한 것은 서버가 URI를 결정한다는 것이다.


그러면 PUT 기반 등록은 뭐가 다를까?

이번에는 회원이 아니라 파일 관리 시스템을 생각해보자.

필요한 기능은 다음과 같다.

파일 목록 조회
파일 조회
파일 등록
파일 삭제
파일 대량 등록

API는 다음처럼 설계할 수 있다.

GET /files
→ 파일 목록

GET /files/{filename}
→ 파일 조회

PUT /files/{filename}
→ 파일 등록

DELETE /files/{filename}
→ 파일 삭제

POST /files
→ 파일 대량 등록

여기서 중요한 것은 PUT이다.

PUT /files/star.jpg

POST와 뭔가 다르다.


POST와 PUT의 차이가 여기서 보인다

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 직접 지정

이다.

이 차이가 생각보다 중요했다.


이것이 Store다

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으로 돌아온다.

앞에서 중요한 제약을 하나 봤다.

HTML Form

GET
POST

두 Method만 지원

그러면 문제가 생긴다.

HTTP API에서는

PATCH /members/100

DELETE /members/100

처럼 표현할 수 있었다.

그런데 순수 HTML Form에서는 PATCH나 DELETE를 직접 사용할 수 없다.

그럼 회원 수정과 삭제를 어떻게 표현하지?


HTML Form으로 회원 관리하기

강의 자료에서는 다음과 같이 설계한다.

회원 목록
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에 동사를 넣지 말라고 하지 않았나?

여기서 앞에서 배운 내용과 충돌하는 것처럼 보였다.

우리는 분명 URI에는

/members/100

처럼 리소스를 표현하고,

GET
POST
PATCH
DELETE

같은 HTTP Method로 행위를 표현한다고 배웠다.

그런데 갑자기

/new

/edit

/delete

같은 동사가 URI에 들어왔다.

"URI에는 리소스만 넣으라고 했는데 이건 뭐지?"

이때 등장하는 것이 Control 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로 최대한 설계해보고, 정말 표현하기 애매할 때 사용하는 최후의 수단에 가깝다.

정도로 기억하면 될 것 같다.


HTTP API 설계를 다시 정리하면

여기까지 배우고 나니 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 설계 개념을 정리해보자

이번 수업 마지막에는 URI를 설계할 때 참고할 만한 네 가지 개념이 등장했다.

Document

Collection

Store

Controller

처음 보면 이름이 비슷해서 헷갈리는데 결국 누가 무엇을 식별하고 관리하느냐를 보면 조금 쉽게 구분할 수 있었다.


Document

Document는 하나의 개별 리소스다.

예를 들어

/members/100

은 100번 회원 하나를 나타낸다.

/files/star.jpg

star.jpg 파일 하나를 나타낸다.

Document
→ 단일 개념
→ 객체 하나
→ DB Row 하나
→ 파일 하나

정도로 생각할 수 있다.


Collection

Collection은 서버가 관리하는 리소스 디렉터리다.

/members

를 생각하면 된다.

POST /members
        ↓
Server
        ↓
/members/100 생성

핵심은

서버가 새로운 Resource URI를 결정한다.


Store

Store는 클라이언트가 관리하는 리소스 저장소다.

/files

를 생각하면 된다.

PUT /files/star.jpg

처럼 클라이언트가 직접 URI를 알고 지정한다.

핵심은

클라이언트가 Resource URI를 결정한다.


Controller

마지막은 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
→ 그것을 가지고 무엇을 하는가?

역할을 나눠놓은 것이다.


URI와 Query Parameter도 역할이 다르다

여기서 또 하나 헷갈릴 수 있는 것이 있다.

/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


Content-Type은 왜 필요할까?

데이터 전송 방식을 정리하면서 계속 등장하는 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


POST 기반 등록에서 서버가 URI를 만든다는 것은 무슨 뜻일까?

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 CreatedLocation으로 응답하는 예시를 사용한다. 5.http-method-use.pdf


PUT 기반 Store에서는 왜 클라이언트가 URI를 알아야 할까?

이번에는 파일을 저장한다고 해보자.

클라이언트가 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를 알고 있고,
그 위치의 리소스를 대체한다."

라고 이해하는 편이 더 정확했다.


Collection과 Store를 비교하면

처음에는 둘 다

"리소스 여러 개가 들어 있는 곳 아닌가?"

라는 생각이 들었다.

실제로 /members, /files 모두 여러 리소스를 관리한다.

하지만 차이는 새로운 리소스의 URI를 누가 결정하는가에 있었다.

구분CollectionStore
대표 MethodPOSTPUT
URI 결정서버클라이언트
예시/members/files
등록 요청POST /membersPUT /files/star.jpg
새 리소스서버가 /members/100 생성클라이언트가 /files/star.jpg 지정

이 기준 하나를 잡으니 두 개념을 외우기가 훨씬 쉬웠다.

Collection
→ 서버가 관리

Store
→ 클라이언트가 관리

Document는 결국 하나의 리소스다

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는 정말 필요할 때 사용한다

마지막으로 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 요청 안에서도 각각의 요소가 맡고 있는 역할이 다르다.


내가 이해한 HTTP API 설계 순서

여기까지 공부한 내용을 가지고 내가 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만 보고 어떤 의미인지 이해할 수 있을까?"

를 한 번 더 생각해보는 것이 중요할 것 같다.

profile
도전을 멈추지 않는 개발자

0개의 댓글