资讯专栏INFORMATION COLUMN

connect-history-api-fallback分析

jsummer / 2042人阅读

摘要:研究下,地址,当然它也可以作为的中间件使用中介绍的很清楚解释一下首页比如我使用访问,发现显示,点击,显示,地址栏变为,一切正常,这时候刷新当前页面或或点击浏览器的刷新按钮或在地址栏上再敲一下回车,发现了哦文档

研究下connect-history-api-fallback v1.3.0,地址:https://github.com/bripkens/c...,当然它也可以作为express的中间件使用

README中介绍的很清楚

Single Page Applications (SPA) typically only utilise one index file that is accessible by web browsers: usually index.html. Navigation in the application is then commonly handled using JavaScript with the help of the HTML5 History API. This results in issues when the user hits the refresh button or is directly accessing a page other than the landing page, e.g. /help or /help/online as the web server bypasses the index file to locate the file at this location. As your application is a SPA, the web server will fail trying to retrieve the file and return a 404 - Not Found message to the user.

This tiny middleware addresses some of the issues. Specifically, it will change the requested location to the index you specify (default being /index.html) whenever there is a request which fulfills the following criteria:

The request is a GET request

which accepts text/html,

is not a direct file request, i.e. the requested path does not contain a . (DOT) character and

does not match a pattern provided in options.rewrites (see options below)

解释一下:

server.js

const path = require("path")

const express = require("express")
const router = express.Router()

const indexRoute = router.get("/", (req, res) => {
  res.status(200).render("index", {
    title: "首页"
  })
})

app.set("views", path.join(__dirname, "templates"))
app.set("view engine", "html")
app.engine("html", ejs.__express)

app.use("/static", express.static(path.join(__dirname, "public")))

app.use(history({
  rewrites: [
    { from: /^/abc$/, to: "/" }
  ]
}))

app.get("/", indexRoute)

app.use((req, res) => {
  res.status(404).send("File not found!")
})

app.listen(9090, "127.0.0.1", () => {
  console.log("ther server is running at port " + 9090)
})

index.html

index.js

Vue.use(VueRouter)

var s = "
Go to Foo - Go to Bar - Go to Home
" var Home = { template: "
" + s + "
home
" + "
", created: function() { console.log("home") } } var Foo = { template: "
" + s + "
foo
" + "
", created: function() { console.log("foo") }} var Bar = { template: "
" + s + "
bar
" + "
", created: function() { console.log("bar") }} var NotFoundComponent = { template: "
" + s + "
not found
" + "
", created: function() { console.log("not found") }} var routes = [ { path: "/", component: Home }, { path: "/foo", component: Foo }, { path: "/bar", component: Bar }, { path: "*", component: NotFoundComponent } ] var router = new VueRouter({ mode: "history", routes: routes }) new Vue({ router: router }).$mount("#test")

比如我使用vue-router, 访问http://localhost:9090,发现显示home,点击Go to Foo,显示foo,地址栏变为http://localhost:9090/foo,一切正常,ok
这时候刷新当前页面(ctrl+R或ctrl+command+R或点击浏览器的刷新按钮或在地址栏上再敲一下回车),发现404了哦
vue-router文档针对这种情况做了很好的解释:

Not to worry: To fix the issue, all you need to do is add a simple catch-all fallback route to your server. If the URL doesn"t match any static assets, it should serve the same index.html page that your app lives in. Beautiful, again!

如果express server使用了connect-history-api-fallback middleware,在你定义router的前面app.use(history({ rewrites: [ { from: /^/abc$/, to: "/" } ] }))一下

再刷新页面,发现地址仍然http://localhost:9090/foo,然而走进了咱们的前端路由,chrome控制台显示了foo,真的是beautiful again

其实过程也很简单啦,请求/foo,走到了咱们的history-api-fallback中间件,然后他看你没有rewrite,那么好,我把req.url改成"/",于是vue-router发现地址/foo,所以根据routes的map,渲染了Foo组件

但是万一有人输入地址/abc,怎么办? vue-router定义了{ path: "*", component: NotFoundComponent }用来catch-all route within your Vue app to show a 404 page

Alternatively, if you are using a Node.js server, you can implement the fallback by using the router on the server side to match the incoming URL and respond with 404 if no route is matched.

地址输入/abc,回车,走到vue-router,会显示not found

地址输入/xyz,回车,走到服务端路由,http状态为404,然后显示File not found!

source code 分析

贴下代码

"use strict";

var url = require("url");

exports = module.exports = function historyApiFallback(options) {
  options = options || {};
  var logger = getLogger(options);

  return function(req, res, next) {
    var headers = req.headers;
    if (req.method !== "GET") {
      logger(
        "Not rewriting",
        req.method,
        req.url,
        "because the method is not GET."
      );
      return next();
    } else if (!headers || typeof headers.accept !== "string") {
      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) {
      logger(
        "Not rewriting",
        req.method,
        req.url,
        "because the client prefers JSON."
      );
      return next();
    } else if (!acceptsHtml(headers.accept, options)) {
      logger(
        "Not rewriting",
        req.method,
        req.url,
        "because the client does not accept HTML."
      );
      return next();
    }

    var parsedUrl = url.parse(req.url);
    var rewriteTarget;
    options.rewrites = options.rewrites || [];
    for (var i = 0; i < options.rewrites.length; i++) {
      var rewrite = options.rewrites[i];
      var match = parsedUrl.pathname.match(rewrite.from);
      if (match !== null) {
        rewriteTarget = evaluateRewriteRule(parsedUrl, match, rewrite.to);
        logger("Rewriting", req.method, req.url, "to", rewriteTarget);
        req.url = rewriteTarget;
        return next();
      }
    }

    if (parsedUrl.pathname.indexOf(".") !== -1 &&
        options.disableDotRule !== true) {
      logger(
        "Not rewriting",
        req.method,
        req.url,
        "because the path includes a dot (.) character."
      );
      return next();
    }

    rewriteTarget = options.index || "/index.html";
    logger("Rewriting", req.method, req.url, "to", rewriteTarget);
    req.url = rewriteTarget;
    next();
  };
};

function evaluateRewriteRule(parsedUrl, match, rule) {
  if (typeof rule === "string") {
    return rule;
  } else if (typeof rule !== "function") {
    throw new Error("Rewrite rule can only be of type string of function.");
  }

  return rule({
    parsedUrl: parsedUrl,
    match: match
  });
}

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) {
    return console.log.bind(console);
  }
  return function(){};
}

getLogger, 默认不输出,options.verbose为true,则默认console.log.bind(console),但不知道这里bind意义何在 - -,也可以直接传logger,比如debug

如果req.method != "GET",灰,结束
如果!headers || !headers.accept != "string" (有这情况?),灰,结束
如果headers.accept.indexOf("application/json") === 0,灰,结束

acceptsHtml函数a判断headers.accept字符串是否含有["text/html", "/"]中任意一个
当然不够这两个不够你可以自定义到选项options.htmlAcceptHeaders中
!acceptsHtml(headers.accept, options),灰,结束

然后根据你定义的选项rewrites(没定义就相当于跳过了)
按定义的数组顺序,字符串依次匹配路由rewrite.from,匹配成功则走rewrite.to,to可以是字符串也可以是函数,绿,结束

判断dot file,即pathname中包含.(点),并且选项disableDotRule !== true,即没有关闭点文件限制规则,灰,结束
那么剩下的情况(parsedUrl.pathname不含点,或者含点但关闭了点文件规则)
rewriteTarget = options.index || "/index.html",绿结束

稍微注意下,他是先匹配自定义rewrites规则,再匹配点文件规则

测试部分

用的是nodeunit,具体用法https://github.com/caolan/nod...
随便看两个测试用例

var sinon = require("sinon");
var historyApiFallback = require("../lib");

var tests = module.exports = {};

var middleware;
var req = null;
var requestedUrl;
var next;

tests.setUp = function(done) {
  middleware = historyApiFallback();
  requestedUrl = "/foo";
  req = {
    method: "GET",
    url: requestedUrl,
    headers: {
      accept: "text/html, */*"
    }
  };
  next = sinon.stub();

  done();
};

// ....

tests["should ignore requests that do not accept html"] = function(test) {
  req.headers.accept = "application/json";
  // 调用middleware
  middleware(req, null, next);
  // 测试req.url是否等于requestedUrl
  test.equal(req.url, requestedUrl);
  // next是否被调用过
  test.ok(next.called);
  // 测试结束
  test.done();
};

// ...

tests["should rewrite requests when the . rule is disabled"] = function(test) {
  req.url = "js/app.js";
  middleware = historyApiFallback({
    disableDotRule: true
  });
  middleware(req, null, next);
  // 测试req.url是否等于/index.html
  // 因为pathname中有点,且关闭了点规则
  // req.url应该被rewrit成了/index.html
  test.equal(req.url, "/index.html");
  test.ok(next.called);
  test.done();
};

// ...
tests["should test rewrite rules"] = function(test) {
  req.url = "/socer";
  middleware = historyApiFallback({
    rewrites: [
      {from: //soccer/, to: "/soccer.html"}
    ]
  });
     
  middleware(req, null, next);
  // 因为没有匹配上rewrites里的规则
  // 而req.url pathname又不含点
  // 所以req.url 倒退到了index.html
  test.equal(req.url, "/index.html");
  test.ok(next.called);
  test.done();
};

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/86595.html

相关文章

  • 前端临床手札——webpack构建逐步解构(上)

    摘要:前言由于博主最近又闲下来了,之前觉得的官方文档比较难啃一直放到现在。文章会逐步分析每个处理的用意当然是博主自己的理解,不足之处欢迎指出沟通交流。后续将会补上构建生产的配置分析,案例参考。前端临床手札构建逐步解构下 前言 由于博主最近又闲下来了,之前觉得webpack的官方文档比较难啃一直放到现在。细心阅读多个webpack配置案例后觉得还是得自己写个手脚架,当然这个案例是基于vue的,...

    lowett 评论0 收藏0
  • 网站子目录部署VUE webpack 打包资源文件路径的正确引用方式

    摘要:是目前使用最为火热的打包工具,各大知名的框架类库都用其打包,国内使用最近也火热起来。但是坑也很多,比如说图片,字体等文件的路径。 webpack 是目前使用最为火热的打包工具,各大知名的框架类库都用其打包,国内使用最近也火热起来。它在单页应用和类库打包上帮助许多人从代码管理中解脱了出来,成为了当下风靡一时的打包工具。 但是坑也很多,比如说图片,字体等文件的路径。 刚开始用webpack...

    zgbgx 评论0 收藏0
  • vue-cli 项目结构

    摘要:项目结构为我们搭建开发所需要的环境目录结构及主要功能项目构建相关代码生产环境构建代码检查等版本热重载相关构建本地服务器构建工具相关基础配置开发环境配置生产环境配置项目开发环境配置开发环境 Vue-cli 项目结构 vue-cli 为我们搭建开发所需要的环境 目录结构及主要功能 |-- build // 项目构建(webpack)...

    int64 评论0 收藏0
  • vue-cli 项目结构

    摘要:项目结构为我们搭建开发所需要的环境目录结构及主要功能项目构建相关代码生产环境构建代码检查等版本热重载相关构建本地服务器构建工具相关基础配置开发环境配置生产环境配置项目开发环境配置开发环境 Vue-cli 项目结构 vue-cli 为我们搭建开发所需要的环境 目录结构及主要功能 |-- build // 项目构建(webpack)...

    Markxu 评论0 收藏0

发表评论

0条评论

jsummer

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<