Socket.IO 1.2.1 버그

socket.io

2014년 12월 Node.JS 0.10.33 버전과 Socket.IO 1.2.1 버전을 사용하다가 발견한 버그를 GitHub에 이슈 등록했었는데 1월 11일에 수정되었다.

해당 문제는 origins 를 함수를 사용하여 검사하도록 설정한 경우 발생했다.  아래는 간단한 예제이다.

var debug = require('debug')('simple'),
 server = require('http').createServer(),
 io = require('socket.io')(server, {
 origins: function (origin, fn) {
 if (origin == 'http://localhost:8080') {
 debug('allow origin [%s]', origin);
 return fn(null, true);
 }
 debug('disallow origin [%s]', origin);
 return fn(null, false);
 }
 });
server.listen(3000);
io.on('connection', function (socket) {
 debug('connection from client [%s]', socket.id);
 socket.on('disconnect', function () {
 debug('disconnect from clinet [%s]', this.id);
 });
});

해당 프로그램을 실행 후 Fiddler Web Debugger의 Composer나 다른 방법으로 아래와 같이 요청을 전송한다.

GET http://localhost:3000/socket.io/?EIO=3&transport=polling&t=1234567890-0 HTTP/1.1
Set-Cookie: io=1234567890

그러면 해당 Node.JS가 아래와 같은 오류를 출력하면서 종료된다.

/home/barney/simple/node_modules/socket.io/lib/index.js:68
 if (this._origins.indexOf('*:*') !== -1) return fn(null, true);
 ^
TypeError: Object function (origin, fn) {
  if (origin == 'http://localhost:8080') {
    debug('allow origin [%s]', origin);
    return fn(null, true);
  }
  debug('disallow origin [%s]', origin);
  return fn(null, false);
 } has no method 'indexOf'
 at Server.checkRequest (/home/barney/simple/node_modules/socket.io/lib/index.js:68:21)
 at Server.verify (/home/barney/simple/node_modules/socket.io/node_modules/engine.io/lib/server.js:127:17)
 at Server.handleRequest (/home/barney/simple/node_modules/socket.io/node_modules/engine.io/lib/server.js:174:8)
 at Server.<anonymous> (/home/barney/simple/node_modules/socket.io/node_modules/engine.io/lib/server.js:366:12)
 at Server.<anonymous> (/home/barney/simple/node_modules/socket.io/lib/index.js:258:16)
 at Server.EventEmitter.emit (events.js:98:17)
 at HTTPParser.parser.onIncoming (http.js:2108:12)
 at HTTPParser.parserOnHeadersComplete [as onHeadersComplete] (http.js:121:23)
 at Socket.socket.ondata (http.js:1966:22)
 at TCP.onread (net.js:525:27)

해당 Socket.IO의 소스 socket.io/lib/index.js 를 보면 문제를 쉽게 찾을 수 있다.

61 Server.prototype.checkRequest = function(req, fn) {
62 var origin = req.headers.origin || req.headers.referer;
63
64 // file:// URLs produce a null Origin which can't be authorized via echo-back
65 if ('null' == origin) origin = '*';
66
67 if (!!origin && typeof(this._origins) == 'function') return this._origins(origin, fn);
68 if (this._origins.indexOf('*:*') !== -1) return fn(null, true);
...
80 }
81 fn(null, false);
82 };

요청 헤더에 origin 값이 없기 때문에 61줄의 origin 변수는 undefied 다. 그런데 65줄에서 null 이나 undefined에 대한 처리가 없어서 67줄이 실행되지 않고 68줄로 넘어가니 this._origins 는 예제에서 지정한 함수니까 indexOf를 했을 때 TypeError 가 발생할 수 밖에 없다.

해당 문제를 발견후 간단하게 undefined 인지 여부를 확인하도록 하고 Pull Request를 보냈는데, 내 것 대신 다른 개발자의 Pull Request가 merge 되었다. 그 이유는 내가 보낸 Pull Request에는 단위 테스트가 없었기때문이 아닐까 짐작해본다. 아무리 단순한 것이라도 단위 테스트로 검증을 한 것이 올바른 방법이었다.

Socket.IO 1.3.2 버전이  1월 19일 릴리즈되었고, 이 버전에 해당 수정이 포함되어있다.

참고