Node学习入门篇(六):Connect

内容

  • 搭建一个Connect程序
  • Connect中间件的工作机制
  • 为什么中间件的顺序很重要
  • 挂载中间件和服务器创建可配置的中间件
  • 使用错误处理中间件

Connect常用的组件

在Connect中,中间件组件是一个函数,它拦截HTTP服务器提供的请求和响应对象,执行逻辑,然后或者结束响应,或者把它传递给下一个中间件组件。

Connect用分派器把中间件“连接”在一起

  • 请求日志
  • 静态文件服务
  • 请求体解析
  • 会话管理

这里写图片描述

使用connect

1)安装

npm install connect

2)引入
Connect创建的“程序”实际上是一个JavaScript函数,用来接收HTTP请求并把它派发给你指定的中间件

它依次调用所有附着的中间件组件,直到其中一个决定响应该请求。如果直到中间件列表末尾还没有组件决定响应,程序会用404作为响应

var connect = require('connect');
var app = connect();

app.listen(3000);

connect工作机制

在Connect中,中间件组件是一个JavaScript函数,按惯例会接受三个参数:

一个请求对象,
一个响应对象,
还有一个通常命名为next的参数,它是一个回调函数,表明这个组件已经完成了它的工作,可以执行下一个中间件组件了

  • 使用中间件

    中间件组件hello的参数中没有next回调。因为这个组件结束了HTTP响应,从不需要把控制权交回给分派器

    var connect = require('connect');
    var app = connect();
    
    app.use(logger);
    app.use(hello);
    app.listen(3000, function() {
        console.log('listening...');
    });
    
    function logger(req, res, next) {
        console.log('%s %s', req.method, req.url);
        next();
    }
    
    function hello(req, res) {
        res.setHeader('Content-Type','text/plain');
        res.end('hello world');
    }
  • 中间件的顺序

    1) 中间件.use()调用的顺序很重要,以及如何策略性地调整顺序改变程序的工作方式
    2) 当一个组件不调用next()时,命令链中的后续中间件都不会被调用
    3) 使用中间件执行认证,只允许有效的用户访问

  • 挂载中间件和服务器

    挂载将只对路径前缀(挂载点)内的请求调用中间件或程序,通过拦截请求进行处理

    • basic认证
    • Basic认证是一种简单的认证机制,借助带着Base64编码认证信息的HTTP请求头中的authorization字段进行认证
    • 中间件组件解码认证信息,检查用户名和密码的正确性
    function restrict(req, res, next) {
        var authorization = req.headers.authorization;
        if(!authorization) return next(new Error('Unauthorized'));
    
        var parts = authorization.split(' ');
        var scheme = parts[0];
        var auth = new Buffer(parts[1], 'base64').toString().split(':');
        var user = auth[0];
        var pass = auth[1];
    
        authenticateWithDatabase(user, pass, function(err) {
            if(err) return next(err);
    
            next();
        });
    }

    注意:用Error做参数调用next 注意前面例子中用Error对象做参数的next函数调用。这相当于通知Connect程序中出现了错误,也就是对于这个HTTP请求而言,后续执行的中间件只有错误处理中间件

  • 使用挂载后的程序

    var connect = require('connect');
    var app = connect();
    
    app.use(logger);
    app.use('/admin', restrict);
    app.use('/admin', admin);
    app.use(hello);
    app.listen(3000, function() {
        console.log('listening...');
    });
    
    function logger(req, res, next) {
        console.log('%s %s', req.method, req.url);
        next();
    }
    
    function hello(req, res) {
        res.setHeader('Content-Type','text/plain');
        res.end('hello world');
    }
    
    function restrict(req, res, next) {
        var authorization = req.headers.authorization;
        if(!authorization) return next(new Error('Unauthorized'));
    
        var parts = authorization.split(' ');
        var scheme = parts[0];
        //解密得到string
        var auth = new Buffer(parts[1], 'base64').toString().split(':');
        var user = auth[0];
        var pass = auth[1];
        //进行验证
        authenticateWithDatabase(user, pass, function(err) {
            if(err) return next(err);
    
            next();
        });
    }
    //模拟认证操作
    function authenticateWithDatabase(user, pass, callback) {
        if (user == 'fafa' && pass == 'fafa') {
            console.log('ok');
            callback(null);
        } else {
        //出错了,回调处理函数
            callback(new Error('unauthorized user'));
        }
    }
    function admin(req, res, next) {
        switch (req.url) {
            case '/':
                res.end('try /users');
                break;
            case '/users':
                res.setHeader('Content-Type','application/json');
                res.end(JSON.stringify(['fafa','papa','nana']));
                break;
            default:
                // statements_def
                break;
        }
    }
  • 创建可配置中间件

    • 更通用的、可重用的中间件
    • 中间件通常会遵循一个简单的惯例:用函数返回另一个函数(这是一个强大的JavaScript特性,通常称为闭包)
    • 向其中传入额外的参数来改变它的行为

    作为一个中间件模块

    function setup(format) {
        //正则匹配请求的属性
        var regexp = /:(\w+)/g;
        return function logger(req, res,next) {
            var str = format.replace(regexp, function(match, property){
                return req[property];
            });
            console.log("query:" + str);
            next();
        }
    }
    
    module.exports = setup;

    在主文件中引入时传入参数,实现重用

    var connect = require('connect');
    var app = connect();
    var logger = require('./midware/logger');
    app.use(logger(':method:url'));
    app.use('/admin', restrict);
    app.use('/admin', admin);
    app.use(hello);
    app.listen(3000, function() {
        console.log('listening...');
    });
  • 构建路由中间件

    • 把请求URL映射到实现业务逻辑的函数上
    • HTTP谓词和路径被表示为一个简单的对象和一些回调函数
    var parse = require('url').parse;
    
    module.exports = function route(obj) {
        return function(req, res, next) {
            if(!obj[req.method]) {
                //req.method未定义
                next();
                return;
            }
            var routes = obj[req.method];
            var url = parse(req.url);
            //将路径放入数组中,准备开始遍历数组
            var paths = Obeject.keys(routes);
    
            for(var i = 0; i < paths.length; i++) {
                var path = paths[i];
                var fn = routes[path];
                path = path.replace(/\//g, '\\/')
                           .replace(/:(\w+)/g, '([^\\/]+)');
                var re = new RegExp('^' + path + '$');
                var captures = url.pathname.match(re);
                if (captures) {
                    var args = [req, res].concat(captures.slice(1));
                    //有函数匹配到,返回,防止后续的Next()
                    fn.apply(null, args);
                    return;
                }
            }
            next();
        }
    };
    

    在app.js中引入,并在app.js中指定不同路径的处理函数

    var connect = require('connect');
    var app = connect();
    var logger = require('./midware/logger');
    var router = require('./midware/router');
    var routers = {
        GET:{
            '/users':function(req, res) {
                res.end('fafa,papa,nana');
            },
            'user/:id':function(req, res, id) {
                res.end('user' + id);
            }
        },
        DELETE:{
            'user/:id':function(req, res, id) {
                res.end('delete user' + id);
            }
        }
    };
    
    app.use(logger(':method:url'));
    app.user(router(routers))
    app.use('/admin', restrict);
    app.use('/admin', admin);
    app.use(hello);
    app.listen(3000, function() {
        console.log('listening...');
    });
  • 使用错误处理中间件

    • Connect的默认错误处理器
    • 默认情况下,Connect给出的响应是状态码500,包含文本“Internal Server Error”以及错误自身详细信息的响应主体。这很好,但在任何实际的程序中,你很可能都会对那些错误做些特殊的处理,比如将它们发送给一个日志守护进程
    • 自行处理程序错误
    • 在Connect中,你还可以用错误处理中间件自行处理程序错误。比如说,在开发时你可能想用JSON格式把错误发送到客户端,做简单快捷的错误报告,而在生产环境中,你可能只想响应一个简单的“服务器错误”,以免把敏感的内部信息(比如堆栈跟踪,文件名和行号等)暴露给潜在的攻击者
    • 错误处理中间件函数必须接受四个参数:err、req、res和next,如下面的代码清单所示,而常规的中间件只有三个参数:req、res和next
    function errorHandler() {
    
        var env = process.env.NODE_ENV || 'development';
        return function(err, req, res, next) {
            res.statusCode = 500;
            //根据不同环境执行不同操作
            switch (env) {
                case 'development':
                    res.setHeader('contentType','application/json');
                    res.end(JSON.stringify(err));
                    break;
                default:
                    res.end('Server error');
                    break;
            }
        }
    }

    用NODE_ENV设定程序的模式Connect通常是用环境变量NODE_ENV (process. env.NODE_ENV)在不同的服务器环境之间切换,比如生产和开发环
    这里写图片描述
    比如在前面那个管理程序中,如果给用户路由的路由中间件组件出现了错误,blog和admin中间件组件都会被跳过去,因为从它们的表现来看都不是错误处理中间件,只定义了三个参数。然后Connect看到接受错误参数的errorHandler,就会调用它

connect()
    .use(router(require('./router/user')))
    .use(router(require('./router/blog')))
    .use(router(require('./router/admin')))
    .use(errorHandler());

猜你喜欢

转载自blog.csdn.net/w_bu_neng_ku/article/details/79950644