📝

HTTP 요청

1. 현대 인터넷의 공용어, HTTP

2대의 컴퓨터가 인터넷으로 연결되어 있고 웹을 통해 정보를 교환한다고 가정해보자. 한 컴퓨터에는 웹 브라우저(Web browser)가 설치되어 있고, 나머지 한대에는 웹 서버(Web server)가 설치되어 있다. 웹 서버 컴퓨터는 주소를 가지고 있으며 그 컴퓨터 안에는 index.html이라는 파일이 저장되어 있다.
이때 웹 브라우저 주소창에 웹 서버 컴퓨터의 주소를 입력하면 어떤 일이 벌어질까? 웹 브라우저 컴퓨터는 웹 서버 컴퓨터에 신호를 보내서 index.html 파일을 요청하고, 웹 서버 컴퓨터는 가지고 있는 해당 파일의 정보를 웹 브라우저 컴퓨터로 보내 브라우저 화면에 index.html 페이지를 띄워준다.
여기서 웹 브라우저 컴퓨터를 클라이언트(Client)라고 부르고 웹 서버가 설치된 컴퓨터를 서버(Server)라고 부른다. 전 세계 웹 브라우저와 서버는 HTTP를 통해 대화한다. 1989년 팀 버너스 리(Tim Berners Lee)에 의하여 처음 설계된 HTTP(HyperText Transfer Protocol)는 HTML과 같은 하이퍼미디어 문서를 전송하기 위해 사용되는 통신규약이다.
 

1.1 HTTP 트랜잭션

HTTP는 HTML 문서나 이미지 파일과 같은 리소스를 가져오기 위해 사용하므로 일반적으로 HTTP 요청은 웹 브라우저에 의해 시작된다. 웹 브라우저는 텍스트, 이미지, 비디오 등과 같이 여러 리소스를 완전한 문서로 재구성한다. 예를 들어, HTML 문서를 한 번의 트랜잭션으로 가져온 뒤 첨부된 이미지와 그래픽 조각 등을 가져오기 위해 추가로 HTTP 트랜잭션을 수행한다.
HTTP 트랜잭션은 요청 명령과 응답 결과로 구성된다. 이 상호작용은 HTTP 메시지를 통해 이루어진다.

1.2 HTTP 메시지

HTTP 메시지는 단순한 줄 단위의 문자열이다. HTTP는 초기 버전의 단순한 외형을 유지하면서 확장성 있게 진화해왔다. 초기 HTTP는 파일 요청 문제가 발생했을 때 사람이 해결할 수 있도록 파일 내부에 문제에 대한 설명과 함께 되돌아오기도 했다(HTTP/0.9). 이처럼 HTTP 메시지는 사람이 읽고 쓰기 쉽다는 특징이 있다.

1.2.1 HTTP 버전

HTTP의 버전은 여러 가지가 있지만, 그중 표준화된 프로토콜인 HTTP/1.1과 HTTP/2.0의 특징은 다음과 같다.
  • HTTP/1.1
    • 요청과 응답이 순차적으로 이루어진다. 이에 따라 요청문서 내에 포함된 많은 양의 리소스를 처리하려면 요청할 리소스의 개수에 비례하여 대기시간이 길어질 수 있다. 예를 들어, 한 웹 페이지에 여러 아이콘 이미지가 있다면 그 개수만큼 파일 요청을 하게 된다.
  • HTTP/2.0
    • 2022년 5월 기준으로 모든 웹사이트의 46.4%가 HTTP/2.0 버전을 사용하고 있다. HTTP/2.0은 HTTP/1.1과 완전히 다른 HTTP가 아니라 속도 향상이라는 목적에 맞춰 개선된 버전이다. 응답은 요청 순서에 상관없이 이루어지고 HTTP 메시지를 압축하여 전송한다.

1.2.2 요청 메시지의 구조

HTTP 요청 메시지 예시HTTP 요청 메시지 예시
HTTP 요청 메시지 예시
요청 메시지는 다음과 같이 구성된다. 위 사진은 Wireshark라는 프로그램을 통해 google.com(172.217.161.238)에 접속했을 때 전달되는 HTTP 요청 메시지를 확인한 것이다.
  • HTTP 요청 메서드
    • 클라이언트가 서버에게 리소스에 대해 수행해주길 바라는 동작이다. 즉, 클라이언트가 웹 서버에 요청하는 목적과 그 종류를 알리는 수단이 HTTP 요청 메서드다. ‘GET’, ‘POST’와 같이 한 단어로 되어 있다.
      메서드 종류
      설명
      GET
      리소스 취득을 목적으로 클라이언트가 서버에게 URL에 해당하는 자료의 전송을 요청한다.
      POST
      서버에 데이터를 보내기 위해 사용한다. 주로 HTML 폼(Form)을 지원하기 위해 사용된다. 채워진 폼에 담긴 데이터는 서버로 전송되며, 서버는 이를 모아서 필요로 하는 곳에 보낸다.
      PUT
      GET 메서드가 서버로부터 문서를 읽어 들이는 데 반해 PUT 메서드는 서버에 문서를 생성 또는 교체한다. 서버는 요청 메시지의 본문을 가지고 요청 URL의 이름대로 새 문서를 만들거나, 이미 URL이 존재한다면 메시지의 본문으로 교체한다.
      PATCH
      리소스의 일부분을 수정할 때 사용된다. PUT 메서드는 문서 전체의 완전한 교체만을 허용하는 반면, PATCH 메서드는 수정하고 싶은 부분만 수정할 수 있다.
      DELETE
      클라이언트가 서버에게 URL로 지정한 리소스를 삭제해줄 것을 요청한다.
  • 경로(Path)
    • 요청 대상이 되는 리소스의 경로를 의미한다. 가져오려는 리소스의 경로는 프로토콜(http://), 도메인(developer.mozilla.org), 또는 TCP 포트(80)인 요소들을 제거한 리소스의 URL이다.
  • HTTP 버전
    • 이 메시지에서 사용 중인 HTTP의 버전을 나타낸다. 정확하게는 지원할 수 있는 가장 높은 HTTP 버전을 의미한다. 예를 들어, 응답 메시지의 HTTP 버전이 HTTP/1.1이라는 것은 응답 메시지를 보낸 애플리케이션이 HTTP/1.1까지 이해할 수 있다는 것을 의미한다.
  • 헤더(Headers)
    • 요청 메시지에 대한 추가적인 정보를 담는다. 기본적으로 이름-값 쌍의 목록이다. 헤더의 예시는 다음과 같다.
      헤더의 예
      설명
      Host: developer.mozilla.org
      요청하려는 서버 호스트 이름과 포트번호를 나타낸다.
      Accept-Language: fr
      클라이언트가 지원할 수 있는 언어를 나열한다.
      Accept: image/gif, image/jpeg, text/html
      클라이언트가 처리 가능한 MIME 타입* 종류를 나열한다.
      Content-type: image/gif
      메시지 본문 리소스의 media 타입*을 명시한다.
      Content-length: 15040
      메시지 본문의 바이트 단위 크기를 의미한다.
      Accept 헤더에서 나타내는 MIME 타입은 무엇일까? 다목적 인터넷 메일 확장자(Multipurpose internet mail extensions)라는 뜻으로, 처음에는 메일의 첨부 파일에 사용되어서 붙여진 이름이다. 서버는 웹에서 전송되는 객체 각각에 MIME 타입이라는 데이터 포맷 라벨을 붙인다. 웹 브라우저는 그 라벨을 통해 다룰 수 있는 객체인지 확인한 후 실행 준비를 할 수 있다. MIME 타입은 월드와이드웹이 보편화되면서 ‘media type’이라는 이름으로 확장되었다.
  • 본문(Body)
    • 요청 메시지에서 본문은 선택적으로 사용된다. ‘POST’, ‘PUT’ 메서드 등은 본문을 가진다.
 

1.2.3 응답 메시지의 구조

HTTP 응답 메시지 예시HTTP 응답 메시지 예시
HTTP 응답 메시지 예시
응답 메시지는 다음과 같이 구성된다. 위 사진은 1.2.1 요청 메시지의 구조에 대한 응답 메시지를 Wireshark를 통해 확인한 것이다.
  • HTTP 버전
    • 요청 메시지와 내용이 동일하다.
  • 상태 코드(Status code)
    • 메서드가 서버에게 무엇을 해야 하는지 말해주는 것처럼, 상태코드는 요청이 성공적으로 완료되었는지를 나타낸다. 상태코드는 크게 다섯 가지로 나뉜다. 100번대 상태코드는 정보 상태, 200번대는 성공 상태, 300번대는 리다이렉션 상태, 400번대는 클라이언트 에러, 500번대는 서버 에러를 의미한다. 가장 흔한 상태 코드 중 몇 가지를 나열하면 다음과 같다.
      상태코드
      설명
      200
      서버가 요청을 제대로 처리했음을 의미한다.
      301
      요청한 리소스가 주어진 URL로 완전히 옮겨졌다는 것을 의미한다.
      302
      클라이언트가 요청한 리소스가 주어진 URL에서 일시적으로 이동되었음을 의미한다.
      401
      인증이 필요한 요청임을 의미한다. 서버는 로그인이 필요한 페이지에 대해 이 요청을 전달할 수 있다.
      403
      요청이 서버에 의해 거부되었음을 의미한다. 일반적으로 서버가 거절의 이유를 숨기고 싶을 때 사용된다.
      404
      요청한 리소스를 서버에서 찾을 수 없음을 의미한다.
      500
      서버에 오류가 발생하여 요청을 수행할 수 없음을 의미한다.
  • 사유 구절(Status message)
    • 사람이 HTTP 메시지를 이해하는 데 도움이 되는 텍스트 설명이다. 상태 코드에 대해 간략한 정보를 제공하지만 그 내용을 완전히 신뢰할 수는 없다는 특징이 있다.
  • 헤더(Headers)
    • 헤더에는 일반 헤더, 요청 헤더, 응답 헤더, 엔터티 헤더 총 4가지 종류가 있다. 일반 헤더는 요청과 응답 모두에 사용할 수 있지만 최종적으로 전송되는 본문(Body) 데이터와는 무관하다. 요청 헤더는 요청하는 리소스나 클라이언트에 대한 자세한 정보를 포함하며, 응답헤더는 서버에 대한 정보와 같이 응답에 대한 부가적인 정보를 갖는다. 엔터티 헤더는 콘텐츠 길이나 MIME 타입과 같이 엔터티 본문(Body)에 대한 자세한 정보를 포함한다.
  • 본문(Body)
    • 가져온 리소스를 담고 있다.

2. JSON

HTTP 통신을 하며 필요한 정보를 주고받고, 응답으로 받은 데이터를 사용하기 위해서는 이때 사용되는 데이터 뭉치의 형식을 알아야 한다. 과거에는 XML이라는 데이터 포맷이 자주 사용되었다. XML은 HTML과 같은 마크업 언어이며, 사용자가 임의로 태그를 작성할 수 있다는 특징이 있다. 이처럼 XML은 태그를 기반으로 작성되기 때문에 JSON에 비해 가독성이 좋지 않고 용량이 크다는 단점이 있어 현재는 JSON이 XML을 대체하게 되었다.
<book> <title>나라사랑 비동기사랑</title> <author>김사자</author> <publish_date>2022-10-10</publish_date> <category>IT</category> </book>
XML 예시
JSON(JavaScript Object Notation)은 이름에서 알 수 있듯 자바스크립트의 객체 표기법을 기반으로 만들어진 형식이다. 하지만 다른 프로그래밍 언어에서도 JSON의 데이터를 핸들링 할 수 있는 라이브러리가 있어 범용적으로 사용되고 있으며 사람과 기계 모두 읽기 편하다는 장점이 있다.

2.1 JSON 표기법

자바스크립트의 객체처럼 키와 값으로 구성되어 있으며, 키와 문자열은 반드시 큰따옴표로 묶어야 한다. 값으로는 문자열, 숫자, 배열, 객체, boolean, null과 같은 자료형이 가능하지만 undefined, 함수, Symbol은 사용할 수 없다.
{ "name": "Unicorn Startup", "location": "Seoul", "business": ["e-commerce", "fashion"] "established": 2020 }

2.2 JSON.stringify

stringify 메서드는 객체를 JSON 문자열로 변환한다. 서버가 이해할 수 있는 JSON 형식으로 바꿔주는 이 과정을 직렬화(serializing)라고 한다.
const profile = { name: "Lee", age: 30, language: ["JavaScript", "Python"] } const str = JSON.stringify(profile); console.log(str);
JSON.stringify(profile) 콘솔 출력 결과JSON.stringify(profile) 콘솔 출력 결과
JSON.stringify(profile) 콘솔 출력 결과

2.3 JSON.parse

반대로, parse 메서드는 JSON 문자열을 객체 형식으로 변환한다. 서버에서 받아온 문자열의 데이터를 가공할 때 사용되며, 이 과정을 역직렬화(deserializing)라고 한다. stringify 예제코드에서 JSON으로 변환된 문자열을 다시 객체로 바꿔보자.
const obj = JSON.parse(str); console.log(obj);
JSON.parse(str) 콘솔 출력 결과JSON.parse(str) 콘솔 출력 결과
JSON.parse(str) 콘솔 출력 결과

3. Ajax

3.1 Ajax의 등장

Ajax는 ‘Asynchronous JavaScript and XML’의 약자로, HTTP 통신을 하며 비동기적으로 데이터를 교환할 수 있는 기능을 의미한다. 명칭에 XML이 포함되어 있긴 하지만, 개발 당시 사용되었던 주된 데이터 포맷이 XML이었을 뿐 현재는 JSON 형식이 주로 사용되고 있다.
Ajax가 등장하기 이전에는 클라이언트가 서버로부터 HTML 전체를 전송받기 때문에 변경이 필요 없는 부분까지 렌더링해야 하는 문제가 있었다. 하지만 Ajax의 등장으로 웹페이지의 일부분만 렌더링할 수 있게 되었고 새로고침 없이 비동기적인 갱신과 부드러운 화면 전환이 가능하게 되었다. 당시로는 혁신적인 기능이지만, 마이크로소프트가 Ajax의 개념을 처음 소개한 1999년 당시에는 ActiveX 확장 프로그램을 따로 설치해야 해서 번거롭고 성능이 느려 큰 주목을 받지 못했다. 하지만 2000년대 초반 플러그인 설치 없이 개발된 구글의 GMail과 구글 맵스의 성공을 보며 사람들은 Ajax의 가능성을 보기 시작했고 Ajax라는 용어가 공식적으로 사용되기 시작했다.
Ajax가 인기를 얻게 된 이후로 개발자들은 이 기능을 더 효율적으로 사용하기 위해 jQuery 내에 ajax 메서드를 도입하게 되었고 2015년 ES6에서는 동일한 기능을 하는 Fetch API가 등장했다. Ajax는 뒤에서 설명될 XMLHttpRequest 객체를 기반으로 작동하는데, 써야 하는 코드의 양도 많고 복잡해서 현재는 Ajax보다 fetch API가 더 보편적으로 사용되고 있다. 하지만 fetch는 폴리필 없이 인터넷 익스플로러에서 지원되지 않기 때문에 Ajax에 대한 내용은 크로스 브라우징을 위해서, 그리고 뒤에 나올 fetch의 문법을 이해하는 데도 도움이 될 것이다.
www.caniuse.com에서 fetch 검색 시www.caniuse.com에서 fetch 검색 시
www.caniuse.com에서 fetch 검색 시

3.2 여러 비동기 기술의 비교

3.2.1 네트워크 탭으로 동작 확인하기

Ajax는 프로그래밍 언어가 아니라 웹페이지로부터 비동기적으로 서버와 브라우저 간의 데이터 통신을 돕는 기술이다. 페이지를 다시 로딩하지 않고 페이지를 업데이트하거나, 페이지가 로딩된 이후, 웹 서버로부터 데이터를 읽을 수 있으며, 사용자의 동작에 영향을 주지 않고 백그라운드에서 웹 서버에 데이터를 보내는 행위 등을 할 수 있다.
다음 그림을 통해 이해해보자.
www.google.com에서 ‘what is Ajax?’를 검색 시www.google.com에서 ‘what is Ajax?’를 검색 시
www.google.com에서 ‘what is Ajax?’를 검색 시
위 그림처럼 크롬 브라우저 내에서 개발자 도구(F12)를 클릭하여 Network 탭을 들어가 보자. 그리고 ‘What is Ajax’라고 검색했을 때, 맨 위에 뜨는 페이지에 접속해 보자.
What is Ajax - W3SchoolsWhat is Ajax - W3Schools
What is Ajax - W3Schools
위 그림을 보면 페이지가 변경되면서 데이터들을 받아오는 것을 볼 수 있다. 이건 동기적으로 실행되는 것이다.
what is 까지 검색어를 입력했을 때 나타나는 비동기 데이터what is 까지 검색어를 입력했을 때 나타나는 비동기 데이터
what is 까지 검색어를 입력했을 때 나타나는 비동기 데이터
notion imagenotion image
검색창에 ‘what is’까지 검색하고 기다리면 추천 키워드들이 자동 완성되는 것을 볼 수 있다. 이것 또한 서버로부터 데이터를 받아오는 행위지만 페이지가 깜빡이거나 하며 페이지가 바뀌지 않고 비동기적으로 서버로부터 데이터를 받아오는 것을 확인할 수 있다. (네트워크 탭에서 추가적으로 데이터를 받는 것을 확인할 수 있다.)

3.2.2 XMLHttpRequest와 jQuery 사용 비교

비동기 실행을 위해서 XMLHttpRequest(이하 XHR로 표기) 객체를 많이 사용했었다. XHR은 비동기 통신을 위한 브라우저에서 제공하는 객체이고 fetchaxios가 나오기 이전에 사용된 기술이다. 또한, jQuery를 많이 사용한 시절에는 XHR을 간단하게 사용할 수 있었다. 코드를 눈으로 보며 한번 이해해 보자.
const xhr = new XMLHttpRequest(); // (1) xhr.onreadystatechange = function() { // (2) if (xhr.readyState === xhr.DONE) { // (3) if (xhr.status === 200 || xhr.status === 201) { // (4) console.log(xhr.responseText); } else { console.error(xhr.responseText); } } }; xhr.open('GET', 'https://jsonplaceholder.typicode.com/users'); xhr.send();
XHR을 이용해 데이터를 불러오는 코드
위 코드를 분석해보자.
(1) XMLHttpRequest 객체를 사용하기 위해선 객체를 불러와 생성자 함수를 이용해 인스턴스를 만들어 주어야 한다.
(2) onreadystatechange프로퍼티는 XMLHttpRequest 객체의 readyState 프로퍼티 값이 변할 때 마다 자동으로 호출되는 함수를 설정한다. 속성이 변경될 때 마다 발생하는 이벤트라고 볼 수 있다.
(3) readyStateXMLHttpRequest 객체의 현재 상태를 나타내며, 다음과 같은 주기로 변화한다.
  1. UNSENT (숫자 0) : XMLHttpRequest 객체가 생성됨.
  1. OPENED (숫자 1) : open() 메서드가 성공적으로 실행됨.
  1. HEADERS_RECEIVED (숫자 2) : 모든 요청에 대한 응답이 도착함.
  1. LOADING (숫자 3) : 요청한 데이터를 처리 중임.
  1. DONE (숫자 4) : 요청한 데이터의 처리가 완료되어 응답할 준비가 완료됨.
readyState 메서드의 상태 결과를 콘솔 창에 출력readyState 메서드의 상태 결과를 콘솔 창에 출력
readyState 메서드의 상태 결과를 콘솔 창에 출력
(4) status 프로퍼티는 서버의 문서 상태를 나타낸다.
- 200 : 서버에 문서가 존재함.
- 404 : 서버에 문서가 존재하지 않음.
(5), (6) 그리고 주로 사용되는 4가지 메서드는 다음과 같다.
notion imagenotion image
이번엔, jQuery를 사용해서 어떻게 비동기 실행 코드를 만들 수 있는지 확인해 보자.
const url = "https://jsonplaceholder.typicode.com/users"; $.ajax({ url: url, type: "GET", success: function onData(data) { console.log(data); }, error: function onError(error) { console.error(error); }, });
같은 코드를 jQuery를 이용했을 때의 코드
XHR만을 이용한 코드와 jQuery를 이용한 코드를 작성해 비교해 보았다. jQuery에서는 XHR$.ajax()라는 메서드를 사용해 간단하게 비동기 코드를 작성했으며, 가독성을 높였다. 그리고 편리하게 사용할 수 있었다. 또한 1번 코드의 경우 여러 메서드를 이용해야 한다. 그래서 jQuery를 많이 사용했던 환경에서는 XHR의 사용이 많았지만, ES6Promise라는 객체가 나오고 jQuery의 사용량이 줄어들면서 XHR의 사용 또한 줄어들게 되었다. ‘Promise’를 통해 기존의 여러 문제점들을 해결할 수 있기 떄문이다.
문제점의 예시로 콜백지옥을 알아보기 위해 XHR 코드를 다시 확인해 보자. 현재는 한 번의 요청을 ‘if문’ 으로 요청했지만, 여러 번의 요청과 좀 더 세부적인 내용을 요청할 경우, 코드의 양이 늘어나고 ‘depth’ 또한 여러 단계로 늘어나게 될 것이다. 그렇게 된다면, 더욱 가독성이 떨어지게 된다. 그래서 이러한 콜백 지옥을 벗어나기 위해 XHR을 대신해서 ES6에서는 ‘Promise’를 기반으로 사용할 수 있는 브라우저 객체인 fetch와 라이브러리인 axios가 등장했다.

3.2.3 XHR과 fetch의 차이

XHLHttpRequset는 1999년 IE5.0 ActiveX 컴포넌트로 처음 모습을 드러냈다. 그리고 여러 개발을 거쳐 2006년에는 웹 표준이 되며 여러 브라우저에서도 사용이 가능해졌다. 반면 fetch는 2015년 ES6와 함께 소개된 비교적 최신 기술이다.
fetchXMLHttpRequest보다 더 좋은 대체제의 역할을 하며, ‘서비스 워커’ 등 다른 기술에서도 쉽게 사용할 수 있는 API이다. 또한 ’CORS와 같이 ‘HTTP’와 관련된 다른 개념들을 한곳에 모아서 정의할 수 있는 논리적인 장소도 제공한다.
fetch가 반환하는 ‘Promise’ 객체는 404, 500과 같은 HTTP 오류 상태를 수신해도 거부되지 않으며, fetch의 프로미스는 서버에서 헤더를 포함한 응답을 받는 순간 정상적으로 이행한다. 대신, 응답의 상태가 200-299를 벗어날 경우 ok 속성이 false로 설정된다. 프로미스가 거부되는 경우는 네트워크 연결이 실패하는 경우를 포함, 아예 요청을 완료하지 못한 경우로 한정된다.

3.2.4 fetch와 axios의 차이

fetch는 XHR을 대체하기 위해 더 나은 역할을 수행해 왔다. 개발자에게 효율성이란 가장 근간이 되는 중요한 부분이다. 그래서 fetch를 더 효율적으로 편리하게 사용하기 위해 axios가 개발되었다고 이해하면 쉽다.
fetch는 자바스크립트 내에 기본적으로 내장되어있는 함수이지만, axios는 서드파티 라이브러리로서 패키지를 따로 설치해주어야 하는 차이점이 있다. fetch는 데이터를 ‘json’ 형식으로 받기 위해 json()메서드를 사용해 주어야 하지만, axios에서는 기본적인 데이터 타입을 ‘json’으로 가져갈 수 있어서 코드를 줄일 수 있다. 또한, fetch에서는 JSON 데이터를 문자열로 반환하기 위해 JSON.stringify() 와 같은 메서드를 사용 해주지만 axios에서는 그럴 필요가 없다. 자동으로 문자열로 변환해주며, ‘json’ 데이터 타입을 미리 지정해줄 수도 있다.
에러 처리 등 여러 부분에서 간결하게 사용가능하기에 axios 또한 많이 사용하고 있다. 개인의 선택사항이라고 볼 수 있다.

4. REST API

지금까지 살펴본 것과 같이 우리는 XMLHttpRequestfetch, axios API를 통해 비동기적으로 데이터를 주고받는 ajax 통신을 할 수 있다. 이때 서버는 클라이언트로부터 리퀘스트를 받아서 적절한 처리를 한 후 리스폰스를 줄 수 있도록 미리 설계해야 하며, 이렇게 설계된 매커니즘을 Web API라고 한다.
https://test.api.weniv.co.kr/mall’ URI로 API 요청을 보내 통신을 체험해볼 수 있다. Web API를 이용하면, 서버에 리소스를 생성, 요청 , 교체, 삭제(CRUD) 할 수 있다. 아래 이미지는 Web API를 이용해 서버에 리소스를 요청하여 받은 리스폰스를 캡쳐한 예시이다.
GET 요청의 리스폰스 예시GET 요청의 리스폰스 예시
GET 요청의 리스폰스 예시
REST API란 REST 제약 조건을 준수하여 Web API를 설계하기 위해 노력하는 일종의 규칙을 의미한다. REST는 ‘Representational State Transfer’의 약자로 클라이언트가 서버의 리소스에 접근하는 방식을 규정한 아키텍처 스타일이다. REST API는 HTTP 프로토콜의 인프라를 그대로 사용하기 때문에 별도의 인프라를 구축할 필요가 없으며, HTTP 표준 프로토콜에 따르는 모든 플랫폼에서 사용할 수 있다. 또한 REST API 메시지는 리소스에 대한 URI와 HTTP 요청 메서드로 이루어져 있기 때문에, 의도하는 바를 명확하게 파악할 수 있으며 서버와 클라이언트의 역할을 명확하게 분리해줄 수 있다는 장점이 있다. 하지만 HTTP 요청 메서드 만을 한정적으로 사용할 수 있다는 단점 또한 존재한다.
💡
이런 REST의 기본 원칙을 잘 지킨 서비스를 “RESTful 서비스”라고 하며, 까다롭고 복잡한 설계원칙 때문에 REST API 설계원칙을 모두 지켜서 실제 프로덕트가 나오는 경우는 드물다.

4.1 REST architecture란?

미국의 컴퓨터 과학자이자 아파치 HTTP 서버 프로젝트의 공동 설립자인 로이 필딩(Roy Fielding)이 박사 논문 'Architectural Styles and the Design of Network-based Software Architectures'에서 제시한 개념으로 웹이 갖추어야 할 이상적인 아키텍처를 의미한다.

4.2 REST API의 구성

REST API는 다음 3가지 요소로 구성되며, 이 요소들만으로도 HTTP 요청의 내용을 이해할 수 있어야 한다.
  • 리소스(Resource) 리소스를 의미하며 URI(엔드포인트)로 표현한다.
  • 행위(Verb) 리소스에 대한 행위를 의미하며 HTTP 요청 메서드로 표현한다.
  • 표현(Representations) 리소스에 대한 행위의 구체적인 내용을 의미하며 전송되는 데이터로 표현한다.
💡
같은 리소스여도 여러가지 표현이 있을 수 있다. 서버에 리소스를 요청했을 때, 클라이언트는 여러가지 데이터 형식으로 리소스를 받을 수 있다. 이 데이터 형식을 리소스의 표현이라고 한다. 어떤 특정 데이터를 나타내는 추상적인 개념인 리소스와, 실제로 우리가 다루게 되는 리소스의 표현을 구분하는 것이 REST architecture의 특징이다.

4.3 REST API 설계원칙

다음과 같은 6가지 기준을 충족하면 REST architecture로 인정된다.
 
  • 클라이언트 / 서버 구조(Client-Server)
  • 무상태 (Stateless)
  • 캐시 처리 가능 (Cacheable)
  • 유니폼 인터페이스 (Uniform Interface)
  • 계층화 (Layered System)
  • 바로 실행할 수 있는 코드 (Code on Demand)
 
각 기준에 대해서 간단하게 알아보자.
1) 클라이언트 / 서버 구조(Client-Server)
리소스를 요청하는 클라이언트와 리소스를 가지고 있는 서버로 나뉜 구조를 통해, 각자의 문제에 집중하여 의존성이 줄어들고 독립적으로 운영될 수 있다.
2) 무상태 (Stateless)
서버는 클라이언트의 요청에 관해 맥락(context)을 저장하지 않는다. 매 리퀘스트는 독립적으로 처리되며 따라서 요청에는 필요한 모든 정보가 담겨야 한다.
3) 캐시 처리 가능 (Cacheable)
HTTP가 가진 강력한 특징 중 하나인 캐시를 활용해서 성능을 향상시키고 네트워크 비용을 절감할 수 있다. 서버는 리스폰스에 Cacheable 여부를 담아서 보내야 한다.
4) 유니폼 인터페이스 (Uniform Interface)
URI로 지정한 리소스 조작 인터페이스는 일관성 있어야 한다는 규칙으로 REST API에서 가장 중요한 원칙이다. 다음의 4가지 하위 조건을 준수해야 한다.
  • 식별 가능한 리소스 (identification of resources)
  • 표현을 통한 리소스 조작 (manipulation of resources through representations)
  • 자체 표현 구조 (self-descriptive messages)
  • 하이퍼 링크에 의해 구동되는 API (HATEOAS: hypermedia as the engine of application state)
5) 계층화 (Layered System)
클라이언트는 REST API 서버만 호출하고 REST API 서버는 다중 계층으로 구성될 수 있다. 또한 클라이언트와 서버 사이에는 프록시(PROXY), 게이트웨이(gateway)와 같은 중간 매체를 두고 보안, 로드 밸런싱 등을 수행할 수 있다.
6) 바로 실행할 수 있는 코드 (Code on Demand)
클라이언트는 서버로 부터 받은 script 파일을 바로 실행할 수 있다. 이 조건은 선택 가능한 조건으로 반드시 충족할 필요는 없다.

4.4 식별할 수 있는 리소스 (identification of resources)

REST API 설계원칙 중에서도 가장 중요한 기본 원칙이 있다. 바로 URI는 리소스를 표현하는 데 집중하고, 행위에 대한 정의는 HTTP 요청 메서드를 통해 해야 한다는 규칙이다. ‘유니폼 인터페이스’의 하위 조건인 ‘식별할 수 있는 리소스’에 대해 알아보자.
1️⃣
URI는 리소스를 표현해야 하고, 리소스에 대한 행위는 요청 메서드로 표현해야 한다.
다시 말해 URI는 리소스에 대한 행위를 드러내면 안 된다는 규칙이다. 또한 리소스를 식별할 수 있는 이름은 동사보다는 명사를 사용한다.
// 아래 URI는 설명을 위해 추가했을 뿐 작동하지 않습니다. const url = "https://jsonplaceholder.typicode.com/get/users" fetch(url) .then(response => response.json()) .then(json => console.log("users: ", json)
리소스에 대한 행위는 메서드로 표현해야 한다는 규칙을 준수하지 않은 예시
위 예제 코드는 URI에서 리소스뿐 아니라 리소스에 대한 처리(GET)까지 나타내고 있다. 따라서 REST API 설계원칙을 준수하지 않은 것이다. URI는 리소스인 users를 나타내는 용도로만 사용하고, 리소스에 대한 처리는 HTTP 요청 메서드로 표현해야 한다.
 
2️⃣
도큐먼트는 단수 명사로, 컬렉션은 복수 명사로 표시해야 한다.
리소스는 그 특징에 따라 여러 종류로 나눌 수 있다. 이 중에 하나의 객체로 표현할 수 있는 리소스를 '도큐먼트'라고 하고, 여러 개의 '도큐먼트'를 담을 수 있는 리소스를 '컬렉션'이라고 한다. URI에서 '도큐먼트'를 나타낼 때는 단수형 명사를, '컬렉션'을 나타낼 때는 복수형 명사를 사용해야 한다.
 
예를 들어 다음과 같은 URI를 전체 유저 정보를 조회하기 위해 사용할 수 있다.
const url = "https://jsonplaceholder.typicode.com/users" fetch(url) .then(response => response.json()) .then(json => console.log("users: ", json))
컬렉션 리소스임을 식별할 수 있는 URI
users는 유저 정보들을 담을 수 있는 컬렉션에 해당하는 개념이기 때문에 복수 명사를 사용해야 한다. 만약 특정 유저 정보를 조회하거나 수정, 삭제해야 할 경우 users/1과 같이 도큐먼트를 나타내기 위해 단수 명사 대신 유저 고유 식별자인 id 값을 쓰는 경우 단복수 문제가 없다.
const url = "https://jsonplaceholder.typicode.com/users/1/posts" fetch(url) .then(response => response.json()) .then(json => console.log("users: ", json))
컬렉션 리소스와 고유 식별자 값으로 계층적 관계가 나타나는 URI
리소스의 계층적 관계를 잘 나타내면, URI만으로 무슨 리소스를 의미하는지 쉽게 이해할 수 있다. Web API를 설계할 때는 이렇게 가독성 좋고, 이해하기 쉬운 URI를 설계해야 한다.
다음으로 예제 코드를 통해 REST API 실습을 해보자

4.5 REST API 실습

💡
모든 실습 코드는 https://jsonplaceholder.typicode.com/ 에서 제공하는 실습용 API로, 서버에서 실제로 동작하는 것처럼 보이지만 리소스가 실제로 업데이트 되지는 않는다.

4.5.1 GET 요청

// fetch는 기본 메서드가 GET이다. const url = "https://jsonplaceholder.typicode.com/users" fetch(url) .then(response => response.json()) .then(json => console.log("users: ", json)) // expected output users: [ { "id": 1, "name": "Leanne Graham", "username": "Bret", "email": "Sincere@april.biz", "address": { "street": "Kulas Light", "suite": "Apt. 556", "city": "Gwenborough", "zipcode": "92998-3874", "geo": { "lat": "-37.3159", "lng": "81.1496" } }, "phone": "1-770-736-8031 x56442", "website": "hildegard.org", "company": { "name": "Romaguera-Crona", "catchPhrase": "Multi-layered client-server neural-net", "bs": "harness real-time e-markets" } }, {"id": 2, "name": "....",}, {"id": 3, "name": "....",}, /* ... */ {"id": 10, "name": "....",}, ]
GET 요청 실습 코드
유저 전체 정보라는 리소스를 취득하기 위해 GET 메서드를 사용하였고, users는 컬렉션 개념이기 때문에 복수명사로 요청을 보낸 것을 확인할 수 있다.

4.5.2 POST 요청

const url = "https://jsonplaceholder.typicode.com/posts" fetch(url, { method: "POST", body: JSON.stringify({ title: "test title", body: "test body", userId: 1, }), headers: { 'Content-type': 'application/json; charset=UTF-8', }, }) .then((response) => response.json()) .then((json) => console.log(json)); // expected output { "title": "test title", "body": "test body", "userId": 1, "id": 101 }
POST 요청 실습 코드
리소스를 생성하기 위해 사용하는 POST 메서드이다. POST 요청을 보낼 때는, 컬렉션에 하나의 도큐먼트를 추가하는 것이기 때문에 엔드포인트로 컬렉션 타입의 리소스를 사용한다. 위 실습 코드는 posts 컬렉션에 새로운 post를 생성하는 요청이다.

4.5.3 PUT 요청

const url = "https://jsonplaceholder.typicode.com/posts/1" fetch(url, { method: 'PUT', body: JSON.stringify({ id: 1, title: "updated title", body: "updated body", userId: 1, }), headers: { 'Content-type': 'application/json; charset=UTF-8', }, }) .then((response) => response.json()) .then((json) => console.log(json)); // expected output { "id": 1, "title": "updated title", "body": "updated body", "userId": 1 }
PUT 요청 실습 코드
특정 리소스 전체를 교체할 때는 PUT 메서드를 사용한다. posts컬렉션의 고유 식별값이 1 인 post 리소스를 교체한다.

4.5.4 PATCH 요청

const url = "https://jsonplaceholder.typicode.com/posts/1" fetch(url, { method: "PATCH", body: JSON.stringify({ title: "patch title", }), headers: { 'Content-type': 'application/json; charset=UTF-8', }, }) .then((response) => response.json()) .then((json) => console.log(json)); // expected output { "userId": 1, "id": 1, "title": "patch title", "body": "..." }
PATCH 요청 실습 코드
PATCH 메서드는 특정 리소스의 일부분을 수정할 때 사용한다.

4.5.5 DELETE 요청

const url = "https://jsonplaceholder.typicode.com/posts/1" fetch(url, { method: "DELETE", });
DELETE 요청 실습 코드
DELETE 메서드는 특정 리소스를 삭제할 때 사용한다.
PUT과 PATCH, DELETE 메서드의 공통점은 컬렉션 리소스의 고유 식별값(id)를 사용하여 요청을 보낸다는 것이다. 물론 컬렉션을 사용할 수도 있지만 데이터 전체를 수정하거나 삭제해야 할 일은 거의 발생하지 않고 위험한 동작이기 때문에, Web API 설계에 반영하지 않거나 서버에서 허용하지 않는 경우가 일반적이다.
Web API를 사용하여 HTTP 요청을 보내는 더 자세한 내용과 실습코드는 이어지는 Fetch & Axios 챕터에서 살펴볼 수 있다.