diff --git a/README.md b/README.md index 0ffbbc1f..4294fd27 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,7 @@ This will install `http-server` globally so that it may be run from the command |`-c` |Set cache time (in seconds) for cache-control max-age header, e.g. `-c10` for 10 seconds. To disable caching, use `-c-1`.|`3600` | |`-U` or `--utc` |Use UTC time format in log messages.| | |`--log-ip` |Enable logging of the client's IP address |`false` | +|`--spa` |Fallback to index.html (for single page applications)` |`false`| |`-P` or `--proxy` |Proxies all requests which can't be resolved locally to the given url. e.g.: -P http://someurl.com | | |`--proxy-options` |Pass proxy [options](https://github.com/http-party/node-http-proxy#options) using nested dotted objects. e.g.: --proxy-options.secure false | |`--username` |Username for basic authentication | | @@ -73,17 +74,10 @@ This will install `http-server` globally so that it may be run from the command ## Magic Files - `index.html` will be served as the default file to any directory requests. -- `404.html` will be served if a file is not found. This can be used for Single-Page App (SPA) hosting to serve the entry page. +- `404.html` will be served if a file is not found. -## Catch-all redirect +For Single-Page App (SPA) hosting, use the `--spa` option. That will serve the entry page by `index.html`. -To implement a catch-all redirect, use the index page itself as the proxy with: - -``` -http-server --proxy http://localhost:8080? -``` - -Note the `?` at the end of the proxy URL. Thanks to [@houston3](https://github.com/houston3) for this clever hack! ## TLS/SSL diff --git a/bin/http-server b/bin/http-server index 3b9e59d2..e9a666f2 100755 --- a/bin/http-server +++ b/bin/http-server @@ -44,6 +44,8 @@ if (argv.h || argv.help) { ' -U --utc Use UTC time format in log messages.', ' --log-ip Enable logging of the client\'s IP address', '', + ' --spa Fallback to index.html (for single page applications)', + '', ' -P --proxy Fallback proxy if the request cannot be resolved. e.g.: http://someurl.com', ' --proxy-options Pass options to proxy using nested dotted objects. e.g.: --proxy-options.secure false', '', @@ -148,6 +150,7 @@ function listen(port) { robots: argv.r || argv.robots, ext: argv.e || argv.ext, logFn: logger.request, + spa: argv.spa, proxy: proxy, proxyOptions: proxyOptions, showDotfiles: argv.dotfiles, diff --git a/lib/core/defaults.json b/lib/core/defaults.json index d919f292..26a868bb 100644 --- a/lib/core/defaults.json +++ b/lib/core/defaults.json @@ -11,6 +11,7 @@ "brotli": false, "defaultExt": ".html", "handleError": true, + "spa": false, "contentType": "application/octet-stream", "weakEtags": true, "weakCompare": true, diff --git a/lib/core/index.js b/lib/core/index.js index 920e55b9..18bb0e23 100644 --- a/lib/core/index.js +++ b/lib/core/index.js @@ -108,6 +108,7 @@ module.exports = function createMiddleware(_dir, _options) { const baseDir = opts.baseDir; let defaultExt = opts.defaultExt; const handleError = opts.handleError; + const spa = opts.spa; const headers = opts.headers; const weakEtags = opts.weakEtags; const handleOptionsMethod = opts.handleOptionsMethod; @@ -359,6 +360,11 @@ module.exports = function createMiddleware(_dir, _options) { url: `${parsed.pathname}.${defaultExt}${(parsed.search) ? parsed.search : ''}`, headers: req.headers, }, res, next); + } else if (spa) { + middleware({ + url: `/${path.join(baseDir, `index.${defaultExt}`)}`, + headers: req.headers, + }, res, next); } else { // Try to serve default ./404.html const rawUrl = (handleError ? `/${path.join(baseDir, `404.${defaultExt}`)}` : req.url); diff --git a/lib/core/opts.js b/lib/core/opts.js index ec1b2cbc..22a5bbd8 100644 --- a/lib/core/opts.js +++ b/lib/core/opts.js @@ -17,6 +17,7 @@ module.exports = (opts) => { let brotli = defaults.brotli; let defaultExt = defaults.defaultExt; let handleError = defaults.handleError; + let spa = defaults.spa; const headers = {}; let contentType = defaults.contentType; let mimeTypes; @@ -117,6 +118,10 @@ module.exports = (opts) => { return false; }); + if (typeof opts.spa !== 'undefined' && opts.spa !== null) { + spa = opts.spa; + } + aliases.cors.forEach((k) => { if (isDeclared(k) && opts[k]) { handleOptionsMethod = true; @@ -193,6 +198,7 @@ module.exports = (opts) => { gzip, brotli, handleError, + spa, headers, contentType, mimeTypes, diff --git a/lib/http-server.js b/lib/http-server.js index dfe4c474..b5358f0b 100644 --- a/lib/http-server.js +++ b/lib/http-server.js @@ -57,6 +57,7 @@ function HttpServer(options) { this.autoIndex = options.autoIndex !== 'false'; this.showDotfiles = options.showDotfiles; this.gzip = options.gzip === true; + this.spa = options.spa === true; this.brotli = options.brotli === true; if (options.ext) { this.ext = options.ext === true @@ -133,6 +134,7 @@ function HttpServer(options) { autoIndex: this.autoIndex, defaultExt: this.ext, gzip: this.gzip, + spa: this.spa, brotli: this.brotli, contentType: this.contentType, mimetypes: options.mimetypes,