如何让Nodejs支持H5 History模式(connect-history-api-fallback源码分析)


Posted in NodeJs onMay 30, 2019

导读

本文主要是对connect-history-api-fallback库进行一次源码分析。connect-history-api-fallback是一个用于支持SPA History路由模式的nodejs库。阅读本文前,应对HTML5 History模式有一定程度的了解!

源码分析

/** 
 * 前端需要开启history模式,而后端根据url并不知道前端在请求api还是在请求页面,如localhost:4200/home这种url,前端理所当然认为“我需要得到html,并跳转到首页”,然而后端并不能区分。
 * 因此需要一种判断机制,来使得后端能分析出前端的请求目的。
 * connect-history-api-fallback 这个中间件正好帮我们完成了上述分析操作,来看下它是怎么实现的吧!
 * 第一次把自己的源码分析思路写出来,说得不对的地方,请指出!
 */

'use strict';

var url = require('url');

exports = module.exports = function historyApiFallback(options) {
 // 接收配置参数
 options = options || {};
 // 初始化日志管理器
 var logger = getLogger(options);

 // 中间件是要返回一个函数的,函数形参有req, res, next
 return function(req, res, next) {
  var headers = req.headers;
  if (req.method !== 'GET') {
   // 如果请求方法不是GET类型,说明不需要返回html,那么就调用next(),把请求交给下一个中间件
   logger(
    'Not rewriting',
    req.method,
    req.url,
    'because the method is not GET.'
   );
   return next();
  } else if (!headers || typeof headers.accept !== 'string') {
   // 如果没有请求头,或者请求头中的accept不是字符串,说明不是一个标准的http请求,也不予处理,把请求交给下一个中间件
   logger(
    'Not rewriting',
    req.method,
    req.url,
    'because the client did not send an HTTP accept header.'
   );
   return next();
  } else if (headers.accept.indexOf('application/json') === 0) {
   // 如果客户端希望得到application/json类型的响应,说明也不是在请求html,也不予处理,把请求交给下一个中间件
   logger(
    'Not rewriting',
    req.method,
    req.url,
    'because the client prefers JSON.'
   );
   return next();
  } else if (!acceptsHtml(headers.accept, options)) {
   // 如果请求头中不包含配置的Accept或者默认的['text/html', '*/*'],那么说明也不是在请求html,也不予处理,把请求交给下一个中间件
   logger(
    'Not rewriting',
    req.method,
    req.url,
    'because the client does not accept HTML.'
   );
   return next();
  }

  // 走到这里说明是在请求html了,要开始秀操作了

  // 首先利用url模块的parse方法解析下url,会得到一个对象,包括protocol,hash,path, pathname, query, search等字段,类似浏览器的location对象
  var parsedUrl = url.parse(req.url);
  var rewriteTarget;
  // 然后得到配置中的rewrites,也就是重定向配置;
  // 重定向配置是一个数组,每一项都包含from和to两个属性;
  // from是用来正则匹配pathname是否需要重定向的;
  // to则是重定向的url,to可以是一个字符串,也可以是一个回调方法来返回一个字符串,回调函数接收一个上下文参数context,context包含三个属性(parsedUrl,match,request)
  options.rewrites = options.rewrites || [];
  // 遍历一波重定向配置
  for (var i = 0; i < options.rewrites.length; i++) {
   var rewrite = options.rewrites[i];
   // 利用字符串的match方法去匹配
   var match = parsedUrl.pathname.match(rewrite.from);
   if (match !== null) {
    // 如果match不是null,说明pathname和重定向配置匹配上了
    rewriteTarget = evaluateRewriteRule(parsedUrl, match, rewrite.to, req);

    if(rewriteTarget.charAt(0) !== '/') {
     // 推荐使用/开头的绝对路径作为重定向url
     logger(
      'We recommend using an absolute path for the rewrite target.',
      'Received a non-absolute rewrite target',
      rewriteTarget,
      'for URL',
      req.url
     );
    }

    logger('Rewriting', req.method, req.url, 'to', rewriteTarget);
    // 进行重定向url操作
    req.url = rewriteTarget;
    return next();
   }
  }

  var pathname = parsedUrl.pathname;
  // 首先说明一下:校验逻辑默认是会去检查url中最后的.号的,有.号的说明在请求文件,那就跟history模式就没什么鸟关系了
  // 我暂且将上述规则成为“点号校验规则”
  // disableDotRule为true,代表禁用点号校验规则
  if (pathname.lastIndexOf('.') > pathname.lastIndexOf('/') &&
    options.disableDotRule !== true) {
   // 如果pathname的最后一个/之后还有.,说明请求的是/a/b/c/d.*的文件(*代表任意文件类型);
   // 如果此时配置disableDotRule为false,说明开启点号校验规则,那么不予处理,交给其他中间件
   logger(
    'Not rewriting',
    req.method,
    req.url,
    'because the path includes a dot (.) character.'
   );
   return next();
  }

  // 如果pathname最后一个/之后没有.,或者disableDotRule为true,都会走到最后一步:重写url
  // 重写url有默认值/index.html,也可以通过配置中的index自定义
  rewriteTarget = options.index || '/index.html';
  logger('Rewriting', req.method, req.url, 'to', rewriteTarget);
  // 重写url
  req.url = rewriteTarget;
  // 此时再将执行权交给下一个中间件(url都换成index.html了,后面的路由等中间件也不会再处理了,然后前端接收到html就开始解析路由了,目的达到!)
  next();
 };
};

// 判断重定向配置中的to
function evaluateRewriteRule(parsedUrl, match, rule, req) {
 if (typeof rule === 'string') {
  // 如果是字符串,直接返回
  return rule;
 } else if (typeof rule !== 'function') {
  // 如果不是函数,抛出错误
  throw new Error('Rewrite rule can only be of type string or function.');
 }

 // 执行自定义的回调函数,得到一个重定向的url
 return rule({
  parsedUrl: parsedUrl,
  match: match,
  request: req
 });
}

// 判断请求头的accept是不是包含在配置数组或默认数组的范围内
function acceptsHtml(header, options) {
 options.htmlAcceptHeaders = options.htmlAcceptHeaders || ['text/html', '*/*'];
 for (var i = 0; i < options.htmlAcceptHeaders.length; i++) {
  if (header.indexOf(options.htmlAcceptHeaders[i]) !== -1) {
   return true;
  }
 }
 return false;
}

// 处理日志
function getLogger(options) {
 if (options && options.logger) {
  // 如果有指定的日志方法,则使用指定的日志方法
  return options.logger;
 } else if (options && options.verbose) {
  // 否则,如果配置了verbose,默认使用console.log作为日志方法
  return console.log.bind(console);
 }
 // 否则就没有日志方法,就不记录日志咯
 return function(){};
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

NodeJs 相关文章推荐
nodejs URL模块操作URL相关方法介绍
Mar 03 NodeJs
nodejs中使用HTTP分块响应和定时器示例代码
Mar 19 NodeJs
用Nodejs搭建服务器访问html、css、JS等静态资源文件
Apr 28 NodeJs
使用nodejs爬取前程无忧前端技能排行
May 06 NodeJs
Nodejs实现多房间简易聊天室功能
Jun 20 NodeJs
nodejs判断文件、文件夹是否存在及删除的方法
Nov 10 NodeJs
nodejs实现简单的gulp打包
Dec 21 NodeJs
nodejs实现的简单web服务器功能示例
Mar 15 NodeJs
详解nodejs通过响应回写的方式渲染页面资源
Apr 07 NodeJs
nodejs(officegen)+vue(axios)在客户端导出word文档的方法
Jul 31 NodeJs
NodeJs生成sitemap站点地图的方法示例
Jun 11 NodeJs
详解nodejs内置模块
May 06 NodeJs
nodejs中实现修改用户路由功能
May 24 #NodeJs
nodejs实现用户登录路由功能
May 22 #NodeJs
基于nodejs的微信JS-SDK简单应用实现
May 21 #NodeJs
nodejs中实现用户注册路由功能
May 20 #NodeJs
nodejs实现日志读取、日志查找及日志刷新的方法分析
May 20 #NodeJs
NodeJS读取分析Nginx错误日志的方法
May 14 #NodeJs
nodejs搭建本地服务器并访问文件操作示例
May 11 #NodeJs
You might like
在PHP3中实现SESSION的功能(三)
2006/10/09 PHP
利用discuz自带通行证整合dedecms的方法以及文件下载
2007/03/06 PHP
PHP大小写问题:函数名和类名不区分,变量名区分
2013/06/17 PHP
php实现的农历算法实例
2015/08/11 PHP
如何使用PHP给图片加水印
2016/10/12 PHP
关于Laravel Route重定向的一个注意点
2017/01/16 PHP
ThinkPHP框架整合微信支付之刷卡模式图文详解
2019/04/10 PHP
PHP 并发场景的几种解决方案
2019/06/14 PHP
PHP实现简单用户登录界面
2019/10/23 PHP
Javascript 日期对象Date扩展方法
2009/05/30 Javascript
jquery.ui.progressbar 中文文档
2009/11/26 Javascript
JavaScript Timer实现代码
2010/02/17 Javascript
由点击页面其它地方隐藏div所想到的jQuery的delegate
2013/08/29 Javascript
禁止选中文字兼容IE、Chrome、FF等
2013/09/04 Javascript
JS判断对象是否存在的10种方法总结
2013/12/23 Javascript
简单选项卡 js和jquery制作方法分享
2014/02/26 Javascript
jquery 实现输入邮箱时自动补全下拉提示功能
2015/10/04 Javascript
jQuery拖拽排序插件制作拖拽排序效果(附源码下载)
2016/02/23 Javascript
JavaScript知识点总结(十)之this关键字
2016/05/31 Javascript
AngularJS修改model值时,显示内容不变的实例
2018/09/13 Javascript
浏览器事件循环与vue nextTicket的实现
2019/04/16 Javascript
原生JS与CSS实现软件卸载对话框功能
2019/12/05 Javascript
JavaScript实现点击自制菜单效果
2021/02/02 Javascript
[03:22]DOTA2超级联赛专访单车:找到属于自己的英雄
2013/06/08 DOTA
[27:08]完美世界DOTA2联赛PWL S2 SZ vs Rebirth 第二场 11.21
2020/11/23 DOTA
Python的Flask框架与数据库连接的教程
2015/04/20 Python
python使用PyGame模块播放声音的方法
2015/05/20 Python
Python设计模式之观察者模式简单示例
2018/01/10 Python
Python os.rename() 重命名目录和文件的示例
2018/10/25 Python
python把1变成01的步骤总结
2019/02/27 Python
Django 解决model 反向引用中的related_name问题
2020/05/19 Python
Python字符串的15个基本操作(小结)
2021/02/03 Python
KELLER SPORTS荷兰:在线订购最好的运动产品
2020/10/13 全球购物
2014年应急管理工作总结
2014/11/26 职场文书
2015年清剿火患专项行动工作总结
2015/07/27 职场文书
redis 限制内存使用大小的实现
2021/05/08 Redis