Skip to content

Commit

Permalink
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
Show file tree
Hide file tree
Showing 3 changed files with 127 additions and 60 deletions.
91 changes: 69 additions & 22 deletions README.md
@@ -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));
```
91 changes: 54 additions & 37 deletions lib/index.js
@@ -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();
};
5 changes: 4 additions & 1 deletion package.json
@@ -1,6 +1,6 @@
{
"name": "connect-history-api-fallback",
"version": "0.0.5",
"version": "0.2.0",
"description": "Provides a fallback for non-existing directories so that the HTML 5 history API can be used.",
"keyswords": ["connect", "html5", "history api", "fallback", "spa"],
"engines": {
Expand All @@ -19,6 +19,9 @@
"email": "bripkens.dev@gmail.com",
"url": "http://bripkens.de"
},
"contributors": [
"Craig Myles <cr@igmyles.com> (http://www.craigmyles.com)"
],
"license": "MIT",
"devDependencies": {
"grunt": "~0.4.1",
Expand Down

0 comments on commit 63b3f37

Please sign in to comment.