세션은 자주 변하는 데이터를 저장해주는 서버 사이드의 데이터 저장 방식입니다. 쿠키를 이용하면 브라우저에서 간단히 데이터를 저장할 수 있습니다. 하지만 민감한 정보들은 쿠키에 저장하기는 위험하므로 세션에는 예를 들면 사용자의 인증 상태(로그인 상태 등..)를 저장합니다. 여기서 데이터 저장 방식이라고 한 이유는 세션 데이터를 저장하는 곳은 따로 존재하기 때문입니다.
기본적으로 Express 어플리케이션에서 세션을 사용하면 휘발성 메모리로서 저장이 되어 서버를 껐다가 키면 저장되었던 데이터가 전부 날아가게 됩니다. 따라서 실 사용되는 어플리케이션에서는 세션 데이터를 데이터베이스에 저장하는데 이 때 많이 사용되는 데이터베이스에는 일반적인 DBMS들이 있을 수 있지만 간단하고 빠르게 작동하는 오픈 소스 데이터 스토어인 redis를 많이 사용합니다.
많은 회사들이 사용하는 것으로 봐선 굉장히 좋은가봅니다.
윈도우를 사용 중이라면 홈페이지에서 redis를 설치할 수 있습니다. 만약 윈도우에서 choco를 사용 중이라면
choco install redis-64 --version=3.0.503
이 커맨드로 설치할 수 있고 맥이라면 brew를 통해 설치하고 저는 WSL을 사용 중이므로
sudo apt update
sudo apt install redis-server
를 통해 설치하였습니다.
그리고 터미널에
redis-cli
를 입력하면 redis가 실행되었는지 그리고 포트번호도 확인할 수 있습니다.
만약 실행이 안되어 있다고 뜬다면 redis를 실행해주어야 합니다. 우분투 기준
sudo service redis-server start
라는 커맨드로 실행해줄 수 있습니다.
Redis와 NodeJS
프로젝트 폴더를 생성하고
yarn init -y
를 통해 package.json
파일을 만들어줍니다. 그리고 우리가 필요한 패키지들을 설치해줍니다.
yarn add redis express node-fetch
그리고 저는 추가적으로
yarn add --dev nodemon babel-cli babel-preset-env babel-preset-state-3
를 통해 개발환경을 구성하였습니다.
그리고 메인 파일인 server.js
파일을 만들고 아래처럼 작성해줍니다.
import express from "express"
import redis from "redis"
import fetch from "node-fetch"
const REDIS_PORT = 6379
const PORT = 5000
const app = express()
const client = redis.createClient(REDIS_PORT)
const GIT_URL = 'https://api.github.com/users/'
const getRepos = () => {}
app.get('/:username', getRepos)
app.listen(PORT, () => console.log('server started...'))
위 코드는 username을 파라미터로 받아서 깃허브의 리포지토리 개수를 가져오는 코드의 베이스입니다.
redis.createClient(PORT)
를 통해 redis 객체를 만들어주고 username이라는 url에 접속하면 getRepos라는 미들웨어를 실행해주도록 만들면 됩니다.
const getRepos = async (req, res, next) => {
try{
const { username } = req.params
const response = await fetch(`${GIT_URL}${username}`)
const data = await response.json()
const repos = data.public_repos
client.set(username, repos)
res.send(genView(username, repos))
}catch(e){
if(e) console.log("error: " ,e)
}
}
위 처럼 getRepos 함수를 만들어줍니다. fetch를 통해 github api에서 유저의 리포지토리 갯수를 가져옵니다. 그리고
client.set(username, repos)
메소드를 통해 입력된 username의 키로 repos라는 리포지토리 갯수의 number값을 밸류로서 저장해줍니다.(redis는 키 밸류 페어로 연결된 아주 간단한 nosql입니다.)
그리고 genView라는 함수를 res.send로 리턴해줍니다.
const genView = (username, repos) => {
return `<h1>${username} \'s numbers of repositories are ${repos}</h1>`
}
genView는 위와 같이 설정해줍니다. username과 repo를 받아서 html 형태로 간단하게 보여줍니다.
코드 작성이 완료가 되었다면 node server.js를 통해 서버를 실행하고
http://localhost:PORT/<YOUR_GIT_USERNAME>
에 접속하여 리포지토리 갯수와 유저이름을 확인합니다.
서버를 실행하고 로컬호스트에 접속하여 F12를 눌러서 개발자 도구 옵션에서 Network탭에 접속해봅니다.
위와 같이 peppermint100이라는 요청을 계속해서 수행하고 있습니다. 만약 이 리포지토리의 갯수가 로컬 데이터베이스인 Redis에 저장되어 있다면 굳이 다시 fetch를 이용하여 서버에 부담을 줄 필요가 없습니다. 따라서 getCache함수를 아래와 같이 작성합니다.
const getCache = (req, res, next) => {
const { username } = req.params
client.get(username, (err, data) => {
if(err) throw err;
if(data){
res.send(genView(username, data))
}else{
next()
}
})
}
username과 같은 키에 맞는 밸류, 즉 repo정보가 있다면 그 정보로 genView 함수를 통해 렌더링을 합니다. get 함수의 경우에는 첫 번째 파라미터에는 키를 두 번째는 err, data를 포함한 콜백함수로 data에 키에 맞는 밸류가 저장 되어 있습니다.
그리고
app.get('/:username',getCache, getRepos)
미들웨어로 getCache를 getRepos 전에 체크해주도록 하면 됩니다. 그리고 다시 앱을 실행시키면
처음 요청에만 시간이 많이 걸리고 지금은 Time이 2ms로 급격히 줄어든것을 볼 수 있습니다.
이는 다시 깃허브의 api에서 불러오는게 아닌 로컬 redis에서 정보를 불러오므로 시간이 단축된 결과입니다.
그러면 이번엔 세션 세팅을 해보겠습니다. 일반적으로 express-session에서 세션은 위에서 말씀드렸듯이 휘발성 인메모리 데이터베이스에 잠깐 저장되고 서버가 종료되면 사라집니다. 하지만 우리는 위에서 배운 redis를 활용하여 세션 정보를 저장하도록 하겠습니다.
yarn add express-session connect-redis
위 라이브러리를 추가로 설치해줍니다. connect-redis를 redis와 express-session을 연결해주는 역할을 합니다.
그리고
import session from "express-session"
import connectredis from "connect-redis"
const RedisStore = connectredis(session)
위 처럼 세션 라이브러리를 불러오고 연결에 필요한 connect-redis도 불러온 다음 둘을 연결해서 RedisStore라고 지정합니다. 그리고
app.use(session({
secret: "secret",
saveUninitialized: true,
resave: false,
store: new RedisStore({
client
})
}))
세션 세팅에서 store를 RedisStore로 지정해줍니다.
이렇게 하면 세션 정보가 전부 redis에 저장이 됩니다. 그리고 브라우저에서 F12를 눌러서 개발자 도구의 Application 탭에 cookies 탭을 확인하면
위와 같이 세션 정보를 이어주는 id를 찾을 수 있고 redis-cli에서
KEY "*"
을 통해 모든 키 값을 조회하면 세션 키가 이어지는 정보가 담겨 있음을 확인할 수 있습니다.
그러면 이제 아래와 같은 미들웨어 함수를 또 만들어줍니다.
const getSession = (req, res, next) => {
console.log("You Are In Get Session!")
client.get(`sess:${req.session.id}`, (err, data) => {
if(err) res.json({ err })
if(data){
res.send(genView(req.session.username, req.session.repos));
}else{
next()
}
})
}
redis 스토어에는 sess:
라는 문자가 앞에 붙은 키 이름으로 저장이 됩니다. 따라서 sess를 붙여줍니다. 그 다음 세션에 일치하는 id 정보가 있다면 그 세션 내의 username과 repos를 불러와서 genView
를 실행해줍니다.
그리고
app.get('/:username', getSession, getRepos)
미들웨어 함수를 getSession
으로 변경해주면
먼저 redis 스토어 내에 세션 데이터가 존재하는지 확인을 하고 그렇지 않다면 node-fetch를 이용해 fetch를 하는 형식의 최적화가 가능해집니다.