Node--Connect通过中间件实现代码复用
Connect
Connect是一个基于HTTP服务器的工具集,它提供了一种新的组织代码的方式来与请求、响应对象进行交互,称为中间件。
中间件能够实现代码复用。
为了说明通过中间件进行代码复用的好处,我们分别用HTTP和Connect来实现一个简单的网站。
验证方式:访问网站的照片
HTTP方法访问网站的照片
1、创建模块
{
"name": "A demo to compare HTTP with Connect",
"description": "A demo to compare HTTP with Connec",
"version": "1.1.0"
}
2、引入http模块用来创建服务器以及fs模块,用来读取文件;初始化服务器并处理请求——响应,最后监听。
// index.js
/*
* 引入模块
*/
var http = require('http'),
fs = require('fs');
/*
* 初始化服务器
*/
var server = http.createServer(function(req,res){
// ...
});
/*
* 监听
*/
server.listen(3000);
3、补充createServer回调函数
回调函数需要包含一下功能:
- 检查URL是否和服务器目录下的文件匹配
- 如果匹配,则读取文件并展示出来。
- 如果URL为’/’,则响应index.html;
- 否则,发送404 Not Found
var server = http.createServer(function(req,res){
if (req.method == 'GET' && req.url.substr(0,7) == '/images' && req.url.substr(-4) == '.jpg') {
// ..
} else if (req.method == 'GET' && req.url == '/') {
server(__dirname + '/index.html','text.html');
} else {
res.writeHead(200);
res.end('Not Found');
}
});
这里,使用__dirname 来获取服务器所在的路径。
4、若请求是images目录下的图片,则查找文件
- 使用fs.stat查找文件是否存在
- 如果文件查找出错或者路径所表示的并非是文件,返回404 Not Found
- 否则,返回图片信息
if (req.method == 'GET' && req.url.substr(0,7) == '/images' && req.url.substr(-4) == '.jpg') {
fs.stat(__dirname + req.url, function(err,stat) {
if (err || !stat.isFile()) {
res.writeHead(200);
res.end('Not found');
return ; // 注意这里要加上,以便终止进程
} else {
serve(__dirname + req.url, 'application/jpg');
}
});
}
serve函数接受文件路径和请求类型,返回响应。
function serve(path, type) {
res.writeHead(200, {"Content-Type" : type});
fs.createReadStream(path).pipe(res);
}
HTTP响应对象是一个只写流,而从文件出来的流是只读的。同时,可以将文件系统流接(pipe)到HTTP响应流中。
其实上面的代码等同于:
fs.createReadStream(path)
.on('data', function(data) {
res.write(data);
})
.on('end',function() {
res.end();
})
5、准备html文件访问图片
// index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>image request</title>
<style type="text/css">
body, html {
margin: 0;
padding: 0;
}
img {
transform:scale(0.3);
}
</style>
</head>
<body>
<h1>My image</h1>
<img src="images/1.jpg">
<img src="images/2.jpg">
<img src="images/3.jpg">
<img src="images/4.jpg">
</body>
</html>
结果:
Connect方法访问网站的照片
前面这个例子展示了创建网站时一些常见的任务:
- 创建静态文件
- 处理错误以及损坏或者不存在的URL
- 处理不同类型的请求
基于http模块API之上的Connect,提供了一些工具方法能够让这些重复的处理便于实现。
1、安装connect模块
npm install connect
2、引入Connect模块,通过Connect模块创建服务器,并使用use()的方法来添加static中间件,然后进行监听。
/*
* 引入模块
*/
var connect = require('connect');
/*
* 创建服务器
*/
var server = connect.createServer();
/*
* 使用中间件
*/
server.use(connect.static(__dirname + '/website'));
server.listen(3000);
中间件
中间件其实就是一个简单的JavaScript函数,通过参数给connect.static方法,该方法本身会返回一个方法,是托管文件到某个目录下的方法(一般是托管静态文件),确保没有将不想托管的文件放进去。
对于中间件这样的概念,我认为,可以简单的理解,如果一个请求需要对多种可能的情况进行不同的处理,如果将这样的处理放在一系列if-else语句中显然太复杂。所以,如果我们将这个请求依次经过不同的处理,每个处理叫做一个中间件,每个处理结束之后会调用下一个处理,直到任务结束。
简单地说,中间件由函数组成,它除了处理req和res对象之外,还接收一个next函数来做流控制。
为了更好的说明Connect的强大,我们处理更多的任务:
- 记录请求处理事件
- 托管静态文件
- 处理授权
若采用中间件模式满足上述的应用,会是这样的:
server.use(connect.static(__dirname + '/website'));
server.use(function(req,res,next) {
// 记录日志
console.error(' %s %s', req.method, req.url);
next();
});
server.use(function(req,res,next) {
if (req.method == 'GET' && req.url.substr(0,7) == '/images') {
// 托管图片
} else {
// 交给其他中间件去处理
next();
}
});
server.use(function(req,res,next) {
if (req.method == 'GET' && req.url == '/') {
// 响应index.html
} else {
// 交给其他中间件处理
next();
}
});
可以理解为每次调用server.use(function(req,res,next){})都是在connect创建的服务器对象中注册一个方法,每个方法都是依次注册的,每个方法处理完都要调用下一个方法。所以在每个方法要接收一个next,这个next就是下一个要执行的方法。
这就是为什么connect使用中间件时需要注意注册顺序的原因了。(被坑过,伤不起)
完整代码:
// index.js
/*
* 引入模块
*/
var connect = require('connect');
var fs = require('fs');
var serverStatic = require('serve-static');
/*
* 创建服务器
*/
var server = connect();
/*
* 使用中间件
*/
server.use(serverStatic(__dirname + '/')); // 这里路径如果改为 /webside ,那么后面的图片或文档请求资源的路径也要发生改变,也就是说,index.html中的图片请求路径需要改为<img src="webside/images/1.jpg"></img>
server.use(function(req,res,next) {
// 记录日志
console.error(' %s %s', req.method, req.url);
next();
});
server.use(function(req,res,next) {
if (req.method == 'GET' && req.url.substr(0,7) == '/images') {
fs.stat(__dirname + req.url, function(err,stat) {
if (err || !stat.isFile()) {
res.writeHead(200);
res.end('Not found');
return ; // 注意这里要加上,以便终止进程
} else {
res.writeHead(200, {"Content-Type" : 'application/jpg'});
fs.createReadStream(__dirname + req.url).pipe(res);
}
});
} else {
// 交给其他中间件去处理
next();
}
});
server.use(function(req,res,next) {
if (req.method == 'GET' && req.url == '/') {
res.writeHead(200, {"Content-Type" : 'text/html'});
fs.createReadStream(__dirname + req.url).pipe(res);
} else {
// 交给其他中间件处理
next();
}
});
server.use(function(req,res,next) {
res.writeHead(404);
res.end('Not Found');
});
server.listen(3000);
使用HTTP和Connect访问资源的不同
不同之处在于Connect使用中间件,将应用拆分为更小单元(使得代码有更强大的表达能力),还表现很好的重用性。