node.js의 connect 이해하기
express로 프로그래밍하다 보면, connect라는 라이브러리가 범상치 않은 놈임을 눈치챌 수 있다.
이녀석을 잘 설명한 문서를 찾다 아래 문서를 발견했다. rough하게 발번역하고, 요약해보았다.
[참고] http://howtonode.org/connect-it
connect는 node를 가능한 작게 추상화하고 리패키징한 것.
그 결과 API는 상당히 node-specific하고, 견고함.
그리고 connect는 unique한 한가지 요소를 node HTTP 서버에 추가했다는데, 그것이 바로 layer.
통합(integration) 문제
다음은 일반적인 node HTTP서버 코드
var http = require('http');
// Start an HTTP server
http.createServer(function (request, response) {
// Every request gets the same "Hello Connect" response.
response.writeHead(200, {"Content-Type": "text/plain"});
response.end("Hello Connect");
}).listen(8081);
이는 잘 동작하지만, 항상 같은 response를 주기 때문에, 실제로는 별 쓸모 없다는 것.
사용자는 요청에 대한 라우팅이나, 혹은 더 향상된 기능을 제공하고 싶어함. ( 이를 테면, 응답(response body) 압축, 괜찮은 캐싱, 로그, 에러 핸들링 등 )
이런 것들을 프로젝트를 수행할 때마다 매번 구현하는 것은 당연히 엄청난 노가다. 물론 node 커뮤니티에 다양한 모듈들이 이미 있지만…. 문제는 이에 대한 스펙 자체가 없다는 것.
따라서 이 모듈들 통합하는 것은 각자 알아서, 지 맘대루. 이것이 문제!
구원자 Layer! ( Layers to the Rescue )
그래서 layer란 개념을 도입! 앱은 마치 양파(-_-)와 같은 구조가 된다. 모든 요청은 양파의 바깥에서부터 들어가서 각 층(layer)별로 돌아다님. 해당 요청을 처리하고, 응답을 만들 때 까지..
connect에서는 이들을 filters 와 providers로 부른다. 일단 특정 layer가 응답을 제공하면, 다시 거꾸로 쭉쭉~
connect 프레임웍은 단순히 최초 요청과 node의 http 콜백으로부토 오는 응답을 취한 후, layer 별로 전달한다. 이 layer들은 이미 앱에서 설정된 미들웨어 모듈들.
위 예제를 connect를 써서 구현한 코드
var Connect = require('connect');
Connect.createServer(function (req, res, next) {
// Every request gets the same "Hello Connect" response.
res.simpleBody(200, "Hello Connect");
}).listen(8080);
앱과 layer를 구현해보자 ( Walkthrough Writing Layers and an Application )
서버에서 요청된 파일을 열어서 제공하고, 램에 캐쉬하고, 요청과 응답을 로깅하는 서버를 만들기.
var Connect = require('connect');
module.exports = Connect.createServer(
require('./log-it')(),
require('./serve-js')()
);
보는 바와 같이 Connect.createServer에 2가지 핸들러를 넘겨 호출하고 있다.
모든 connect layer는 setup 함수를 export하고, 핸들러 함수를 리턴한다. setup 함수는 서버 시작시에 호출되고, 설정 정보를 넘길 수 있다.
그리고나서, 요청이 들어왔을 때 다음과 같은 옵션이 있다.
(A) 응답
(B) 다음 layer에 컨트롤 넘기기
파일 내용 제공하기 ( Serve Some Files )
var fs = require('fs');
module.exports = function serveJsSetup() {
return function serveJsHandle(req, res, next) {
// Serve any file relative to the process.
// NOTE security was sacrificed in this example to make the code simple.
fs.readFile(req.url.substr(1), function (err, data) {
if (err) {
next(err);
return;
}
res.simpleBody(200, data, "application/javascript");
});
};
};
simpleBody는 connect에서 제공하는 helper 함수.
로깅하기 (Log It)
요청들어오면 콘솔에 출력하고, 다음 layer로~
module.exports = function logItSetup() {
// Initialize the counter
var counter = 0;
return function logItHandle(req, res, next) {
var writeHead = res.writeHead; // Store the original function
counter++;
// Log the incoming request
console.log("Request " + counter + " " + req.method + " " + req.url);
// Wrap writeHead to hook into the exit path through the layers.
res.writeHead = function (code, headers) {
res.writeHead = writeHead; // Put the original back
// Log the outgoing response
console.log("Response " + counter + " " + code + " " + JSON.stringify(headers));
res.writeHead(code, headers); // Call the original
};
// Pass through to the next layer
next();
};
};
위에서 눈여겨 볼 것은 node의 API인 writeHead를 hooking 한다는 것.
결국 res.writeHead를 호출하면 위 함수가 먼저 호출되고, original이 호출될 것임.
hooking의 간단하고도 효율적인 방법.
빌트인 미들웨어 ( Built-in Middleware )
손쉬운 사용을 위해 connect는 몇가지 빌트인 미들웨어 layer를 제공.
var Connect = require('connect');
module.exports = Connect.createServer(
Connect.logger(), // Log responses to the terminal using Common Log Format.
Connect.responseTime(), // Add a special header with timing information.
Connect.conditionalGet(), // Add HTTP 304 responses to save even more bandwidth.
Connect.cache(), // Add a short-term ram-cache to improve performance.
Connect.gzip(), // Gzip the output stream when the browser wants it.
Connect.staticProvider(__dirname) // Serve all static files in the current dir.
);
Future and Goals of Connect
원문참조바람.