S2 Unit 10. Web Server 기초

나현·2022년 10월 13일
0

학습일지

목록 보기
27/53
post-thumbnail

💡 이번에 배운 내용

  • Section2.
    서버와 통신이 가능한 구조적인 Web App을 만들 수 있다.
  • Unit 10. Web Server 기초: cors가 무엇인지 알아보고 node.js를 이용하여 서버를 구축한다.

느낀점

서버와 네트워크 통신은 역시 어렵다! 간신히 이해했어도 실제로 실습해보니 더욱 어렵다. 그런 와중에 잠을 못자 피곤하기도 하고 갖은 어려움이 있었지만 오늘도 이렇게 학습을 마치고 느낀점을 적어본다. 아직까지도 1유닛 1블로그를 놓치지 않고 따라와서 참 다행이라 생각한다. 앞으로 더 어려운 길만 남았는데, 내가 그저 포기하지않길 바랄 뿐이다. 지금보다는 더 열심히 해야겠지?


키워드

SOP, CORS, 프리플라이트 요청(preflight request), Node.js HTTP 모듈, express, 라우팅, 미들웨어, express.json()


학습내용

Ch1. CORS

CORS가 등장한 배경 - SOP

CORS는 SOP로 발생되는 상황을 해결하기 위해 등장했다.
SOP(Same-Origin Policy)란 동일 출처 정책-같은 '출처'의 리소스만 공유가 가능하다는 의미다.
여기서 출처란 URL의 프로토콜, 호스트, 포트를 의미하며 이 중에 하나라도 다르면 같은 출처라고 할 수 없다.
예를 들어 https 와 http만 달라도, 뒤의 포트 번호만 5000과 4999로 달라도 다른 출처라 한다.

SOP(동일 출처 정책)의 장점

  • 문서를 분리함으로써 해킹의 위협을 받을 수 있는 경로를 줄인다.
  • SOP는 다른 사이트와의 리소스 공유를 제한하므로 모든 브라우저에서 기본적으로 사용하고 있는 정책이다.

그런데 이 상황에서 만약 다른 출처의 리소스를 받와야 한다면, CORS를 사용해야 한다.

CORS란?

CORS(Cross-Origin Resource Sharing)는 교차 출처 리소스 공유로, 추가 HTTP 헤더를 사용하여 한 출처의 웹 앱이 다른 출처의 리소스에 접근할 수 있는 권한을 부여하도록 브라우저에 알려준다.
원래 브라우저는 SOP에 의해 기본적으로 다른 출처의 리소스 공유를 막지만, CORS를 사용하면 접근 권한을 얻을 수 있게 된다.

만약 CORS에 접근권한을 얻지 못하고 리소스 요청을 하면
아래처럼 콘솔에 에러가 뜬다.

콘솔 CORS 에러

이 에러는 다른 출처의 리소스를 가져오려고 했지만 SOP 때문에 접근이 불가능하기 때문에 발생했다. 따라서 CORS 설정을 통해 서버의 응답 헤더에 ‘Access-Control-Allow-Origin’을 작성하면 접근 권한을 얻을 수 있다.

CORS 동작 방식

1) 프리플라이트 요청 (Preflight Request)
실제 요청을 보내기 전, OPTIONS 메서드로 해당 출처 리소스에 접근 권한이 있는지부터 확인하는 요청을 프리플라이트 요청이라고 한다.
즉 클라이언트에서 실제 요청을 보내면, 브라우저는 서버에 실제 요청을 보내기 전에 프리플라이트 요청을 보낸다.
요청을 보내고 만약 응답 헤더의 Access-Control-Allow-Origin으로 요청을 보낸 출처가 돌아오면 실제 요청을 보내게 된다.
반대로 요청을 보낸 출처가 접근 권한이 없다면 브라우저에서 CORS 에러를 띄우게 되고, 실제 요청은 전달되지 않는다.

CORS는 다른 출처의 리소스를 가져오는 만큼 먼저 접근 권한을 확인하는 프리플라이트 요청이 필수적이다. 그 이유를 정리하면 아래와 같다.

프리플라이트 요청이 필요한 이유

  • 실제 요청을 보내기 전 미리 권한 확인만 먼저 할 수 있으므로 효율적이다.
  • 다른 출처의 접근 권한부터 확인하기 때문에 서버를 보호할 수 있다.

2) 단순 요청 (Simple Request)
단순 요청은 특정 조건이 만족되면 프리플라이트 요청을 생략하고 요청을 보내는 것을 의미한다.
특정 조건은 아래와 같다.

프리플라이트 요청이 생략 가능한 조건

  • 요청 메서드는 GET, HEAD, POST 중 하나여야만 한다.
  • 자동으로 설정되는 헤더 외에 Accept, Accept-Language, Content-Language, Content-Type 헤더의 값만 수동으로 설정할 수 있다.
  • Content-Type 헤더에는 application/x-www-form-urlencoded, multipart/form-data, text/plain 값만 허용된다.

3) 인증정보를 포함한 요청 (Credentialed Request)
출처가 다를 경우 요청 헤더에 인증 정보가 포함되었을 때는 별도의 설정을 해야 한다. 이 경우에는 프론트, 서버 양측 모두 CORS 설정이 필요하다.

  • 프론트 측: 요청 헤더에 withCredentials : true 설정
  • 서버 측: 응답 헤더에 Access-Control-Allow-Credentials : true 설정

만약 서버 측에서 Access-Control-Allow-Origin 을 설정할 때, *를 사용해 모든 출처를 허용하면 에러가 발생한다. 인증 정보를 다루는 만큼 출처를 정확하게 설정해주어야 한다.

CORS 설정 방법

  • Node.js 서버
    Node.js로 간단한 HTTP 서버를 만들 경우, 다음과 같이 응답 헤더를 설정한다.
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

  • Express 서버
    Express 프레임워크를 사용해서 서버를 만드는 경우 cors 미들웨어를 사용해서 아래와 같이 CORS 설정을 해줄 수 있다.
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" });
});

Ch2. Express

Express 란?

Express는 node.js 환경에서 웹 서버, API 서버를 제작할 때 사용하는 프레임워크이다. MERN(MongoDB, Express, React, Node.js) stack 중 하나이기도 하다.

Express, Node.js HTTP 모듈의 차이

Express가 있다면 Node.js HTTP 모듈로만 서버를 구현했을 때보다 편리하기도 하고 몇가지 차이점이 있다.
1 미들웨어를 추가할 수 있다.
2 라우터를 제공한다.

Express 설치와 기본 예제

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만 사용했을 떄보다 좀 더 쉽고 효율적으로 작업이 가능하다.
미들웨어 모듈에는 여러 종류가 있으며 주로 사용하는 상황은 다음과 같다.

  • (POST)요청에 포함된 body(payload)를 쉽게 가져올 때(구조화)
  • 모든 요청/응답에 CORS 헤더를 붙여야 할 때
  • 모든 요청의 url, 메서드를 확인할 때
  • 요청 헤더에 사용자 인증 정보가 담겨있는지 확인할 때

상황 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가 담겨져 있습니다.
});

🔗HTTP 트랜잭션 해부 | Node.js

그러나 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')
  }
})

이처럼 미들웨어는 문제 발생시 문제를 걸러내는 역할도 수행한다.


profile
프론트엔드 개발자 NH입니다. 시리즈로 보시면 더 쉽게 여러 글들을 볼 수 있습니다!

0개의 댓글