Node.js

미들웨어

쿠와와 2020. 12. 21. 11:59

익스프레스는 미들웨어로 구성됨

  • 요청과 응답의 중간에 위치하여 미들웨어
  • app.use(미들웨어)로 장착
  • 위에서 아래로 순서대로 실행됨.
  • 미들웨어는 req, res, next 매개변수인 함수
  • req: 요청, res: 응답 조작 가능
  • next()로 다음 미들웨어로 넘어감.

에러가 발생하면 에러 처리 미들웨어로

  • err, req, rs, next까지 매개변수가 4개
  • 첫 번째 err에는 에러가 관한 정보가 담김
  • res.status 메서드로 HTTP 상태 코드를 지정 가능(기본값 200)
  • 에러 처리 미들웨어를 안 연결해도 익스프레스가 에러를 알아서 처리해주긴 함.
  • 특별한 경우가 아니면 가장 아래에 위치하도록 함.
const express = require('express');
const morgan = require('morgan');
const cookieParser = require('cookie-parser');
const session = require('express-session');
const dotenv = require('dotenv');
const path = require('path');


// dotenv -> 우리의 시스템 설정, 비밀키 관리를 할 수 있음   & 따라가자 
// 소스코드에 내 비밀을 안써도 됨 환경변수에 숨겨둠 .env 파일에 저장해두면 됨 
dotenv.config();    // 최대한 위쪽에 배치
const app = express();
app.set('port', process.env.PORT || 3000);

// 미들웨어 간에도 순서가 중요하다. 거의 대부분은 내부적으로 next를 실행하고 있음 
// 모건 -> (세션) -> static -> 그 다음 쭉쭉 진행 (좀 더 효율적임 성능적인 문제)

// app.use 모든 라우터에서 공통되는 코드를 모아서 실행함 
// next를 이용해서 다음 일치하는 곳을 찾아서감 즉 /다음에 오는것이 일치하는 것을 찾아감
// 와일드 카드는 다른 미들웨어들 (라우터)보다 아래 위치해야함, 범위가 넓은 라우터도 
// 미들웨어는 함수부분이고 그걸 use에 장착한 것임 
app.use(morgan('dev'));   // 모건 사용 어떤 요청이 왔는지 기록해줌 
// app.use(morgan('conbined')); // 이거의 장점은 dev보다 조금 더 자세해짐

// -------------------------------------------------------
// static - 정적파일도 다 제공해줄 수 있음 - 보안에도 많은 도움이 됨
// app.use('요청경로', express.static('실제 경로'));
app.use('/', express.static(path.join(__dirname, 'public')));
// -------------------------------------------------------

// -------------------------------------------------------
// body 파서가 이렇게 바뀜 3~4년 전에 2016~ 2017??
// 알아서 데이터가 파잉이 되서 사용할 수 있음 
app.use(express.json());    // json 데이터를 파싱해서 해줌 
app.use(express.urlencoded({ extended: true })); // form 데이터를 보내줬을 때
// true 면 qs, flase 면 querystring -> qs가 더 강력하기 때문에 true가 더 좋음
// form 에서 이미지를 보낼 때 못 받기 때문에 multer를 사용해야함 

app.get('/upload/:name', (req, res) => {
  req.body.name   // 이런식으로 바디 데이터가 들어가 있음 
  res.sendFile(path.join(__dirname, 'multipart.html'));
});
// -------------------------------------------------------

// -------------------------------------------------------
// cookie Parser
// 쿠기가 있으면 알아서 파싱해서 줌 
app.use(cookieParser(process.env.COOKIE_SECRET));     // &1 내 비밀키 숨겼음 
app.get('/', (req, res, next) => {
  // 4.2 참고
  // 'Set-Cookie': `name=${encodeURIComponent(name)}; Expires=${expires.toGMTString()}; HttpOnly; Path=/`, // /아래 주소에서는 쿠키가 유효하다라는 설정
  req.cookies
  req.signedCookies;    // 쿠키를 서명화 할 수 있음 &1 
  res.cookie('name', encodeURIComponent(name), {
    expires: new Date(),
    httpOnly: true,
    path: '/',
  })
  res.clearCookie('name', encodeURIComponent(name), {
    httpOnly: true,
    path: '/',
  })
  // 이런식으로 쿠기 관련 처리가 편해짐 
});
// -------------------------------------------------------

// -------------------------------------------------------
// session
app.use(session({   // 보통 이렇게 씀
  resave: false,        
  saveUninitialized: false,
  secret: process.env.COOKIE_SECRET,    // 비번
  cookie: {     // 세션 큐키 
    httpOnly: true,   // JS로 공격 안받기 위해
    secure: false,
  },
  name: 'session-cookie',   // connect.sid 읽을 수 없게 보통 이렇게 씀 
}));

// 패턴 익혀두기 
app.use('/', (req, res, next) => {
  if (req.session.id){
    express.static(path.join(__dirname, 'public'))(req, res, next) // 미들웨어 확장
  } else {
    next();
  }
});
// -------------------------------------------------------

// data를 다음 미들웨어에 보내는 방법 밖에다 let쓰는거는 안좋음 
// app.set도 안좋음 -> 공유되어 버림 (전체 다)
app.get('/', (req, res, next) => {
  req.session.data = 'kuwhawha';    // 단점 다음 요청때도 가지고 있음
  req.data = '일회성 kuwhawha';     // 1회성
  next();
});
// -------------------------------------------------------

// -------------------------------------------------------
// -----------------------MULTER--------------------------
// form 태그를 받아서 사용하기 위해서 사용
const multer = require('multer');   // import
const fs = require('fs');

// 서버 시작전에 업로드 파일이 있는지 확인할 때는 sync 사용해도 됨
try {
  fs.readdirSync('uploads');
} catch (error) {
  console.error('uploads 폴더가 없어 uploads 폴더를 생성합니다.');
  fs.mkdirSync('uploads');
}
// multer 안에는 4가지의 미들웨어가 있음
const upload = multer({   // 호출 결과를 업로드
  storage: multer.diskStorage({   // 업로드한 파일을 어디에 저장할지 (s3 클라우드도 가능)
    destination(req, file, done) {    // 최종 목적지 : 어디에 저장할지
      done(null, 'uploads/');   // 파일을 만들어 줘야함
    },
    filename(req, file, done) {
      // 확장자 추출
      const ext = path.extname(file.originalname);
      // 어떤 이름으로 저장할지 Date.now -> 겹치는거 방지
      done(null, path.basename(file.originalname, ext) + Date.now() + ext);
      // done은 보통 (null(에러처리), value(성공했을 때 값))
    },
  }),
  limits: { fileSize: 5 * 1024 * 1024 }, // 5MB내의 파일만 업로드 가능하도록
});
app.get('/upload', (req, res) => {
  res.sendFile(path.join(__dirname, 'multipart.html'));
});

// 와일드 카드
app.get('/upload/:name', (req, res) => {
  res.sendFile(path.join(__dirname, 'multipart.html'));
});
// 업로드라는 객체를 라우터에 장착
// 한개의 파일만 업로드 할때 single 사용 여러개를 받을 때엔 array로 받, none도 있음 
app.post('/upload', upload.single('image'), (req, res) => {
  // 여러개의 이름이 다른 파일 = upload.fields([{name: 'sd'}, {name: 'sd'}]) 이렇게
  console.log(req.file);  // 업로드에 대한 정보를 req.file에 저장 or files
  res.send('ok');
});
// -----------------------MULTER--------------------------
// -------------------------------------------------------


// -------------------------------------------------------
// 같은 라우터가 2개가 있을 때
app.get('/', (req, res, next) => {
  res.sendFile(path.join(__dirname, 'index.html'));   // html 소환
  if (true){
    next('route');    // 일단 다음 라우터로 넘어감
  } else {
    next()      // 중복을 줄이는데 유용하게 쓰임
  }
}, (req, res, next) => {  // 그럼 여기 부분은 뭘까요?? 
  // 실무에서 위에 next가 if문에 들어 갈때 실행됨
  console.log('실행되나요?');
});
app.get('/', (req, res) => {
  console.log('실행 됨! ');
});
// -------------------------------------------------------

app.get('/', (req, res, next) => {
  res.json({hell:'kuwhawha!'});     // json을 받음 
  res.render;           // 응답을 받음 
});

// -------------------------------------------------------
// 한 라우터에게 send를 여러번 하면 에러가 뜸 -> send, sendFile, json 등등 
// 요청 1번에 응답1번 보내야함 
app.get('/', (req, res, next) => {
  console.log('GET / 요청에서만 실행됩니다.');
  next();
}, (req, res) => {    // 대놓고 에러가 이렇게 나지는 않음 
  // 보통 next(error); 에서 밑에 있는 error 처리 미들웨어로 감
  throw new Error('에러는 에러 처리 미들웨어로 갑니다.')
});

// 404 에러 처리 
app.use((req, res, next) =>{
  res.status(404).send('404 error')   // error를 뻥칠 수 있음 보통 200, 404 함 
})

// error 은 요소 4개가 다 들어있어야한다. 요소를 안쓰면 다른 함수로 인식됨
app.use((err, req, res, next) => {
  console.error(err);
  res.status(500).send(err.message);
});

app.listen(app.get('port'), () => {
  console.log(app.get('port'), '번 포트에서 대기 중');
});
// -------------------------------------------------------

 

미들웨어의 진가는 남이 만들어 냈던것을 ( 세션, 요청 받는 것 ) 지저분하지 않게 (코드가 깨끗하게 ) 코딩할 수 있다.

몇 가지를 사용해보자. -> 위에 코드에 사용한 것을 주석처리해 놨음

 

dotenv

 .env 파일을 읽어서 process.env로 만듦

  • dot() + env
  • process.env.COOKIE_SECRETcookiesecret 값이 할당됨(=값 형식)
  • 비밀 키들을 소스 코드에 그대로 적어두면 소스 코드가 유출되었을 때 비밀 키도 같이 유출됨
  • .env 파일에 비밀 키들을 모아두고 .env 파일만 잘 관리하면 됨
morgan

 서버로 들어온 요청과 응답을 기록해주는 미들웨어

  • 로그의 자세한 정도 선택 가능(dev, tiny, short, common, combined)

  • 예시) GET / 200 51.267 ms – 1539
  • 순서대로 HTTP요청 요청주소 상태코드 응답속도 응답바이트
  • 개발환경에서는 dev, 배포환경에서는 combined를 애용함.
  • 더 자세한 로그를 위해 winston 패키지 사용(15장에서)

 

 

static

정적인 파일들을 제공하는 미들웨어

  • 인수로 정적 파일의 경로를 제공
  • 파일이 있을 때 fs.readFile로 직접 읽을 필요 없음
  • 요청하는 파일이 없으면 알아서 next를 호출해 다음 미들웨어로 넘어감
  • 파일을 발견했다면 다음 미들웨어는 실행되지 않음

컨텐츠 요청 주소와 실제 컨텐츠의 경로를 다르게 만들 수 있음

  • 요청 주소 localhost:3000/stylesheets/style.css
  • 실제 컨텐츠 경로 /public/stylesheets/style.css
  • 서버의 구조를 파악하기 어려워져서 보안에 도움이 됨

 

body-parser

요청의 본문을 해석해주는 미들웨어

  • 폼 데이터나 AJAX 요청의 데이터 처리
  • json 미들웨어는 요청 본문이 json인 경우 해석, urlencoded 미들웨어는 폼 요청 해석
  • put이나 patch, post 요청 시에 req.body에 프런트에서 온 데이터를 넣어줌

  • 버퍼 데이터나 text 데이터일 때는 body-parser를 직접 설치해야 함

  • Multipart 데이터(이미지, 동영상 등)인 경우는 다른 미들웨어를 사용해야 함
cookie-parser

요청 헤더의 쿠키를 해석해주는 미들웨어

  • parseCookies 함수와 기능 비슷
  • req.cookies 안에 쿠키들이 들어있음

비밀 키로 쿠키 뒤에 서명을 붙여 내 서버가 만든 쿠키임을 검증할 수 있음

실제 쿠키 옵션들을 넣을 수 있음

  • expires, domain, httpOnly, maxAge, path, secure, sameSite
  • 지울 때는 clearCookie(expiresmaxAge를 제외한 옵션들이 일치해야 함)

express-session

세션 관리용 미들웨어

  • 세션 쿠키에 대한 설정(secret: 쿠키 암호화, cookie: 세션 쿠키 옵션)
  • 세션 쿠키는 앞에 s%3A가 붙은 후 암호화되어 프런트에 전송됨
  • resave: 요청이 왔을 때 세션에 수정사항이 생기지 않아도 다시 저장할지 여부
  • saveUninitialized: 세션에 저장할 내역이 없더라도 세션을 저장할지
  • req.session.save로 수동 저장도 가능하지만 할 일 거의 없음
next
  • next를 호출해야 다음 코드로 넘어감
  • next를 주석 처리하면 응답이 전송되지 않음
  • 다음 미들웨어(라우터 미들웨어)로 넘어가지 않기 때문
  • next에 인수로 값을 넣으면 에러 핸들러로 넘어감(‘route’인 경우 다음 라우터로)

multer
  • multer 함수를 호출
  • storage는 저장할 공간에 대한 정보
  • diskStorage는 하드디스크에 업로드 파일을 저장한다는 것
  • destination은 저장할 경로
  • filename은 저장할 파일명(파일명+날짜+확장자 형식)
  • Limits는 파일 개수나 파일 사이즈를 제한할 수 있음.

  • 실제 서버 운영 시에는 서버 디스크 대신에 S3같은 스토리지 서비스에 저장하는 게 좋음
  • Storage 설정만 바꿔주면 됨