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>

结果:
Node--Connect通过中间件实现代码复用

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使用中间件,将应用拆分为更小单元(使得代码有更强大的表达能力),还表现很好的重用性。