앞선 포스팅을 통해 우리는 웹 브라우저의 작동 원리와 HTTP 프로토콜에 대해 배웠다.
여기서 한 가지 의문이 든다.
웹 페이지 구성 시 필요한 리소스를 가져올 수 있는 더 간단한 방법이 없을까?
이에 대한 해답으로 API가 있다.
API와 REST API
API (Application Programming Interface)란 "응용 프로그램 프로그래밍 인터페이스"로, 클라이언트에게 리소스를 잘 활용할 수 있도록 서버에서 제공하는 인터페이스다. 좀 더 은유적으로 표현하자면 고객(사용자)이 식당(서버)에서 음식(리소스)을 주문하기 위해 필요한 메뉴판과 같다.
하지만 만약 메뉴판이 위와 같다면 어떨까?
사용자가 메뉴판을 읽을 수 없으니 주문을 할 수 없을 것이다.
물론 인내심이 많은 고객은 위 메뉴판을 천천히 읽어보고 음식을 주문할 수 있지만, 고통스러운 경험임에는 변함이 없다.
위와 같은 참사를 막기 위해 '메뉴판을 깔끔하고 예쁘게 만드는 가이드'를 제공한 것이 바로 REST API다.
REST API에서 REST는 “Representational State Transfer”의 약자로, HTTP의 주요 저자 중 하나이자 미국의 컴퓨터 과학자 로이 필딩이 박사 학위 논문에서 REST API는 웹에서 사용되는 데이터나 자원(Resource)을 HTTP URI로 표현하고, HTTP 프로토콜을 통해 요청과 응답을 정의하는 방식으로, 웹(http)의 장점을 최대한 활용할 수 있는 아키텍처라고 소개했다.
즉, REST는 '네트워크 통신 기반 프로그램(API) 설계 시 준수하기 위한 구조(아키텍쳐)와 디자인'이라고 해석할 수 있다.
REST 방법론은 많은 개발자들에게 채택 됐고, REST를 준수하는 API를 REST(-ful) API라고 한다.
REST API를 디자인하는 방법
REST 방법론이 주류가 됨에 따라 레오나르드 리차드슨(Leonard Richardson)은 REST API를 잘 적용하기 위한 4단계 모델을 만들었다.
0 단계: POX (Plain Old XML; 기본 XML) 늪
REST 성숙도 모델 0단계에 도달하기 위해서는 API가 단순히 POX (Plain Old XML; 기본 XML)를 주고 받는, 즉 HTTP 프로토콜을 사용하기만 하면 된다.
0단계에서의 HTTP 프로토콜은 단순히 통신을 위한 프로토콜이며 하나의 URL과 POST 메서드만을 사용한다.
예를 들어 지역 포트(8000)을 통해 커피의 데이터를 가져오는 웹 사이트가 있다고 가정할 시, 데이터를 가져오는 것과 업데이트/생성(POST) 하는 것은 동일한 HTTP 프로토콜을 사용한다.
데이터를 가져올 때(GET): POST http://localhost:8000/coffee
데이터를 보낼 때(POST): POST http://localhost:8000/coffee
물론 HTTP 프로토콜을 사용했다고 해서 REST API라고는 할 수 없기 때문에, 0 단계는 REST API를 작성하기 위한 기본 단계라고 생각하면 된다.
1 단계: 자원(Resources)
0단계보다 나아간 1단계에서는 개별 리소스(Resource)와의 통신을 준수해야 한다.
또한 REST API의 특징인 웹에서 사용되는 데이터나 자원(Resource)을 "HTTP URI로 표현"하기 위해 개별 리소스에 맞는 엔드포인트를 사용해야 한다.
좀 더 수월한 이해를 위해, 프렌차이즈 커피 브랜드의 애플리케이션에서 원격 주문을 위한 API를 만든다고 가정해보자.
요청 내용 | 요청 | 응답 |
메뉴 확인 | POST /locations/sinchon HTTP/ 1.1 [헤더 생략] { "coffe" : ["latte","americano", ...] } |
HTTP/1.1 200 OK [헤더 생략] { "items": [ {"id": 10, "location": "all", "coffee": "latte"}, ..., {"id": 330, "location": "sinchon", "coffee": "bigPieLatte"} {"id": 331, "location": "gangnam", "coffee": "pumpkinLatte"} ] } |
원격 주문 | POST order/items/330 HTTP/1.1 [헤더 생략] { "name": "아메리카노 시키신" } |
HTTP/1.1 200 OK [헤더 생략] { "order": { "item": { "id": 330, "location": "sinchon", "coffee": "bigPieLatte"}, "name": "아메리카노 시키신" } } |
REST API 1단계를 만족한 API는 위와 같을 것이다.
위의 예시에서 메뉴 확인이라는 요청의 응답으로 받게 되는 자원(리소스)은 해당 지점에서 제공 가능한 커피 메뉴로, 요청 시 /locations/sinchon 이라는 엔드포인트를 사용했다. 또한 원격으로 접수된 주문은 시스템에서 제공된 커피 메뉴와 사용자가 지정한 이름을 사용했다.
이처럼 기존 0단계에서는 어떠한 요청을 하든 데이터 '덩어리'를 넘겼다면, 1단계부터는 요청에 따라 어떤 리소스를 변화시키는지 혹은 어떤 응답이 제공되는지에 따라 각기 다른 엔드포인트를 사용해야 한다.
더불어 요청에 따른 응답으로 리소스를 전달할 때에도 사용한 리소스에 대한 정보와 함께 리소스 사용에 대한 성공/실패 여부를 반환해야 한다.
만약 신촌점에서 331번 id를 가진 커피를 주문했다면 리소스 사용에 대한 실패 여부를 포함한 응답을 아래와 같이 제공해야 한다.
요청 내용 | 요청 | 응답 |
메뉴 확인 | POST order/locations/sinchon HTTP/ 1.1 [헤더 생략] { "coffe" : ["latte","americano", ...] } |
HTTP/1.1 200 OK [헤더 생략] { "items": [ {"id": 10, "location": "all", "coffee": "latte"}, ..., {"id": 330, "location": "sinchon", "coffee": "bigPieLatte"} {"id": 331, "location": "gangnam", "coffee": "pumpkinLatte"} ] } |
원격 주문 | POST order/items/330 HTTP/1.1 [헤더 생략] { "name": "아메리카노 시키신" } |
HTTP/1.1 200 OK [헤더 생략] { "orderFailure": { "item": { "id": 330, "location": "sinchon", "coffee": "bigPieLatte"}, "name": "아메리카노 시키신", "reason": "해당 커피는 현재 사용 중이신 지점에서는 제공하지 않습니다" } } |
하지만 1 단계의 조건을 만족했더라도 여전히 REST API로 분류될 수 없다.
2 단계: HTTP 메서드
REST 성숙도 모델 2단계에서는 CRUD(Create, Read, Update, Delete)에 맞게 적절한 HTTP 메서드를 사용해야 한다.
이전 0단계와 1단계의 예시에서는 모든 요청을 POST 메서드를 사용해서 처리했기 때문에 CRUD에 따른 적합한 메서드를 사용하지 않았다.
예시에서 "메뉴 확인"은 메뉴를 조회(READ)하는 행위 이므로 GET 메서드를 사용하여 요청을 보내야 한다. 또한, GET 메서드는 HTTP 메세지의 구성 요소인 body를 가지지 않기 때문에 query parameter를 사용하여 필요한 리소스를 전달해야 한다.
"원격 주문"의 경우, 주문을 보낸다는 것은 해당 매장에 주문을 생성(CREATE)한다는 것과 같으므로 POST 메서드를 사용하여 요청을 보내야 한다. 이 때 POST 요청에 대한 응답이 어떻게 반환되는지가 중요하다. 따라서 명확한 응답 코드(201 Created)를 반환해야 하며 관련 리소스를 클라이언트가 확인할 수 있도록 그에 걸맞는 헤더(Location)을 작성해야 한다.
요청 내용 | 요청 | 응답 |
메뉴 확인 | GET /locations/sinchon/items?coffee=lattte HTTP/ 1.1 [헤더 생략] |
HTTP/1.1 200 OK [헤더 생략] { "items": [ {"id": 10, "location": "all", "coffee": "latte"}, ..., {"id": 330, "location": "sinchon", "coffee": "bigPieLatte"} {"id": 331, "location": "gangnam", "coffee": "pumpkinLatte"} ] } |
원격 주문 | POST order/items/330 HTTP/1.1 [헤더 생략] { "name": "아메리카노 시키신" } |
HTTP/1.1 201 Created Location: items/330/order [헤더 생략] { "order": { "item": { "id": 330, "location": "sinchon", "coffee": "bigPieLatte"}, "name": "아메리카노 시키신" } } |
이 외에도 HTTP 메서드를 사용할 때 유의할 몇 가지 규칙들이 준수해야 한다. (메서드의 구분과 기능에 대해서는 MDN을 참고하자.)
- GET 메서드는 서버의 데이터를 변화시키지 않는 요청에 사용한다.
- POST 메서드는 요청마다 새로운 리소스를 생성하고, PUT 메서드는 요청마다 같은 리소스를 반환한다. 이렇듯 매 요청마다 같은 리소스를 반환하는 특징(멱등성 idempotent)을 가지는 경우와 그렇지 않은 경우를 명확히 구분해 알맞는 HTTP 메서드를 사용해야 한다.
- PUT 메서드는 교체, PATCH 메서드는 수정의 용도로 사용하므로 잘 구분해서 사용해야 한다.
2단계 까지 완성된 API는 HTTP API라고 불리며, 로이 필딩의 주장에 따라 REST API의 모든 조건에 만족되지는 않았으므로 REST API라고 정의할 수 없다.
3 단계: 하이퍼미디어 컨트롤
마지막 단계는 HATEOAS(Hypermedia As The Engine Of Application State)의 약자인 하이퍼미디어 컨트롤을 적용해야 한다.
하이퍼미디어 컨트롤이란 리소스를 포함한 응답에서 응답 이후에 할 수 있는 다양한 행동들을 위한 링크(URI)들을 포함해 놓은 것을 말한다. 이는 리소스와 응답에 대한 설명이 용이하기 위함이다.
이전 예시의 경우 메뉴 조회 후 주문을 위한 링크, 주문 후에는 주문 확인 또는 취소를 위한 링크를 남겨놓아 사용자가 HTTP 메서드 사용 이후에도 취할 수 있는 행동에 대해 알 수 있어야 한다.
요청 내용 | 요청 | 응답 |
메뉴 확인 | GET /locations/sinchon/items?coffee=lattte HTTP/ 1.1 [헤더 생략] |
HTTP/1.1 200 OK [헤더 생략] { "items": [ {"id": 10, "location": "all", "coffee": "latte"}, ..., {"id": 330, "location": "sinchon", "coffee": "bigPieLatte"} {"id": 331, "location": "gangnam", "coffee": "pumpkinLatte"} ], "links": { "order" : { "href": "http://locathost:8000/order/items/330" "method": "POST" } } } |
원격 주문 | POST order/items/330 HTTP/1.1 [헤더 생략] { "name": "아메리카노 시키신" } |
HTTP/1.1 201 Created Location: items/330/order [헤더 생략] { "order": { "item": { "id": 330, "location": "sinchon", "coffee": "bigPieLatte"}, "name": "아메리카노 시키신" }, "links": { "checkOrder" : { "href": "http://locathost:8000/order/items/330" "method": "GET" }, "cancel": { "href": "http://locathost:8000/order/items/330/cancel" "method": "DELETE" }, } } |
OPEN API와 API Key
우리는 API와 REST API란 무엇인지에 대해 학습했지만, 아직 첫 질문에 대해 시원한 해답을 듣지는 못했다. API를 활용하면 필요한 데이터를 가져올 수 있다는 사실을 인지했지만, 매번 필요한 데이터를 구하기 위해 API를 작성하는 것은 매우 막연한 일이다.
이럴 때 "공공"의 API를 사용할 수 있다.
예를 들어 정부에서 제공하는 공공데이터의 경우, 사용자가 쉽게 접근할 수 있도록 정부는 Open API의 형태로 공공데이터를 제공하고 있다. 해당 API에는 "Open"이라는 키워드가 붙어있는데, 말 그대로 누구에게나 열려있음을 뜻한다. 하지만 "무제한으로 이용할 수 있다"는 의미가 아니므로 헷갈리지 말자.
단, 각 API는 정해진 이용 수칙이 있고, 해당 이용 수칙에 따라 추가 결제 필요, 제공된 데이터 정보의 제한 등 제한사항이 있을 수 있다.
Open API의 경우 누구나 사용할 수 있지만, 서버 입장에서 조건 없이 익명의 클라이언트에게 데이터를 제공할 의무는 없다. 또한, API 제공을 위한 서버 운영에도 비용도 발생하므로 API를 이용하기 위해서 이용자가 누구인지 서버에게 밝혀야 한다. 이 때 사용되는 것이 API Key로, 서버에 로그인한 이용자에게 리소스에 접근할 수 있는 권한을 API Key의 형태로 제공한다.
참조
- 메뉴 사진
- 로이 필딩의 박사 학위 논문
- MDN: HTTP 요청 메서드
- 리차드슨 성숙도 모델 (RMM) 참고: Wikipedia, Richardson Maturity Model for REST APIs, steps toward the glory of REST
'Front-End > 웹 (Web)' 카테고리의 다른 글
React(SPA)에서 Next.js(SSR)를 써야하는 이유 (0) | 2023.07.21 |
---|---|
[Web] 웹 표준과 접근성 (0) | 2022.11.20 |
[Web] UI와 UX (0) | 2022.11.03 |
[HTTP/네트워크] HTTP와 브라우저 작동 원리 (0) | 2022.10.19 |
[HTTP/네트워크] 웹 애플리케이션 구조 (0) | 2022.10.17 |
댓글