메서드를 공부하기 전에 결국은 HTTP 메서드를 코드에서 사용해야 하는 거 아닌가? 그러면 직접 사용해보고 나면
메서드 하나하나가 좀 더 잘 와닿지 않을까 하여, 직접 코드로 일단 구현하면서 메서드를 사용해보기로 결정했다.
const xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 200) {
console.log(xhr.responseText);
} else {
console.log(`Error: ${xhr.status}`);
}
}
};
xhr.open('GET', 'https://www.naver.com');
xhr.send();
일단 해당 코드를 이용해서 https://www.naver.com 을 입력했을 때의 GET 메세지는 무엇일까? 라는 궁금증 때문에 인터넷에 돌아다니는 코드를 아무거나 가지고 와서 실행해보았다.
아직 아무것도 모르기 때문에 코드를 가져와서 사용해도 뭐가 뭔지 잘 모른다. 그냥 객체를 하나 만들었고, 상태코드를 이용해서 OK 일때만 응답메세지를 출력하게 하는 것 같다는 정도만 이해하고 있다.
그럼 이제부터 제대로 이해하기 위해 위의 코드를 하나씩 이해해보도록 하겠다.
XMLHttpRequest는 WebAPI다. 그러니까 자바스크립트 기본사양이 아니라 브라우저에서 제공하는 것이다.
위의 코드에도 나왔지만 일단 생성자 함수를 통해서 객체를 생성한다. 그리고 브라우저에서 제공하는 API이기 때문에 브라우저 환경에서만 동작한다.
그럼 XMLHttpRequest는 어떤 메서드와 프로퍼티를 제공하는지 알아보자.
프로퍼티:
readyState: 현재 XMLHttpRequest 객체의 상태를 나타냅니다. 0 ~ 4의 값이 있으며, 4는 요청이 완료되었음을 나타냅니다.
responseText: 서버로부터 받은 응답 데이터를 문자열로 반환합니다.
responseXML: 서버로부터 받은 응답 데이터를 XML Document 객체로 반환합니다.
status: 서버로부터 받은 HTTP 상태 코드를 반환합니다.
statusText: 서버로부터 받은 HTTP 상태 메시지를 반환합니다.
주요 이벤트:
onreadystatechange: readyState 값이 변경된 경우
onload: HTTP 요청이 성공적으로 완료되었을 때 호출됩니다.
onerror: HTTP 요청이 실패했을 때 호출됩니다.
onabort: HTTP 요청이 취소되었을 때 호출됩니다.
정적 프로퍼티 :
UNSENT(0) : open 메서드 호출 이전
OPENED(1) : open 메서드 호출 이후
HEADERS_RECEIVED(2) : send 메서드 호출 이후
LOADING(3) : 서버 응답 중(응답 데이터 미완성 상태)
DONE(4) : 서브 응답 완료
메서드:
open(method, url[, async, user, password]): HTTP 요청을 초기화합니다. method는 HTTP 메서드를, url은 요청할 URL을 나타냅니다. async는 비동기 여부를 나타내며, true일 경우 비동기 방식으로 요청을 처리합니다. user와 password는 인증 정보를 넣을 때 사용합니다.
send([body]): HTTP 요청을 서버로 보냅니다. body는 요청 데이터를 나타냅니다. GET 요청의 경우에는 body를 사용하지 않으며, POST 요청 등에서 사용됩니다.
setRequestHeader(header, value): HTTP 요청에 특정 헤더를 추가합니다. header는 헤더 이름을, value는 헤더 값입니다.
abort(): HTTP 요청을 취소합니다.
getAllResponseHeaders(): 서버에서 받은 모든 응답 헤더를 문자열로 반환합니다.
getResponseHeader(header): 서버에서 받은 특정 응답 헤더를 반환합니다. header는 헤더 이름을 나타냅니다.
여기까지 알아보고 위의 코드를 다시 해석해보자.
클라이언트와 서버가 통신하기 위해서 자바스크립트 코드를 통해 브라우저 환경에서 확인할 수 있는 API를 사용한다. XMLHttpRequest WebAPI를 사용했다.
맨처음 XMLHttpRequest 객체를 생성하고 서버가 응답했을 때 그 상태코드가 200(OK)라면 문자열로 반환한 서버로부터 받은 응답 데이터 console.log로 출력해준다. 상태코드가 200이 아니라면 xhr 객체의 상태코드를 에러메세지 처럼 출력해준다.
그 후에 open 메서드를 통해 GET 메서드와 해당 URL의 정보를 객체에 등록한 뒤, send 메서드를 통해 요청을 서버로 보내는 것이다.
프로퍼티와 메서드를 통해 코드는 어느 정도 이해했고, XMLHttpRequest에 대해서 좀 더 자세하게 알아보자.
XMLHttpRequest는 웹 클라이언트에서 서버와 데이터를 비동기적으로 교환할 수 있는 API입니다.
AJAX(Asynchronous JavaScript and XML)라고도 불리며, 웹 페이지의 일부분만 갱신하거나,
웹 애플리케이션에서 데이터를 동적으로 가져오기 위해 사용됩니다.
XMLHttpRequest를 사용하면 브라우저는 페이지를 새로고침하지 않고도 서버로부터 데이터를 가져와서
웹 페이지를 업데이트할 수 있습니다. 이를 통해 사용자 경험을 향상시키고, 서버 부하를 줄일 수 있습니다.
이 객체를 사용하면 HTTP 메서드(GET, POST, PUT, DELETE 등)를 이용하여 서버와 통신할 수 있습니다.
XMLHttpRequest 객체를 사용하여 HTTP 요청을 보내면, 서버에서는 해당 요청에 대한 응답을 반환합니다.
이때, 서버에서 반환하는 응답 데이터는 XML, JSON, HTML, TEXT 등 다양한 형태일 수 있습니다.
클라이언트에서는 responseText 속성을 사용하여 서버에서 받은 응답 데이터를 처리할 수 있습니다.
XMLHttpRequest를 사용할 때 주의할 점은 CORS(Cross-Origin Resource Sharing) 정책입니다.
이는 보안상의 이유로 브라우저에서 요청하는 도메인과 서버의 도메인이 다를 경우, HTTP 요청이 차단됩니다.
이를 해결하기 위해서는 서버 측에서 CORS 정책을 설정해야 합니다.
서버에서 데이터를 받아오는 방법에 XMLHttpRequest만 있을 것 같지 않아 추가적으로 알아보았다.
서버에서 데이터를 받아오는 방법은 XMLHttpRequest 뿐만 아니라 Fetch API, Axios, jQuery AJAX 등
여러 가지 방법이 있습니다.
이러한 라이브러리와 API는 XMLHttpRequest와 비슷한 방식으로 작동하지만 더욱 간단하고 직관적인 방법으로
데이터를 요청하고 받아올 수 있습니다.
또한 이러한 라이브러리와 API는 XMLHttpRequest와는 달리 Promise나 async/await와 같은 비동기 처리
방식을 제공해줍니다.
최근에는 Fetch API가 많이 사용되고 있습니다.
Fetch API는 XMLHttpRequest와 달리 Promise 기반의 비동기 처리 방식을 사용하며, 간단하고 직관적인
API를 제공하여 사용하기 쉽습니다.
또한 Request와 Response 객체를 사용하여 요청과 응답을 조작할 수 있습니다.
또한 Axios라는 라이브러리도 인기가 있습니다. Axios는 Promise 기반의 HTTP 클라이언트 라이브러리로,
간단한 API와 다양한 설정 옵션을 제공하여 사용하기 편리합니다.
CORS는 Cross-Origin Resource Sharing의 약자로, 다른 출처(origin)로부터 리소스를 가져오는
웹 페이지에서 발생하는 보안 문제를 해결하기 위한 메커니즘입니다.
브라우저는 보안상의 이유로 다른 출처(origin)로부터 리소스를 가져오는 것을 막습니다.
예를 들어, 웹 사이트 A에서 AJAX를 사용하여 웹 사이트 B의 데이터를 가져오는 경우, 브라우저는 이 요청을
차단합니다. 이러한 보안 기능을 우회하기 위해 CORS 정책을 사용합니다.
CORS는 다음과 같은 방식으로 작동합니다.
1.브라우저에서 웹 페이지가 다른 출처(origin)로 요청을 보냅니다.
2.서버는 요청 헤더에 Origin이라는 키와 함께 요청한 출처(origin)를 담아 응답합니다.
3.브라우저는 서버에서 받은 응답의 헤더를 검사하여, 요청한 출처(origin)가 서버가 허용한 출처인지 확인합니다.
4.출처가 허용되었다면, 브라우저는 서버가 보낸 데이터를 웹 페이지로 전송합니다.
CORS 정책은 보안을 유지하면서 다른 출처(origin)로부터 리소스를 가져올 수 있도록 합니다.
서버에서는 Access-Control-Allow-Origin이라는 헤더를 사용하여 요청한 출처(origin)를
허용할 수 있습니다. 또한, 브라우저에서는 XMLHttpRequest 객체의 withCredentials 속성을 사용하여
인증 정보를 함께 보낼 수 있습니다.
CORS는 웹 애플리케이션에서 다른 출처(origin)로부터 리소스를 가져와야 하는 경우에 유용합니다.
그러나 잘못 구성된 CORS 정책은 보안 문제를 유발할 수 있으므로, 정확하게 구성하는 것이 중요합니다.
그러니까 브라우저의 보안때문에 외부 리소스를 함부로 사용할 수 없는데 그걸 해결하는 것이 CORS라는 것이다.
실제로 위에서 작성했던 코드를 www.naver.com를 GET 메서드를 통해 요청을 보내면, 아래의 오류가 발생한다.
Access to XMLHttpRequest at 'https://www.naver.com/' from origin
'http://127.0.0.1:5500' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
const xhr = new XMLHttpRequest();
xhr.withCredentials = true;
xhr.onreadystatechange = function () {
if (xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 200) {
console.log(xhr.responseText);
} else {
console.log(`Error: ${xhr.status}`);
}
}
};
xhr.open('GET', 'https://www.naver.com');
xhr.send();
withCredentials 속성을 사용하여 인증정보를 나름 보내서 GET 메서드에 대한 응답메세지를 받아보려고 했지만, 실패..오류는 동일하게 나온다.
그 이유는 아래에 있다.
올바른 CORS 요청을 보내기 위해서는 서버 측에서 Access-Control-Allow-Origin 헤더를 설정해야 합니다. 이 헤더는 응답의 헤더에 설정되며, 허용되는 출처를 나타냅니다. 만약 모든 출처에서 접근을 허용하고자 한다면 Access-Control-Allow-Origin: *와 같이 설정할 수 있습니다.
따라서 클라이언트에서 서버의 데이터를 요청하고자 할 때, 서버가 CORS를 허용해야 합니다.
클라이언트에서는 XMLHttpRequest 객체의 withCredentials 속성을 true로 설정하여 인증 정보를 함께
보낼 수 있습니다.
withCredentials 속성을 true로 설정하면, 브라우저가 요청을 보낼 때 쿠키와 같은 인증 정보도 함께
보내게 됩니다.
이러한 인증 정보는 서버에 로그인 상태를 유지하는 데 사용되는 세션 ID와 같은 것일 수 있습니다.
그러나 이러한 인증 정보를 함께 보내기 위해서는, 브라우저에서 해당 도메인의 쿠키를 허용하는 것이 필요합니다.
따라서, 서버에서 적절한 CORS 설정을 해주어야 합니다.
만약 서버에서 Access-Control-Allow-Credentials 헤더를 설정하지 않거나, 해당 헤더의 값이 false로
설정되어 있다면, 브라우저는 인증 정보를 함께 보내지 않습니다.
따라서, 해당 코드를 입력하는 것만으로 인증 정보가 자동으로 보내지는 것은 아닙니다.
서버에서 인증 정보를 허용하는 CORS 설정을 해준 후, withCredentials 속성을 true로 설정하여
인증 정보를 함께 보내야 합니다.
한 마디로 withCredentials 속성을 true로 설정해준다고 해도 서버에서 Access-Control-Allow-Credentials 헤더를 설정하지 않으면 브라우저에서 인증 정보 자체를 서버에 보내지도 않으니, GET 메서드에 대한 정보도 받아올 수 없는 것이다.
구글이나 깃허브 등 웬만한 사이트는 전부 넣어봤지만 같은 오류만 발생했다.
그래서 모던 자바스크립트 Deep Dive에 첨부되어 있던 사이트를 가지고 시도했다.
https://jsonplaceholder.typicode.com 은 Fake Rest API를 제공하는 서비스다.
Fake Rest API는 가짜 데이터를 생성하고 이를 이용해 REST API를 시뮬레이션하는 도구입니다.
실제 서버를 만들지 않고도, 개발자가 원하는 형태의 데이터를 생성하여 API를 테스트하거나 프론트엔드 개발에
사용할 수 있습니다.
보통 Fake Rest API를 만드는 방법으로는 json-server, Mirage.js, faker.js 등이 있습니다.
이 도구들은 각각의 방식으로 가짜 데이터를 생성하고, REST API를 모방하며 이를 이용해 프론트엔드 개발을
진행할 수 있습니다.
또한, 이 도구들은 데이터베이스와 같은 백엔드와 프론트엔드 간의 인터페이스를 명확하게 만들어주기 때문에,
백엔드와 프론트엔드 개발을 병행하여 진행할 때도 유용하게 사용됩니다.
Representational State Transfer(REST)는 웹 기반의 소프트웨어 아키텍처의 한 형식으로,
HTTP 프로토콜을 따르는 API 디자인 규칙의 모음입니다.
REST API는 HTTP URI(Uniform Resource Identifier)를 사용하여 자원을 명시하고,
HTTP 메서드(GET, POST, PUT, DELETE 등)를 사용하여 해당 자원에 대한
CRUD(Create, Read, Update, Delete) 작업을 수행합니다.
https://jsonplaceholder.typicode.com 이 사이트에서 HTTP 메서드를 시뮬레이션 할 수 있다는 건데, REST API를 사용해서 가짜 서버를 만들어서 사용할 수 있다는 것 같다.
하지만 사이트를 직접 이용한다기보다는 적어도 HTTP 메서드를 시뮬레이션 해주는 사이트라면 서버에서 Access-Control-Allow-Credentials 헤더는 설정해놓았을거라 생각했다. 아니면 애초에 보안이 강하게 적용되어 있지 않을 수도 있을 것 같다.
그래서 url 바꿔서 코드 수정하고 다시 GET 요청!!
const xhr = new XMLHttpRequest();
xhr.withCredentials = true;
xhr.onreadystatechange = function () {
if (xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 200) {
console.log(xhr.responseText);
} else {
console.log(`Error: ${xhr.status}`);
}
}
};
xhr.open('GET', 'https://jsonplaceholder.typicode.com/');
xhr.send();
console 창에 정상적으로 응답메세지의 메세지바디 부분이 출력된다. 즉, 모든 html 코드가 출력된다. 실제 사이트에 가서 개발자도구의 Element 탭에서 코드를 비교해봤는데, 동일한 코드였다.
제대로 받아온 것 같다.
이번에는 HEAD 메서드를 사용해보고 싶어 GET을 HEAD로 바꿔서 시도했다. 하지만 console 창에는 아무것도 출력되지 않았다. 이유는 responseText 프로퍼티를 사용하지 말고 getAllResponseHeaders() 메서드를 사용해야 헤더 정보를 받아올 수 있다.
const xhr = new XMLHttpRequest();
xhr.withCredentials = true;
xhr.onreadystatechange = function () {
if (xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 200) {
console.log(xhr.getAllResponseHeaders());
} else {
console.log(`Error: ${xhr.status}`);
}
}
};
xhr.open('HEAD', 'https://jsonplaceholder.typicode.com/');
xhr.send();
cache-control: public, max-age=43200
content-type: text/html; charset=UTF-8
last-modified: Sun, 26 Mar 2023 04:08:54 GMT
메세지 헤더 전부가 아니라 응답라인을 제외한 순수한 헤더 정보만을 받아온다. 메세지 헤더라는 개념이 헤더와 헷갈릴 수 있지만 HTTP 메세지를 크게 두 가지로 분류한다면 메세지 헤더와 메세지 바디로 분류할 수 있다. 그리고 메세지 헤더는 다시 시작라인과 헤더로 분류할 수 있다.
getAllResponseHeaders() 메서드는 응답라인이 없는 헤더만을 받아온다.
그외의 PUT, POST, DELETE 메서드는 서버측에서 수신할 수 있는 코드를 작성해야 하기 때문에 안되고, TRACE 메서드는 보안상의 문제 때문에 정보를 받아오지 못한다.
그럼 PUT, POST, DELETE도 사용해보기 위해서 간단하게 라도 서버를 구축해볼까하는 생각이 들었다.
이 부분은 다음 포스팅에서 다루도록 하겠다.
Fetch API는 웹 개발에서 사용되는 JavaScript API로, 네트워크 요청과 응답을 처리하는 방법을 제공합니다. 이를 통해 웹 애플리케이션에서 서버와 통신하고 데이터를 가져올 수 있습니다.
Fetch API는 XMLHttpRequest와 비슷한 기능을 제공하지만, 더 직관적이고 유연하며 프로미스 기반으로
작동합니다. 이러한 특징으로 인해 비동기 네트워크 요청을 더욱 쉽게 처리할 수 있습니다.
Fetch API를 사용하면 URL, 요청 옵션, 응답 상태 및 데이터와 같은 여러 가지 정보를 포함한
Request 객체를 생성하고, 이를 사용하여 네트워크 요청을 보낼 수 있습니다.
이후에는 서버로부터의 응답을 처리하기 위해 Response 객체를 사용할 수 있습니다.
Fetch API는 Promise 객체를 반환하기 때문에, then() 및 catch() 메서드를 사용하여 비동기적으로
처리할 수 있습니다.
또한, async/await와 함께 사용하여 비동기적으로 데이터를 가져오는 코드를 보다 간결하고 직관적으로
작성할 수 있습니다.
Fetch API는 현재 모든 주요 브라우저에서 지원되고 있으며,
웹 개발에서 자주 사용되는 HTTP 프로토콜과 함께 RESTful API와 같은 다양한 웹 서비스와 통신하기 위한
중요한 도구입니다.
해당 설명을 보고 나름 정리해보자면 fetch API는 자바스크립트 API고 XMLHttpRequest 는 브라우저 API다.
그럼 둘의 공통점과 차이점, 그리고 특징에 대해 정리해보자.
프로미스 기반 비동기 작동 방식과 AJAX 기술의 비동기 작동 방식의 공통점과 차이점은 다음과 같습니다.
공통점:
비동기 방식으로 작동하여, 네트워크 요청을 보낸 후 서버로부터 응답을 기다리지 않고 다른 작업을 수행할 수
있습니다.
JavaScript를 사용하여 웹 페이지에서 동적으로 데이터를 가져올 수 있습니다.
차이점:
AJAX는 XMLHttpRequest 객체를 사용하여 비동기 네트워크 요청을 처리하고,
프로미스 기반 비동기 작동 방식은 Fetch API를 사용합니다.
AJAX는 일반적으로 콜백 함수를 사용하여 비동기 작업을 처리하고,
프로미스 기반 비동기 작동 방식은 Promise 객체를 반환하여 비동기 작업을 처리합니다.
프로미스 기반 비동기 작동 방식은 then() 및 catch() 메서드를 사용하여 비동기 작업을 처리하는 반면,
AJAX는 이벤트 핸들러를 사용하여 비동기 작업을 처리합니다.
프로미스 기반 비동기 작동 방식의 특징
코드를 간결하게 작성할 수 있습니다.
에러 처리가 용이합니다.
비동기 작업의 처리 상태를 좀 더 명확하게 파악할 수 있습니다.
콜백 지옥(callback hell)을 피할 수 있습니다.
AJAX 기술의 특징
XMLHttpRequest 객체를 사용하기 때문에, 모든 브라우저에서 지원됩니다.
브라우저의 URL을 변경하지 않고도 페이지를 업데이트할 수 있습니다.
서버와 비동기적으로 데이터를 주고받을 수 있으므로, 페이지 로딩 시간을 단축할 수 있습니다.
서버로부터 데이터를 받은 후에도 DOM을 동적으로 조작할 수 있습니다.
fetch API의 장점은 코드를 좀 더 간결하게 작성할 수 있다는 점이 눈에 띈다. 브라우저 API냐, 자바스크립트 API냐에 따라 작동 방식에 어떤 차이가 있을지는 아직 모르겠지만 일단 시도해보자.
fetch('https://jsonplaceholder.typicode.com/', { method: 'GET' })
.then((response) => response.text())
.then((data) => console.log(data))
.catch((error) => console.error(error));
첫번째 줄에서 method를 생략할 수도 있다. 생략하는 경우 기본값은 GET이다. response.text로 텍스트 형식의 응답메세지를 받아올 수 있다. 리소스의 자원이 html 형식이라면 json으로는 받아올 수 없기 때문에 이 경우에는 text로 정보를 받아와야 한다. 만약 해당 리소스가 json 데이터 형식이 아닌 것이 들어있다면 오류를 출력한다.
fetch('https://jsonplaceholder.typicode.com/', { method: 'GET' })
.then((response) => response.json())
.then((data) => console.log(data))
.catch((error) => console.error(error));
SyntaxError: Unexpected token '<', "
<!DOCTYPE "... is not valid JSON
하지만 적절한 데이터가 들어있는 URI라면 제대로 응답메세지를 제대로 출력한다.
fetch('https://jsonplaceholder.typicode.com/posts')
.then((response) => response.json())
.then((data) => console.log(data))
.catch((error) => console.error(error));
이 경우에도 당연히 메서드는 GET 이다.
fetch('https://jsonplaceholder.typicode.com/', { method: 'HEAD' })
.then((response) => {
// 응답 헤더 정보 출력
for (const [key, value] of response.headers) {
console.log(`${key}: ${value}`);
}
})
.catch((error) => console.error(error));
reponse 객체의 headers 메서드는 Headers 객체를 반환하는데 이는 이터러블이다. 그래서 for of를 통해 순회해서 정보를 받아왔다.
기존에 XMLHttpRequest에서 출력한 헤더 정보와 동일하게 출력된다.
그리고 fetchAPI의 경우에도 PUT, POST, DELETE는 서버 측에서 해당 요청을 처리할 수 있는 API가 구현되어 있어야 한다.
TRACE는 보안상의 이유로 기본적으로 브라우저에서 지원하지 않는다. 이는 TRACE 요청이 요청 헤더와 바디를 모두 반환하기 때문에, 이를 이용한 크로스 사이트 스크립팅(XSS) 공격 등 보안 위협을 초래할 수 있기 때문이다.
따라서, TRACE 요청을 테스트하기 위해서는 직접 서버를 구축하고 해당 요청에 대한 응답을 작성해야 한다. 이를 위해서는 Node.js가 필요하다. 모던 자바스크립트 Deep Dive에 나와있는 JSON.server를 이용해서 일단 시도해보고 다른 방법이 있는지도 알아보려고 한다. 이는 다음 포스팅에서 다루도록 하겠다.
Reference
모던 자바스크립트 Deep Dive
(https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS)
https://developer.mozilla.org/ko/docs/Web/API/XMLHttpRequest/withCredentials
https://restfulapi.net/
https://ko.wikipedia.org/wiki/REST
https://developer.mozilla.org/ko/docs/Web