研究下connect-history-api-fallback v1.3.0,地址:https://github.com/bripkens/c...,固然它也能夠做爲express的中間件使用javascript
README中介紹的很清楚html
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:vue
The request is a GET requestjava
which accepts text/html,node
is not a direct file request, i.e. the requested path does not contain a . (DOT) character andgit
does not match a pattern provided in options.rewrites (see options below)github
server.jsweb
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.htmlvue-router
<div id="test"> <router-view></router-view> </div>
index.js
Vue.use(VueRouter) var s = '<div><router-link to="/foo">Go to Foo</router-link> - <router-link to="/bar">Go to Bar</router-link> - <router-link to="/">Go to Home</router-link></div>' var Home = { template: '<div>' + s + '<div>home</div>' + '</div>', created: function() { console.log('home') } } var Foo = { template: '<div>' + s + '<div>foo</div>' + '</div>', created: function() { console.log('foo') }} var Bar = { template: '<div>' + s + '<div>bar</div>' + '</div>', created: function() { console.log('bar') }} var NotFoundComponent = { template: '<div>' + s + '<div>not found</div>' + '</div>', 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!
貼下代碼
'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(); };