express(2)

2020-07-15

Middleware

express.js의 공식 문서에 ‘Making Middleware’를 보면
middleware는 사실 함수의 형태를 띠고 있는 것을 알 수 있다.

var myLogger = function(req, res, next) {
  console.log('LOGGED');
  next();
};
app.use(myLogger);

인자로 주어지는 next에는
그 다음에 호출되어야 할 middleware가 담겨있다.
next()라는 함수를 이용해서, 그 다음에 올 middleware가 실행될 지 말지를
바로 앞의 middleware가 결정하도록 해 놓은 것.

사실, 우리가 express에서 사용하는 (req, res)를 인자로 받는
모든 함수들이 전부 middleware라고 볼 수 있다.

전부 프로그램의 일부를 이루는, 하나의 작은 프로그램인 것.
핵심은 req, res를 인자로 받아서 그것들을 변형할 수 있다는 것이다.

app이라는 변수로 등록된 application 객체의 use, get, post
(app.use, app.get, app.post) 등을 이용해 등록한 middleware를
application-level middleware라고 한다.

Tip) app.use의 첫 번째 인자로 path를 줘서,
특정 경로에서만 middleware가 동작하게 할 수도 있다.
app.get을 이용하면 메소드가 get 방식인 경우에만 middleware가 동작하게 할 수도 있음.
(app.post도 마찬가지)

Middleware의 실행 순서

함수 여러 개를 연속적으로 인자로 줌으로써
middleware를 여러 개 붙이는 것이 가능하다.

app.use('/user/:id', function(req, res, next) {
  console.log('Request URL:', req.originalUrl);
  next();
}, function (req, res, next) {
  console.log('Request Type:', req.method);
  next();
});

첫 번째 인자의 함수 내에서 next()를 호출하고 있는데,
이렇게 되면 그 next()는 두 번째 인자로 주어진 함수를 호출한다.

app.get('/user/:id', function (req, res, next) {
  console.log('ID:', req.params.id);
  next();
}, function (req, res, next) {
  res.send('User Info');
});

app.get('/user/:id', function (req, res, next) {
  res.end(req.params.id);
});

두 app.get의 path가 동일하다.

우선 첫 번쨰 app.get의 첫 번째 인자로 주어진 middleware가 가장 먼저 실행.
거기서 next()가 호출되기 때문에 두 번째 인자의 middleware가 그 다음으로 실행됨.

근데, 거기서는 next()가 호출되지 않고 있다.
그렇기 때문에 두 번째 app.get의 middleware는 실행되지 않는다.

app.get('/user/:id', function (req, res, next) {
  // if the user ID is 0, skip to the next route
  if (req.params.id == 0) next('route');
  // otherwise pass the control to the next middleware function in this stack
  else next(); //
}, function (req, res, next) {
  // render a regular page
  res.render('regular');
});

// handler for the /user/:id path, which renders a special page
app.get('/user/:id', function (req, res, next) {
  res.render('special');
});

조건문을 통해서 어떤 middleware가 실행될지를 처리할 수도 있다.

만약 req.params.id의 값이 0이라면 next(‘route’)가 실행되는데
이는 다음 route의 middleware를 실행하라는 뜻이다.
따라서 두 번재 app.get의 middleware가 실행된다.

그렇지 않다면, else의 인자가 없는 next()가 실행되고
이로 인해 첫 번째 app.get의 두 번째 인자로 주어진 middleware가 실행된다.

이런 식으로, middleware가 실행되는 흐름을 자유자재로 설계하는 것이 가능하다.

정적인 파일의 서비스

대표적인 케이스가 바로 이미지 파일.

정적인 파일을 서비스하기 위해서는, 그것을 허용하고 기본적인 세팅을 해줘야 함.
역시나 middleware를 사용해서 서비스되는데, express가 기본적으로 제공하는
static이라는 middleware를 사용한다.

// express.static(root, [options])
app.use(express.static('public'));

‘public’이 root로 주어졌기 때문에 ‘public’이라는 디렉토리 안에서 static file을 찾게 된다.
만약 내가 ‘public/images’ 폴더 안에 ‘fiji.jpg’라는 사진 파일을 넣어놨다고 치자.
그러면 root를 public으로 지정했으니, 해당 nodejs 파일에서 img태그를 이용하여

<img src="/images/fiji.jpg">

라고 이미지에 접근할 수가 있다.

요점은, app.use(express.static(‘public’))을 이용하면
public이라는 디렉토리 아래에 있는 파일이나 디렉토리를 URL path를 통해 접근할 수 있는데,
그러면 public 디렉토리 외의 다른 부분에는 접근할 수가 없기 때문에 훨씬 안전해진다는 것.

에러 처리 방법

  • 404 not found
// 맨 마지막에 추가하면 됨(listen 앞에)
app.use((req, res, next) => {
  res.status(404).send('Sorry can't find that!');
});

middleware는 순차적으로 실행되기 때문에
404 not found에 대한 middleware는 거의 맨 마지막에 추가함.

더 이상 path에 해당하는 middleware를 찾지 못하고,
맨 마지막에 404 not found를 마주하면 앞에서 아무것도 못 찾았다는 뜻이니깐.
그때서야 ‘404’ 상태를 보내주고, 페이지를 찾지 못했다는 페이지를 보여주는 것.

  • Error handling

에러가 발생하는 경우에는 next 함수에다가 error 객체를 인자로 넣는다.

if(err) {
  next(err);
}

그리고 이 next(err)로 인해 호출되는 error handler는
404 not found보다도 더 뒤에, 맨 마지막에 정의한다.

app.use(function(err, req, res, next) {
// 인자를 4개 받는 함수는 error handler로 지정
  console.error(err.stack);
  res.status(500).send('Something broke!');
});

next(err)는 인자가 4개인 middleware를 호출하도록 약속되어있기 때문에
이 middleware가 호출되면서 error handling을 하게 되는 것.

자세한 내용