nodemon is a tool that helps develop node.js based applications by automatically restarting the node application when file changes in the directory are detected.
간단하게 말하면 우리가 node.js로 만든 애플리케이션의 변화(코드 수정)가 있을 때 자동으로 애플리케이션을 재실행시켜주는 도구이다.
1 - 1) 설치
npm install -g nodemon //전역적 설치
npm install --save-dev nodemon //특정 디렉토리 내에서만 사용
1 - 2) 사용법
node.js 애플리케이션을 서버로 동작시킬 때 node app.js 와 같이 사용하는데 이때 node 부분을 nodemon으로만 바꿔주면
nodemon을 사용할 수 있다.
1 - 3) cli options
Configuration
--config <file> .......... alternate nodemon.json config file to use
--exitcrash .............. exit on crash, allows nodemon to work with other watchers
-i, --ignore ............. ignore specific files or directories
--no-colors .............. disable color output
--signal <signal> ........ use specified kill signal instead of default (ex. SIGTERM)
-w, --watch path ......... watch directory "dir" or files. use once for each
directory or file to watch
--no-update-notifier ..... opt-out of update version check
Execution
-C, --on-change-only ..... execute script on change only, not startup
--cwd <dir> .............. change into <dir> before running the script
-e, --ext ................ extensions to look for, ie. "js,pug,hbs"
-I, --no-stdin ........... nodemon passes stdin directly to child process
--spawn .................. force nodemon to use spawn (over fork) [node only]
브라우저의 개발자 도구에서 Location의origin프로퍼티에 접근함으로써 손 쉽게 애플리케이션이 실행되고 있는 출처를 알아낼 수 있다.
2. 같은 출처와 다른 출처의 구분
위에서 같은 출처란 프로토콜, 호스트, 포트 번호까지 모두 같으면 같은 출처이고 그중 하나라도 다르면 다른 출처로 인식한다고 했다.
그럼 그러한 출처를 비교하는 로직이 어디에서 이루어지냐 하면 브라우저에 구현되어 있는 스펙이다.
만약 우리가 CORS 정책을 위반하는 리소스 요청을 하더라도 해당 서버가 같은 출처에서 보낸 요청만 받겠다는 로직을 가지고 있는 경우가 아니라면 서버는 정상적으로 응답을 하고, 이후 브라우저가 이 응답을 분석해서 CORS 정책 위반이라고 판단되면 그 응답을 사용하지 않고 그냥 버리는 순서인 것이다.
3. CORS 동작 원리
그럼 본격적으로 어떤 방법을 통해 서로 다른 출처를 가진 리소스를 안전하게 사용할 수 있는지 알아보도록 하자.
기본적으로 웹 클라이언트가 다른 출처의 리소스를 요청할 때는 HTTP 프로토콜을 사용하여 요청을 보내게 되는데, 이때 브라우저는 요청 헤더에Origin이라는 필드에 현재 요청을 보내는 출처를 함께 담아 보낸다.
이후 서버가 이 요청에 대한 응답을 할 때 응답 헤더의Access-Control-Allow-Origin이라는 값에 “허용된 출처”를 내려주고, 이후 응답을 받은 브라우저는 자신이 보냈던 요청의Origin과 서버가 보내준 응답의Access-Control-Allow-Origin의 값을 비교해본 후 이 응답이 유효한 응답인지 아닌지를 결정한다.
기본적인 흐름은 이렇게 간단하지만, 사실 CORS가 동작하는 방식은 한 가지가 아니라 세 가지의 시나리오에 따라 변경되기 때문에 여러분의 요청이 어떤 시나리오에 해당되는지 잘 파악해야 CORS 정책 위반으로 인한 에러를 디버깅하는 것이 한결 쉬울 것이다.
3 - 1) Preflight Request
프리플라이트(Preflight)방식은 일반적으로 우리가 개발할 때 가장 많이 마주치는 시나리오이다.
이 시나리오에 해당하는 상황일 때 브라우저는 요청을 한 번에 보내지 않고 예비 요청과 본 요청으로 나누어서 서버로 전송한다.
이때 브라우저가 본 요청을 보내기 전에 보내는 예비 요청을 Preflight라고 부르는 것이며, 이 예비 요청의 HTTP 메서드는
OPTIONS 메소드가 사용된다.
예비 요청의 역할은 본 요청을 보내기 전에 브라우저 스스로 이 요청을 보내는 것이 안전한지 확인하는 것이다.
우리가 fetchAPI를 사용하여 브라우저에게 리소스를 받아오라는 명령을 내리면 브라우저는 서버에게 예비 요청을 먼저 보내고, 서버는 이 예비 요청에 대한 응답으로 현재 어떤 것들을 허용하고, 어떤 것들을 금지하고 있는지에 대한 정보를 응답 헤더에 담아서 브라우저에게 다시 보내주게 된다.
이후 브라우저는 자신이 보낸 예비 요청과 서버가 응답에 담아준 허용 정책을 비교한 후, 이 요청을 보내는 것이 안전하다고 판단되면 같은 엔드포인트로 다시 본 요청을 보내게 된다. 이후 서버가 이 본 요청에 대한 응답을 하면 브라우저는 최종적으로 이 응답 데이터를 자바스크립트에게 넘겨준다.
3 - 2) Simple Request
단순 요청은 예비 요청을 보내지 않고 바로 서버에게 본 요청을 한 후, 서버가 이에 대한 응답의 헤더에Access-Control-Allow-Origin과 같은 값을 보내주면 그때 브라우저가 CORS 정책 위반 여부를 검사하는 방식이다. 즉, 프리플라이트와 전반적인 로직 자체는 같되, 예비 요청의 존재 유무만 다르다.
하지만 아무 때나 단순 요청을 사용할 수 있는 것은 아니고, 특정 조건을 만족하는 경우에만 예비 요청을 생략할 수 있다. 게다가 이 조건이 조금 까다롭기 때문에 일반적인 방법으로 웹 애플리케이션 아키텍처를 설계하게 되면 거의 충족시키기 어려운 조건들이라 이런 경우는 드물다. 조건은 아래와 같다.
요청의 메서드는GET,HEAD,POST중 하나여야 한다.
Accept,Accept-Language,Content-Language,Content-Type,DPR,Downlink,Save-Data,Viewport-Width,Width를 제외한 헤더를 사용하면 안 된다.
만약Content-Type를 사용하는 경우에는application/x-www-form-urlencoded,multipart/form-data,text/plain만 허용된다.
사실 1번 조건의 경우는 그냥PUT이나DELETE같은 메서드를 사용하지 않으면 되는 것뿐이니 그렇게 보기 드문 상황은 아니다.
하지만 2번이나 3번 조건 같은 경우는 조금 까다롭다.
애초에 저 조건에 명시된 헤더들은 진짜 기본적인 헤더들이기 때문에, 이 헤더들 외에 추가적인 헤더를 사용하지 않는 경우는 드물다.
당장 사용자 인증에 사용되는Authorization헤더 조차 저 조건에는 포함되지 않는다.
게다가 대부분의 API는application/json 컨텐츠 타입을 가지도록 설계되기 때문에 사실 상 이 조건들을 모두 만족시키는 상황을 만들기는 그렇게 쉽지 않은 것이 현실이다.
3 - 3) Credentialed Request
3번째 시나리오는 인증된 요청을 사용하는 방법이다.
기본적으로 브라우저가 제공하는 비동기 리소스 요청 API인XMLHttpRequest객체나fetchAPI는 별도의 옵션 없이 브라우저의 쿠키 정보나 인증과 관련된 헤더를 함부로 요청에 담지 않는다.
이때 요청에 인증과 관련된 정보를 담을 수 있게 해주는 옵션이 바로credentials옵션이다.
이 옵션에는 총 3가지의 값을 사용할 수 있으며, 각 값들이 가지는 의미는 다음과 같다.
same-origin (기본값)
같은 출처 간 요청에만 인증 정보를 담을 수 있다
include
모든 요청에 인증 정보를 담을 수 있다
omit
모든 요청에 인증 정보를 담지 않는다
만약 same-origin이나include와 같은 옵션을 사용하여 리소스 요청에 인증 정보가 포함된다면, 이제 브라우저는 다른 출처의 리소스를 요청할 때 단순히Access-Control-Allow-Origin만 확인하는 것이 아니라 검사 조건을 추가하게 된다.
만약 인증 모드가include일 경우, 브라우저는 모든 요청을 허용한다는 의미의*를Access-Control-Allow-Origin헤더에 사용하면 안 된다고 이야기한다.
이처럼 요청에 인증 정보가 담겨있는 상태에서 다른 출처의 리소스를 요청하게 되면 브라우저는 CORS 정책 위반 여부를 검사하는 룰에 다음 두 가지를 추가하게 된다.
Access-Control-Allow-Origin에는*를 사용할 수 없으며, 명시적인 URL 이어야 한다.
응답 헤더에는 반드시Access-Control-Allow-Credentials: true가 존재해야 한다.
4. 끝으로
CORS 정책 위반으로 인해 생기는 문제를 해결할 때 가장 번거로운 점은 문제를 겪는 사람과 문제를 해결해야 하는 사람이 다르다는 것이다.
CORS 정책은 브라우저의 구현 스펙이기 때문에 정책 위반으로 인해 문제를 겪는 사람은 대부분 프런트엔드 개발자이지만, 정작 문제를 해결하기 위해서는 백엔드 개발자가 서버 애플리케이션의 응답 헤더에 올바른Acccess-Control-Allow-Origin이 줄 수 있도록 세팅해줘야 하기 때문이다.
요청과 응답에 모두 사용되는 헤더를 공통 헤더라 한다. 이 중에서 Content 시리즈는 엔티티 헤더라고 불린다.
Date
HTTP 메시지가 만들어진 시각이다. 자동으로 만들어진다.
Date: Thu, 12 Jul 2018 03:12:27 GMT
Connection
Connection: keep-alive
HTTP/2를 사용하지 않는다면 보통 HTTP/1.1을 사용하게 되는데 Connection은 기본적으로 keep-alive로 되어있는데 사실상 아무런 의미도 없다. HTTP/2에서는 아예 사라져 버렸다.
Cache-Control
매우 중요하고 알아두어야 하는 헤더이기 때문에 따로 추후에 블로깅을 하자.
Content-Length
요청과 응답 메시지의 본문 크기를 바이트 단위로 표시해준다. 마찬가지로 자동으로 만들어진다.
Content-Length: 52
Content-Type
Content-Type: text/html; charset=utf-8
콘텐츠의 타입(MIME)과 문자열 인코딩(utf-8 등등)을 명시할 수 있다. 조금 뒤에 나오는 Accept 헤더, Accept-Charset 헤더와 대응된다. 위에 예시로 든 헤더는 현재 메시지 내용이 text/html 타입이고 문자열은 utf-8 문자열임을 알려주고 있다.
Content-Language
사용자의 언어를 뜻한다.
Content-Encoding
Content-Encoding: gzip, deflate
Content-Encoding은 콘텐츠가 압축된 방식입니다. 응답 컨텐츠를 br, gzip, deflate 등의 알고리즘으로 압축해서 보내면, 브라우저가 알아서 해제해서 사용한다. 이 외에도 다양한 압축 알고리즘이 존재하고 컨텐츠 용량이 줄어들기 때문에 압축을 권장한다.
3 - 4) request headers
Host
서버의 도메인 네임이 나타나는 부분이다(포트 포함).
Host: www.naver.com
Host 헤더는 반드시 하나가 존재해야 한다.
User-Agent
Host보다 더 유명한 헤더는 User-Agent다.
현재 사용자가 어떤 클라이언트(운영체제와 브라우저 같은 것)를 이용해 요청을 보냈는지 나온다.
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36
Accept
Accept 헤더는 요청을 보낼 때 서버에게 이런 타입(MIME)의 데이터를 보내줬으면 좋겠다고 명시할 때 사용한다.
예를 들어 요청의 헤더로
Accept: text/html
를 보내면 HTML 형식인 응답을 기대한다는 뜻이다.
Accept: image/png, image/gif Accept: text/*
콤마로 여러 타입을 동시에 적어줄 수도 있고, *로 "텍스트이기만 하면 돼"라고 적어줄 수도 있다.
Accept로 원하는 형식을 보내면, 서버가 그에 맞춰 보내주면서 응답 헤더의 Content를 알맞게 설정할 것이다.
만약 주소를 일일이 지정하기 싫다면 *으로 모든 주소에 CORS 요청을 허용하면 된다. 단 그만큼 보안이 취약해질 것이다.
유사한 헤더로Access-Control-Request-Method, Access-Control-Request-Headers,Access-Control-Allow-Methods,Access-Control-Allow-Headers 등이 있습니다.Request랑 Allow에서 Method 단수 복수 주의하자!
CORS 요청 시에는 미리OPTIONS 주소로 서버가 CORS를 허용하는지 물어본다. 이때 Access-Control-Request-Method로 실제로 보내고자 하는 메서드를 알리고, Access-Control-Request-Headers로 실제로 보내고자 하는 헤더들을 알린다. Allow 친구들은 Request에 대응되는 애들로, 서버가 허용하는 메서드와 헤더를 응답하는 데 사용된다. Request랑 Allow가 일치하면 CORS 요청이 이루어지는 것이다.
Allow
Allow 헤더는 Access-Control-Allow-Methods랑 비슷하지만, CORS 요청 외에도 적용된다는 데에 차이가 있다.
즉 GET www.naver.com 은 되고, POST www.naver.com 은 허용하지 않는 경우, 405 Method Not Allowed 에러를 응답하면서 헤더로
Allow: GET
를 같이 보내면 된다. GET 요청만 받겠다는 뜻이다.
Content-Disposition
응답 본문을 브라우저가 어떻게 표시해야 할지 알려주는 헤더다. inline인 경우 웹페이지 화면에 표시되고, attachment인 경우 다운로드된다.
다운로드되길 원하는 파일은 attachment로 값을 설정하고, filename 옵션으로 파일명까지 지정해줄 수 있다.
파일용 서버인 경우 이 태그를 자주 사용하게 될 것이다.
Location
300번대 응답이나 201 Created 응답일 때 어느 페이지로 이동할지를 알려주는 헤더이다.
HTTP/1.1 302 Found Location: /
이런 응답이 왔다면 브라우저는 / 주소로 리다이렉트 한다.
Content-Security-Policy
다른 외부 파일들을 불러오는 경우, 차단할 소스와 불러올 소스를 여기에 명시할 수 있다. 하나의 웹 페이지는 다양한 외부 소스들을 불러온다. 이미지도 불러오고 script 태그로 자바스크립트 파일들도 불러온다. 폰트나 스타일, 아이프레임도 불러온다. 하지만 해커들에 의해 원하지 않는 파일을 불러오게 될 수도 있다. XSS 공격 같은 것이 하나의 예시다. 이럴 때 Content-Security-Policy로 허용할 외부 소스만 지정할 수 있다.
self로 지정하면 자신의 도메인의 파일들만 가져올 수 있다. www.google.com에서는 www.google.com/logo.jpg를 가져올 수 있지만, www.never.com/logo.jpg는 못 가져오는 것이다. https:로 지정하면 https를 통해서만 파일을 가져올 수 있게 된다. 'none'으로 지정하면 가져올 수 없다.
default-src는 모든 외부 소스에 대해 적용되는 것이고 각각 따로 지정할 수도 있다. 두 개나 세 개 정도만 추려서 지정할 수도 있다.
font-src, script-src, img-src, style-src, object-src 등이 있고, 소스 옵션으로는 도메인이나, https:, 'unsafe-inline'(인라인 태그 허용), 'unsafe-eval'(eval 함수 허용) 등이 있다. 옵션들이 매우 많으므로 자세한 내용은여기 서 참고하자!