express 最佳实践(二):中间件
express 最佳实践(二):中间件
express 中最重要的就是中间件了,可以说中间件组成了express,中间件就是 express 的核心。下面来讲几个有用的中间件的写法。
错误处理中间件
这块中间件非常基础,分成两个维度,第一个维度:客户端错误,服务器端错误;第二个维度:页面错误,ajax错误。
之所以分成两个维度来说,是因为,客户端和服务器端处理的错误的地方在两个中间件中,不在一个地方处理。
客户端的发出的错误只可能是:路由错误。也就是说是没有找到该找的页面和接口,在 express 中就要这样写:
// 在app 中注册使用
// * 代表的是所有的路由都能匹配
app.all('*', pageNotFound);
// 这样处理页面
function pageNotFound (req, res, next) {
if (req.xhr) {
return res.status(404).json({
code: 404,
message: '抱歉,页面不存在!'
});
}
res.render('error-404.njk');
}
代码中对 ajax 和 页面请求进行单独的处理,当然你也可以,在里面加些逻辑进行处理,不过不建议加到这里,因为这里的错误很有可能是用户自己无意输错的。
服务器端的错误情况就多了,很有可能是业务代码的问题,也有可能是参数的问题,服务器端的错误就要用 express 处理错误的中间件形式:(err, req, res, next)
必须是写成这样:
app.all('*', pageNotFound);
// 要放到客户端问题下面
app.use(serverError);
function serverError (err, req, res, next) {
if (req.xhr) {
return res.json({
code: 500,
message: 'server error'
});
}
res.render('error-500.njk');
next(err) // 也可以不要
}
服务器端也对页面请求和 ajax 分别进行了处理,只是最后的错误没有吞掉,仍然 next 到下一个中间件。
这个中间件应该是所有中间件的最后的一个,只应该有一个错误处理中间件。
express 中最后的一个中间件是 finalhandler, 如果到这个中间件话,开发环境下会打到网页上,前提是你没有 render ,我这里就不行,如果是生产环境的话,就会打印到控制台中。有很多第三方工具,就在这里接入一个中间件,就能把所有的错误都收集起来。
这部分代码能在我的项目:github,core 目录中找到。
用户鉴权中间件
在网站中,用户系统是非常重要的一块,比如用户中心,只能已经登录的用户才能访问,未登录的用户访问就让他跳到登录页,登录后跳回原来要访的页面。
并不是每个页面都需要用户登录,因此,我们要做一个中间件,只要需要用户登录的地方加上,他就能实现以上功能。
这个中间件我叫 auth
:
function auth(req, res, next) {
let refer = req.method === 'GET' ? req.get('Referer') : '';
let loginAPI = helpers.urlFormat('/passport/login', {refer: refer});
let loginPage = helpers.urlFormat('/passport/login', {refer: req.fullUrl()});
if (_.isEmpty(req.user) || !req.user.uid || !req.user.uid.isValid()) {
if (req.xhr) {
return res.json({
code: 400,
message: '抱歉,您暂未登录!',
data: {refer: loginAPI}
});
}
return res.redirect(loginPage);
}
next();
};
这个中间件的逻辑就是也分成 页面请求和 ajax ,就去判断用户信息是否有效,如果有效,就 next(), 如果没有效,就跳转到登录页。
登录页会根据 url 中的 refer 进行登录成功后的跳转。
这里没有讲 session 之类的处理,可以使用 memcached,也可以使用加密的 cookie。
如何佣使用这个中间件:
// 针对单个路由使用
router.get('/home/account', auth, change)
router.post('/home/order', auth, list)
// 也可以针对整个模块使用,这样整个模块都需要用户登录后才能使用
subApp.use(auth)
子域名中间件
有很多大型的网站,都有子域名。比如: map.baidu.com, blog.leancloud.cn。
一般来说,我们把 www 叫成主域名,map, blog 叫成子域名,其实 www 也是二级域名。
当然也有三级域名:list.m.yohobuy.com 这个就是有三级域名。
忘了说一级域名:就是 baidu.com 之类的。
express 中对域名有一个设置是专门对于这块的:
app.set('subdomain offset', 2); // 这里的设置就是说最多到三级域名
在写的时候没有使用 express vhost 中间件,因为这个中间不好使用,设置的时候还要带全域名。
我们在处理子域的时候,就在进所以的进入业务逻辑之前进行处理,通过 req.subDomains 改写 req.url:
function subDomain(req, res, next) {
if (req.subdomains.length) {
switch (req.subdomains[0]) {
case 'www':
case 'shop':
case 'new':
case 'item':
break;
case 'guang':
case 'search': {
let searchReg = /^\/product\//;
if (!searchReg.test(req.path)) {
if (req.path === '/api/suggest') {
req.url = '/product/api/suggest';
} else {
req.url = '/product/search/index';
}
}
break;
}
case 'list':
case 'sale':
default: {
let queryString = (function() {
if (!_.isEmpty(req.query)) {
return '&' + qs.stringify(req.query);
} else {
return '';
}
}());
req.query.domain = req.subdomains[0];
if (!req.path || req.path === '/') {
req.url = `/product/index/brand?domain=${req.subdomains[0]}${queryString}`;
} else if (req.path === '/about') {
req.url = `/product/index/brand?domain=${req.subdomains[0]}${queryString}`;
}
break;
}
}
}
next();
}
这个就是针对特殊情况进行处理,当然你也可以来个简单的的,要注意处理只能针对有 req.url 进行处理。
url跳转中间件
当网站大了,有需求做 seo, 要把动态请求改成伪静态页面的时候,这就会有问题,你的模块都是参数写好的,你不能因为 url 要改就要变业务代码,新老的url 都要支持,因为老的 url 都分享出去了,都在用了,不能让不能使用。
有需求要变化的统一都在这个中间件中进行处理:核心还是改写新的url到老的,还有301 重定向。
function (req, res, next) {
if (req.subdomains.length > 1 && req.subdomains[1] === 'www') {
return res.redirect(301, helpers.urlFormat(req.path, req.query || '', req.subdomains[0]));
}
req.isMobile = /(nokia|iphone|android|ipad|motorola|^mot\-|softbank|foma|docomo|kddi|up\.browser|up\.link|htc|dopod|blazer|netfront|helio|hosin|huawei|novarra|CoolPad|webos|techfaith|palmsource|blackberry|alcatel|amoi|ktouch|nexian|samsung|^sam\-|s[cg]h|^lge|ericsson|philips|sagem|wellcom|bunjalloo|maui|symbian|smartphone|midp|wap|phone|windows ce|iemobile|^spice|^bird|^zte\-|longcos|pantech|gionee|^sie\-|portalmmm|jig\s browser|hiptop|^ucweb|^benq|haier|^lct|opera\s*mobi|opera\*mini|320x320|240x320|176x220)/i.test(req.get('user-agent')); // eslint-disable-line
if (req.xhr || isJsonp(req)) {
return next();
}
let rules = loadRule(req.subdomains[0]);
let useRule = _.find(rules, rule => isNeedHandle(req, rule));
if (!useRule) {
return next();
}
let step1x = stepX(_.partial(step1, req, useRule, _));
let step2x = stepX(_.partial(step2, req, useRule, _));
let step3x = stepX(_.partial(step3, req, useRule, _));
let processAfter = _.partial(getResultStatus, req, useRule, _);
let processing = _.flow(step1x, step2x, step3x);
let process = _.flow(processing, processAfter);
let result = process(req.url);
if (result.process) {
if (result.needRedirect) {
return res.redirect(301, result.url);
}
if (result.needNext) {
req.url = result.url;
return next();
}
}
return next();
};
这里是核心模块,重要的思想都在里面,rule 中放的规则,按二级域名进行划分,框架启动时载入。
限流中间件
这个中间件,主要是解决爬虫的问题。但是有两个问题需要解决。我们的爬虫是跟据一类的 url , 而不是某一个 url。 也就是文要拿到某个url 对应的 router。第二个问题,是针对整站的统计,而不针对某个具体的页面,请求次数在哪个地方进行统计。
第一个问题:
当我们拿到 req 的时候,就有 req.app.mountpath, 这个就是 subApp 的挂载点; req.route 就是当前的部分路由,因此就把两者拼起来,就能获得当前页面的完整路由了。
第二个问题:
我们可以在页面进来的时候就进行处理,也可以在页面处理完发送之前处理,还可以在页面发送完成后进行处理。
第一种处理方式,把中间件放在第一个中间件,然后对用户IP和完整路由进行累加,然后判断频率进行处理。
第二种处理方式:需要知道页面什么时候模板渲染完,也就是调用 render 之后,send 之前,可以参照 on-rendered,这也是我现在在用的方式。
第三种处理方式:因为 res 是一个写入流,因此会以一个 finish 方法,可以在这里面做文章。
总结
这一部分,主要是针对一些通用的中间件进行了一些讲解,主要还是要理解在 express 中那些能改,那些不能改。从哪重获得参数:
如何改写 url , 如何获得 router, 如何进行参数处理。
大部分的功能都已经讲到了。
该项目的 github
express 最佳实践(二):中间件的更多相关文章
- nodejs 实践:express 最佳实践(二) 中间件
express 最佳实践(二):中间件 第一篇 express 最佳实践(一):项目结构 express 中最重要的就是中间件了,可以说中间件组成了express,中间件就是 express 的核心. ...
- nodejs 实践:express 最佳实践系列
nodejs 实践:express 最佳实践系列 nodejs 实践:express 最佳实践(一) 项目结构 nodejs 实践:express 最佳实践(二) 中间件 nodejs 实践:expr ...
- nodejs 实践:express 最佳实践 (一)
express 最佳实践 (一) 最近,一直在使用 nodejs 做项目,对 nodejs 开发可以说深有体会. 先说说 nodejs 在业务中的脚色,, 在 web同构 方面, nodejs 的优势 ...
- nodejs 实践:express 最佳实践 (一) 项目结构
express 最佳实践 (一) 第二篇: express 最佳实践(二):中间件 最近,一直在使用 nodejs 做项目,对 nodejs 开发可以说深有体会. 先说说 nodejs 在业务中的脚色 ...
- nodejs 实践:express 最佳实践(四) express-session 解析
nodejs 实践:express 最佳实践(四) express-session 解析 nodejs 发展很快,从 npm 上面的包托管数量就可以看出来.不过从另一方面来看,也是反映了 nodejs ...
- nodejs 实践:express 最佳实践(三) express 解析
nodejs 实践:express 最佳实践(三) express 解析 nodejs 发展很快,从 npm 上面的包托管数量就可以看出来.不过从另一方面来看,也是反映了 nodejs 的基础不稳固, ...
- nodejs 实践:express 最佳实践(五) connect解析
nodejs 实践:express 最佳实践(五) connect解析 nodejs 发展很快,从 npm 上面的包托管数量就可以看出来.不过从另一方面来看,也是反映了 nodejs 的基础不稳固,需 ...
- nodejs 实践:express 最佳实践(六) express 自省获得所有的路由
nodejs 实践:express 最佳实践(六) express 自省获得所有的路由 某些情况下,你需要知道你的应用有多少路由,这在 express 中没有方法可以.因此我这边曲线了一下,做成了一个 ...
- nodejs 实践:express 最佳实践(七) 改造模块 connect2 解析
nodejs 实践:express 最佳实践(七) 改造模块 connect2 解析 nodejs 发展很快,从 npm 上面的包托管数量就可以看出来.不过从另一方面来看,也是反映了 nodejs 的 ...
随机推荐
- javaCV开发详解之7:让音频转换更加简单,实现通用音频编码格式转换、重采样等音频参数的转换功能(以pcm16le编码的wav转mp3为例)
javaCV系列文章: javacv开发详解之1:调用本机摄像头视频 javaCV开发详解之2:推流器实现,推本地摄像头视频到流媒体服务器以及摄像头录制视频功能实现(基于javaCV-FFMPEG.j ...
- 初始化CSS
为什么要初始化CSS? 建站老手都知道,这是为了考虑到浏览器的兼容问题,其实不同浏览器对有些标签的默认值是不同的,如果没对CSS初始化往往会出现浏览器之间的页面差异.当然,初始化样式会对SEO有一定的 ...
- 文件快速删除工具, 解决你的node_modules
摘要: 还在为删除文件慢烦恼吗?强大工具dlf来帮助你.作为一名前端开发,最常见的就是node_modules,如果dependencies很多,osx系统删除还好,Windows用户就麻烦了.本文分 ...
- C++构造函数(一)
本篇是介绍C++的构造函数的第一篇(共二篇),属于读书笔记,对C++进行一个系统的复习. 构造函数的概念和作用 全局变量未初始化时为0,局部变量未初始化时的值却是无法预测的.这是因为,全局变量的初始化 ...
- 框架篇:Spring+SpringMVC+Mybatis整合开发
前言: 前面我已搭建过ssh框架(http://www.cnblogs.com/xrog/p/6359706.html),然而mybatis表示不服啊. Mybatis:"我抗议!" ...
- ASP.NET MVC5(五):身份验证、授权
使用Authorize特性进行身份验证 通常情况下,应用程序都是要求用户登录系统之后才能访问某些特定的部分.在ASP.NET MVC中,可以通过使用Authorize特性来实现,甚至可以对整个应用程序 ...
- memcache与mysql的连接
MySQL官方在5.6版本以上有一个与memcache连接的插件,名字叫做innodb_memcache. 1),没有安装过MySQL的,可以在CMAKE的时候加入:-DWITH_INNODB_MEM ...
- svg制作风车旋转
首先用ai画一个简单的风车 例如: svg代码: <animateTransform attributeName="transform" begin="0s&quo ...
- 《Java编程思想》第一二章
前段时间一直通过网络教程学习Java基础,把面向对象部分学完之后本来打算继续深入学习,但是感觉自己操之过急了,基础根本不够扎实,所以入手了一本<Java编程思想>,希望先把基础打好,再深入 ...
- java IO之 字符流 (字符流 = 字节流 + 编码表) 装饰器模式
字符流 计算机并不区分二进制文件与文本文件.所有的文件都是以二进制形式来存储的,因此, 从本质上说,所有的文件都是二进制文件.所以字符流是建立在字节流之上的,它能够提供字符 层次的编码和解码.列如,在 ...