Express - Rate Limiting & Caching

임쿠쿠·2021년 10월 24일
0
post-thumbnail

1. Rate Limiting & Caching 요약

  • 요청에 대해 일정시간 같은 response를 줘야한다면, express에서 매번 response를 주는게 아니라 클라이언트에서 max-age값을 기준으로 시간이 지나기 전까지는 해당 캐시를 이용한다.
  • redis에 expire시간을 주고 해당 시간 만료전이라면 redis value 전달
  • 만일 DOS공격과 같은 끊임없는 재요청을 방지 하기 위해서 Rate Limiting을 적용할 수 있다.

2. Rate Limiting

// index.js
const express = require('express')
const rateLimit = require('express-rate-limit')
require('dotenv').config()

const PORT = process.env.PORT || 5000

const app = express()

// // Rate limiting
const limiter = rateLimit({
  windowMs: 10 * 60 * 1000, // 10 Mins
  max: 100,
})
app.use(limiter)
app.set('trust proxy', 1)

// // Routes
app.use('/api', require('./routes'))

app.listen(PORT, () => console.log(`Server running on port ${PORT}`))
  • express-rate-limit 라이브러리 사용
  • 요청이 무분별하게 100번 지속되면, 10분간 Block

  • Postman으로 요청을 보낼 때 마다 Remaining 감소하고 100번이 지나면 10분간 Block

3. local Caching

// ./router/index.js

const url = require('url')
const express = require('express')
const router = express.Router()
const needle = require('needle')
const apicache = require('apicache')

const API_BASE_URL = process.env.API_BASE_URL
const API_KEY_NAME = process.env.API_KEY_NAME
const API_KEY_VALUE = process.env.API_KEY_VALUE

let cache = apicache.middleware

router.get('/', cache('1 minutes'), async(req, res) => {
  try{

    const params = new URLSearchParams({
      [API_KEY_NAME]:API_KEY_VALUE,
      ...url.parse(req.url, true).query
    })

    const apiRes = await needle('get', `${API_BASE_URL}?${params}`)
    const data = apiRes.body

    if(process.env.NODE_ENV !== 'production') {
      console.log(`요청: ${API_BASE_URL}?${params}`)
    }

    res.status(200).json(data);
  } catch(err) {
    res.status(500).json({err})
  }
})

module.exports = router
  • apicahe라이브러리 사용

  • apicache 미들웨어로 1분 설정 시, 같은 요청에 대해 클라이언트는 max-age가 지나기 전까지는 캐시 사용

  • [tips] 동적으로 params를 조정해야 한다면, URLSearchParams를 사용해 보자.

  • cache-control에서 max-age 확인 가능

4. redis caching

(1) Redis 기초(참고)

[기초]
SET 'key' 'value'
GET 'key'
DEL 'key'
keys * ~ 모든 키 조회
flushall ~ 모든 키 삭제
setex 'key' 10 'value' - 10초 후 해당 키 삭제됨
ttl 'key' - expire 남은 시간 조회

[리스트]
lplush 'key' 'value' (좌측 삽입)
rplush 'key' 'value' (우측 삽입)
lrange 'key' 0 -1 (배열 조회)
lpop 'key' (좌측 삭제)
rpop 'key' (우측 삭제)

[Set]
SADD 'key' 'value' (set 삽입)
SREM 'key' 'value' (set 삭제)
SMEMBERS 'key' (set 조회)

[Hash]
HSET person name 'value' (삽입)
HSET person age 'value' (삽입)
HGETALL person (조회)
HDEL person age (age 삭제)

(2) Redis 캐싱 적용

const express = require('express')
const axios = require('axios')
const Redis = require('redis')

const redisClient = Redis.createClient()
const DEFAULT_EXPIRATION = 5


const PORT = process.env.PORT || 5000

const app = express()

app.use(express.urlencoded({extended: true}))

app.get('/photos', async (req, res) => {
  
  const albumId = req.query.albumId
  const photos = await getOrSetCache(`photos?albumId=${albumId}`, async () => {
      const {data} = await axios.get(
        'https://jsonplaceholder.typicode.com/photos',
        {params: {albumId}}
      )
      return data;
  })
  res.json(photos);
  // redisClient.get(`photos?albumId=${albumId}`, async (err, photos) => {
  //   if(err) console.log(err)
  //   if(photos !== null) {
  //     return res.json(JSON.parse(photos))
  //   } else {
  //     const {data} = await axios.get(
  //       'https://jsonplaceholder.typicode.com/photos',
  //       {params: {albumId}}
  //     )
  //     redisClient.setex(`photos?albumId=${albumId}`, DEFAULT_EXPIRATION, JSON.stringify(data))
  //     res.json(data)
  //   }
  // })
})

function getOrSetCache(key, cb) {
  return new Promise((resolve, reject) => {
    redisClient.get(key, async (err, data) => {
      if(err) return reject(err)
      if(data !== null) return resolve(JSON.parse(data))
      const freshData = await cb()
      redisClient.setex(key, DEFAULT_EXPIRATION, JSON.stringify(freshData))
      resolve(freshData)
    })
  })
}

app.listen(PORT, () => console.log(`Server running on port ${PORT}`))
  • 요청의 결과 값을 redis key, photos?albumId=${albumId}에 data를 담고 expire을 5초로 설정
  • expire 만료 전까지 해당 key의 value를 리턴

참고 - Build an API Proxy Server - Hide Your API Keys, Rate Limiting & Caching

profile
Pay it forward

0개의 댓글