본문 바로가기

[Network] Express의 미들웨어(Middleware)

[Network] Express의 미들웨어(Middleware)

Express로 구현한 서버가 Node.js HTTP 모듈로 작성한 서버와 다른 점은 다음과 같다.

  1. 라우터를 제공한다. (이전 포스팅)
  2. 미들웨어를 추가할 수 있다. (해당 포스팅)

이번 포스팅에서는 미들웨어에 대해 알아보겠다. 

 

미들웨어 (Middleware)란?

 

사전적 의미

  • 미들웨어(서로 다른 여러 프로그램을 함께 운용할 수 있는 소프트웨어)
  • software that acts as a bridge between an operating system or database and applications, especially on a network

미들웨어(Middleware)는 자동차 공장의 공정과 비슷하다.

  • 자동차 공장에서는 컨베이어 벨트 위에 올려진 자동차의 뼈대에, 공정마다 부품을 추가한다.
  • 모든 부품이 추가되면 완성된 자동차가, 어딘가 문제가 있다면 불량품이 결과물로 나오게 된다.
  • 컨베이어 벨트 위에 올라가 있는 요청(Request)필요한 기능을 더하거나, 문제가 발견된 불량품을 밖으로 걷어내는 역할을 한다.
  • 미들웨어는 express의 가장 큰 장점이라고 할 수 있다.

미들웨어 함수는 요청 오브젝트(req), 응답 오브젝트 (res), 그리고 애플리케이션의 요청-응답 주기 중 그 다음의 미들웨어 함수 대한 액세스 권한을 갖는 함수이다.

그 다음의 미들웨어 함수는 일반적으로 next라는 이름의 변수로 표시되며  다음과 같은 태스크를 수행할 수 있다.

1. 모든 코드를 실행
2. 요청 및 응답 오브젝트에 대한 변경을 실행
3. 요청-응답 주기를 종료
4. 스택 내의 그 다음 미들웨어를 호출.

현재의 미들웨어 함수가 요청-응답 주기를 종료하지 않는 경우에는 next()를 호출하여 그 다음 미들웨어 함수에 제어를 전달해야 한다. (그렇지 않으면 해당 요청은 정지된 채로 방치)

** 단순 요청(Simple Request)은 프리플라이트 요청(Preflight Request)을 생략하는 요청으로 Express 미들웨어를 사용하는 상황으로 보기는 어렵다.

 

자주 사용하는 미들웨어

미들웨어를 사용하는 상황은 다음과 같다.

  1. POST 요청 등에 포함된 body(payload)를 구조화할 때(쉽게 얻어내고자 할 때)
  2. 모든 요청/응답에 CORS 헤더를 붙여야 할 때
  3. 모든 요청에 대해 url이나 메서드를 확인할 때
  4. 요청 헤더에 사용자 인증 정보가 담겨있는지 확인할 때

미들웨어를 이용하면 Node.js 만으로 구현한 서버에서는 번거로울 수 있는 작업을 보다 쉽게 적용할 수 있다.
Express로 구현한 서버에서 흔하게 사용하는 미들웨어인 1번과 2번의 경우는 아래와 같다.

 

1. POST 요청 등에 포함된 body(payload)를 구조화할 때

  • Node.js로 HTTP body(payload)를 받을 때는 Buffer를 조합해서 다소 복잡한 방식으로 body를 얻을 수 있다.
  • 네트워크상의 chunk를 합치고, buffer를 문자열로 변환하는 작업이 필요하다.
  • 자세한 설명은 'HTTP 모듈을 이용한 서버 다뤄보기' 포스팅 참고
Node.js로 HTTP 요청 body를 받는 코드
let body = [];
request.on('data', (chunk) => {
  body.push(chunk);
}).on('end', () => {
  body = Buffer.concat(body).toString();
  // body 변수에는 문자열 형태로 payload가 담겨져 있다.
});

 

 

  • body-parser 미들웨어를 사용하면 이 과정을 간단하게 처리할 수 있다.
  • Express 내장 미들웨어인 express.json을 이용하여 요청 body(payload)를 받는 작업을 하면 된다.
body-parser 미들웨어를 이용한 코드 (이전 버전 : 참고만 하고 아래로 내려간다.)
// ** 우선 npm으로 body-parser를 설치

npm install body-parser
const bodyParser = require('body-parser');
const jsonParser = bodyParser.json();

// 생략
app.post('/users', jsonParser, function (req, res) {

})

 

 

Express v4.16.0부터는 body-parser를 따로 설치하지 않고, Express 내장 미들웨어인 express.json()을 사용한다.
const jsonParser = express.json();

// 생략
app.post('/api/users', jsonParser, function (req, res) {

})

 

express.json() 미들웨어 사용에 에러가 난다면?

  • express.json([options])의 options에 해답이 있다.
  • This is a built-in middleware function in Express. It parses incoming requests with JSON payloads and is based on body-parser.
  • Object 형태가 아닌 String 등 형태도 받을 수 있게 하려면 options {strict: false}를 추가한다.  
const jsonParser = express.json({strict: false});

// 생략
app.post('/api/users', jsonParser, function (req, res) {

})

 

Property Description Type Default
strict Enables or disables only accepting arrays and objects;
when disabled will accept anything JSON.parse accepts.


-> request로부터 온 어떤 데이터(예: String)든지 parse해준다.
Boolean true

 

2. 모든 요청/응답에 CORS 헤더를 붙일 때

  • Node.js HTTP 모듈을 이용한 코드에 CORS 헤더를 붙이려면, 응답 객체의 writeHead 메서드를 이용할 수 있다.
  • Node.js에서는 이 메서드 등을 이용하여 라우팅마다 헤더를 매번 넣어주어야 한다.
  • 그뿐만 아니라, OPTIONS 메서드에 대한 라우팅도 따로 구현해야 한다.
Node.js에 CORS를 적용하는 예시
const defaultCorsHeader = {
  '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
};

// 생략
if (req.method === 'OPTIONS') {
  res.writeHead(200, defaultCorsHeader);
  res.end()
}

 

cors 미들웨어를 사용하면 이 과정을 간단하게 처리할 수 있다.

(시간이 되면, 깃헙 cors 미들웨이 코드를 찬찬히 살펴봐도 좋을 것 같다.)

  • Express에서 출처 접근 권한을 설정하려면 cors 미들웨어를 사용하면 된다.
  • 세부적으로 출처를 설정해주기 위해서는 cors의 전달인자에 Configuration Options를 설정해야 한다.
  • Configuration Options의  origin을 설정한다.
npm install cors
모든 요청에 대해 CORS 허용
const cors = require('cors');

// 생략
app.use(cors());

 

특정 요청에 대해 CORS 허용
const cors = require('cors')

// 생략
app.get('/products/:id', cors(), function (req, res, next) {
  res.json({msg: 'This is CORS-enabled for a Single Route'}) // JSON 응답을 전송
})

 

 

 

3. 모든 요청에 대해 url이나 메서드를 확인할 때

미들웨어는 말 그대로 프로세스 중간에 관여하여 특정 역할을 수행한다.

  • Express에서 제공되는 미들웨어도 사용할 수 있으며, 미들웨어를 작성하여 사용할 수도 있다.
  • 수많은 미들웨어가 있지만, 가장 단순한 미들웨어 함수인 로거(logger)를 예로 들겠다.
  • 로거 디버깅이나, 서버 관리에 도움이 되기 위해 console.log로 적절한 데이터나 정보를 출력한다.
  • 데이터가 여러 미들웨어를 거치는 동안 응답할 결과를 만들어야 한다면, 미들웨어 사이사이에 로거를 삽입하여 현재 데이터를 확인하거나, 디버깅에 사용할 수 있다.
  • 이런 미들웨어는 일반적으로 다음과 같이 구성되어 있다.

[그림] 미들웨어의 구성

 

  • 위 그림은 endpoint가 /이면서, 클라이언트로부터 GET 요청을 받았을 때 적용되는 미들웨어이다.
  • 파라미터의 순서에 유의해야 한다. req, res는 요청(request), 응답(response)이고 next는 다음 미들웨어를 실행하는 역할을 한다.

[ next() ]

이 함수를 호출하면 앱 내의 그 다음 미들웨어 함수가 호출된다. 
next() 함수는 Node.js 또는 Express API의 일부가 아니지만, 미들웨어 함수에 전달되는 세 번째 인수이다. 
next() 함수에는 어떠한 이름을 지정해도 좋지만, 일반적으로 항상 “next”라는 이름을 갖는다. 

 

만약 특정 enpoint가 아니라, 모든 요청에 동일한 미들웨어를 적용하려면?

메서드 app.use 를 사용하면 된다. 아래 코드를 직접 실행해 보면, 모든 요청에 대해 'LOGGED'가 출력되는 걸 확인할 수 있다. 

새로 고침 때마다LOGGED가 출력

 use 메서드로 모든 요청에 대하여 미들웨어를 적용할 수 있다.
const express = require('express');
const app = express();

const myLogger = function (req, res, next) { // 이 미들웨어 함수는 myLogger라는 이름의 변수에 지정되어 있다.
  console.log('LOGGED');
  next();
};

app.use(myLogger);

// 루트 경로에 대한 라우팅 이후에 myLogger가 로드되면, 루트 경로의 라우트 핸들러가 
// 요청-응답 주기를 종료하므로 요청은 절대로 myLogger에 도달하지 못하며 앱은 “LOGGED”를 인쇄하지 않는다.
app.get('/', function (req, res) {
  res.send('Hello World!');
});

app.get('/mango', (req, res) => {
  res.send('Love you Mango!')
})

app.listen(3000);

 

 

만약 아래 그림과 같이, 모든 요청에 대해 메서드와 url을 출력하려면?

 

모든 요청에 대해 메서드와 url을 출력
const express = require('express')
const app = express()
const port = 3000

const myLogger = function (req, res, next) {
  console.log(req.method, req.url);       // 요청에 대한 메서드와 url
  next();
};

app.use(myLogger);

app.get('/', function (req, res) {
  res.send('Hello World!');
});

app.get('/mango', (req, res) => {
  res.send('Love you Mango!')
})

app.listen(3000);

요청을 보낼 때마다 출력이 되는 모습

 

4. 요청 헤더에 사용자 인증 정보가 담겨있는지 확인할 때

다음은 HTTP 요청에서 토큰(주로 사용자 인증에 사용)이 있는지를 판단하여,

  • 이미 로그인한 사용자일 경우 성공,
  • 아닐 경우 에러를 보내는 미들웨어 예제이다.
토큰을 통해 로그인 여부를 확인하는 미들웨어 예시
app.use((req, res, next) => {
  // 토큰이 있는지 확인, 없으면 받아줄 수 없음.
  if(req.headers.token){
    req.isLoggedIn = true;
    next();
  } else {
    res.status(400).send('invalid user')
  }
})

 

로그인 없이 웹사이트에 접근을 시도했을 때, 로그인 창으로 되돌려 보내는 경우를 경험해 본 적이 있을 것이다.

서버에서는 요청에 포함된 데이터를 통해 미들웨어가 요구하는 조건에 맞지 않으면, 불량품으로 판단하고 돌려보내도록 구현할 수 있다.

728x90
⬆︎