Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
Added rewrite targets and index override. Includes breaking changes a…
…s module.exports no longer returns the middleware, instead requires execution to return the middleware
- Loading branch information
Craig Myles
authored and
Craig Myles
committed
Apr 10, 2015
1 parent
8781b66
commit 63b3f37
Showing
3 changed files
with
127 additions
and
60 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,60 +1,107 @@ | ||
# history-api-fallback [![Build Status](https://secure.travis-ci.org/bripkens/connect-history-api-fallback.png?branch=master)](http://travis-ci.org/bripkens/connect-history-api-fallback) | ||
<h1 align="center">connect-history-api-fallback</h1> | ||
<p align="center">Middleware to proxy requests through a specified index page, useful for Single Page Applications that utilise the HTML5 History API.</p> | ||
|
||
Single page applications typically only have one file that is directly | ||
accessible by web browsers: The `index.html`. Navigation in the application | ||
## Introduction | ||
|
||
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 | ||
[HTML 5 history API](http://www.w3.org/html/wg/drafts/html/master/single-page.html#the-history-interface). | ||
[HTML5 History API](http://www.w3.org/html/wg/drafts/html/master/single-page.html#the-history-interface). | ||
This results in issues when the user hits the refresh button or is directly | ||
accessing a page other than the landing page, e.g. `/impress` or | ||
`/bripkens/history-api-fallback`, as the web server is then trying to retrieve | ||
a file which is existing 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* | ||
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 `index.html` whenever there is a request which | ||
fulfils the following criteria: | ||
the requested location to the index you specify (default being `index.html`) | ||
whenever there is a request which fulfils the following criteria: | ||
|
||
1. The request is a GET request | ||
2. which accepts `text/html` and | ||
2. which accepts `text/html`, | ||
3. is not a direct file request, i.e. the requested path does not contain a | ||
`.` (DOT) character. | ||
`.` (DOT) character and | ||
4. does not match a pattern provided in options.rewrites (see options below) | ||
|
||
## Usage | ||
|
||
The middleware is available through NPM and can easily be added. | ||
|
||
``` | ||
npm install --save connect-history-api-fallback | ||
npm install --save-dev connect-history-api-fallback | ||
``` | ||
|
||
Import the library | ||
|
||
```javascript | ||
var history = require('connect-history-api-fallback'); | ||
``` | ||
|
||
Now you only need to add the middleware to your application like so | ||
|
||
```javascript | ||
var connect = require('connect'); | ||
var historyApiFallback = require('connect-history-api-fallback'); | ||
var middleware = history(); | ||
|
||
var app = connect() | ||
.use(historyApiFallback) | ||
.use(middleware) | ||
.listen(3000); | ||
``` | ||
|
||
Of course you can also use this piece of middleware with express: | ||
|
||
```javascript | ||
var express = require('express'); | ||
var historyApiFallback = require('connect-history-api-fallback'); | ||
var middleware = history(); | ||
|
||
var app = express(); | ||
app.use(historyApiFallback); | ||
app.use(middleware); | ||
``` | ||
|
||
Activate logging of rewrite reasons: | ||
## Options | ||
|
||
You can optionally pass options to the library when obtaining the middleware | ||
|
||
```javascript | ||
var historyApiFallback = require('connect-history-api-fallback'); | ||
historyApiFallback.setLogger(console.log.bind(console)); | ||
var options = {}; | ||
var middleware = history({}); | ||
``` | ||
|
||
var app = express(); | ||
app.use(historyApiFallback); | ||
### index | ||
|
||
Override the index (default `index.html`) | ||
|
||
```javascript | ||
var middleware = history({ | ||
index: 'default.html' | ||
}); | ||
``` | ||
|
||
### rewrites | ||
|
||
Override the index when the request url matches a regex pattern | ||
|
||
```javascript | ||
var middleware = history({ | ||
rewrites: [ | ||
{ pattern: '/soccer', target: '/soccer.html'}, | ||
{ pattern: '/tennis', target: '/tennis.html'}, | ||
}); | ||
``` | ||
|
||
### verbose | ||
|
||
Output logging (default `false`) | ||
|
||
```javascript | ||
var middleware = history({ | ||
verbose: true | ||
}); | ||
``` | ||
|
||
Alternatively use your own logger | ||
|
||
```javascript | ||
var middleware = history(); | ||
history.setLogger(console.log.bind(console)); | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,47 +1,64 @@ | ||
'use strict'; | ||
'use strict' | ||
|
||
var url = require('url'); | ||
|
||
var logger = function () {}; | ||
var rewriteTarget = '/index.html'; | ||
var logger; | ||
|
||
function acceptsHtml (header) { | ||
return header.indexOf('text/html') !== -1 || header.indexOf('*/*') !== -1; | ||
} | ||
|
||
exports = module.exports = function historyApiFallback (req, res, next) { | ||
var headers = req.headers; | ||
if (req.method !== 'GET') { | ||
logger('Not rewriting %s %s because the method is not GET.', | ||
req.method, req.url); | ||
return next(); | ||
} else if (!headers || typeof headers.accept !== 'string') { | ||
logger('Not rewriting %s %s because the client did not send an HTTP ' + | ||
'accept header.', req.method, req.url); | ||
return next(); | ||
} else if (headers.accept.indexOf('application/json') === 0) { | ||
logger('Not rewriting %s %s because the client prefers JSON.', | ||
req.method, req.url); | ||
return next(); | ||
} else if (!acceptsHtml(headers.accept)) { | ||
logger('Not rewriting %s %s because the client does not accept HTML.', | ||
req.method, req.url); | ||
return next(); | ||
} | ||
|
||
var parsedUrl = url.parse(req.url); | ||
if (parsedUrl.pathname.indexOf('.') !== -1) { | ||
logger('Not rewriting %s %s because the path includes a dot (.) character.', | ||
req.method, req.url); | ||
return next(); | ||
} | ||
|
||
logger('Rewriting %s %s to %s', req.method, req.url, rewriteTarget); | ||
req.url = rewriteTarget; | ||
next(); | ||
}; | ||
function defaultLogger (options) { | ||
return function () { | ||
var verbose = options.verbose || false; | ||
if (verbose) { | ||
console.log(Array.prototype.join.call(arguments, ' ')); | ||
} | ||
}; | ||
} | ||
|
||
exports = module.exports = function historyApiFallback (options) { | ||
var options = options || {}; | ||
|
||
module.exports.setLogger = function (newLogger) { | ||
logger = newLogger || function () {}; | ||
logger = defaultLogger(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)) { | ||
logger('Not rewriting', req.method, req.url, 'because the client does not accept HTML.'); | ||
return next(); | ||
} | ||
|
||
var parsedUrl = url.parse(req.url); | ||
if (parsedUrl.pathname.indexOf('.') !== -1) { | ||
logger('Not rewriting', req.method, req.url, 'because the path includes a dot (.) character.'); | ||
return next(); | ||
} | ||
|
||
var rewriteTarget = '/index.html'; | ||
options.rewrites = options.rewrites || []; | ||
for (var i in options.rewrites) { | ||
var rewrite = options.rewrites[i], | ||
pattern = new RegExp(rewrite.pattern); | ||
if (parsedUrl.pathname.match(pattern) !== null) { | ||
rewriteTarget = rewrite.target; | ||
} | ||
} | ||
|
||
logger('Rewriting', req.method, req.url, 'to', rewriteTarget); | ||
req.url = rewriteTarget; | ||
next(); | ||
}; | ||
}; | ||
|
||
module.exports.setLogger = function (newLogger) { | ||
logger = newLogger || defaultLogger(); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters