fetch 를 사용하면 다운로드 과정을 추적할 수 있다. 현재는 업로드를 추적할 수는 없어서 업로드를 추적하려면 WMLHttpRequest 를 사용해야 함.
response.body 속성을 사용해서 접근할 수 있다. ReadableStream이라는 청크 단위로 제공하는 특수 객체를 이요한다.
reponse.text()나 response.json() 와는 다르게 reponse.body 는 전체 과정에을 카운트 할 수 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13
// instead of reponse.json() and other methods const reader = reponse.body.getReader();
// infinite loop while the body is downloading while (true) { // done is true for the last chunk // value is Uint8Array of the chunk bytes const { done, value } = await reader.read();
if (done) { break; } }
await reader.read()에 결과는 다음 두가지 프로퍼티를 가진 결과이다.
done : 읽기가 모두 완료 됬으면 true, 아니면 false
value : Uint8Array 타입인 Array
ReadableStream 은 for awiat ..of도 지원한다, 하지만 아직까지 모든 브라우저에서 지원하는 것이 아니기 때문에 여기서는 while를 사용했다.
// step 1 : start the fecth and obtain a reader let response = await fetch( "https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits?per_page=100" );
const reader = response.body.getReader();
// step 2 : get total length const contentLength = +response.headers.get("Content-Length");
// step 3 : read the data let recivedLength = 0; // received that many bytes at the momnet let chunks = []; // array of received binary chunks (comprises the body) while (true) { const { done, value } = await reader.read();
console.log(`Received ${receivedLength} of ${contentLength}`); }
// step 4: concatenate chunks into single Uint8Array let chunksAll = newUint8Array(receivedLength); let position = 0; for (let chunk of chunks) { chunksAll.set(chunk, position); position += chunk.length; }
// step 5: decode into a string let result = new TextDecoder("utf-8").decode(chunksAll);
// We're done! let commits = JSON.parse(result); alert(commits[0].author.login);
“Received 79656 of 0” “Received 437910 of 0”
response.json()을 통해 데이터를 읽지 않았다.
Content-Length 헤더를 통해서 response에 길이를 알 수 있다.
chunks 배열에 chunk를 모은다. response는 한번 읽으면 다시 읽을 수 없기 때문이다.
chunksAll에 하나의 결과로 모은다. 아쉽게도 다른 메서드가 없기 때문에 반복문을 사용햇 가각의 chunk를 .set을 통해 복사한다. 하지만 아직도 바이트 어레이이다.
byte를 string으로 해석할 필요가 있다. TextDecoder은 원하는 딱 이런 작업을 해줌.
fetch 는 promise를 반환하고 자바스크립트에서는 fetch를 중단하는 개념이 없다. 이런 목적을 위해 내장된 객체인 AbortController가 있다. 이것은 fetch 뿐만 아니라 비동기 작업에서도 사용될 수 있다.
The AbortController object
1
let controller = new AbortController();
반환하는 객체는 간단하다.
abort() 메서드를 갖는다.
이벤트 등록을 위한 signal 프로퍼티를 갖는다.
abort()가 호출되었을 때
controller.signal 은 abort 이벤트를 내보낸다.
controller.signal.aborted 프로퍼티가 true가 된다.
취소 가능한 작업을 controller.signal에 리스너로 등록한다. controller.abort()를 호출할때 리스너가 트리거 된다.
fetch를 포함하지 않은 예제는 다음과 같다.
1 2 3 4 5 6 7 8 9 10 11 12 13
let controller = new AbortController(); let signal = controller.signal;
// teh party that performs a cancelable operation // gets "signal" object // and sets the listener to trigger when controller.abort() is called signal.addEventListener("abort", () => alert("abort!!"));
// the other party, that cancels (at any point later) controller.abort(); // abort!!
//the event triggers and signal.aborted becomes true alert(signal.aborted); // true
코드에서 AbortController 없이 비슷한 작업을 할 수 있지만 fecth 가 AbortController 객체와 함께 작동하고 통합된다는것이 중요하다.
Using with fetch
AbortController에 signal프로퍼티를 fetch에 옵션으로 전달해 주면 된다.
1 2 3 4
let controller = new AbortController(); fetch(url, { signal: controller.signal, });
fetch가 중단되면 AbortError를 던진다. try..catch를 통해서 에러를 잡을 수 있다.
전체 예제
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// abort in 1 second let controller = new AbofrtController(); setTimeout(() => controller.abort(), 1000);
도메인이나 서브도메인, 프로토콜, 포트가 다른 곳에 요청을 보내는 것을 Croww-Origin Request(크로스 오리진 요청) 이라고 한다. 크로스 오리진 요청을 보내려면 리모트 오리진에서 전송받은 특별한 헤더가 필요하다. 이러한 정책을 CORS라고 부른다.
왜 CORS 가 필요한가에 대한 짧은 역사
과거 수 년 동안, 한 사이트의 스크립트에서 다른 사이트에 있는 콘텐츠에 접근할 수 없다는 제약이 있었다. 더 강력한 작업을 위해서 다른 웹사이트로에 요청이 필요했다.
여러가지 트릭이 있었지만, 오랜 논의 끝에 크로스 오리진 요청을 허용 하기로 결정했다. 대신 크로스 오리진 요청은 서버에서 명시적으로 크로스 오리진 요청을 ‘허가’ 했다는 것을 알려주는 특별한 헤더를 전송받았을 때만 가능하도록 제약을 걸었다.
안전한 요청
안전한 요청은 다음과 같은 두 가지 조건 모두를 충족한다.
안전한 메서드 - GET이나 POST, HEAD를 사용한 요청
안전한 헤더 - 다음 목록에 속하는 헤더
Accept
Accept-Language
Content-Language
값이 application/x-www-form-urlencoded나 multipart/form-data, text/plain 인 Content-Type
표준이 아닌 헤더가 들어있거나 안전하지 않은 메서드를 사용한 요청은 안전한 요청이 될 수 없다. 그런데 시간이 지나고 개발자가 스크립트를 사용해 안전하지 않은 요청을 보낼 수 있게되자, 브라우저는 안전하지 않은 요청을 서버에 전송하기 전에 preflight 요청을 먼저 전송해 서버가 크로스 오리진 요청을 받을 준비가 되어있는지 확인한다.
이때 서버에서 크로스 오리진 요청은 허용하지 않는다는 정보를 담은 헤더를 브라우저에 응답하면 안전하지 않은 요청은 서버에 전송되지 않는다.
CORS와 안전한 요청
크로스 오리진 요청을 보낼 경우 브라우저는 항상 Origin이라는 헤더를 요청에 추가해서 자신에 도메인을 적는다.
1 2 3 4
GET /request Host: anywhere.com Origin: https://javascript.info ...
서버는 Origin을 검사하고 요청을 받아들이기로 동의한 상태라면 특별한 헤더 Access-Control-Allow-Origin을 응답에 추가한다. 여기에 오리진 정보나 * 를 붙히면 응답에 성공하고 그렇지 않으면 실패하게 된다.
이때 브라우저는 중재인의 역할을 수행한다. 서버에서 크로스 오리진 요청을 수행한 경우, prefilght 요청에 응답은 다음과 같은 형태를 뛴다.
1 2 3
200 OK Content-Type:text/html; charset=UTF-8 Access-Control-Allow-Origin: https://javascript.info
응답 헤더
크로스 오리진 요청이 이루어진 경우, 자바스크립트는 기본적으로 안전한 응답 헤더로 분류되는 헤더에만 접속할 수 있다.
Cache-Control
Content-Language
Content-Type
Expires
Last-Modified
Pragma
이 외의 응답 헤더에 접근하면 에러가 발생한다.
위 리스트엔 Contnet-Length헤더는 없다.
안전하지 않는 응답 헤더에 접근하려면 서버에서 Access-Contorol-Expose-Headers라는 헤더를 보내줘야 한다. 여기에 자바스크립트 접근을 허용하는 안전하지 않은 헤더 목록이 담겨 있다.
$ git status -s M README MM Rakefile A lib/git.rb M lib/simplegit.rb ?? LICENSE.txt
아직 추적하고 있지 않는 파일은 ?? 로 뜬다. README 파일은 아직 staged 상태가 안된 파일이여서 오른쪽에만 M이 붙는다. Rakefile 같은 경우는 수정 후 한번 add 했다가 다시 수정해서 왼쪽에 staged 된 변경사항이라는 정보와 오늘쪽에 staged 안된 변경 사항이라는 정보가 같이 적힌다(MM). staged 상태로 추가한 파일 중 새로 생성한 파일 앞에는 A 표시가 붙난다.
Staged와 Unstaged 상태의 변경 내용을 보기
git diff 명령을 수행하면 수정했지만 아직 staged 상태가 아닌 파일을 비교해 볼 수 있다.
만약 커밋하려고 Staging Area에 넣은 파일의 변경 부분을 보고 싶으면 git diff --staged 옵션을 사용한다.
commit 하기
git commit -v 옵션을 추가하면 편집기에 diff 메세지도 추가된다. 기본적으로넌 git status 명령의 결과가 채워진다.
파일 삭제하기
git rm 명령으로 Tracked 상태의 파일을 삭제한 후 (Staging Area에서 삭제 하는 것) 커밋을 실시. 그냥 제거 하게 되면 Unstaged 상태로 파일이 지워 졌다고 표시함. 반면에, git rm 명령어를 사용하면 파일이 지워졌다는 메세지와 함께 staged 된다(파일도 함께 지워짐). 만약 변경하고 Staging Area 에 추가했다면 -f 옵션을 주어 강제로 삭제 해야 한다.
Staging Area에서만 제거하고 워킹 디렉토리에 있는 파일은 지우지 않고 남겨두고 싶다면 --cached 옵션을 사용한다.
git rm -- cached README
파일 이름 변경하기
이름을 바꿀 때에는 git mv명령을 사용한다. git mv file_from file_to