HTTP API
HTTP API를 만들어보자
· 5 min read
회원 정보 관리 API 만들어보기 #
Photo by Ashwini Chaudhary(Monty) on Unsplash
요구 사항
- 회원 목록 조회 /read-member-list
- 회원 조회 /read-member-by-id
- 회원 등록 /create-member
- 회원 수정 /update-member
- 회원 삭제 /delete-member
각 기능마다 API URL을 위와 같이 설계하면 그럴듯하게 완성은 된다. 하지만 정말 좋은 URI인가?
좋은 URI 설계 #
URI(Uniform Resource Identifier) 설계에서 가장 중요한 것은 리소스 식별이다. 이를 위해 리소스의 의미를 URI에 담아야 한다. 그러면 리소스는 무엇인가? 리소스는 회원을 등록하고 수정하고 조회하는 것이 아니다. 회원이라는 개념 자체가 바로 리소스이다. 그러므로 등록, 수정, 조회의 개념은 모두 배제하고 회원이라는 리소스만 식별할 수 있으면 된다. 따라서 회원 리소스를 URI에 매핑시켜야 한다.
‘미네랄을 캐라’에서 리소스는 ‘캐라’가 아니라 ‘미네랄’이다!
다시 더 알맞은 API URI 설계를 해보면 아래와 같이 할 수 있다.
- 회원 목록 조회 /members
- 회원 조회 /members/{id}
- 회원 등록 /members/{id}
- 회원 수정 /members/{id}
- 회원 삭제 /memebers/{id}
계층 구조상 상위를 컬렉션으로 볼 수 있으므로 복수형으로 사용하는 것이 권장된다
이렇게 URI 매핑을 하면 조회, 등록, 수정, 삭제 등이 모두 똑같이 매핑되어 어떻게 구분할 수 있을지가 궁금해진다. 리소스와 행위를 분리하여 URI 설계를 하였기 때문에 URI에는 리소스만 식별되게 하고 행위는 HTTP 메서드를 통해 구분하게 된다.
HTTP 메서드 #
HTTP 주요 메서드는 아래와 같다.
- GET: 리소스 조회
- POST: 요청 데이터 처리, 주로 등록
- PUT: 리소스 대체, 해당 리소스 없으면 생성
- PATCH: 리소스 부분 변경(특정 필드만 변경)
- DELETE: 리소스 삭제
- HEAD: GET과 동일, 메세지 부분을 제외하고 Status line과 header만 반환
- OPTIONS: 대상 리소스에 대한 통신 가능 옵션을 설명
- CONNECT: 대상 자원으로 식별되는 서버에 대한 터널을 설정
- TRACE: 대상 리소스에 대한 경로를 따라 메시지 루프백 테스트를 수행
GET #
GET /serach?q=hello&hl=ko HTTP/1.1
Host: www.google.com
GET 메소드는 말 그대로 리소스를 조회하는 메서드이다. 서버에 전달하고 싶은 데이터는 쿼리를 통해서 전달한다. 물론 메시지 바디를 통해 데이터를 전달할 수 있지만 지원하지 않는 서버가 많아서 권장되지 않는다. 따라서 데이터를 전달할 때에는 query parameter와 query string을 이용하는 것이 좋다.
graph LR Client[Client] ---->|GET| Server[Server]
클라이언트가 아래 HTTP Request를 서버로 전송한다.
GET /members/100 HTTP/1.1
Host: www.google.com
서버가 요청 메시지를 받으면 다시 서버는 클라이언트에게 아래의 예시와 같은 응답 메시지를 전송한다.
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 32
{
"username": "kim",
"age": 25
}
POST #
POST 메서드는 요청 데이터를 처리하며 메시지 바디를 통해 서버로 요청 데이터를 전달한다. 서버는 요청 데이터를 처리하며 메시지 바디를 통해 들어온 데이터를 처리하는 모든 기능을 수행한다. 주로 신규 리소스 등록, 프로세스 처리에 사용된다.
클라이언트가 아래와 같이 요청 리퀘스트를 서버로 보내게 된다.
POST /members HTTP/1.1
Content-Type: application/json
{
"username": "kim",
"age": 25
}
서버는 신규 유저를 해당 리퀘스트 바디의 내용을 토대로 생성하게 된다. 그러면 아래와 같이 서버는 다시 클라이언트에게 응답 데이터를 보낸다.
HTTP/1.1 201 Created
Content-Type: application/json
Content-Length: 32
{
"username": "kim",
"age": 25
}
POST 메서드의 의미
- HTML 양식에 입력된 필드와 같은 데이터 블록을 데이터 처리 프로세스에 제공
- 게시판, 뉴스 그룹, 메일링 리스트, 블로그 또는 유사한 기수 그룹에 메시지 게시
- 서버가 아직 식별하지 않은 새 리소스 생성
- 기존 자원에 데이터 추가
o Providing a block of data, such as the fields entered into an HTML form, to a data-handling process;
o Posting a message to a bulletin board, newsgroup, mailing list, blog, or similar group of articles;
o Creating a new resource that has yet to be identified by the origin server; and
o Appending data to a resource’s existing representation(s).2
리소스 URI에 POST 요청이 어떻게 동작할지와 리소스를 따로 정해주어야 한다.
POST의 동작 예시 정리
- 등록(새 리소스 생성)
- 결제완료 $\rightarrow$ 배달 시작 $\rightarrow$ 배달 완료 처럼 단순한 값 변경을 넘어 프로세스의 상태가 변겨오디는 경우
- POST의 결과로 새로운 리소스가 생성되지 않을 수도 있음
- POST /orders/(orderId)/start-delievery (컨트롤 URI)
- 하지만 이런 경우 이상적 URI 설계는 아니지 않는가?
- 물론 리소스로 최대한 URI를 설계하되 실무에서 어쩔 수 없이 행위를 가지고도 URI를 설계하게 될 수도 있다.
- POST의 결과로 새로운 리소스가 생성되지 않을 수도 있음
- 다른 메서드로 처리하기 애매한 경우, 즉 조회용으로 Message Body를 사용하고자 할 때
- JSON으로 조회 데이터를 넘겨야 하는데, GET 메서드를 사용하기 어려운 경우
- 애매하면 POST 메서드 이용
- GET의 경우 Message Body는 서버단에서 허용하지 않는 경우가 많기 때문
- 그러면 다 POST로 하면 되는거 아닌가?
- Cache와 관련한 기능에서 GET이 조회에서는 더 좋은 지원을 받는다.
PUT #
PUT 메서드는 리소스를 대체한다. ‘대체’는 기존 파일이 있으면 대체되고 없으면 생성됨을 의미한다.(Copy & Paste처럼 생각하면 편하다.) POST 메소드와 다르게 PUT 메소드는 클라이언트가 리소스를 식별한다. 즉 클라이언트가 리소스 위치를 알고 URI를 지정한다.
PUT /members/100 HTTP/1.1
Content-Type: application/json
{
"username": "lee",
"age": 50
}
위와 같이 PUT Request를 전송할 때 /members/100에 기존의 리소스가 존재한다면 “username"과 “age"가 변경되겠지만 아예 없었다면 새로이 생성된다. 만약 아래와 같이 나이만 변경하는 PUT Request를 전송한다면 “username” 필드가 삭제된다. 즉 기존 리소스는 삭제한 뒤 덮어쓰여진다. 따라서 PUT 메서드는 기존의 리소스를 수정한다기 보다는 아예 교체해버린다.
PUT /members/100 HTTP/1.1
Content-Type: application/json
{
"age": 50
}
PATCH #
리소스의 부분 변경을 위해서는 PATCH를 사용할 수 있다.
PATCH /members/100 HTTP/1.1
Content-Type: application/json
{
"age": 50
}
기존에 존재하는 리소스에서 “age"필드만 50으로 변경되게 할 수 있다.
만약 PATCH가 정상 작동하지 않고 일부 수정만 하고 싶다면 POST를 사용하면 된다.
DELETE #
리소스의 완전 제거를 위하여 DELETE 메서드를 사용할 수 있다.
DELETE /members/100 HTTP/1.1
Host: localhost:8080
위와 같이 Request를 전송하면 서버측에서는 아래와 같이 응답 메시지를 전송할 수 있다.
{
"username": "kim",
"age": 25
}
HTTP 메서드의 속성 #
안전(Safe) #
호출해도 리소스를 변경하지 않음을 의미한다. GET 메서드는 안전하다고 할 수 있다. 하지만 POST, PUT, PATCH, DELETE는 안전하지 않다고 할 수 있다.
멱등(Idempotent) #
호출의 횟수와 상관없이 결과가 똑같음을 의미한다. $$ f(f(f(\cdots f(x)))) = f(x) $$ GET, PUT, DELETE의 경우는 여러번 해도 같은 결과가 조회, 대체되며 삭제는 여러번 요청해도 첫번째 호출에서 삭제된 뒤 똑같이 결과는 삭제되어 있으므로 멱등하다. POST의 경우 결제, 배송 등이 여러본 호출되면 결과가 다르게 나온다.
멱등의 개념이 왜 필요한가?
- 자동 복구 매커니즘
- 서버가 응답이 없으면 동일 요청을 여러번 해서 복구할 수 있으므로
멱등의 경우 리소스가 변경되는 것은 고려하지 않는다. 예를 들어 GET 후 PUT을 통해 대체한 뒤 다시 GET으로 조회한다면 처음의 GET과 마지막의 GET은 다른 값을 리턴할 것이다. 하지만 멱등은 외부 요인으로 변경되는 것은 고려하지 않기 때문에 여전히 멱등하다고 할 수 있다.
Cachable #
응답 결과 리소스를 캐시해서 사용해도 되는가?
GET, HEAD, POST, PATCH 메서드는 스펙상 캐시가 가능하나 실제로는 GET, HEAD 정도만 캐시로 사용한다. POST, PATCH는 본문 내용까지 캐시 키로 고려해야 하는데 구현이 쉽지 않다.3
References #
Ashwini, black and white love neon light signage. 2020. [Online]. Available: https://unsplash.com/photos/black-and-white-love-neon-light-signage-36kuP8NEZz0 ↩︎
https://datatracker.ietf.org/doc/html/rfc7231#section-4.3.3 ↩︎
https://www.inflearn.com/course/http-웹-네트워크/dashboard ↩︎