[JavaScript] 4-6. Modern_Web

Modern_Web


Express Application 은 기본적으로 일련의 Middleware 함수 호출입니다.

Express Middleware



Middleware 가 하는 일



Middleware 로 하는 일

const secret = req.query.secret || req.body.secret;
if (secret !== process.env.SECRET) {
  res.status(403);
  res.send("403 Forbidden");
} else {
  next(); // 다음 미들 웨어로 과정을 넘긴다.
}



왜 Middleware 를 사용하는가?

미들웨어로 할 수 있는 모든 일은 라우트 핸들러에서도 할 수 있으나,
여러 라우터에서 사용해야 하는 기능을 중복 작성하는 불편을 덜고
코드를 재사용하기 위해 미들웨어를 사용하는 것입니다.



Middleware 생태계

https://www.npmjs.com/search?q=express%20middleware&page=3&perPage=20

https://expressjs.com/ko/resources/middleware.html



미들웨어 VS 라우트 핸들러

app.get("/", (req, res, next) => {
  if (!someCondition) {
    next(); // 요청을 처리를 하지 않고 다른 핸들러로 넘김
  } else {
    res.send("hello");
  }
});



app.use

  1. 미들웨어를 앱 전체에서 동작하도록 주입할 때
app.use(helloMiddleware);
  1. 특정 경로에서만 동작하도록 주입할 때
app.use("/some-path", helloMiddleware);
  1. 한 번에 여러 개 주입할 때
app.use(middleware1, middleware2, middleware3, ...)



Express 오류 처리

Express 는 내장된 오류 핸들러와 함께 제공되며, 내장 오류 핸들러는 앱에서 발생할 수 있는 모든 오류를 처리합니다.
이러한 기본 오류 처리 미들웨어 함수는 미들웨어 함수 스택의 끝에 추가됩니다.

next()로 오류를 전달하지만 오류 핸들러에서 해당 오류를 처리하지 않는 경우,
기본 제공 오류 핸들러가 해당 오류를 처리하며, 해당 오류는 클라이언트에 스택 추적과 함께 기록됩니다.
스택 추적은 프로덕션 환경에 포함되어 있지 않습니다.

응답의 기록을 시작한 후에 오류가 있는 next()를 호출하는 경우(예: 응답을 클라이언트로 스트리밍하는 중에 오류가 발생하는 경우),
Express 의 기본 오류 핸들러는 해당 연결을 닫고 해당 요청을 처리하지 않습니다.

따라서 사용자 정의 오류 핸들러를 추가할 때,
헤더가 이미 클라이언트로 전송된 경우에는 다음과 같이 Express 내의 기본 오류 처리 메커니즘에 위임해야 합니다.

function errorHandler(err, req, res, next) {
  if (res.headersSent) {
    return next(err);
  }
  res.status(500);
  res.render("error", { error: err });
}



쿠키는 클라이언트 로컬(웹 브라우저)에 저장되는 키와 값이 들어있는 작은 데이터 파일입니다.
쿠키에는 이름, 값, 만료날짜(쿠키 저장지간), 경로 정보, 접근 권한등 다양한 옵션이 들어있습니다.
쿠키는 일정시간동안 데이터를 저장할 수 있습니다. (로그인 상태 유지에 활용합니다.)
쿠키는 클라이언트의 상태 정보를 로컬에 저장했다가 참조합니다.



쿠키 전송 절차

  1. 서버는 브라우저에 저장하고 싶은 정보를 응답과 같이 실어 보낸다 (Set-Cookie 헤더)
HTTP/1.1 200 OK
Set-Cookie: cookieName=cookieValue; Secure; Max-Age=60000
...
  1. 브라우저는 같은 서버에 요청이 일어날 때마다 해당 정보를 요청에 같이 실어서 서버에 보낸다 (Cookie 헤더)
GET / HTTP/1.1
Cookie: cookieName=cookieValue; anotherName=anotherValue
...







자바스크립트로도 쿠키를 읽고 쓰는 방법이 존재하지만,
보안 상 문제를 일으킬 수 있으므로 이런 접근 방식은 거의 사용되지 않는다.

자바스크립트에서 쿠키에 접근하지 못하도록 HttpOnly 를 항상 설정하는 것이 제일 좋습니다.



쿠키의 한계점



웹개발 방식 - 알고가기

전통적 웹개발 방식 - 요청시마다 새로고침이 일어나며 페이지가 로딩될때마다 서버로부터
리소스들을 전달받아 해석한뒤 화면에 렌더링 하게 됩니다.

현대적 웹개발 방식 - 캐싱과 압축이라는 방식으로 어느정도 해소하였지만 결국 브라우저는
모든 CSS, 자바스크립트, HTML 을 해석한 뒤에 이들을 화면에 렌더링합니다.



Ajax

비동기적인 웹 어플리케이션의 제작을 위한 클라이언트 측 웹 개발 기법이였으나
요즘에는 의미가 변형되어 웹 브라우저에서 XMLHttpRequest 혹은
fetch 를 이용해서 보내는 HTTP 요청을 통칭하기도 합니다.



Ajax model



Ajax 의 장점



Ajax 의 단점



Axios



Axios Method

// config 객체
axios
  .get("/api/todos", {
    params: {
      // query string
      title: "react 공부"
    },
    headers: {
      // 요청 헤더
      "X-Api-Key": "my-api-key"
    },
    timeout: 1000 // 1초 이내에 응답이 오지 않으면 에러로 간주
  })
  .then(res => {
    prettyPrint(res.data);
  });

//  axios 요청 메소드의 두 번째 인자로 config 객체를 넘길 수 있습니다.
// config 객체를 통해 요청의 쿼리 스트링, 요청 헤더, 쿠키 포함 여부 등 많은 것들을 설정할 수 있습니다.



Json-Server

아무 보안도 없고, 실무에서 사용하지 못하지만,
간단히 백엔드 기능이 필요할 때 사용하는 것이 좋습니다.


GET / posts;
GET / posts / 1;
POST / posts;
PUT / posts / 1;
PATCH / posts / 1;
DELETE / posts / 1;
GET    /profile
POST   /profile
PUT    /profile
PATCH  /profile
GET    /profile
GET /posts?title=json-server&author=typicode
GET /posts?id=1&id=2

GET /comments?author.name=typicode
GET /posts?_page=7
GET /posts?_page=7&_limit=20

기본적으로 10 개의 항목이 반환됩니다.

GET /posts?_sort=views&_order=asc
GET /posts/1/comments?_sort=votes&_order=asc

여러 필드일 경우

GET /posts?_sort=user,views&_order=desc,asc
GET /posts?_start=20&_end=30
GET /posts/1/comments?_start=20&_end=30
GET /posts/1/comments?_start=20&_limit=10

Array.slice 와 똑같이 작동합니다 (예 : _start 가 포함되고 _end 만 포함).

GET /posts?views_gte=10&views_lte=20

_ne를 사용하면 값을 배재합니다.

GET /posts?id_ne=1

_like를 사용하면 필터를 할 수 있습니다. (정규표현식도 지원합니다.)

GET /posts?title_like=server
GET /posts?q=internet
GET /posts?_embed=comments
GET /posts/1?_embed=comments

parent 리소스를 포함하려면 _embed를 추가하면됩니다.

GET /comments?_expand=post
GET /comments/1?_expand=post

중첩 된 리소스를 가져 오거나 만들려면 (기본적으로 한 수준에서는 추가 경로를 추가하십시오)

GET  /posts/1/comments
POST /posts/1/comments



Json-Server Tip

{
  "todos": [
    {
      "id": 1,
      /* json 서버에 들어가는 객체는 아이디는  적어주도록 하자 */
      "content": "할 일 텍스트",
      "complete": false
    }
  ]
}



Same-origin Policy (동일 출처 정책)



Same-origin Policy 실습 - 1

const child = window.open("http://www.fastcampus.co.kr");
// 새로 열린 웹 페이지의 콘솔에서
window.foo = "bar";
// 이전 웹 페이지의 콘솔에서
child.foo;
// 출처가 같다면 접근 가능, 아니면 불가



Content-Security-Policy

Content-Security-Policy 헤더를 이용하면,
동일하지 않은 출처에 대한 리소스를 불러올지 말지 결정할 수 있습니다.



CORS (Cross-Origin Resource Sharing)



Cross-origin 요청의 위험성

아래 상황을 가정해봅시다.

mywebsite.com 에서 서비스 중인 웹 사이트는 mywebsite.com/api 에서
REST API 를 통해 필요한 정보를 얻습니다.
mywebsite.com/api 경로에 대한 인증은 쿠키로 이루어지고 있습니다.

그런데 만약 evil.com 웹 사이트의 스크립트에서
mywebsite.com API 에 요청을 마음대로 보낼 수 있다면,
이미 my-website.com 도메인에 대해 브라우저에 저장된 쿠키를
이용해서 API 를 마음대로 호출할 수 있을 것입니다.



Cross-origin 요청



CORS 에 관여하는 응답 헤더



CORS 에 관여하는 요청 헤더



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 가 발생하지 않습니다.)



CORS with credentials

cross-origin 요청에는 기본적으로 쿠키가 포함되지 않으나,
XMLHttpRequest 혹은 fetch 를 통해서 요청을 보낼 때
쿠키를 포함시키는 옵션을 줄 수 있고 이 때 CORS 요건이 더 엄격해집니다.

(Access-Control-Allow-Credentials 헤더 설정 필요하며, Access-Control-Allow-Origin 헤더에 와일드카드 허용 안됩니다.)



복잡하면 그냥…

  1. 프론트엔드와 API 서버를 같은 도메인으로 제공합니다.
  2. 불가피하게 둘을 다른 도메인으로 제공해야 한다면



쿠키의 단점



Token Based Auth





토큰 사용의 장점



토큰 사용의 단점



Web Storage





보안 상 주의사항



JSON Web Token



JWT 실습

// 토큰 받아오기
let token;
axios
  .post("/auth", {
    username: "fast",
    password: "campus"
  })
  .then(res => {
    token = res.data.token;
    console.log(`token: ${token}`);
  });

// 토큰으로 요청하기 1
axios
  .get("/auth", {
    headers: {
      Authorization: `Bearer ${token}`
    }
  })
  .then(res => {
    prettyPrint(res.data);
  });

// 토큰으로 요청하기 2
axios
  .get("/some-api", {
    headers: {
      Authorization: `Bearer ${token}`
    }
  })
  .then(res => {
    prettyPrint(res.data);
  });

// 토큰으로 요청하기 3
axios
  .post("/count", null, {
    headers: {
      Authorization: `Bearer ${token}`
    }
  })
  .then(res => {
    prettyPrint(res.data);
  });

// Axios는 중복된 설정을 하지 않도록 해주는 편의도구를 제공하고 있습니다.
// 그것을 Axios instance라고 부르는데,
// 한 번 config 객체를 넘겨서 Axios instance를 생성하면 해당 instance를 통해 보내는 요청에는 config 객체가 자동으로 설정됩니다.

// Axios.create
const authedAxios = axios.create({
  headers: {
    Authorization: `Bearer ${token}`
  }
});
authedAxios.get("/auth").then(res => {
  prettyPrint(res.data);
});
authedAxios.get("/some-api").then(res => {
  prettyPrint(res.data);
});
authedAxios.post("/count").then(res => {
  prettyPrint(res.data);
});



Fetch API



Axios vs Fetch API

Axios 는 여러 편의기능(instance 와 같이 설정을 재사용하거나 요청중인 연결을 취소하는 등)을 제공합니다.
다만, Axios 는 내부적으로 XMLHttpRequest 를 사용하고 있는데 Service Worker 등의 최신 기술이 XMLHttpRequest 를 지원하지 않으므로,
Service Worker 를 사용할 예정에 있는 프로젝트에서는 Axios 대신 Fetch API 를 사용해야만 합니다.

Service Worker 공부해보기

https://developers.google.com/web/fundamentals/primers/service-workers/?hl=ko



PWA 도 공부해보기

https://developers.google.com/web/fundamentals/codelabs/your-first-pwapp/?hl=ko



Cache

컴퓨터 분야에서의 캐시는 (주로 접근 속도의 개선을 위해) 데이터를 미리 복사해 놓는
임시 저장소, 혹은 그 임시 저장소에 데이터를 저장하는 행위를 가리킵니다.
‘cache’ 혹은 ‘caching’이라는 용어 자체는 특정 기술을 가리키는 것이 아니라,
접근 속도를 개선하기 위해 따로 저장소를 두는 ‘방법’을 가리킵니다.
컴퓨터의 아주 많은 부분(CPU, GPU, HDD, 네트워크, 웹, 데이터베이스…)에서 사용되고 있습니다.



HTTP Cache




Cache 관련 헤더

(요청, 응답) 캐시와 관련된 다양한 기능을 하는 지시자를 포함. no-cache, max-age 가 많이 사용됩니다. no-cache, max-age=0 지시자는 캐시를 사용하지 않도록 하거나, 캐시를 아직도 쓸 수 있는지 검증하기 위해 사용됩니다. (각각의 자세한 의미)

(응답) 캐시의 검증을 위해 사용되는 자원의 식별자. 주로 자원의 해시값이 사용되나, 마지막으로 수정된 시각, 혹은 버전 넘버를 사용하기도 합니다

해시

해시 함수(hash function)는 임의의 길이의 데이터를 고정된 길이의 데이터로 매핑하는 함수입니다.
해시 함수에 의해 얻어지는 값은 해시 값, 해시 코드, 해시 체크섬 또는 간단하게 해시라고 합니다.

해시의 특징

  1. 값이 조금이라도 변하면 JWT 와는 다르게 결과값이 완전히 달라집니다. (입력값이 다르면 출력값도 다릅니다.)
  2. 입력값의 길이가 얼마가 되었든 출력은 그 길이가 항상 일정합니다.
  3. x 로부터 h(x)의 값은 얻기 쉽지만 h(x)에서 x 를 구하기는 힘듭니다. (1-wayness 성질을 지닙니다.)


(응답) 캐시를 만료시킬 시각을 서버에서 명시적으로 지정합니다.

(응답) 원래 자료가 마지막으로 수정된 시각입니다.

(요청) 검증을 위해 사용됩니다. 이전에 저장해두었던 자원의 ETag 값을 If-None-Match 헤더의 값으로
요청에 포함시켜서 보내면, 서버는 해당 경로에 있는 자원의 ETag 와 비교해보고 자원의 전송 여부를 결정합니다.

(요청) 검증을 위해 사용됩니다. 이전에 저장해두었던 자원의 Last-Modified 값을
If-Modified-Since 헤더의 값으로 요청에 포함시켜서 보내면, 서버는
해당 경로에 있는 자원의 Last-Modified 와 비교해보고 자원의 전송 여부를 결정합니다.

아래처럼 엮어서 씁니다.
[ETag] - [If-None-Match]
[Last-Modified] - [If-Modified-Since]



Cacheable Methods

POST 메소드는 Cacheable 범주에 포함되기는 하지만,
특별한 조건을 만족시켜야 하며 실무에서는 POST chace 가 거의 사용되지 않습니다.



캐시의 사용

일단은 별다른 추가작업 없이도 편하게 캐시 기능을 사용할 수 있지만
우리가 원하는대로 캐시가 동작하지 않을 때 그 원인을 파악하기 위해
캐시 관련 헤더는 숙지해두는 것이 좋습니다.
그리고 HTTP method 를 용도에 맞게 사용하는 것도 중요합니다.



REST API 의 단점



GraphQL



2. Today I Found Out

한 주동안 백엔드 쪽에 대해서 공부를 해보았는데
처음은 어렵다가도 하나씩 차근차근 이해를 하려고 노력할수록
이해가 되어서 오늘 있었던 cookie, session, ajax, axios등..
크게 어렵지 않게 이해를 할 수 있었습니다.
하나하나 체인처럼 연결되어 있어 이해할수록 재미가 있습니다.



3. refer

https://fds9.github.io/fds-nodejs-http/

http://vuejs.kr/update/2017/01/04/http-request-with-axios/

https://wpsn-axios-example.glitch.me/

http://jeong-pro.tistory.com/80