Ethan Hur's blog

Expressjs Http Error Handling

2017-09-21

회사에서 Back Office 로 Node.js + Express 를 이용한 웹 서버를 제공하고 있다.

나 혼자서 처음부터 끝까지 다 관리하는 거고, 내가 바닥에서부터 짠 코드가 아니라서 유지보수를 하면서 구조를 어떻게 짜야할 지 + 배운 것들을 정리해 보겠다.

HTTP 에러 코드

여기 를 보면 알 수 있겠지만, HTTP 에러 코드는 중요하다. 사실 내가 만지는 Back Office 수준에서는 고려를 안 해도 되지만 실제 웹 서비스를 한다고 생각하면 고려하지 않을 수 없는 요소이다.

또한 에러 코드 뿐만이 아니라, 404 일때는 특정 페이지로 리다이렉션을 시킨다던지 하는 그런 보안 규정을 만족시켜야 하기 때문에 에러 코드 별로 잘 처리가 되게 하는 것이 중요하다.

Express 미들웨어

Express 는 미들웨어 기반의 마이크로 프레임워크이므로, 이를 잘 이해하고 있다면 이를 이용하여 잘 처리할 수 있을 것이다. 기존의 경우에는

1
2
3
app.get('/asdf', (req, res) => {
res.status(500).send({ error: "error"});
});

또는

1
2
3
app.get('/fdsa', (req, res) => {
throw err; // 나중에 바깥에서 캐치함
});

요런 형식으로 보내주고 있었는데 이렇게 보내는 건 규정에도 맞지 않을 뿐더러 체계적이지 못한 접근법이었다.

그래서 제대로 수정을 하기 위해선

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
const app = require('express')();
/*
* a lot of middlewares here
*/
app.get('/a', (req, res, next) => { //next 인자가 추가되었다
/* Error 발생! */
next(err);
});
/*
* a lot of routes here
*/
app.get((req, res, next) => {
/* 아무 라우팅에도 걸리지 않으면 404 처리 */
let err = new Error('Not Found');
err.status = 404;
next(err);
});
app.use((err, req, res, next) => {
/* 로그를 찍습니다 */
console.log(err);
next(err);
});
app.use((err, req, res, next) => {
/* 여기서 에러를 처리합니다 */
let map = {
404: pageNotFoundHandler,
500: internalServerErrorHandler,
...
};
if(map[err.status])
map[err.status](err, req, res);
else
defaultErrorHandler(err, req, res);
});

그리고 각 핸들러 함수에서 처리를 해주면 된다. 이렇게 하면 Express 의 미들웨어 패턴을 즉당히 잘 사용하여 처리를 할 수 있다.

(물론 에러 객체를 만들어서 에러 객체를 넘긴 뒤 instanceof PageNotFoundError 이런 식으로 하는 게 더 고급지다 ㅎㅎ)

나의 경우에는 500 에러가 났을 경우 특정 페이지로 리다이렉션을 하게 했는데 문제가 있었다.

ajax post call 에서 500이 났을 땐 리다이렉션이 동작하지 않았다.

XMLHttpRequest

Ajax call 을 날릴 땐 위의 제목과 같은 XMLHttpRequest 를 통해 데이터를 전송하게 된다. 데이터를 전송하는 것이므로 res.redirect 같은 것을 바로 사용할 수 없다. 그래서 Ajax call 에서 오류가 났을 땐 다르게 처리해주어야 한다.

요즘은 더군다나 SPA 도 많기 때문에 실제로 404 Page Not Found 를 보는 경우가 많지 않다.

그래서 이를 구별하기 위해 req.xhr 인자를 활용하여 처리할 수 있다.

1
2
3
4
5
6
7
function internalServerErrorHandler(err, req, res) {
if (req.xhr) {
return res.send({err: "에러닷!"});
}
res.redirect('https://asdf');
}

기타

예제에서는 에러코드를 기준으로 함수를 나눴지만, xhr 여부를 기준으로 미들웨어 함수 2개로 쪼개는 것도 가능하다 Express 의 에러처리 예제 참고.

뭐가 더 좋은지는 아직 잘 모르겠다.

그리고 만약 개발할 때 바로바로 error stack을 브라우저에서 받고 싶다면

1
2
3
if (app.get('env') === 'development') {
return res.render('error', {error: err.stack});
};

이런 식으로 보내는 것도 가능하다.

결론

보안은 어쨌든 중요한 거고, 에러 코드도 어쨌든 잘 처리를 해야 한다.

처음에 귀찮다고 그냥 대충대충 해버리고 제대로 구조를 안 잡고 서비스의 크기가 커지게 되면 유지 및 보수의 어려움이나, 의미 없는 코드의 반복이 일어날 수도 있을 거라고 생각한다.

내가 경험한 것과 같이 (본문에서는 언급을 안했지만 내가 발견한 케이스 중, 그냥 throw 한 다음에 Error 를 catch 해서 그냥 에러코드를 200으로 보내는 경우도 존재했다.)

힘든 일을 적게 하려면 처음부터 구조를 잘 잡는 게 중요한 거 같다.

그렇다고 아는 게 아무것도 없는데 구조를 잘 잡을 수는 없기에 이런 삽질도 해봐야 하는 거 같다.

결국은 삽질기다 ㅎㅎ

Tags: Express