이 Codelab은 Google Developers 교육팀에서 개발한 프로그레시브 웹 앱 개발 교육 과정의 일부입니다. Codelab을 순서대로 진행하면 이 과정의 학습 효과를 극대화할 수 있습니다.
과정에 관한 자세한 내용은 프로그레시브 웹 앱 개발 개요를 참고하세요.
소개
이 실습에서는 리소스를 가져오는 간단한 인터페이스이자 XMLHttpRequest API의 개선된 버전인 Fetch API를 사용하는 방법을 안내합니다.
학습할 내용
- Fetch API를 사용하여 리소스를 요청하는 방법
- fetch를 사용하여 GET, HEAD, POST 요청을 만드는 방법
- 맞춤 헤더를 읽고 설정하는 방법
- CORS의 사용 및 제한사항
유의해야 할 사항
- 기본 JavaScript 및 HTML
- ES2015 Promise의 개념 및 기본 문법에 관한 지식
필요한 항목
참고: Fetch API는 현재 모든 브라우저에서 지원되지 않지만 polyfill이 있습니다.
GitHub에서 pwa-training-labs 저장소를 다운로드하거나 클론하고 필요한 경우 LTS 버전의 Node.js를 설치합니다.
컴퓨터의 명령줄을 엽니다. fetch-api-lab/app/
디렉터리로 이동하여 로컬 개발 서버를 시작합니다.
cd fetch-api-lab/app npm install node server.js
Ctrl-c
을 사용하여 언제든지 서버를 종료할 수 있습니다.
브라우저를 열고 localhost:8081/
으로 이동합니다. 요청을 위한 버튼이 있는 페이지가 표시됩니다 (아직 작동하지는 않음).
참고: 서비스 워커가 실습을 방해하지 않도록 서비스 워커를 등록 취소하고 localhost의 모든 서비스 워커 캐시를 지우세요. Chrome DevTools에서는 Application(애플리케이션) 탭의 Clear storage(저장소 지우기) 섹션에서 Clear site data(사이트 데이터 지우기)를 클릭하여 이를 달성할 수 있습니다.
선호하는 텍스트 편집기에서 fetch-api-lab/app/
폴더를 엽니다. app/
폴더에서 실습을 빌드합니다.
이 폴더에는 다음이 포함됩니다.
echo-servers/
에는 테스트 서버를 실행하는 데 사용되는 파일이 포함되어 있습니다.examples/
에는 가져오기 실험에 사용하는 샘플 리소스가 포함되어 있습니다.js/main.js
는 앱의 기본 JavaScript이며 여기에 모든 코드를 작성합니다.index.html
은 샘플 사이트/애플리케이션의 기본 HTML 페이지입니다.package-lock.json
및package.json
은 개발 서버 및 에코 서버 종속 항목의 구성 파일입니다.server.js
은 노드 개발 서버입니다.
Fetch API는 비교적 간단한 인터페이스를 가지고 있습니다. 이 섹션에서는 fetch를 사용하여 기본 HTTP 요청을 작성하는 방법을 설명합니다.
JSON 파일 가져오기
js/main.js
에서 앱의 Fetch JSON 버튼이 fetchJSON
함수에 연결됩니다.
examples/animals.json
파일을 요청하고 응답을 로깅하도록 fetchJSON
함수를 업데이트합니다.
function fetchJSON() {
fetch('examples/animals.json')
.then(logResult)
.catch(logError);
}
스크립트를 저장하고 페이지를 새로고침합니다. JSON 가져오기를 클릭합니다. 콘솔에 가져오기 응답이 로깅되어야 합니다.
설명
fetch
메서드는 가져오려는 리소스의 경로를 매개변수로 허용합니다(이 경우 examples/animals.json
). fetch
은 Response 객체로 확인되는 프로미스를 반환합니다. 프로미스가 해결되면 응답이 logResult
함수에 전달됩니다. 프로미스가 거부되면 catch
가 인계되고 오류가 logError
함수에 전달됩니다.
응답 객체는 요청에 대한 응답을 나타냅니다. 여기에는 응답 본문과 유용한 속성 및 메서드가 포함됩니다.
잘못된 응답 테스트
콘솔에서 로깅된 응답을 검토합니다. status
, url
, ok
속성 값을 기록해 둡니다.
fetchJSON
의 examples/animals.json
리소스를 examples/non-existent.json
로 바꿉니다. 업데이트된 fetchJSON
함수는 다음과 같습니다.
function fetchJSON() {
fetch('examples/non-existent.json')
.then(logResult)
.catch(logError);
}
스크립트를 저장하고 페이지를 새로고침합니다. JSON 가져오기를 다시 클릭하여 존재하지 않는 리소스를 가져옵니다.
가져오기가 성공적으로 완료되었고 catch
블록이 트리거되지 않았는지 확인합니다. 이제 새 응답의 status
, URL
, ok
속성을 찾습니다.
두 파일의 값은 달라야 합니다 (이유를 아시나요?). 콘솔 오류가 발생한 경우 값이 오류의 컨텍스트와 일치하나요?
설명
실패한 응답이 catch
블록을 활성화하지 않은 이유는 무엇인가요? 이는 가져오기 및 프로미스에 관한 중요한 참고사항입니다. 잘못된 응답 (예: 404)은 여전히 해결됩니다. 가져오기 프로미스는 요청을 완료할 수 없는 경우에만 거부되므로 항상 응답의 유효성을 확인해야 합니다. 다음 섹션에서 응답을 검증합니다.
추가 정보
대답 유효성 확인
대답의 유효성을 확인하도록 코드를 업데이트해야 합니다.
main.js
에서 대답을 검증하는 함수를 추가합니다.
function validateResponse(response) {
if (!response.ok) {
throw Error(response.statusText);
}
return response;
}
그런 다음 fetchJSON
을 다음 코드로 바꿉니다.
function fetchJSON() {
fetch('examples/non-existent.json')
.then(validateResponse)
.then(logResult)
.catch(logError);
}
스크립트를 저장하고 페이지를 새로고침합니다. JSON 가져오기를 클릭합니다. 콘솔을 확인합니다. 이제 examples/non-existent.json
에 대한 응답이 catch
블록을 트리거해야 합니다.
fetchJSON
함수에서 examples/non-existent.json
를 원래 examples/animals.json
로 바꿉니다. 업데이트된 함수는 다음과 같습니다.
function fetchJSON() {
fetch('examples/animals.json')
.then(validateResponse)
.then(logResult)
.catch(logError);
}
스크립트를 저장하고 페이지를 새로고침합니다. JSON 가져오기를 클릭합니다. 이전과 마찬가지로 응답이 성공적으로 로깅되는 것을 확인할 수 있습니다.
설명
이제 validateResponse
검사를 추가했으므로 잘못된 응답 (예: 404)이 오류를 발생시키고 catch
이 인계됩니다. 이를 통해 실패한 응답을 처리하고 예기치 않은 응답이 가져오기 체인 아래로 전파되는 것을 방지할 수 있습니다.
대답 읽기
Fetch 응답은 ReadableStreams (스트림 사양)으로 표시되며 응답 본문에 액세스하려면 읽어야 합니다. 응답 객체에는 이를 수행하는 메서드가 있습니다.
main.js
에서 다음 코드를 사용하여 readResponseAsJSON
함수를 추가합니다.
function readResponseAsJSON(response) {
return response.json();
}
그런 다음 fetchJSON
함수를 다음 코드로 바꿉니다.
function fetchJSON() {
fetch('examples/animals.json') // 1
.then(validateResponse) // 2
.then(readResponseAsJSON) // 3
.then(logResult) // 4
.catch(logError);
}
스크립트를 저장하고 페이지를 새로고침합니다. JSON 가져오기를 클릭합니다. 콘솔에서 examples/animals.json
의 JSON이 로깅되는지 확인합니다 (Response 객체 대신).
설명
무슨 일이 일어나고 있는지 살펴보겠습니다.
1단계: 가져오기는 리소스 examples/animals.json
에서 호출됩니다. Fetch는 Response 객체로 확인되는 프로미스를 반환합니다. 프로미스가 확인되면 응답 객체가 validateResponse
에 전달됩니다.
2단계: validateResponse
는 응답이 유효한지 (200인지) 확인합니다. 그렇지 않으면 오류가 발생하여 나머지 then
블록을 건너뛰고 catch
블록이 트리거됩니다. 이 점이 특히 중요합니다. 이 검사 없이는 잘못된 응답이 체인을 따라 전달되어 유효한 응답을 수신하는 데 의존하는 후속 코드가 중단될 수 있습니다. 응답이 유효하면 readResponseAsJSON
에 전달됩니다.
3단계: readResponseAsJSON
는 Response.json() 메서드를 사용하여 응답 본문을 읽습니다. 이 메서드는 JSON으로 확인되는 프로미스를 반환합니다. 이 프로미스가 해결되면 JSON 데이터가 logResult
에 전달됩니다. (response.json()
의 프로미스가 거부되면 catch
블록이 트리거됩니다.)
4단계: 마지막으로 examples/animals.json
에 대한 원래 요청의 JSON 데이터가 logResult
에 의해 로깅됩니다.
추가 정보
가져오기는 JSON에만 국한되지 않습니다. 이 예에서는 이미지를 가져와 페이지에 추가합니다.
main.js
에서 다음 코드를 사용하여 showImage
함수를 작성합니다.
function showImage(responseAsBlob) {
const container = document.getElementById('img-container');
const imgElem = document.createElement('img');
container.appendChild(imgElem);
const imgUrl = URL.createObjectURL(responseAsBlob);
imgElem.src = imgUrl;
}
그런 다음 응답을 Blob로 읽는 readResponseAsBlob
함수를 추가합니다.
function readResponseAsBlob(response) {
return response.blob();
}
다음 코드를 사용하여 fetchImage
함수를 업데이트합니다.
function fetchImage() {
fetch('examples/fetching.jpg')
.then(validateResponse)
.then(readResponseAsBlob)
.then(showImage)
.catch(logError);
}
스크립트를 저장하고 페이지를 새로고침합니다. 이미지 가져오기를 클릭합니다. 페이지에 귀여운 강아지가 막대기를 물어오는 모습이 표시됩니다 (물어오기 농담입니다).
설명
이 예에서는 이미지를 가져오고 있습니다(examples/fetching.jpg
). 이전 연습과 마찬가지로 응답은 validateResponse
로 검증됩니다. 그런 다음 응답이 이전 섹션의 JSON 대신 Blob으로 읽힙니다. 이미지 요소가 생성되어 페이지에 추가되고 이미지의 src
속성이 Blob을 나타내는 데이터 URL로 설정됩니다.
참고: URL 객체의 createObjectURL()
메서드는 Blob을 나타내는 데이터 URL을 생성하는 데 사용됩니다. 이 점에 유의해야 합니다. 이미지의 소스를 Blob으로 직접 설정할 수는 없습니다. Blob을 데이터 URL로 변환해야 합니다.
추가 정보
이 섹션은 선택사항 챌린지입니다.
fetchText
함수를 다음과 같이 업데이트합니다.
- 가져오기
/examples/words.txt
validateResponse
로 대답을 검증합니다.- 응답을 텍스트로 읽습니다 (힌트: Response.text() 참고).
- 페이지에 텍스트를 표시합니다.
이 showText
함수를 최종 텍스트를 표시하는 도우미로 사용할 수 있습니다.
function showText(responseAsText) {
const message = document.getElementById('message');
message.textContent = responseAsText;
}
스크립트를 저장하고 페이지를 새로고침합니다. 텍스트 가져오기를 클릭합니다. fetchText
를 올바르게 구현했다면 페이지에 추가된 텍스트가 표시됩니다.
참고: innerHTML
속성을 사용하여 HTML을 가져와 추가하고 싶을 수 있지만 주의해야 합니다. 이렇게 하면 사이트가 교차 사이트 스크립팅 공격에 노출될 수 있습니다.
추가 정보
기본적으로 가져오기는 특정 리소스를 가져오는 GET 메서드를 사용합니다. 하지만 가져오기는 다른 HTTP 메서드도 사용할 수 있습니다.
HEAD 요청하기
headRequest
함수를 다음 코드로 바꿉니다.
function headRequest() {
fetch('examples/words.txt', {
method: 'HEAD'
})
.then(validateResponse)
.then(readResponseAsText)
.then(logResult)
.catch(logError);
}
스크립트를 저장하고 페이지를 새로고침합니다. HEAD 요청을 클릭합니다. 로깅된 텍스트 콘텐츠가 비어 있는지 확인합니다.
설명
fetch
메서드는 두 번째 선택적 매개변수인 init
를 수신할 수 있습니다. 이 매개변수를 사용하면 요청 메서드, 캐시 모드, 사용자 인증 정보, 기타와 같은 가져오기 요청을 구성할 수 있습니다.
이 예에서는 init
매개변수를 사용하여 가져오기 요청 메서드를 HEAD로 설정합니다. HEAD 요청은 응답 본문이 비어 있다는 점을 제외하고 GET 요청과 같습니다. 이러한 요청은 파일에 관한 메타데이터만 필요하고 파일의 모든 데이터를 전송할 필요가 없는 경우에 사용할 수 있습니다.
선택사항: 리소스 크기 확인
examples/words.txt
의 가져오기 응답의 헤더를 살펴보고 파일 크기를 확인해 보겠습니다.
응답 headers
의 content-length
속성을 로깅하도록 headRequest
함수를 업데이트합니다 (힌트: 헤더 문서 및 get 메서드 참고).
코드를 업데이트한 후 파일을 저장하고 페이지를 새로고침합니다. HEAD 요청을 클릭합니다. 콘솔은 examples/words.txt
의 크기 (바이트)를 로깅해야 합니다.
설명
이 예에서는 HEAD 메서드를 사용하여 리소스 자체를 실제로 로드하지 않고 리소스의 크기 (바이트)를 요청합니다 (content-length
헤더에 표시됨). 실제로 이는 전체 리소스를 요청해야 하는지 (또는 요청 방법)를 결정하는 데 사용할 수 있습니다.
선택사항: 다른 방법을 사용하여 examples/words.txt
의 크기를 확인하고 응답 헤더의 값과 일치하는지 확인합니다 (특정 운영체제에서 이 작업을 수행하는 방법을 찾아보세요. 명령줄을 사용하면 보너스 점수가 부여됩니다).
추가 정보
가져오기는 POST 요청과 함께 데이터를 전송할 수도 있습니다.
에코 서버 설정
이 예시에서는 에코 서버를 실행해야 합니다. fetch-api-lab/app/
디렉터리에서 다음 명령어를 실행합니다 (명령줄이 localhost:8081
서버에 의해 차단된 경우 새 명령줄 창이나 탭을 엽니다).
node echo-servers/cors-server.js
이 명령어는 localhost:5000/
에서 간단한 서버를 시작하여 전송된 요청을 다시 에코합니다.
ctrl+c
을 사용하여 언제든지 이 서버를 종료할 수 있습니다.
POST 요청하기
postRequest
함수를 다음 코드로 바꿉니다 (섹션 4를 완료하지 않은 경우 showText
함수를 정의했는지 확인).
function postRequest() {
fetch('http://localhost:5000/', {
method: 'POST',
body: 'name=david&message=hello'
})
.then(validateResponse)
.then(readResponseAsText)
.then(showText)
.catch(logError);
}
스크립트를 저장하고 페이지를 새로고침합니다. POST 요청을 클릭합니다. 페이지에 에코된 전송된 요청을 확인합니다. 이메일에는 이름과 메시지가 포함되어야 합니다 (양식에서 아직 데이터를 가져오지 않음).
설명
fetch를 사용하여 POST 요청을 하려면 init
매개변수를 사용하여 메서드를 지정합니다 (이전 섹션에서 HEAD 메서드를 설정한 방식과 유사함). 여기에서 요청의 본문(이 경우 간단한 문자열)도 설정합니다. 본문은 전송하려는 데이터입니다.
참고: 프로덕션 환경에서는 항상 민감한 사용자 데이터를 암호화해야 합니다.
데이터가 localhost:5000/
에 POST 요청으로 전송되면 요청이 응답으로 다시 에코됩니다. 그런 다음 응답이 validateResponse
로 검증되고, 텍스트로 읽혀 페이지에 표시됩니다.
실제로 이 서버는 서드 파티 API를 나타냅니다.
선택사항: FormData 인터페이스 사용
FormData 인터페이스를 사용하여 양식에서 데이터를 쉽게 가져올 수 있습니다.
postRequest
함수에서 msg-form
양식 요소로부터 새 FormData
객체를 인스턴스화합니다.
const formData = new FormData(document.getElementById('msg-form'));
그런 다음 body
매개변수의 값을 formData
변수로 바꿉니다.
스크립트를 저장하고 페이지를 새로고침합니다. 페이지에서 양식 (이름 및 메시지 필드)을 작성한 다음 POST 요청을 클릭합니다. 페이지에 표시된 양식 콘텐츠를 확인합니다.
설명
FormData
생성자는 HTML form
을 가져와 FormData
객체를 만들 수 있습니다. 이 객체는 양식의 키와 값으로 채워집니다.
추가 정보
CORS가 아닌 에코 서버 시작
명령줄에서 ctrl+c
를 눌러 이전 에코 서버를 중지하고 다음 명령어를 실행하여 fetch-lab-api/app/
디렉터리에서 새 에코 서버를 시작합니다.
node echo-servers/no-cors-server.js
이 명령어는 이번에는 localhost:5001/
에서 또 다른 간단한 에코 서버를 설정합니다. 하지만 이 서버는 크로스 오리진 요청을 수락하도록 구성되어 있지 않습니다.
새 서버에서 가져오기
이제 새 서버가 localhost:5001/
에서 실행 중이므로 가져오기 요청을 보낼 수 있습니다.
localhost:5000/
대신 localhost:5001/
에서 가져오도록 postRequest
함수를 업데이트합니다. 코드를 업데이트한 후 파일을 저장하고 페이지를 새로고침한 다음 POST 요청을 클릭합니다.
콘솔에 CORS Access-Control-Allow-Origin
헤더가 누락되어 크로스 오리진 요청이 차단되었다는 오류가 표시됩니다.
오류 로그에 표시된 대로 no-cors 모드를 사용하고 validateResponse
및 readResponseAsText
호출을 삭제하는 다음 코드로 postRequest
함수의 fetch
를 업데이트합니다(아래 설명 참고).
function postRequest() {
const formData = new FormData(document.getElementById('msg-form'));
fetch('http://localhost:5001/', {
method: 'POST',
body: formData,
mode: 'no-cors'
})
.then(logResult)
.catch(logError);
}
스크립트를 저장하고 페이지를 새로고침합니다. 그런 다음 메시지 양식을 작성하고 POST 요청을 클릭합니다.
콘솔에 로깅된 응답 객체를 확인합니다.
설명
Fetch 및 XMLHttpRequest는 동일한 출처 정책을 따릅니다. 즉, 브라우저가 스크립트 내에서 교차 출처 HTTP 요청을 제한합니다. 교차 출처 요청은 한 도메인 (예: http://foo.com/
)이 별도의 도메인 (예: http://bar.com/
)에서 리소스를 요청할 때 발생합니다.
참고: 교차 출처 요청 제한은 종종 혼동을 야기합니다. 이미지, 스타일시트, 스크립트와 같은 많은 리소스가 도메인 간 (즉, 교차 출처)으로 가져옵니다. 하지만 이는 동일 출처 정책의 예외입니다. 크로스 오리진 요청은 여전히 스크립트 내에서 제한됩니다.
앱 서버의 포트 번호가 두 에코 서버와 다르므로 에코 서버에 대한 요청은 크로스 오리진으로 간주됩니다. 하지만 localhost:5000/
에서 실행되는 첫 번째 에코 서버는 CORS를 지원하도록 구성되어 있습니다 (echo-servers/cors-server.js
을 열어 구성을 검사할 수 있음). localhost:5001/
에서 실행되는 새 에코 서버는 그렇지 않습니다 (따라서 오류가 발생함).
mode: no-cors
을 사용하면 불투명 응답을 가져올 수 있습니다. 이렇게 하면 응답을 가져올 수 있지만 JavaScript로 응답에 액세스할 수는 없습니다 (따라서 validateResponse
, readResponseAsText
또는 showResponse
를 사용할 수 없음). 응답은 여전히 다른 API에서 사용하거나 서비스 워커에 의해 캐시될 수 있습니다.
요청 헤더 수정
Fetch는 요청 헤더 수정도 지원합니다. localhost:5001
(CORS 없음) 에코 서버를 중지하고 섹션 6에서 localhost:5000
(CORS) 에코 서버를 다시 시작합니다.
node echo-servers/cors-server.js
localhost:5000/
에서 가져오는 postRequest
함수의 이전 버전을 복원합니다.
function postRequest() {
const formData = new FormData(document.getElementById('msg-form'));
fetch('http://localhost:5000/', {
method: 'POST',
body: formData
})
.then(validateResponse)
.then(readResponseAsText)
.then(showText)
.catch(logError);
}
이제 Header 인터페이스를 사용하여 Content-Type
헤더가 application/json
와 같은 messageHeaders
라는 postRequest
함수 내에 Headers 객체를 만듭니다.
그런 다음 init
객체의 headers
속성을 messageHeaders
변수로 설정합니다.
body
속성을 문자열화된 JSON 객체로 업데이트합니다.
JSON.stringify({ lab: 'fetch', status: 'fun' })
코드를 업데이트한 후 파일을 저장하고 페이지를 새로고침합니다. 그런 다음 POST 요청을 클릭합니다.
에코된 요청의 Content-Type
가 이전의 multipart/form-data
가 아닌 application/json
으로 변경되었습니다.
이제 messageHeaders
객체에 맞춤 Content-Length
헤더를 추가하고 요청에 임의의 크기를 지정합니다.
코드를 업데이트한 후 파일을 저장하고 페이지를 새로고침한 다음 POST 요청을 클릭합니다. 이 헤더는 에코된 요청에서 수정되지 않습니다.
설명
헤더 인터페이스를 사용하면 헤더 객체를 만들고 수정할 수 있습니다. Content-Type
와 같은 일부 헤더는 가져오기로 수정할 수 있습니다. Content-Length
와 같은 다른 항목은 보호되어 있으며 보안상의 이유로 수정할 수 없습니다.
커스텀 요청 헤더 설정
Fetch는 맞춤 헤더 설정을 지원합니다.
postRequest
함수의 messageHeaders
객체에서 Content-Length
헤더를 삭제합니다. 임의의 값 (예: 'X-CUSTOM': 'hello world'
')이 포함된 맞춤 헤더 X-Custom
를 추가합니다.
스크립트를 저장하고 페이지를 새로고침한 다음 POST 요청을 클릭합니다.
에코된 요청에 추가한 X-Custom
속성이 표시됩니다.
이제 Headers 객체에 Y-Custom
헤더를 추가합니다. 스크립트를 저장하고 페이지를 새로고침한 다음 POST 요청을 클릭합니다.
콘솔에 다음과 비슷한 오류가 표시됩니다.
Fetch API cannot load http://localhost:5000/. Request header field y-custom is not allowed by Access-Control-Allow-Headers in preflight response.
설명
교차 출처 요청과 마찬가지로 리소스가 요청되는 서버에서 맞춤 헤더를 지원해야 합니다. 이 예에서 에코 서버는 X-Custom
헤더는 허용하지만 Y-Custom
헤더는 허용하지 않도록 구성되어 있습니다. echo-servers/cors-server.js
를 열고 Access-Control-Allow-Headers
를 찾아 직접 확인할 수 있습니다. 맞춤 헤더가 설정될 때마다 브라우저에서 프리플라이트 검사를 실행합니다. 즉, 브라우저가 먼저 서버에 OPTIONS 요청을 보내 서버에서 허용하는 HTTP 메서드와 헤더를 확인합니다. 서버가 원래 요청의 메서드와 헤더를 수락하도록 구성된 경우 전송되고, 그렇지 않으면 오류가 발생합니다.
추가 정보
솔루션 코드
작동하는 코드의 사본을 가져오려면 solution 폴더로 이동하세요.
이제 Fetch API를 사용하는 방법을 알게 되었습니다.
리소스
PWA 교육 과정의 모든 Codelab을 확인하려면 과정의 환영 Codelab을 참고하세요.