...
Socket Middleware
소켓 미들웨어에 대해서는 이전 포스팅에서 소개한 바 있다.
하지만 소켓 미들웨어 문법&기능 에서만 소개를 했고, 왜 이러한 로직이 쓰이는지 자세한 설명을 하기위해 추가적으로 포스팅 해본다.
우리는 이전 포스팅에서, 소켓IO의 io객체 를 외부파일에서 사용하는 방법을 배웠다.
const io = req.app.get('io'); // 전역변수로 등록해논 io객체를 가져옴
io.of('/room').emit('newRoom', newRoom); // room네임스페이스에 newRoom 이벤트와 쿼리객체를 보냄
이와 같은 논리로, 소켓 미들웨어는 외부 미들웨어의 데이터를 소켓에서 쓰기 위해 사용하는 기법이라고 보면 된다.
express 미들웨어를 예로 들어보자.
app.use(morgan('dev'));
app.use(express.static(path.join(__dirname, 'public')));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser(process.env.COOKIE_SECRET));
app.use(session({
resave: false,
saveUninitialized: false,
secret: process.env.COOKIE_SECRET,
cookie: {
httpOnly: true,
secure: false,
},
}));
자주 보는 익숙한 패턴이다.
app.use 를 통해 외부 미들웨어를 넣음으로서 외부 파일에서 req.cookies 나 req.session , req.body , req.query 같이 외부 미들웨어에서 생성한 req객체들을 아무 거리낌 없이 사용해왔다.
그럼 소켓 라이브러리에선 어떠한가?
const chat = io.of('/chat');
chat.on('connection', (socket) => {
const req = socket.request; //? req객체 꺼내써 쓰기
// ...
})
socket.request 를 통해 req 객체를 꺼내주었다. 이제 req 객체는 express의 req객체라고 봐도 되긴하다.
하지만, 엄밀히 말하면 socket.request 는 express의 req객체 흉내를 내는 객체일 뿐이다.
express의 req객체 !== socket.request객체
엄밀히 말해 둘은 전혀 다른 객체이다.
다만 express에서 req.headers 처럼, 기본적인 express의 객체 기능을 소켓 내에서도 사용하기 위해 라이브러리가 구현해 지원하는 객체라고 보면 된다.
따라서 express req객체가 기본적으로 지원하는 req.get() , req.host , req.headers 등의 기능을 사용할수는 있지만,
req.session , req.body , req.query 같은 외부 생성 객체는 express의 app.use() 를 통해 넣어준 것이기 때문에 당연히 사용하지 못한다.
즉, cookieParser 같은 외부 미들웨어 객체값을 사용하기 위해서, express는 app.use() 를 통해 얻었다면, 소켓IO는 io.use() 를 통해 얻을 수 있다.
이 io.use() 가 소켓 미들웨어 인 것이다.
소켓 미들웨어의 기본 사용법은 이렇다.
io.use((socket, next) => {
// 외부모듈 미들웨어를 안에다 쓸 있다. 미들웨어 확장 원칙에 따라 res, req인자를 준다
외부미들웨어함수1(socket.request, socket.request.res || {}, next);
외부미들웨어함수2(socket.request, socket.request.res || {}, next);
외부미들웨어함수3(socket.request, socket.request.res || {}, next);
// 에러나면 next(인자)
next(new Error('error'));
});
socketio 최신버젼에서는 socket.request.res 가 지원안한다고 한다. 따라서 빈 객체 {}를 넣어 임시방편으로 삼는다.
기본적으로 소켓IO 미들웨어는 미들웨어 확장패턴을 따른다. 미들웨어 함수 뒤에 (req, res, next) 인자를 붙여주는 것이다.
자주 쓰이는 미들웨어 cookie-parser를 예로 들어보면 코드는 이렇게 된다.
// 외부 미들웨어
const cookieParser = require('cookie-parser');
const SocketIO = require('socket.io');
const io = SocketIO(server, { path: '/socket.io' });
//^ app.js 에서는 쿠키/세션 미들웨어를 app.use()에 장착 시킴으로서, req.cookie/req.session 객체를 얻었던 것 처럼,
//^ app.js의 쿠키/세션 미들웨어를 받아와서 io.use()에 장착 시킴으로서 socket.request.cookie/socket.request.session 객체를 사용할수 있게 된다.
io.use((socket, next) => {
// 외부모듈 미들웨어를 안에다 쓰일수 있다. 미들웨어 확장 원칙에 따라 res, req인자를 준다 (후술)
cookieParser(process.env.COOKIE_SECRET)(socket.request, socket.request.res || {}, next);
});
const req = socket.request;
console.log(req.cookies); // 외부 미들웨어 객체를 사용 가능해진다.
공식문서에는 wrap 이라는 helper함수 기법을 제공해주는데, 이 기법을 사용하면 app.use() 를 사용해 쭉 나열 한것 같이 좀더 깔끔하게 미들웨어들을 배치할 수 있게 된다.
외관상 좋아지긴 하지만, 물론 어디까지나 선택적인 방법일 뿐이다.
// 외부 미들웨어
const cookieParser = require('cookie-parser');
const SocketIO = require('socket.io');
const io = SocketIO(server, { path: '/socket.io' });
// 미들웨어를 인자로 받아 자동으로 (req, res, next) 인자를 붙여줘서 호출하게 만드는 helper 함수
const wrap = (middleware) => (socket, next) => middleware(socket.request, socket.request.res || {}, next);
io.use(wrap(cookieParser(process.env.COOKIE_SECRET)));
io.use(wrap(외부미들웨어1));
io.use(wrap(외부미들웨어2));
io.use(wrap(외부미들웨어3));
const req = socket.request;
console.log(req.cookies); // 외부 미들웨어 객체를 사용 가능해진다.
이 글이 좋으셨다면 구독 & 좋아요
여러분의 구독과 좋아요는
저자에게 큰 힘이 됩니다.