Node学习入门篇(六):Connect

标签: connect node.js middleware
9人阅读 评论(0) 收藏 举报
分类:

内容

  • 搭建一个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());
查看评论

探寻软件的永恒之道

探寻软件的永恒之道——评介《建筑的永恒之道》[1]撰文/透明从模式说起“模式”这个词进入中国软件开发者的视野,是从《设计模式》[2]一书开始的。2000年9月,中国的软件开发图书市场还远不如今天繁荣,...
  • gigix
  • gigix
  • 2002-06-25 09:56:00
  • 3211

react-router withRouter

react-router 提供了一个withRouter组件 withRouter可以包装任何自定义组件,将react-router 的 history,location,match 三个对象传入。...
  • ISaiSai
  • ISaiSai
  • 2017-09-26 14:03:10
  • 6443

react-router 与react-reduct 配合使用时,页面不刷新问题

参考文档:https://reacttraining.com/react-router/core/guides/redux-integration/blocked-updates 异常现象点击Link...
  • ISaiSai
  • ISaiSai
  • 2017-09-25 19:57:23
  • 1112

react-router4 实现按需加载并利用withRouter传递props

bundle.jsximport React, {Component} from 'react'export default class Bundle extends Component { com...
  • oYueMiJinDu
  • oYueMiJinDu
  • 2017-05-01 19:55:27
  • 5242

react-router 只变 link 不更新内容的问题的解决 记录

React + Redux + node.js 的项目 之前遇到类似的问题,是这种情况: 用 withRouter() 解决 Blocked Updates Generally,...
  • Beijiyang999
  • Beijiyang999
  • 2018-01-06 23:47:36
  • 312

connect by用法

在SELECT命令中使用CONNECT BY 和START WITH 子句可以查询表中的树形结构关系。其命令格式如下: SELECT ….. CONNECT BY {PRIOR 列名1=列名2|列...
  • youngxw_2012
  • youngxw_2012
  • 2013-04-22 22:30:56
  • 858

关于 React Router 4 的一切

https://juejin.im/post/5995a2506fb9a0249975a1a4?utm_source=tuicool&utm_medium=referral 我在 React R...
  • sinat_17775997
  • sinat_17775997
  • 2017-08-19 13:12:34
  • 13182

Redux 17 - 进阶:和React Router一起使用(Usage with React Router)

原文 http://testudy.cc/tech/2017/04/29/redux-17-advanced-UsageWithReactRouter.html?utm_source=tuico...
  • sinat_17775997
  • sinat_17775997
  • 2017-04-29 08:44:00
  • 1063

React应用架构设计

前言现在已经有很多脚手架工具,如create-react-app(https://github.com/facebookincubator/create-react-app),支持一键创建一个Reac...
  • uxiAD7442KMy1X86DtM3
  • uxiAD7442KMy1X86DtM3
  • 2018-01-11 00:00:00
  • 272

react技术栈实践

react技术栈实践 背景 最近开发一个全新AB测试平台,思考了下正好可以使用react技术开发。 实践前技术准备 首先遇到一个概念,redux。这货还真不好理解,大体的理解:Stor...
  • ihaveahappyfamily
  • ihaveahappyfamily
  • 2018-04-02 12:23:44
  • 60