Eloy's devlog
Nodejs   Express   rate-limit

express rate limit

summary

rate limit을 사용하게 된 배경을 알아보고, 해당 케이스에서 어떻게 사용했는지 공유한다.

issue

express 서버와 react 클라이언트를 가진 프로젝트가 있었다. 고객은 A 동작을 단건 혹은 일괄로 할 수 있는데, 프로젝트의 사정상 일괄로 진행할 때 단건의 api를 여러번 호출하는 식으로 개발했다. 유저가 많지 않을때는 bulk로 해당 동작을 수행하더라도 50개 이상 된 적이 없었는데, 사용자가 늘고 bulk 동작의 수가 늘어나자 요청이 많아지자 api에서 다음과 같은 응답이 리턴되었다.

429 Too Many Requests

응답 객체에 포함된 정보가 제한적이어서 원인을 찾는데 약간 애를 먹었지만(axios에서 createError를 했다는 정보를 포함) 한 ip에서 동일한 api를 여러번 호출하는 것을 방지한 것이라는 것을 알게 되었다.

rate-limit?

rate-limit이란 DoS (Denial of Service) 공격 등을 예방하기 위해 한 ip에서 들어오는 request의 횟수를 제한하는 것을 의미한다.

solution

rate-limit의 값을 설정하지 않았을 때 express의 기본값은 50~100개 정도 되는것으로 보인다. (정확한 값을 더 리서치 해봐야겠다) rate-limit을 사용하여 특정 api는 rate-limit를 걸지 않고, 나머지 요청에 대해서는 rate-limit을 지정하는 방식으로 수정한다.

Express Rate Limit

Basic rate-limiting middleware for Express. Use to limit repeated requests to public APIs and/or endpoints such as password reset.

미들웨어 기반으로 rate-limit를 설정하는 express-rate-limit 패키지를 사용한다. 프로젝트에서 설정한 세팅값은 다음과 같다.

const rateLimit = require('express-rate-limit');

const hasSession = (req) => {
  if (req.cookies && req.cookies['YOUR_SESSION_COOKIE']) return true;
  return false;
};
const BULK_ALLOW_PATHS = [
  '/YOU_BULK_API/first',
  '/YOU_BULK_API/second',
  '/YOU_BULK_API/third',
];
const isBulkRequest = (req) => {
  const requestPath = req.baseUrl + req.path;
  if (BULK_ALLOW_PATHS.includes(requestPath)) return true;
  return false;
};
const limiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: (req) => {
    if (!hasSession(req)) return 30;
    return 50;
  },
  skip: (req) => {
    if (hasSession(req) && isBulkRequest(req)) return true;
    return false;
  },
});
app.use(limiter);

이외에 자세한 설정값은 공식문서를 통해 할 수 있다.

end

express-rate-limit를 통해 간단하게 미들웨어로 요청값을 제한할 수 있음을 배우고, 적용해보았다. bulk api 같은 경우는 응답에 속도가 길어지면 이벤트나 메시지 기반으로 처리하는 방식으로 수정하는 것이 바람직할 것이다. 필자의 프로젝트에서도 해당 방식으로 bulk api를 새로 개발하는 방식으로 리팩터링한 후, skip 설정을 지울 예정이다.