현재는 REST나 GraphQL을 사용하는 API가 대부분이다.
통합 및 웹 앱(web app)을 위한 API 작성의 표준이다.
REST는 HTTP 프로토콜 위에서 동작한다.
URI는 API 리소스로 동작하기 때문에 엔드포인트로 동사 대신 명사를 사용한다.
REST 리소스에서 수행되는 액션들을 HTTP 메서드로 구분된다.
api/v1/person에서 /api/v1/persons는 엔드포인트 경로, persons는 REST 리소스가 된다.
Clinet - Server 개념을 기반으로 한다.
REST를 이용해 클라이언트(웹 or 앱 등등)가 HTTP 요청과 응답을 사용하여 실행 중인 서버와 통신하게 된다.
해당 HTTP 요청에는 쿼리 매개변수, 헤더 또는 요청 본문 형식의 페이로드가 담긴다.
서버는 성공/실패 표시와 HTTP 응답으로 감싸진 응답 데이터를 보낸다.
클라이언트/서버 측에서 상태를 관리하기 때문에 REST 호출은 Stateless이다.
REST는 리소스와 URI, HTTP 메서드, HATEOAS 3개의 컴포넌트를 사용해서 동작한다.
REST 호출 예시
GET /license HTTP/2
HOST: api.github
JSON 응답 예시
HTTP/2 200 OK
date: Mon, 10 Jul 2023 17:44:04 GMT
content-type: application/json; charset=utf-8
server: GitHub.com
status: 200 OK
cache-control: public, max-age=60, s-maxage=60
vary: Accept, Accept-Encoding, Accept, X-Requested-With, Accept-Encoding
etag: "3cbb5a2e38acf6c92b3d798667e828c7e3584af278aa314f6eb1857bbf2593ba"
<다른 헤더들>
Accept-Ranges: bytes
Content-Length: 2037
X-GitHub-Request-Id: 1C03:5C22:640347:81F9C5:5F70D372
[
{
"key": "agpl-3.0",
"name": "GNU Affero General Public License v3.0",
"spdx_id": "AGPL-3.0",
"url": "https://api.github.com/licenses/agpl-3.0",
"node_id": "MDc6TGljZW5zZTE="
},
{
"key": "apache-2.0",
"name": "Apache License 2.0",
"spdx_id": "Apache-2.0",
"url": "https://api.github.com/licenses/apache-2.0",
"node_id": "MDc6TGljZW5zZTI="
},
...
]
콘텐츠 타입으로는 요청과 응답 모두 JSON을 사용하는 것이 권장된다.
World Wide Web(WWW)상의 모든 문서는 HTTP 관점에서 리소스로 여겨진다.
URI란 웹에서 리소스의 위치, 이름, 또는 이 둘을 모두 사용해 리소스를 식별하는 문자열을 의미한다.
URI에는 URL과 URN의 두가지 타입이 존재한다.
URL은 HTTP, FTP, JDBC 및 MAILTO 등의 많은 프로토콜에도 사용된다.
URL은 리소스의 네트워크 위치를 식별하는 식별자 역할을 한다.
scheme:[//authority]path[?query][#fragment]
스킴(Scheme)
HTTP, HTTPS, MAILTO, FILE, FTP 등을 의미한다.
권한(Authority)
선택적 필드며 //가 앞에 온다. 다음과 같은 선택적 하위 필드로 구성된다:
ex. ftp://user:password@ftp.example.com:21/files
사용자 정보(Userinfo)
선택 사항이며 사용자 이름과 암호를 포함할 수 있는 하위 컴포넌트를 의미한다.
호스트(Host)
IP 주소 또는 등록된 호스트 도메인 이름을 포함하는 하위 컴포넌트다.
포트(Port)
콜론(:) 뒤에 오는 선택적 하위 컴포넌트다.
경로(Path)
경로에는 슬래시 /로 구분된 일련의 세그먼트가 포함된다. 앞의 GitHub REST API 예제에서는 /licenses가 경로로 포함된다.
쿼리(Query)
선택적 컴포넌트로 물음표(?)가 앞에 온다. 쿼리 컴포넌트는 비계층적 데이터인 쿼리 문자열이다.
쿼리 컴포넌트에서 각 매개변수는 앰퍼샌드(&)로 구분되며, 매개변수 값은 등호(=) 연산자를 사용하여 할당한다.
프래그먼트(Fragment)
선택적 필드로 해시(#)가 앞에 온다. 프래그먼트 컴포넌트는 부속 리스트(secondary resource)를 가리키는 프래그먼트 식별자를 가진다.
브라우저는 이 URL을 로드한 뒤 section1이라는 ID를 가진 HTML 요소로 스크롤하거나 이동한다.
URI는 식별자이고 URL은 식별자 뿐만 아니라 리소스에 도달하는 방법도 명시된다.
URL은 리소스 전체 웹 주소를 나타낸다.
ex. https://example.com/page, ftp://ftp.example.com/file.txt
urn이라는 스킴으로 시작하는 URI 타입을 의미한다.
ex. urn:uuid:123e4567-e89b-12d3-a456-426614174000
잘 사용되지 않는다.
리소스 생성 오퍼레이션과 연결된다.
필터 기준이 GET 호출의 길이 제한을 초과할 정도로 매개변수가 많은 검색 오퍼레이션 상황에서는 POST 메서드를 읽기 오퍼레이션으로 사용하는 예외도 존재한다.
GET 쿼리의 문자열은 256자로 제한되지만(URL 길이 제한), POST 메서드는 본문에 수 많은 검색 조건을 담아 데이터를 요청할 수
있기 때문이다.
하지만 RESTful 원칙을 벗어나므로, 클라이언트와 서버 간의 명확한 규약이 필요하다.
즉, HTTP 메서드의 의도를 오해하지 않도록 API 문서에 이를 명확히 기록해야 한다.
리소스 읽기 오퍼레이션과 연결된다.
요청에 성공적으로 응답하면 200 OK 상태코드가 떨어지고, 응답에 데이터가 포함되지 않은 경우에는 204 No Content로 떨어지게 된다.
리소스 갱신 오퍼레이션과 연결된다.
요청에 성공적으로 응답하면 200 OK 상태코드가 떨어지고, 응답에 데이터가 포함되지 않은 경우에는 204 No Content로 떨어지게 된다.
리소스 부분 갱신 오퍼레이션과 연결된다.
요청에 성공적으로 응답하면 200 OK 상태코드가 떨어진다.
HTTP 상태 코드 | 설명 |
---|---|
200 OK | 이미 생성된 것 이외의 성공적인 요청 |
201 Created | 성공적인 생성 요청 |
202 Accepted | 요청을 받았지만 아직 액션을 취하지 않음. 서버가 요청을 수락했지만 즉시 응답 불가한 경우 사용 |
204 No Content | 데이터가 없는 성공적인 오퍼레이션 |
304 Not Modified | 캐싱(caching)을 사용. 서버가 클라이언트에 리소스가 수정되지 않았다고 응답 |
400 Bad Request | 입력 매개변수가 올바르지 않거나 누락되었거나 요청 자체가 불완전하여 실패한 오퍼레이션 |
401 Unauthorized | 인증되지 않은 요청으로 실패한 오퍼레이션. 인증되지 않음을 의미 (unauthenticated) |
403 Forbidden | 호출이 인가되지 않아 실패한 오퍼레이션 |
404 Not Found | 요청한 리소스가 존재하지 않아 실패한 오퍼레이션 |
405 Method Not Allowed | 요청한 리소스에 대한 메서드가 허락되지 않아 실패한 오퍼레이션 |
409 Conflict | 중복된 생성 오퍼레이션을 시도하는 경우 실패한 오퍼레이션 |
429 Too Many Requests | 사용자가 정해진 시간 내에 너무 많은 요청(rate limiting)을 보내 실패 |
500 Internal Server Error | 서버 에러로 인해 실패한 오퍼레이션. 일반적인 에러 |
502 Bad Gateway | 업스트림 서버 호출 실패로 실패한 오퍼레이션. 외부 연결 문제 발생 |
503 Service Unavailable | 과부하 또는 서버의 예상치 못한 문제로 실패한 오퍼레이션 |
HATEOAS(Hypermedia As The Engine Of Application State)는 REST API 설계의 핵심 개념 중 하나로, API가 단순히 데이터를 제공하는 것뿐만 아니라 다음 작업을 수행할 수 있는 링크를 함께 제공해야 한다는 원칙이다.
다음 작업의 의미는 현재 요청한 데이터 결과에 더 많은 데이터가 있을 경우, 해당 데이터에 접근하기 위한 URL을 제공하는 것을 의미한다. REST API에서는 데이터를 한번에 모두 반환하지 않고, 페이지 단위로 나누어 반환하는 페이징(Pagination) 기법을 자주 사용하기 때문이다.
이 접근 방식은 클라이언트와 서버 간의 상호작용을 동적으로 만들며, 클라이언트가 별도의 사전 지식 없이도 API를 탐색할 수 있도록 도와준다.
클라이언트가 추가적인 링크를 따라가며 필요한 정보를 동적으로 탐색할 수 있음.
RESTful 서비스의 특징으로, API 사용자가 서버와 상호작용을 단순화한다.
하이퍼미디어는 REST 호출 응답으로 수신하는 콘텐츠의 일부로 텍스트, 이미지, 비디오 등의 링크가 포함되어 있다.
하이퍼미디어 링크는 HTTP 헤더나 응답 본문에 포함될 수 있다.
ex. curl -v https://api.github.com/users 요청
Trying 20.207.73.85:443...
Connected to api.github.com (20.207.73.85) port 443 (#0)
GET /users HTTP/2
Host: api.github.com
user-agent: curl/7.78.0
< HTTP/2 200
< server: GitHub.com
< date: Sun, 28 Aug 2022 04:31:50 GMT
< content-type: application/json; charset=utf-8
< link: https://api.github.com/users?since=46; rel="next", https://api.github.com/users{?since}; rel="first"
[
{
"login": "mojombo",
"id": 1,
"node_id": "MDQ6VXNlcjE=",
"avatar_url": "https://avatars.githubusercontent.com/u/1?v=4",
"url": "https://api.github.com/users/mojombo",
"html_url": "https://github.com/mojombo",
"followers_url": "https://api.github.com/users/mojombo/followers",
...
}
]
출처: https://medium.com/@jolle93/representational-state-transfer-92aaf2db68c5
GET /licences
클라이언트(프론트)가 자체적으로 REST URL을 구성할 필요가 없다.
엔드포인트 경로의 모든 업그레이드가 자동으로 처리되므로 클라이언트와 개발자를 위한 업그레이드가 쉽다.
ex. fetch("https://api.example.com/users/1/orders")
클라이언트는 서버의 URL 구조를 정확히 알고 있어야 하고, 이를 코드에 직접 작성해야 한다.
만약 서버의 URL 경로가 변경된다면, 클라이언트 코드를 수정하고 다시 배포해야 한다.
HATEOAS 적용시
// 서버측
{
"links": [
{ "rel": "orders", "href": "https://api.example.com/users/1/orders" }
]
}
// 클라이언트측
const ordersLink = response.links.find(link => link.rel === "orders").href;
fetch(ordersLink);
애플리케이션은 여러 버전의 API를 지원해야 한다.
버전 관리는 항상 사용해야 한다.
API 버전은 다음 방법들로 관리한다.
헤더 사용
API 버전을 명시하는 Accept 헤더를 추가한다.
Accept: application/vnd.github.v3+json
- +json은 필수는 아니지만, 데이터 형식을 명확히 하기 위해 사용하는 것이 좋다.
- 특히, API가 여러 데이터 형식을 지원하거나 확장 가능성을 고려한다면, +json을 명시적으로 추가하는 것이 베스트 프랙티스다.
장점
클라이언트가 특정 버전을 명시하지 않으면, 서버는 기본 버전을 제공.
헤더를 사용해 명시적으로 버전을 설정하면, 클라이언트가 최신 API를 사용하는지 확인 가능.
단점
REST 클라이언트가 API 업그레이드 후 변경되지 않을 경우 기능 중단 위험이 있음.
클라이언트가 헤더를 잘못 설정하거나 누락하면 의도한 결과를 얻지 못할 수 있음.
엔드포인트 경로 사용
엔드포인트 경로 자체에 버전(v1)을 추가한다.
https:://demo.app/api/v1/persons
장점
URL만 봐도 현재 사용하는 API 버전을 쉽게 파악할 수 있다.
기존 버전(v1)은 그대로 유지하면서, 새로운 버전(v2)을 도입할 수 있다.
클라이언트는 특정 버전의 기능만 테스트하거나 비교할 수 있다.
단점
URL에 버전을 포함하는 방식이므로, 기본적으로 사용할 버전을 설정하기 어렵다.
버전 정보가 추가되면서 URL 경로가 길어질 수 있다.
여러 버전의 엔드포인트를 관리해야 하므로, 서버 측의 관리 부담이 늘어날 수 있다.
하지만, 기본 버전 설정 문제를 해결하기 위해, 버전을 생략한 URL(https://demo.app/api/persons)을 특정 버전(v1)으로 리다이렉트(포워딩) 할 수 있다.
https://demo.app/api/persons → https://demo.app/api/v1/persons
/customers/1/addresses
위와 같이 두 개 이상의 리소스가 계층적(parent-child) 관계를 가질 때, 이를 RESTful 엔드포인트로 표현하는 방식이다.
부모 리소스와 자식 리소스 간의 관계를 URL 구조에서 명시적으로 나타낸다.
customers는 부모 리소스이다.
addresses는 고객(customer 1)의 주소에 해당하는 자식 리소스이다.
장점
URL 자체가 부모-자식 관계를 명확히 드러내므로, 클라이언트는 리소스 구조를 쉽게 이해할 수 있다.
부모 리소스 하위에 자식 리소스를 배치함으로써 데이터 계층 구조를 직관적으로 설계할 수 있다.
단점
부모와 자식 리소스의 관계를 URL에 모두 포함하므로, 엔드포인트의 URL이 길어질 수 있다.
특정 자식 리소스에 직접 접근하려면 부모 리소스의 ID를 알아야 한다.
중첩된 리소스 구조와 다르게, 부모 리소스를 명시하지 않고 자식 리소스를 독립적으로 접근하는 방식이다.
장점
리소스가 독립적일 때 간단한 구조로 설계가 가능하다.
자식 리소스가 여러 부모와 관계를 가질 수 있는 경우, 더 유연한 설계가 가능하다.
단점
MSA에서는 일반적을 분리된 리소스를 선택하는 것이 더 합리적이다.
중첩된 리소스 사용
리소스 간의 관계가 강하게 연결되어 있고, 자식 리소스가 부모 리소스 없이 의미가 없는 경우.
ex. 고객의 주소, 주문의 결제 내역 등.
분리된 리소스 사용
자식 리소스가 독립적으로 의미가 있거나, 부모 리소스와의 관계가 중요하지 않은 경우.
ex. /payments/1은 특정 주문과의 관계를 드러내지 않고도 결제 정보를 조회 가능.
암호화된 통신을 위해 항상 HTTPS를 사용한다.
항상 OWASP의 주요 API 보안 위협 및 취약점을 살핀다.
문서는 쉽게 접근할 수 있어야 하고 각 버전의 최신 내용을 담고 있어야 한다.
샘플 코드, 예제를 함께 제공한다.
변경 로그 또는 릴리스 로그에는 영향을 받는 모든 라이브러리가 나열되어야 한다.
일부 API가 더 이상 사용되지 않는 경우에는 교체되는 API 또는 해결 방법을 문서 안에 자세히 설명해야 한다.
HTTP 상태 코드 | 설명 |
---|---|
200 OK | 이미 생성된 것 이외의 성공적인 요청 |
201 Created | 성공적인 생성 요청 |
202 Accepted | 요청을 받았지만 아직 액션을 취하지 않음. 서버가 요청을 수락했지만 즉시 응답 불가한 경우 사용 |
204 No Content | 데이터가 없는 성공적인 오퍼레이션 |
304 Not Modified | 캐싱(caching)을 사용. 서버가 클라이언트에 리소스가 수정되지 않았다고 응답 |
400 Bad Request | 입력 매개변수가 올바르지 않거나 누락되었거나 요청 자체가 불완전하여 실패한 오퍼레이션 |
401 Unauthorized | 인증되지 않은 요청으로 실패한 오퍼레이션. 인증되지 않음을 의미 (unauthenticated) |
403 Forbidden | 호출이 인가되지 않아 실패한 오퍼레이션 |
404 Not Found | 요청한 리소스가 존재하지 않아 실패한 오퍼레이션 |
405 Method Not Allowed | 요청한 리소스에 대한 메서드가 허락되지 않아 실패한 오퍼레이션 |
409 Conflict | 중복된 생성 오퍼레이션을 시도하는 경우 실패한 오퍼레이션 |
429 Too Many Requests | 사용자가 정해진 시간 내에 너무 많은 요청(rate limiting)을 보내 실패 |
500 Internal Server Error | 서버 에러로 인해 실패한 오퍼레이션. 일반적인 에러 |
502 Bad Gateway | 업스트림 서버 호출 실패로 실패한 오퍼레이션. 외부 연결 문제 발생 |
503 Service Unavailable | 과부하 또는 서버의 예상치 못한 문제로 실패한 오퍼레이션 |
HTTP는 클라이언트와 서버 간의 효율적인 데이터 전송을 위해 캐싱 메커니즘을 제공한다.
REST API에서 응답 헤더에 캐싱 관련 정보를 포함하면, 클라이언트는 해당 정보를 사용해 리소스의 변경 여부를 확인하고, 필요하지 않으면 새 요청 없이 캐싱된 데이터를 활용할 수 있다.
ETag (Entity Tag)
ETag는 리소스(응답 객체)의 해시 값 또는 체크섬 값을 포함하는 특별한 헤더 값이다.
리소스가 변경되지 않았을 경우, 클라이언트가 캐싱된 데이터를 계속 사용할 수 있도록 하는 역할을 한다.
동작 방식
서버단에서의 ETag 생성
서버는 리소스의 현재 상태를 기준으로 ETag값을 생성하고, 응답 헤더에 포함하여 클라이언트에 전송한다.
ex. ETag: "abc123"
클라이언트 요청
클라이언트는 캐싱된 데이터를 사용할 때, If-None-Match 헤더에 기존 ETag 값을 포함하여 서버에 요청한다.
ex.
GET /resource
If-None-Match: "abc123"
서버 응답
서버는 ETag 값을 비교하여 리소스가 변경되지 않았을 경우 304 Not Modified 상태 코드를 반환한다.
리소스가 변경되었으면, 새로운 리소스와 함께 새로운 ETag 값을 반환한다.
장점
정확한 비교
리소스의 해시값을 기반으로 변경 여부를 확인하므로, 매우 정밀한 검증 가능.
불필요한 데이터 전송 방지
리소스가 변경되지 않았을 경우, 데이터를 다시 전송할 필요 없이 빠른 응답 가능.
Last-Modified
Last-Modified는 리소스가 마지막으로 수정된 시간을 나타내는 헤더 값이다.
클라이언트가 리소스의 변경 여부를 시간 기반으로 확인할 수 있도록 하는 역할을 한다.
동작 방식
서버단에서 Last-Modified 생성
서버는 리소스의 마지막 수정 시간을 응답 헤더에 포함하여 클라이언트에 전송한다.
ex. Last-Modified: Wed, 21 Oct 2015 07:28:00 GMT
클라이언트 요청
클라이언트는 If-Modified-Since 헤더에 기존 Last-Modified 값을 포함하여 서버에 요청한다.
ex.
GET /resource
If-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT
서버 응답
서버는 Last-Modified 값을 비교하여 리소스가 변경되지 않았을 경우 304 Not Modified 상태 코드를 반환한다.
리소스가 변경되었으면, 새로운 리소스와 함께 최신 Last-Modified 값을 반환한다.
장점
간단한 구현
타임스탬프를 기반으로 비교하므로, ETag보다 구현이 단순하다.
캐싱 효율성 증가
리소스가 수정되지 않았으면, 빠르게 캐싱된 데이터 사용 가능하다.
ETag와 Last-Modified의 비교
항목 | ETag | Last-Modified |
---|---|---|
기준 | 리소스의 해시값 또는 체크섬 | 리소스의 마지막 수정 시간 |
비교 방식 | 해시값 비교 | 타임스탬프 비교 |
정확성 | 매우 정확 (작은 변경도 감지 가능) | 상대적으로 덜 정확 (초 단위 비교) |
구현 복잡성 | 복잡 (해시 계산 필요) | 간단 (타임스탬프 제공) |
적용 사례 | 리소스의 정밀한 변경 검증이 필요한 경우 | 단순한 시간 기반 검증으로 충분한 경우 |
캐싱 효율성 | 매우 높음 | 보통 |
적합한 상황 | 데이터가 자주 변경되지 않거나, 정확한 검증이 필요한 경우 | 간단한 검증으로 충분한 경우 |
API 남용을 방지하고, 서버 부하를 방지하기 위해 중요하다.
429 Too Many Requests
클라이언트가 설정된 요청량 제한을 초과했을 때 반환되는 상태 코드이다. 클라이언트는 일정 시간이 지난 후 다시 요청해야 한다.
429 코드와 함께 아래 응답 헤더들을 사용하여 클라이언트가 요청 제한을 이해하고 관리할 수 있도록 한다.
X-RateLimit-Limit
현재 시간 내에 허용된 최대 요청 수를 의미한다.
X-RateLimit-Remaining
현재 시간 내에 남은 요청 가능 수를 의미한다.
X-RateLimit-Reset
제한이 초기화될 시간(초 단위)을 의미한다..
X-RateLimit-Used
현재 시간 내에 사용된 요청 수를 의미한다.