자바스크립트로 서버에 네트워크 요청을 보내고, 새로운 정보를 받아올 수 있습니다.
서버에 요청한 정보를 비동기적으로 받아오는 방법에 대해 알아봅시다.
fetch() 는 대부분의 모던 브라우저가 지원하는 전역 메서드로,
URL 주소에 HTTP 요청을 보내고 응답을 프라미스로 받아옵니다.
let promise = fetch(url, [options])
url: 요청을 보낼 URLoptions: 요청에 적용할 설정을 담은 객체, 아무것도 없으면 GET 메서드로 요청method: GET 이나 POST 등의 요청 메서드headers: 요청 헤더body: 요청 본문프라미스는 바로 반환되지만, 요청에 대한 응답이 왔다는 뜻은 아닙니다.
응답은 두 단계를 거쳐 진행됩니다.
let response = await fetch(url, options); // 응답 헤더와 함께 이행됨
let result = await response.json(); // json 본문을 읽음
- fetch 가 URL 에 네트워크 요청을 보내고 프라미스를 반환
fetch 가 반환하는 프라미스는 요청에 대한 Response 로 이행하는데,
서버에서 응답 헤더를 받으면 이행 상태가 됩니다.
응답에 오류가 있어도 무조건 이행되므로, 이행된 프라미스에서 응답 헤더의 코드를 보고
요청이 성공했는지 실패했는지 확인해야 합니다.
다음과 같은 프로퍼티로 HTTP 상태를 확인할 수 있습니다.
status: HTTP 상태 코드 확인ok: 불린 값, 상태코드가 200번대일 경우 true(성공)let response = await fetch(url);
if (response.ok) { // HTTP 상태 코드가 200~299일 경우
// 응답 본문을 받음
let json = await response.json();
} else {
alert("HTTP-Error: " + response.status);
}
- 프라미스에서 추가 메서드를 호출하여 응답 본문 받기
response 에서 메서드를 사용하여 응답 본문을 읽어옵니다.
사용하는 메서드는 다음과 같습니다.
text(): 응답을 텍스트로 반환json(): 응답을 JSON 형태로 파싱formData(): 응답을 FormData 객체로 반환blob(): 응답을 Blob 형태로 반환arrayBuffer(): 응답을 ArrayBuffer 형태로 반환메서드들은 프라미스를 반환하는데,
Response 스트림에서 본문을 파싱한 결과로 이행됩니다.
파싱이 완료되면 프라미스는 이행 상태가 됩니다.
이때, 본문을 메서드로 읽어왔다면 본문은 이미 소비되기 때문에,
fetch 로 얻어온 응답을 여러 번 소비하는 건(한 응답에 여러 번 메서드를 적용하여 여러 형태로 얻어오는건) 불가능합니다.
let response = await fetch(url);
let commits = await response.json(); // 응답 본문을 읽고 JSON 형태로 파싱
alert(commits[0].author.login);
응답 헤더는 response.headers 에 키-값 으로 저장됩니다.
이는 유사 맵으로, 맵에서 지원하는 메서드를 비슷하게 지원합니다.
let response = await fetch(url);
// 헤더 일부를 추출
alert(response.headers.get('Content-Type')); // application/json; charset=utf-8
// 헤더 전체를 순회
for (let [key, value] of response.headers) {
alert(`${key} = ${value}`);
}
fetch() 에 headers 옵션을 사용하면 요청보낼 때 쓸 헤더를 설정 가능합니다.
let response = fetch(protectedUrl, {
headers: {
Authentication: 'secret'
}
});
이렇게... headers 에 헤더 정보가 담긴 객체를 넘겨줍니다.
이 방식으로 설정 불가능한 헤더도 존재합니다.
이는 안전성을 위해서입니다.
fetch() 로 요청시 메서드를 따로 지정해주지 않으면
GET 메서드로 요청을 보내게 됩니다.
만약 그 외의 메서드로 요청을 보내려면 추가 옵션을 지정합니다.
method: HTTP 메서드, (ex. POST)body: 요청 본문에 담아 서버에 보낼 데이터, 다음과 같은 형식 중 하나로 보내야 함headers 에서 Content-Type 설정: body 에 담을 데이터 형식을 명시함text/plain;charset=UTF-8 로 기본 설정됨application/json 으로 설정해줘야 함다음과 같이 사용하여 POST 요청으로, body 에 데이터를 담아 보낼 수 있습니다.
let user = {
name: 'John',
surname: 'Smith'
};
let response = await fetch('/article/fetch/post/user', {
method: 'POST',
headers: {
'Content-Type': 'application/json;charset=utf-8'
},
body: JSON.stringify(user)
});
let result = await response.json();
alert(result.message);
이런식으로 사용합니다.
Blob 이나 BufferSource 객체를 이용하여 바이너리 데이터를 전송할 수 있습니다.
async function submit() {
let blob = await new Promise(resolve => canvasElem.toBlob(resolve, 'image/png'));
let response = await fetch('/article/fetch/post/image', {
method: 'POST',
body: blob
});
// 전송이 잘 되었다는 응답이 오고 이미지 사이즈가 얼럿창에 출력됩니다.
let result = await response.json();
alert(result.message);
}
위처럼... blob 객체를 만들어
body 에 전달하고 있습니다.
Blob 객체는 내장 타입을 가지므로 Content-Type 을 명시적으로 설정해주지 않아도 됩니다.
toBlob 에 의해 image/png 가 자동으로 설정됩니다.
fetch() 로 보낸 요청을 중간에 중단할 수 있습니다.
이를 위해 빌트인 객체인 AbortController 를 사용합니다.
AbortController 는 fetch 뿐만 아니라 다른 비동기 작업 중단에도 사용 가능합니다.
컨트롤러는 다음과 같이 구성됩니다.
let controller = new AbortController();
abort() 메서드signal 프로퍼티: 이벤트 리스너 등록 가능동작은 다음과 같이 이뤄집니다.
controller.signal 에 abort 이벤트 리스너를 등록abort() 메서드를 호출하여 이벤트 발생 abort() 메서드를 호출하면 controller.signal 이 abort 라는 이벤트를 방출하고,
controller.signal.aborted 프로퍼티가 true 가 됩니다.
간단하게 보면 abort 라는 이벤트를 감지하는 대상과,
그 이벤트를 처리하는 메서드로 이루어져 있습니다.
AbortController 는 그냥 보면 별 기능이 없어 보이는데,
fetch 의 옵션에 등록해주면 abort 를 탐지하여 에러를 만듭니다.
let controller = new AbortController();
fetch(url, {
signal: controller.signal
});
signal 에 controller.signal 을 등록이렇게 signal 을 설정해주면, controller 가 abort() 메서드를 호출했을 때
signal 로부터 abort 이벤트를 받아 요청을 중단합니다.
fetch 가 중단 되면, 프라미스는 AbortError 라는 에러로 reject 됩니다.
// 1초 후 abort
let controller = new AbortController();
setTimeout(() => controller.abort(), 1000);
try {
let response = await fetch('/article/fetch-abort/demo/hang', {
signal: controller.signal
});
} catch(err) {
if (err.name == 'AbortError') { // 중단 핸들링
alert("Aborted!");
} else {
throw err;
}
}
CORS 는 Cross-Origin Resource Sharing 의 줄임말로,
접속 사이트와 origin 이 다른 url 에 요청을 보낼 경우에
특수 헤더를 추가하도록 하는 정책입니다.
만약 origin 이 다른데 특수 헤더가 없다면 요청은 실패합니다.
origin 이란 ❓
origin 은 도메인(hostname), 프로토콜(scheme), 포트 의 세 가지로 결정되는 데요,
이 셋 중 어느 하나가 다르면 서로 다른 origin 을 가졌다고 합니다.
http://example.com 과 http://myapp.com (도메인이 다름)http://example.com:80 과 http://example.com:8080 (포트가 다름)이렇게는 모두 다른 origin 을 가집니다.
그러니 만약 A 사이트에서 B 사이트로 요청을 보내려면,
CORS 를 따라야 요청이 성공하겠죠?
크로스 오리진 요청은 안전한 요청과 그 외의 요청으로 구분되는데요,
안전한 요청은 다음 두 조건을 모두 충족하는 요청입니다,
안전한 헤더 목록은 다음과 같습니다.
application/x-www-form-urlencoded 이나 multipart/form-data, text/plain 인 Content-Type이 두 조건을 충족하지 않으면 안전하지 않은 요청이 됩니다.
크로스 오리진 요청을 보내면 브라우저가 항상 Origin 이라는 헤더를 요청에 추가합니다.
Origin 헤더에는 요청한 페이지의 오리진(도메인, 프로토콜, 포트) 정보가 담기게 됩니다.
https://javascript.info/page 에서 https://anywhere.com/request 에 요청을 보낼 때GET /request
Host: anywhere.com
Origin: https://javascript.info
...
요청을 받은 서버는 요청 헤더의 Origin 을 검사합니다.
그리고 요청에 동의하면 응답에 특별한 헤더 Access-Control-Allow-Origin 을 추가합니다.
이 헤더엔 허가된 오리진에 대한 정보나 * 이 명시됩니다.
만약 오리진 정보나 * 이 들어가지 않으면 응답이 실패하게 됩니다.
브라우저는 응답하는 서버와 요청 측 사이에서 중계 역할을 하며,
다음과 같은 일을 수행합니다.
Origin 확인Access-Control-Allow-Origin 를 확인하여 서버가 크로스 오리진 요청을 허용했는지 확인크로스 오리진 요청이 이뤄지면 자바스크립트는 안전하다고 분류되는 응답 헤더에만 접근 가능합니다.
이 외의 헤더에 접근하면 에러가 발생합니다.
하지만 서버에서 Access-Control-Expose-Headers 라는 헤더로 보내준 값에 해당하는 경우는 접근 가능합니다.
200 OK
Content-Type:text/html; charset=UTF-8
Content-Length: 12345
API-Key: 2c9de507f2c54aa1
Access-Control-Allow-Origin: https://javascript.info
Access-Control-Expose-Headers: Content-Length,API-Key
위처럼... 서버가 허용한 헤더는 접근 가능합니다.
안전하지 않은 요청을 보낸 경우, 요청은 바로 전달 되지 않습니다.
대신 브라우저가 preflight 라는 사전 요청을 서버에 보내 권한을 확인합니다.
OPTIONS 메서드 사용Access-Control-Request-Method: 메서드 정보Access-Control-Request-Headers: 헤더 목록그럼 서버가 상태 코드 200인 응답을 다음 헤더와 함께 브라우저로 보냅니다.
Access-Control-Allow-Origin: * 이나 요청을 보낸 오리진이어야 함Access-Control-Allow-Methods: 허용된 메서드 정보Access-Control-Allow-Headers: 허용된 헤더 목록Access-Control-Max-Age: 권한 정보 캐싱 시간이렇게 preflight 요청에 대한 응답으로 권한을 확인받는 과정을 거친 후에야 본래의 요청을 보냅니다.
이때, preflight 요청이 성공했더라도 본 요청에 대한 응답에는 Access-Control-Allow-Origin 헤더를 꼭 붙여줘야 합니다.
이렇게 사전요청-응답, 본 요청-응답 과정이 끝나야 실제 응답을 읽을 수 있습니다.
자바스크립트로 크로스 오리진 요청을 보내면 HTTP 인증이나 쿠키와 같은
자격 증명을 요청과 함께 전송하지 않습니다.
사용자 동의 없이 인증 정보를 전송하는 경우를 방지하기 위함입니다.
만약 쿠키 등 자격 증명을 함께 전송하고 싶다면, 명시적으로 허용해줘야 합니다.
Request.credentials
요청에서 credentials 는 크로스 오리진 요청의 경우
자격 증명 정보를 함께 전달할 지 여부를 지정합니다.
다음과 같은 값을 사용 가능합니다.
omit: 절대 쿠키를 전송하거나 받지 않음same-origin: 기본값. URL 이 호출과 동일한 오리진이라면, 자격 증명을 전송include: 크로스 오리진 요청이어도 항상 자격 증명을 전송fetch 에서 credentials: "include" 옵션 추가fetch('http://another.com', {
credentials: "include"
});
이렇게 하면 URL 에 대한 인증 쿠키가 요청과 함께 전송됩니다.
자격 증명 정보가 담긴 요청을 서버가 받아들인다면,
응답에 다음과 같이 Access-Control-Allow-Origin 헤더와 함께 Access-Control-Allow-Credentials: true 헤더를 추가해서 보냅니다.
200 OK
Access-Control-Allow-Origin: https://javascript.info
Access-Control-Allow-Credentials: true
자격 증명이 포함된 요청일 경우 Access-Control-Allow-Origin 에는 * 을 쓸 수 없고, 정확한 오리진 정보를 명시해야 합니다.
audio, img, link, script, video 등) 의 crossorigin 속성HTML 요소에 crossorigin 속성을 사용하여 CORS 요청 처리 방식을 명시합니다.
crossorigin 속성은 다음과 같은 값을 가질 수 있습니다.
anonymous: 기본값, credentials 가 same-origin 으로 지정use-credentials: credentials 가 include 로 지정즉, 다음처럼 anonymous 로 지정하거나 빈 값을 할당하면 쿠키 등으로 자격 증명을 교환하지 않습니다.
<script
src="https://example.com/example-framework.js"
crossorigin></script>