[Day 32] 클래스(2) + 큐, 스택, 트리 + 비동기 프로그래밍 + Cookie + Ajax + CORS + Access Token
클래스
클래스 상속 (Class Inheritance)
한 클래스의 기능을 다른 클래스에서 재사용할 수 있다.
class Parent {
// ...
}
class Child extends Parent {
// ...
}
extends
키워드를 통해Child
클래스가Parent
클래스를 상속하고 있다.- 이러한 관계를
부모 클래스 - 자식 클래스
또는슈퍼(super) 클래스 - 서브(sub) 클래스
관계라고 한다. - 클래스 상속 관계가 설정되면 가능한 일
- 자식 클래스 A를 통해 부모 클래스 B의 정적 메소드와 정적 속성을 사용할 수 있다.
- 부모 클래스 B의 인스턴스 메소드와 인스턴스 속성을 자식 클래스 A의 인스턴스에서 사용할 수 있다.
super
-
자식 클래스에 부모 클래스와 같은 이름의 속성(메소드)을 정의한 경우 부모 클래스의 속성에 접근할 수 없는 문제가 생긴다.
- 부모 클래스의 메소드를 확장해서 사용하기 위해 자식 클래스에서 일부러 같은 이름으로 정의해서 사용하는 경우를
메소드 오버라이딩(Method Overriding)
이라고 한다. - 이러한 경우에,
super
키워드를 통해 부모 클래스의 속성에 직접 접근할 수 있다.
class Melon { getColor() { return '제 색깔은 초록색입니다.'; } } class WaterMelon extends Melon { getColor() { return super.getColor() + ' 하지만 속은 빨강색입니다.'; } } const waterMelon = new WaterMelon(); waterMelon.getColor(); // 제 색깔은 초록색입니다. 하지만 속은 빨강색입니다.
- 부모 클래스의 메소드를 확장해서 사용하기 위해 자식 클래스에서 일부러 같은 이름으로 정의해서 사용하는 경우를
-
super
키워드의 작동 방식- 자식 클래스의 생성자 내부에서
super
를 함수처럼 호출하면 부모 클래스의 생성자가 호출된다. - 정적 메소드 내부에서는
super.prop
처럼 써서 부모 클래스의prop
정적 속성에 접근할 수 있다. - 인스턴스 메소드 내부에서는
super.prop
처럼 써서 부모 클래스의prop
인스턴스 속성에 접근할 수 있다.
class Person { constructor({name, age}) { this.name = name; this.age = age; } introduce() { return `제 이름은 ${this.name}입니다.` } } class Student extends Person { constructor({grade, ...rest}) { // 부모 클래스의 생성자를 호출할 수 있습니다. super(rest); this.grade = grade; } introduce() { // 부모 클래스의 `introduce` 메소드를 호출할 수 있습니다. return super.introduce() + ` 저는 ${this.grade}학년입니다.`; } } const s = new Student({grade: 3, name: '윤아준', age: 19}); s.introduce(); // 제 이름은 윤아준입니다. 저는 3학년입니다.
- 자식 클래스의 생성자 내부에서
클래스 상속과 프로토타입 상속
-
클래스 상속은 내부적으로 프로토타입 상속 기능을 활용하고 있다.
class Person {} class Student extends Person const student = new Student();
큐, 스택, 트리
큐 (Queue)
-
데이터를 집어넣을 수 있는 선형(linear) 자료형
-
먼저 집어넣은 데이터가 먼저 나온다. (First In First Out)
-
데이터를 집어넣는 enqueue, 데이터를 추출하는 dequeue 작업이 가능하다.
-
큐는 순서대로 처리해야 하는 작업을 임시로 저장해두는 버퍼(buffer)로서 많이 사용된다.
- 동영상 파일을 재생하는 스트리밍을 할 때 버퍼에 영상을 조금씩 잘라서 저장해놓고 하나씩 보여주는데, 이 때의 버퍼가 큐 형식으로 되어있다.
-
JS에서 큐를 구현한 사례
class Queue { constructor() { this._arr = []; } enqueue(item) { this._arr.push(item); } dequeue() { return this._arr.shift(); } } const queue = new Queue(); queue.enqueue(1); queue.enqueue(2); queue.enqueue(3); queue.dequeue(); // 1
스택 (Stack)
-
데이터를 집어넣을 수 있는 선형(linear) 자료형
-
나중에 집어넣은 데이터가 먼저 나온다. (Last In First Out)
-
데이터를 집어넣는 push, 데이터를 추출하는 pop, 맨 나중에 집어넣은 데이터를 확인하는 peek 등의 작업을 할 수 있다.
-
스택은 서로 관계가 있는 여러 작업을 연달아 수행하면서 이전의 작업 내용을 저장해 둘 필요가 있을 때 사용된다.
- 윈도우에서
ctrl + z
기능이 스택을 통해 작동한다.
- 윈도우에서
-
JS에서 스택을 구현한 사례
class Stack { constructor() { this._arr = []; } push(item) { this._arr.push(item); } pop() { return this._arr.pop(); } peek() { return this._arr[this._arr.length - 1]; } } const stack = new Stack(); stack.push(1); stack.push(2); stack.push(3); stack.pop(); // 3
트리 (Tree)
-
여러 데이터가 계층 구조 안에서 서로 연결된 형태를 나타낼 때 사용된다.
-
JS에서 트리를 구현한 사례
class Node { constructor(content, children = []) { this.content = content; this.children = children; } } const tree = new Node('hello', [ new Node('world'), new Node('and'), new Node('fun', [ new Node('javascript!') ]) ]); function traverse(node) { console.log(node.content); for (let child of node.children) { traverse(child); } } traverse(tree); // hello world and fun javascript!
비동기 프로그래밍
Motivation - 타이머 API
-
웹 브라우저에는 함수를 특정 시간이 지난 뒤에 실행시키거나, 함수를 주기적으로 실행시키는 작업을 할 수 있게 해주는 함수가 내장되어 있다.
setTimeout(() => { console.log('setTimeout이 실행된 지 2초가 지났습니다.'); }, 2000); setInterval(() => { console.log('3초마다 출력됩니다.'); }, 3000);
-
setTimeout
과setInterval
은 각각 타이머 식별자를 리턴한다. 이 식별자로 실행 중인 타이머를 취소할 수 있다.const timeoutId = setTimeout(() => { console.log('setTimeout이 실행된 지 2초가 지났습니다.'); }, 2000); const intervalId = setInterval(() => { console.log('3초마다 출력됩니다.'); }, 3000); clearTimeout(timeoutId); clearInterval(intervalId); // 아무것도 출력되지 않습니다.
-
타이머 사용 시 주의할 점
-
정확한 지연시간을 보장하지 않는다. (실제 지연시간과 약간의 차이가 존재한다)
- 정확한 시간이 매우 중요한 게임이나 주식 관련 App에서는 JS를 쓰면 안된다 ㅠㅠ
-
지연시간을 0으로 줘도 바로 실행시키는 것이 아니다. (JS 코드 실행 과정에 따라 실행된다.)
setTimeout(() => { console.log('hello'); }, 0); console.log('world'); // 출력 결과: // world // hello
-
브라우저의 JavaScript 코드 실행 과정
- 호출 스택 (Call Stack)
- 호출 스택은 스택 형태의 저장소로, JavaScript 엔진은 함수 호출과 관련된 정보를 이곳에서 관리한다.
- 호출 스택에 저장되는 각 항목을 실행 맥락(execution context)라고 부른다.
- 실행 맥락에 저장되는 정보
- 함수 내부에서 사용되는 변수
- 스코프 체인
this
가 가리키는 객체
- 실행 맥락에 저장되는 정보
- 호출 스택에 실행 맥락이 존재하는 동안, 브라우저는 먹통이 된다. 브라우저는 60fps(1초에 60번)로 동작(화면을 그리기)하기 때문에, 대략 16ms 안에 코드의 실행을 완료하지 못하면 브라우저의 애니메이션이 끊기는 것 처럼 보인다.
- 작업 큐 (Task Queue)
- 모든 작업을 16ms 안에 처리할 수는 없다. 어떤 이벤트가 일어날 때까지 기다리거나 큰 데이터에 대한 계산이 완료될 때까지 기다리는데는 시간이 오래걸린다.
- 그래서 오래 기다려야 하는 일을 처리하기 위해 임시 저장소인 작업 큐를 이용한다.
- 처리 순서
- 기다려야 하는 일을 JavaScript 엔진에서 직접 처리하는 것이 아니라 API를 통해 브라우저에 위임한다. 이 때, 일이 끝나면 실행시킬 콜백을 같이 등록한다.
- 위임된 일이 끝나면, 그 결과와 콜백을 작업 큐에 추가한다.
- 브라우저는 호출 스택이 비워질 때마다 작업 큐에서 가장 오래된 작업을 꺼내와서 해당 작업에 대한 콜백을 실행시킨다.
- 브라우저는 이러한 과정을 끊임없이 반복한다. 이를 이벤트 루프(event loop)라고 한다.
- 호출 스택과 작업 큐의 성질
- 각 작업은 작업 큐에 쌓인 순서대로 실행된다.
- 이미 작업 큐에 작업이 쌓여있다면, 뒤늦게 추가된 작업은 앞서 추가된 작업이 모두 실행된 다음에(호출 스택이 비워진 다음에) 실행된다.
- 호출 스택이 비워지지 않는다면, 작업 큐에 쌓여 있는 작업을 처리할 수 없다.
- 각 작업 사이에 브라우저는 화면을 새로 그릴 수 있다. (호출 스택이 비워지지 않으면 화면을 새로 그릴 수 없다.)
비동기 프로그래밍 (Asynchronous Programming)
-
비동기 프로그래밍(Asynchronous Programming)
- 어떤 일이 완료되기를 기다리지 않고(브라우저에 위임하고) 다음 코드를 실행해 나가는 프로그래밍 방식
- 브라우저에서의 비동기 프로그래밍은 주로 통신과 같이 오래 걸리는 작업들을 브라우저에 위임할 때 이루어진다.
-
동기식 프로그래밍(Synchronous Programming)
- 어떤 일이 완료될 때까지 코드의 실행을 멈추고 기다리는 프로그래밍 방식
-
콜백 (Callback)
- 다른 함수의 인수로 넘기는 함수를 말한다.
map
,forEach
등의 Array Function 들도 콜백이지만, 이때는 콜백이 동기식으로 호출된다. 이러한 콜백들은 주의해서 사용해야 한다.- 콜백은 복잡한 비동기 데이터 흐름을 표현하기가 어렵다는 단점이 있다. => 콜백 지옥(callback hell)
-
프라미스(Promise)
-
콜백의 문제를 해결하기 위한 여러 라이브러리들 중 Promise 패턴을 사용한 라이브러리들이 ES2015에서 표준화되었다.
-
프라미스는 ‘언젠가 끝나는 작업’의 결과값을 담는 통과 같은 객체이다.
- 프라미스 객체가 만들어지는 시점에는 그 통 안에 무엇이 들어갈지 모를수도 있다.
-
비동기 작업을 하는 프라미스 객체는
Promise
생성자를 통해 만들 수 있다.const p = new Promise((resolve, reject) => { setTimeout(() => { console.log('2초가 지났습니다.'); resolve('hello'); }, 2000); });
- 콜백 안에서
resolve
를 호출하면 resolve에 인수로 준 값이 곧 프라미스 객체의 궁극적인 결과값이 된다.
- 콜백 안에서
-
프라미스 객체의 결과값을 이용해 추가 작업을 하려면
then
메소드를 호출해야 한다.then
메소드에 콜백을 넘겨서 첫번째 인수로 들어온 결과값을 가지고 추가 작업을 할 수 있다.p.then(msg => { console.log(msg); // hello });
-
중요!!!
then
메소드 자체도 프라미스 객체를 반환한다. (메소드 체인 가능)- 콜백에서 반환한 값이 곧 프라미스의 결과값이 된다.
-
Cookie
쿠키의 필요성
- 개별 클라이언트의 여러 요청에 걸친 정보의 유지
- 장바구니
- 로그인 / 로그아웃
- 방문 기록
- etc…
HTTP Cookie
- 서버가 응답을 통해 웹 브라우저에 저장하는 이름+값 형태의 정보
- 웹 브라우저는 쿠키를 저장하기 위한 저장소를 가지고 있다.
- 저장소는 자료의 유효기간과 접근 권한에 대한 다양한 옵션을 제공한다.
쿠키 전송 절차
-
서버는 브라우저에 저장하고 싶은 정보를 응답과 같이 실어 보낸다 (Set-Cookie 헤더)
HTTP/1.1 200 OK Set-Cookie: cookieName=cookieValue; Secure; Max-Age=60000 ...
-
브라우저는 같은 서버에 요청이 일어날 때마다 해당 정보를 요청에 같이 실어서 서버에 보낸다 (Cookie 헤더)
GET / HTTP/1.1 Cookie: cookieName=cookieValue; anotherName=anotherValue ...
Set-Cookie Options (서버가 브라우저에 쿠키를 저장할 때 주는 옵션)
- Expires, Max-Age
- 쿠키의 지속 시간 설정
- Secure
- HTTPS를 통해서만 쿠키가 전송되도록 설정
- HttpOnly
- 자바스크립트에서 쿠키를 읽지 못하도록 설정
- JS로 쿠키를 조작하는 것은 위험한 일이다.
- Cross-Site Scripting 공격 을 당할 수 있기 때문에 위험하다.
- 해커가 스크립트를 넣어서 쿠키를 가로챌 수 있다.
- Domain, Path
- 쿠키의 scope 설정 (쿠키가 전송되는 URL을 제한)
쿠키의 한계점
- US-ASCII 밖에 저장하지 못한다. 보통 percent encoding을 사용한다.
- 4000 바이트 내외(영문 4000자, percent encoding된 한글 444자 가량) 밖에 저장하지 못한다.
- 브라우저에 저장된다. 즉, 여러 브라우저에 걸쳐 공유되어야 하는 정보나 웹 브라우저가 아닌 클라이언트(모바일 앱)에 저장되어야 하는 정보를 다루기에는 부적절하다.
- 보통, 인증토큰(Authentication Token)을 쿠키에 저장한다. (주로 로그인/로그아웃)
Ajax
- 비동기적인 웹 애플리케이션의 제작을 위한 클라이언트측 웹 개발 기법
- 요즘은 의미가 변형되어 웹 브라우저에서 XMLHttpRequest 혹은 fetch를 이용해서 보내는 HTTP 요청을 통칭하기도 한다.
- HTTP Methods
- POST (When Create)
- GET (When Read)
- PUT, PATCH (When Update)
- DELETE (When delete)
-
Ajax Model
-
Ajax의 장점
- 화면 전체를 다시 로드하지 않고도 내용을 갱신할 수 있어 더 나은 사용자 경험을 제공한다.
- 서버의 응답을 기다리는 동안에도 여전히 웹 애플리케이션을 사용할 수 있다.
- 필요한 자원만 서버에서 받아오게 되므로 트래픽이 줄어든다.
-
Ajax의 단점
- 클라이언트 구현이 굉장히 복잡해진다. (FE 개발자가 할 일이 많아진다… ㅠㅠ)
-
Axios
- Promise based HTTP client
- 브라우저와 Node.js에서 모두 사용 가능하다.
- XMLHttpRequest, fetch에 비해 사용하기 편하고 기능이 더 많다.
CORS
Same-origin Policy (동일 출처 정책)
- 웹페이지에서 리소스를 불러올 때, 리소스의 출처가 웹페이지의 출처와 같으면 안전하다고 보고, 출처가 다르면 해당 리소스는 안전하지 않다고 보는 원칙
- 여기서 ‘출처’란 ‘프로토콜 + 도메인 + 포트번호’의 결합을 가리킨다. 즉, 세 개가 다 같아야 동일 출처라고 할 수 있고, 셋 중에 하나라도 다르면 동일 출처로 간주되지 않는다.
- 웹 보안의 기본 원칙으로, 웹 브라우저의 많은 요소에 적용된다.
Content-Security-Policy
- Content-Security-Policy 헤더를 이용하면, 동일하지 않은 출처에 대한 리소스를 불러올지 말지 결정할 수 있다.
- 특정 주소에서 불러오는 리소스만 허용하는 등의 방법을 이용한다.
- 타 출처(주소)로부터 리소스를 불러오는 경우를 허용하지 않는다.
- ex) 페이스북은 이미지를 업로드만 할 수 있고 주소를 통해 다른 출처의 이미지를 불러올 수 없다.
CORS (Cross-Origin Resource Sharing)
- Ajax 요청을 보낼 때 적용되는 보안 정책
- 클라이언트 측 cross-origin 요청을 안전하게 보낼 수 있는 방법을 정한 표준
- 스크립트가 전혀 다른 출처를 갖는 API 서버를 사용하려고 하는 상황에서는 뭔가 추가적인 처리를 해줘야 한다는 것이다.
Cross-origin 요청
- IE8 이상의 모던 웹 브라우저는 cross-origin 요청에 대해 여러가지 제한을 두고 있다.
- cross-origin 요청을 허용하려면, 서버가 특별한 형태의 응답을 전송해야 한다.
- 만약 서버가 cross-origin 요청을 허용하지 않으면, 웹 브라우저는 에러를 발생시킨다.
CORS에 관여하는 응답 헤더
- Access-Control-Allow-Origin
- Access-Control-Expose-Headers
- Access-Control-Max-Age
- Access-Control-Allow-Credentials
- Access-Control-Allow-Methods
- Access-Control-Allow-Headers
- 이러한 헤더와 관련된 에러메세지가 뜬다면 백엔드 개발자에게 CORS 문제라고 말하자!!
CORS에 관여하는 요청 헤더
- Origin
- Access-Control-Request-Method (preflighted 전용)
- Access-Control-Request-Headers (preflighted 전용)
CORS - Safe, Unsafe
- GET, HEAD 요청은 safe(읽기 전용)이기 때문에 서버에 요청이 도달한다고 해서 서버의 상태에 영향을 미칠 일은 없으므로 웹 브라우저는 일단 해당 요청을 보내본다. 만약 서버가 cross-origin 요청을 허용한다고 응답하면 응답을 그대로 사용하고, 그렇지 않으면 에러를 낸다.
- POST, PUT, PATCH, DELETE 등의 메소드는 요청이 서버에 전송되는 것 자체가 위험하므로 실제 요청을 보내기 전에 서버가 cross-origin 요청을 허용하는지를 알아보기 위해 시험적으로 요청을 한 번 보내본다. 이 요청을 preflighted request 라고 한다.
- 단, 기존 HTML form의 동작방식인
application/x-www-form-urlencoded
혹은multipart/form-data
형태의 POST 요청은 preflighted request가 발생하지 않는다.
- 단, 기존 HTML form의 동작방식인
CORS with credentials
- cross-origin 요청에는 기본적으로 쿠키가 포함되지 않으나, XMLHttpRequest 혹은 fetch를 통해서 요청을 보낼 때 쿠키를 포함시키는 옵션을 줄 수 있고, 이 때 CORS 요건이 더 엄격해진다.
Access-Control-Allow-Credentials
헤더 설정이 필요하다.- 단, `Access-Control-Allow-Origin 헤더에 와일드카드 허용 안된다.
CORS 관련 이슈 해결을 위해서는…
- 프론트엔드와 API 서버를 같은 도메인으로 제공한다. (둘을 똑같이 맞춘다.)
- 불가피하게 둘을 다른 도메인으로 제공해야 한다면
- CORS를 허용한다. (CORS 미들웨어를 사용해서)
- CORS를 허용하는 경우, 쿠키를 쓸 수는 있으나 보안상 허점이 생기기 쉽고 사용하기도 불편하므로 보통 JWT와 같은 토큰 방식의 인증을 사용한다.
Access Token & JWT
쿠키의 단점
- 쿠키를 지원하는 클라이언트에서밖에 사용할 수 없다.
- 적절히 관리되지 않은 쿠키는 보안에 취약하며, 관리를 하려고 해도 CORS 대응이 복잡하다.
Token Based Auth
- 토큰이란, 사용자의 자격증명(아이디, 패스워드 등)을 통해 인증이 이루어진 후, 특정 자원에 대한 자격증명으로서 대신 사용되는 인증 수단을 말한다.
- 서버에 요청을 할 때마다 토큰을 요청에 직접 포함시켜서 전송한다. (주로 Authorization 헤더에 넣어서 전송)
Cookie vs Token
토큰 사용의 장점
- 쿠키를 지원하지 않는 클라이언트에서도 편하게 사용할 수 있다.
- 쿠키를 사용하지 않음으로써 CORS 관련 문제를 회피할 수 있다.
토큰 사용의 단점
- 매 요청에 토큰이 포함되어야 하므로 적당히 짧은 길이를 유지해야 한다.
- 토큰 유출에 대한 대비책이 필요하다.
- 토큰에 유효기간을 두거나, 유출된 토큰을 강제로 무효화하는 등의 방법을 사용한다.
- 쿠키와는 다르게, 클라이언트 개발자가 직접 토큰을 저장하고 관리해야 한다.
Web Storage
- 브라우저에서 키-값 쌍을 저장할 수 있는 저장소
- 쿠키에 비해 사용하기 편리하고 저장 가능한 용량도 크다. (10MB 가량)
- 브라우저 탭이 닫히면 내용이 삭제되는 sessionStorage, 브라우저 탭이 닫혀도 내용이 유지되는 localStorage가 있다.
보안 상 주의사항
- (당연히) HTTPS를 사용해야 한다.
- 토큰을 localStorage에 저장하게 되면 자바스크립트로 토큰을 탈취할 수 있게 되므로, 웹사이트에 악성 스크립트를 삽입하는 공격(XSS)에 노출되지 않도록 신경써야 한다.
댓글남기기