💡 이번에 배운 내용
- Section2.
서버와 통신이 가능한 구조적인 Web App을 만들 수 있다.- Unit 10. Web Server 기초: cors가 무엇인지 알아보고 node.js를 이용하여 서버를 구축한다.
서버와 네트워크 통신은 역시 어렵다! 간신히 이해했어도 실제로 실습해보니 더욱 어렵다. 그런 와중에 잠을 못자 피곤하기도 하고 갖은 어려움이 있었지만 오늘도 이렇게 학습을 마치고 느낀점을 적어본다. 아직까지도 1유닛 1블로그를 놓치지 않고 따라와서 참 다행이라 생각한다. 앞으로 더 어려운 길만 남았는데, 내가 그저 포기하지않길 바랄 뿐이다. 지금보다는 더 열심히 해야겠지?
SOP, CORS, 프리플라이트 요청(preflight request), Node.js HTTP 모듈, express, 라우팅, 미들웨어, express.json()
CORS는 SOP로 발생되는 상황을 해결하기 위해 등장했다.
SOP(Same-Origin Policy)란 동일 출처 정책-같은 '출처'의 리소스만 공유가 가능하다는 의미다.
여기서 출처란 URL의 프로토콜, 호스트, 포트를 의미하며 이 중에 하나라도 다르면 같은 출처라고 할 수 없다.
예를 들어 https 와 http만 달라도, 뒤의 포트 번호만 5000과 4999로 달라도 다른 출처라 한다.
SOP(동일 출처 정책)의 장점
그런데 이 상황에서 만약 다른 출처의 리소스를 받와야 한다면, CORS를 사용해야 한다.
CORS(Cross-Origin Resource Sharing)는 교차 출처 리소스 공유로, 추가 HTTP 헤더를 사용하여 한 출처의 웹 앱이 다른 출처의 리소스에 접근할 수 있는 권한을 부여하도록 브라우저에 알려준다.
원래 브라우저는 SOP에 의해 기본적으로 다른 출처의 리소스 공유를 막지만, CORS를 사용하면 접근 권한을 얻을 수 있게 된다.
만약 CORS에 접근권한을 얻지 못하고 리소스 요청을 하면
아래처럼 콘솔에 에러가 뜬다.
이 에러는 다른 출처의 리소스를 가져오려고 했지만 SOP 때문에 접근이 불가능하기 때문에 발생했다. 따라서 CORS 설정을 통해 서버의 응답 헤더에 ‘Access-Control-Allow-Origin’을 작성하면 접근 권한을 얻을 수 있다.
1) 프리플라이트 요청 (Preflight Request)
실제 요청을 보내기 전, OPTIONS 메서드로 해당 출처 리소스에 접근 권한이 있는지부터 확인하는 요청을 프리플라이트 요청이라고 한다.
즉 클라이언트에서 실제 요청을 보내면, 브라우저는 서버에 실제 요청을 보내기 전에 프리플라이트 요청을 보낸다.
요청을 보내고 만약 응답 헤더의 Access-Control-Allow-Origin으로 요청을 보낸 출처가 돌아오면 실제 요청을 보내게 된다.
반대로 요청을 보낸 출처가 접근 권한이 없다면 브라우저에서 CORS 에러를 띄우게 되고, 실제 요청은 전달되지 않는다.
CORS는 다른 출처의 리소스를 가져오는 만큼 먼저 접근 권한을 확인하는 프리플라이트 요청이 필수적이다. 그 이유를 정리하면 아래와 같다.
프리플라이트 요청이 필요한 이유
2) 단순 요청 (Simple Request)
단순 요청은 특정 조건이 만족되면 프리플라이트 요청을 생략하고 요청을 보내는 것을 의미한다.
특정 조건은 아래와 같다.
프리플라이트 요청이 생략 가능한 조건
3) 인증정보를 포함한 요청 (Credentialed Request)
출처가 다를 경우 요청 헤더에 인증 정보가 포함되었을 때는 별도의 설정을 해야 한다. 이 경우에는 프론트, 서버 양측 모두 CORS 설정이 필요하다.
만약 서버 측에서 Access-Control-Allow-Origin 을 설정할 때, *
를 사용해 모든 출처를 허용하면 에러가 발생한다. 인증 정보를 다루는 만큼 출처를 정확하게 설정해주어야 한다.
const http = require('http');//node의 http모듈 사용
const server = http.createServer((request, response) => {
// CORS설정: 모든 도메인에 접근 권한 부여
response.setHeader("Access-Control-Allow-Origin", "*");
// CORS설정: 특정 도메인에 접근 권한 부여
response.setHeader("Access-Control-Allow-Origin", "https://mySite.com");
// CORS설정: 인증 정보를 포함한 요청을 받을 경우
response.setHeader("Access-Control-Allow-Credentials", "true");
})
Node.js를 사용해 HTTP서버를 만드는 방법은 어렵지만 아래의 공식문서에 가이드가 있다.
🔗HTTP 트랜잭션 해부 | Node.js
const cors = require("cors");
const app = express();
//모든 도메인에 권한 설정
app.use(cors());
//특정 도메인 옵션
const options = {
origin: "https://mySite.com", // 접근 권한을 부여하는 도메인
credentials: true, // 응답 헤더에 Access-Control-Allow-Credentials 추가
optionsSuccessStatus: 200, // 응답 상태 200으로 설정
};
//특정 도메인에 권한 설정
app.use(cors(options));
//특정 요청
app.get("/example/:id", cors(), function (req, res, next) {
res.json({ msg: "example" });
});
Express는 node.js 환경에서 웹 서버, API 서버를 제작할 때 사용하는 프레임워크이다. MERN(MongoDB, Express, React, Node.js) stack 중 하나이기도 하다.
Express가 있다면 Node.js HTTP 모듈로만 서버를 구현했을 때보다 편리하기도 하고 몇가지 차이점이 있다.
1 미들웨어를 추가할 수 있다.
2 라우터를 제공한다.
Express 공식문서를 보면 사용 가이드가 있다.
🔗Express 'Hello world' 예제
먼저 npm install express
을 터미널에 입력하여 express를 설치한다.
이후 서버를 만드는 예제는 아래와 같다.
const express = require('express')
const app = express()
const port = 3000
//root url을 GET 메서드로 요청받았을 때
app.get('/', (req, res) => {
res.send('Hello World!') //응답처리
})
/*
get : 미들웨어 함수가 적용되는 HTTP 메소드. 만약 모든 메서드에 사용하려면 .use 메서드를 사용한다.
'/' : 미들웨어 함수가 적용되는 경로(라우트)
함수 : 미들웨어 함수
-> 첫번째 매개변수(req): HTTP 요청
-> 두번째 매개변수(res): HTTP 응답
-> 세번째 매개변수(주로 next): 콜백함수. 다음 미들웨어 실행
*/
//서버 오픈하기
app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
})
라우팅이란 메서드와 url(엔드포인트)에 따라 응답을 분기(Routing)하는 것을 의미한다.
🔗라우팅 참고: Express 기본 라우팅
만약 Node.js만들 사용하면 요청 메서드, 엔드포인트에 따라 if조건문으로 나눠서 작성해야 한다.
그러나 Express는 그렇지 않고 좀 더 쉽게 코드를 작성할 수 있다. 자체에서 라우터 기능을 지원하기 때문이다. 예제는 아래와 같다.
app.get('/user', (req, res) => {
res.send(data);
})
app.post('/user', (req, res) => {
// do something
})
미들웨어(Middleware)는 일종의 함수로 요청에 필요한 기능을 더하거나 문제 발생시 문제를 걸러내는 역할을 한다. 그리고 Node.js만 사용했을 떄보다 좀 더 쉽고 효율적으로 작업이 가능하다.
미들웨어 모듈에는 여러 종류가 있으며 주로 사용하는 상황은 다음과 같다.
상황 1 : 요청에 포함된 body(payload)를 쉽게 가져올 때
Node.js의 HTTP 모듈을 사용할 때는 Buffer데이터를 받아와서 가공해야 했다. 아래는 위에도 언급한 공식문서의 예제이다.
let body = [];
request.on('data', (chunk) => {
body.push(chunk);
}).on('end', () => {
body = Buffer.concat(body).toString();
// body 변수에는 문자열 형태로 payload가 담겨져 있습니다.
});
그러나 Express는(v4.16.0 이후) 내장 미들웨어인 express.json()을 사용하여 다음과 같이 간단하게 작업할 수 있다.
const app=express();
const jsonParser = express.json();
//express.json({strict:false}) 처럼 옵션을 사용할 수 있다.
//기본값은 true, true일 경우 배열과 개체만 허용하고
//false일 경우 모든 것을 JSON.parse할 수 있다.
// 생략
app.post('/api/users', jsonParser, function (req, res) {
res.json(req.body); //json 형태로 응답받기
})
🔗참고: express 공식문서 - express.json()
상황2 : 모든 요청/응답에 CORS 헤더를 붙일 때
Node.js HTTP 모듈을 이용한 코드에서는 여러 메서드에 CORS를 허용할 경우 OPTIONS 메서드, 라우팅(분기)마다 헤더를 매번 넣어주어야 한다.
// 생략...
if (req.method === 'OPTIONS') {
//이 코드를 라우팅마다 넣어줘야 한다.
res.writeHead(200, corsHeader);
res.end()
}
//생략...
//CORS 헤더 설정
const corsHeader = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Accept',
'Access-Control-Max-Age': 10
};
하지만 express에서는 CORS 미들웨어를 사용하여 다음처럼 간단하게 사용할 수 있다. (사용 전 터미널에 npm install cors
를 입력하여 설치해야 한다.)
const cors = require('cors');
//모든 요청에 CORS 허용
app.use(cors());
// 생략...
//메서드에 CORS 허용
app.get('/products/:id', cors(), function (req, res, next) {
res.json({msg: '하나의 경로에만 CORS 허용'})
})
상황 3 : 모든 요청의 url, 메서드를 확인할 때
모든 요청이 발생할 때마다 url, 메서드 등을 콘솔 메시지로 확인하거나 디버그 용도로 사용할 수도 있다.
const app=express();
const cors = require('cors');
app.use((req, res, next) => {
console.log(`http 요청 메서드: ${req.method}, url: ${req.url}`);
});
위 코드는 아래처럼 바꿀수도 있다.
//생략
const checkReq=(req, res, next) => {
console.log(`http 요청 메서드: ${req.method}, url: ${req.url}`);
next();
}
app.use(checkReq);
상황 4 : 요청 헤더에 사용자 인증 정보가 담겨있는지 확인할 때
HTTP 요청에서 미들웨어를 사용해 토큰이 있는지 아닌지 판단하고 아래처럼 로그인한 사용자(성공)인지 아닌지(에러) 확인할 수 있다.
(토큰(Token)은 주로 사용자 인증에 사용하며 추후 학습 예정이다.)
app.use((req, res, next) => {
//성공 - 로그인한 사용자
if(req.headers.token){
req.isLoggedIn = true;
next();
}
//에러 - 로그인하지 않은 사용자
else {
res.status(400).send('invalid user')
}
})
이처럼 미들웨어는 문제 발생시 문제를 걸러내는 역할도 수행한다.