├── .editorconfig ├── .gitignore ├── .jshintrc ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── package.json ├── src ├── enableMiddlewareShorthand │ └── index.js └── index.js ├── ssl ├── dev-cert.pem └── dev-key.pem └── test ├── fixtures ├── default.html ├── directoryIndexMissing │ └── file.html ├── directoryProxied │ └── index.html └── index.html └── index.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | 9 | [*.js] 10 | indent_size = 2 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | 4 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | /* 3 | * ENVIRONMENTS 4 | * ================= 5 | */ 6 | 7 | // Define globals exposed by Node.js. 8 | "node": true, 9 | 10 | /* 11 | * ENFORCING OPTIONS 12 | * ================= 13 | */ 14 | 15 | // Force all variable names to use either camelCase style or UPPER_CASE 16 | // with underscores. 17 | "camelcase": true, 18 | 19 | // Prohibit use of == and != in favor of === and !==. 20 | "eqeqeq": true, 21 | 22 | // Suppress warnings about == null comparisons. 23 | "eqnull": true, 24 | 25 | // Enforce tab width of 2 spaces. 26 | "indent": 2, 27 | 28 | // Prohibit use of a variable before it is defined. 29 | "latedef": true, 30 | 31 | // Require capitalized names for constructor functions. 32 | "newcap": true, 33 | 34 | // Enforce use of single quotation marks for strings. 35 | "quotmark": "single", 36 | 37 | // Prohibit trailing whitespace. 38 | "trailing": true, 39 | 40 | // Prohibit use of explicitly undeclared variables. 41 | "undef": true, 42 | 43 | // Warn when variables are defined but never used. 44 | "unused": true, 45 | 46 | // Enforce placing 'use strict' at the top function scope 47 | "strict": false, 48 | 49 | // assume strict mode 50 | "globalstrict": true 51 | } 52 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test/ 2 | .travis.yml 3 | .DS_Store 4 | .git* 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | - "0.11" 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Johannes Schickling 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | gulp-webserver [![Build Status](http://img.shields.io/travis/schickling/gulp-webserver.svg?style=flat)](https://travis-ci.org/schickling/gulp-webserver) [![](http://img.shields.io/npm/dm/gulp-webserver.svg?style=flat)](https://www.npmjs.org/package/gulp-webserver) [![](http://img.shields.io/npm/v/gulp-webserver.svg?style=flat)](https://www.npmjs.org/package/gulp-webserver) 2 | ============== 3 | 4 | > Streaming gulp plugin to run a local webserver with LiveReload 5 | 6 | ##### Hint: This is a rewrite of [gulp-connect](https://github.com/AveVlad/gulp-connect/) 7 | 8 | ## Install 9 | 10 | ```sh 11 | $ npm install --save-dev gulp-webserver 12 | ``` 13 | 14 | ## Usage 15 | 16 | The `gulp.src('root')` parameter is the root directory of the webserver. Multiple directories are possible. 17 | 18 | ```js 19 | var gulp = require('gulp'); 20 | var webserver = require('gulp-webserver'); 21 | 22 | gulp.task('webserver', function() { 23 | gulp.src('app') 24 | .pipe(webserver({ 25 | livereload: true, 26 | directoryListing: true, 27 | open: true 28 | })); 29 | }); 30 | ``` 31 | 32 | ## Options 33 | 34 | Key | Type | Default | Description | 35 | --- | --- | --- | --- | 36 | `host` | String | `localhost` | hostname of the webserver 37 | `port` | Number | `8000` | port of the webserver 38 | `path` | String | `/` | path to the webserver 39 | `livereload` | Boolean/Object | `false` | whether to use livereload. For advanced options, provide an object. You can use the 'port' property to set a custom live reload port and the `filter` function to filter out files to watch. The object also needs to set `enable` property to true (e.g. `enable: true`) in order to activate the livereload mode. It is off by default. 40 | `directoryListing` | Boolean/Object | `false` | whether to display a directory listing. For advanced options, provide an object with the 'enable' property set to true. You can use the 'path' property to set a custom path or the 'options' property to set custom [serve-index](https://github.com/expressjs/serve-index) options. 41 | `fallback` | String | `undefined` | file to fall back to (relative to webserver root) 42 | `open` | Boolean/String | `false` | open the localhost server in the browser. By providing a String you can specify the path to open (for complete path, use the complete url `http://my-server:8080/public/`) . 43 | `https` | Boolean/Object | `false` | whether to use https or not. By default, `gulp-webserver` provides you with a development certificate but you remain free to specify a path for your key and certificate by providing an object like this one: `{key: 'path/to/key.pem', cert: 'path/to/cert.pem'}`. 44 | `middleware` | Function/Array | `[]` | a connect middleware function or a list of middleware functions 45 | `proxies` | Array | `[]`| a list of proxy objects. Each proxy object can be specified by `{source: '/abc', target: 'http://localhost:8080/abc', options: {headers: {'ABC_HEADER': 'abc'}}}`. 46 | 47 | ## FAQ 48 | 49 | #### Why can't I reach the server from the network? 50 | 51 | **Solution**: Set `0.0.0.0` as `host` option. 52 | 53 | #### How can I use `html5Mode` for my single page app with this plugin? 54 | 55 | **Solution**: Set the `index.html` of your application as `fallback` option. For example: 56 | 57 | ```js 58 | gulp.task('webserver', function() { 59 | gulp.src('app') 60 | .pipe(webserver({ 61 | fallback: 'index.html' 62 | })); 63 | }); 64 | ``` 65 | 66 | #### How can I pass a custom filter to livereload? 67 | 68 | **Solution**: Set `enable: true` and provide filter function in `filter:` property of the livereload object. For example: 69 | 70 | ```js 71 | gulp.task('webserver', function() { 72 | gulp.src('app') 73 | .pipe(webserver({ 74 | livereload: { 75 | enable: true, // need this set to true to enable livereload 76 | filter: function(fileName) { 77 | if (fileName.match(/.map$/)) { // exclude all source maps from livereload 78 | return false; 79 | } else { 80 | return true; 81 | } 82 | } 83 | } 84 | })); 85 | }); 86 | ``` 87 | 88 | #### How can I kill the running server? 89 | 90 | **Solution**: Either by pressing `Ctrl + C` or programmatically like in this example: 91 | 92 | ```js 93 | var stream = gulp.src('app').pipe(webserver()); 94 | stream.emit('kill'); 95 | ``` 96 | 97 | ## License 98 | 99 | [MIT License](http://opensource.org/licenses/MIT) 100 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gulp-webserver", 3 | "version": "0.9.1", 4 | "description": "Gulp plugin to run a local webserver with LiveReload", 5 | "main": "src/index.js", 6 | "scripts": { 7 | "test": "mocha" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/schickling/gulp-webserver.git" 12 | }, 13 | "keywords": [ 14 | "gulpplugin", 15 | "webserver", 16 | "connect", 17 | "livereload" 18 | ], 19 | "author": "Johannes Schickling (https://github.com/schickling)", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/schickling/gulp-webserver/issues" 23 | }, 24 | "homepage": "https://github.com/schickling/gulp-webserver", 25 | "devDependencies": { 26 | "mocha": "^1.20.1", 27 | "supertest": "^0.13.0" 28 | }, 29 | "dependencies": { 30 | "connect": "^3.0.1", 31 | "connect-livereload": "^0.4.0", 32 | "gulp-util": "^2.2.19", 33 | "isarray": "0.0.1", 34 | "node.extend": "^1.0.10", 35 | "open": "^0.0.5", 36 | "proxy-middleware": "^0.5.0", 37 | "serve-index": "^1.1.4", 38 | "serve-static": "^1.3.0", 39 | "through2": "^0.5.1", 40 | "tiny-lr": "0.1.4", 41 | "watch": "^0.11.0" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/enableMiddlewareShorthand/index.js: -------------------------------------------------------------------------------- 1 | var extend = require('node.extend'); 2 | 3 | // TODO: Make this its own npm module and repo 4 | module.exports = function(defaults, options, props) 5 | { 6 | var originalDefaults = extend(true, {},defaults); 7 | var config = extend(true, defaults, options); 8 | 9 | // If we get a single string, convert it to a single item array 10 | if(Object.prototype.toString.call(props) == '[object String]') { 11 | props = [props]; 12 | } 13 | 14 | // Loop through all of the given middlewares 15 | for (var i = 0, len = props.length; i < len; i++) { 16 | var prop = props[i]; 17 | // If using the shorthand syntax 18 | if (config[prop] === true) { 19 | // Replace the given tree for the tree defaults 20 | config[prop] = extend(true, {}, originalDefaults[prop]); 21 | // Set the enable flag, which then can be reliably used for conditionals 22 | config[prop].enable = true; 23 | } 24 | } 25 | return config; 26 | }; -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | var through = require('through2'); 2 | var gutil = require('gulp-util'); 3 | var http = require('http'); 4 | var https = require('https'); 5 | var connect = require('connect'); 6 | var serveStatic = require('serve-static'); 7 | var connectLivereload = require('connect-livereload'); 8 | var proxy = require('proxy-middleware'); 9 | var tinyLr = require('tiny-lr'); 10 | var watch = require('watch'); 11 | var fs = require('fs'); 12 | var serveIndex = require('serve-index'); 13 | var path = require('path'); 14 | var open = require('open'); 15 | var url = require('url'); 16 | var extend = require('node.extend'); 17 | var enableMiddlewareShorthand = require('./enableMiddlewareShorthand'); 18 | var isarray = require('isarray'); 19 | 20 | 21 | module.exports = function(options) { 22 | 23 | var defaults = { 24 | 25 | /** 26 | * 27 | * BASIC DEFAULTS 28 | * 29 | **/ 30 | 31 | host: 'localhost', 32 | port: 8000, 33 | path: '/', 34 | fallback: false, 35 | https: false, 36 | open: false, 37 | 38 | /** 39 | * 40 | * MIDDLEWARE DEFAULTS 41 | * 42 | * NOTE: 43 | * All middleware should defaults should have the 'enable' 44 | * property if you want to support shorthand syntax like: 45 | * 46 | * webserver({ 47 | * livereload: true 48 | * }); 49 | * 50 | */ 51 | 52 | // Middleware: Livereload 53 | livereload: { 54 | enable: false, 55 | port: 35729, 56 | filter: function (filename) { 57 | if (filename.match(/node_modules/)) { 58 | return false; 59 | } else { return true; } 60 | } 61 | }, 62 | 63 | // Middleware: Directory listing 64 | // For possible options, see: 65 | // https://github.com/expressjs/serve-index 66 | directoryListing: { 67 | enable: false, 68 | path: './', 69 | options: undefined 70 | }, 71 | 72 | // Middleware: Proxy 73 | // For possible options, see: 74 | // https://github.com/andrewrk/connect-proxy 75 | proxies: [] 76 | 77 | }; 78 | 79 | // Deep extend user provided options over the all of the defaults 80 | // Allow shorthand syntax, using the enable property as a flag 81 | var config = enableMiddlewareShorthand(defaults, options, [ 82 | 'directoryListing', 83 | 'livereload' 84 | ]); 85 | 86 | if (typeof config.open === 'string' && config.open.length > 0 && config.open.indexOf('http') !== 0) { 87 | // ensure leading slash if this is NOT a complete url form 88 | config.open = (config.open.indexOf('/') !== 0 ? '/' : '') + config.open; 89 | } 90 | 91 | var app = connect(); 92 | 93 | var openInBrowser = function() { 94 | if (config.open === false) return; 95 | if (typeof config.open === 'string' && config.open.indexOf('http') === 0) { 96 | // if this is a complete url form 97 | open(config.open); 98 | return; 99 | } 100 | open('http' + (config.https ? 's' : '') + '://' + config.host + ':' + config.port + (typeof config.open === 'string' ? config.open : '')); 101 | }; 102 | 103 | var lrServer; 104 | 105 | if (config.livereload.enable) { 106 | 107 | app.use(connectLivereload({ 108 | port: config.livereload.port 109 | })); 110 | 111 | if (config.https) { 112 | if (config.https.pfx) { 113 | lrServer = tinyLr({ 114 | pfx: fs.readFileSync(config.https.pfx), 115 | passphrase: config.https.passphrase 116 | }); 117 | } 118 | else { 119 | lrServer = tinyLr({ 120 | key: fs.readFileSync(config.https.key || __dirname + '/../ssl/dev-key.pem'), 121 | cert: fs.readFileSync(config.https.cert || __dirname + '/../ssl/dev-cert.pem') 122 | }); 123 | } 124 | } else { 125 | lrServer = tinyLr(); 126 | } 127 | 128 | lrServer.listen(config.livereload.port, config.host); 129 | 130 | } 131 | 132 | // middlewares 133 | if (typeof config.middleware === 'function') { 134 | app.use(config.middleware); 135 | } else if (isarray(config.middleware)) { 136 | config.middleware 137 | .filter(function(m) { return typeof m === 'function'; }) 138 | .forEach(function(m) { 139 | app.use(m); 140 | }); 141 | } 142 | 143 | // Proxy requests 144 | for (var i = 0, len = config.proxies.length; i < len; i++) { 145 | var proxyoptions = url.parse(config.proxies[i].target); 146 | if (config.proxies[i].hasOwnProperty('options')) { 147 | extend(proxyoptions, config.proxies[i].options); 148 | } 149 | app.use(config.proxies[i].source, proxy(proxyoptions)); 150 | } 151 | 152 | if (config.directoryListing.enable) { 153 | app.use(config.path, serveIndex(path.resolve(config.directoryListing.path), config.directoryListing.options)); 154 | } 155 | 156 | 157 | var files = []; 158 | 159 | // Create server 160 | var stream = through.obj(function(file, enc, callback) { 161 | 162 | app.use(config.path, serveStatic(file.path)); 163 | 164 | if (config.livereload.enable) { 165 | var watchOptions = { 166 | ignoreDotFiles: true, 167 | filter: config.livereload.filter 168 | }; 169 | watch.watchTree(file.path, watchOptions, function (filename) { 170 | lrServer.changed({ 171 | body: { 172 | files: filename 173 | } 174 | }); 175 | 176 | }); 177 | } 178 | 179 | this.push(file); 180 | callback(); 181 | }) 182 | .on('data', function(f){files.push(f);}) 183 | .on('end', function(){ 184 | if (config.fallback) { 185 | files.forEach(function(file){ 186 | var fallbackFile = file.path + '/' + config.fallback; 187 | if (fs.existsSync(fallbackFile)) { 188 | app.use(function(req, res) { 189 | res.setHeader('Content-Type', 'text/html; charset=UTF-8'); 190 | fs.createReadStream(fallbackFile).pipe(res); 191 | }); 192 | } 193 | }); 194 | } 195 | }); 196 | 197 | var webserver; 198 | 199 | if (config.https) { 200 | var opts; 201 | 202 | if (config.https.pfx) { 203 | opts = { 204 | pfx: fs.readFileSync(config.https.pfx), 205 | passphrase: config.https.passphrase 206 | }; 207 | } else { 208 | opts = { 209 | key: fs.readFileSync(config.https.key || __dirname + '/../ssl/dev-key.pem'), 210 | cert: fs.readFileSync(config.https.cert || __dirname + '/../ssl/dev-cert.pem') 211 | }; 212 | } 213 | webserver = https.createServer(opts, app).listen(config.port, config.host, openInBrowser); 214 | } else { 215 | webserver = http.createServer(app).listen(config.port, config.host, openInBrowser); 216 | } 217 | 218 | gutil.log('Webserver started at', gutil.colors.cyan('http' + (config.https ? 's' : '') + '://' + config.host + ':' + config.port)); 219 | 220 | stream.on('kill', function() { 221 | 222 | webserver.close(); 223 | 224 | if (config.livereload.enable) { 225 | lrServer.close(); 226 | } 227 | 228 | }); 229 | 230 | return stream; 231 | 232 | }; 233 | -------------------------------------------------------------------------------- /ssl/dev-cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFtjCCA54CCQC7B795l1j76zANBgkqhkiG9w0BAQUFADCBnDELMAkGA1UEBhMC 3 | VVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28x 4 | FzAVBgNVBAoTDmd1bHAtd2Vic2VydmVyMQwwCgYDVQQLEwNkZXYxEjAQBgNVBAMT 5 | CWxvY2FsaG9zdDElMCMGCSqGSIb3DQEJARYWZ3VscC13ZWJzZXJ2ZXJAZGV2LmNv 6 | bTAeFw0xNDA4MDIxNjAyNTVaFw00MTEyMTcxNjAyNTVaMIGcMQswCQYDVQQGEwJV 7 | UzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEX 8 | MBUGA1UEChMOZ3VscC13ZWJzZXJ2ZXIxDDAKBgNVBAsTA2RldjESMBAGA1UEAxMJ 9 | bG9jYWxob3N0MSUwIwYJKoZIhvcNAQkBFhZndWxwLXdlYnNlcnZlckBkZXYuY29t 10 | MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAqMlnYTEXjwjD4+cTyZG7 11 | VXKPtDg6U01uxrIv2IpjPJST/88OXMeT6/45hrfNOWcfaoeUWW+jeq5/2kwV2bIZ 12 | 84TGS70kPDMZqSH+G1hcMDjh0RLDfGqBym//GOmBJfIMqEboZ8tE8QGrTyPpYCAi 13 | gnCOm2C2KSamNnDSYls6+Z0KmtIApBqPcaVSo5xwIhNHUL/FRBSz0gLZ8ZefXBJa 14 | Y7I+uGpibrbNz7KdxsrrzepbkxQD6fIIPKG6PfAKftAd1nzJsSx+qCHLvykOpsZr 15 | KgU7T78OqlxiFRKe3Rg1kWii7mTV2YytpKY41XLw0CF/kiartziZKxPXQlTnWhjM 16 | fHiI1Ii5P9mY8dei3J1P0B1ne2M9cBW3MyxXwcs1Le+sFIZXqXKnUQBhQf5/4091 17 | kzx/tXdWn9owbxHTYHruMraYhfZkKGWWzhL3Y3Xyu5gOTtzSoyhwE+c/UiiMfKrS 18 | RHCXiLd+tWVhKPnGEa2A892VKMViS70Qh0spsEsy67+MXAh8Gz550fF/CdmUpKRy 19 | SlLLfSQQu2T9mlA4CFizJneRjLUWnCPSxI3/deqIehGXX/6uQbNC+/b2JAy5GJI1 20 | WdgILgFhLjCWxof4tBB521BDs216trDZ0JM4F+n3raLp2OdEKZsQ+B+j9YFQT9AJ 21 | sdU/eVzjljmFxUh1irHS3nkCAwEAATANBgkqhkiG9w0BAQUFAAOCAgEANmdllY6+ 22 | /5dhhUWMnax0u/Iw9IaY3zuSYQE1f+2UkhLRlrUKYOtdlnlidhhCVvzMrOoNgTQE 23 | nZyGAupGe4Qh0pIbrrgVA/ngP8zwP7KE3b/M3N4u66nGM01WIn+giTFN3SIcHaBy 24 | SWz9zPTb/MmBtURrFYMjxccPs46XcmjLMnYXE6sUL9bqHBh5u+ufV5cl7ihBRl9t 25 | YTKWHLywNTMC9iPMvzOYYVrpRl75GUp//VHMWxWeY7QMA6r1I3MuoGz3DVJY5/X7 26 | NR2pGTf3zeQJZ4/pHWJEHxRZLGhB1FLB/o66GnYhCpHCKRBdzG47S1FXzeSSf1wq 27 | xAZqx8qv65MZcl42i+NWUHsMRuMUsELl/MmGnRVGshMxRUVgm5IkFIQ7DVBGGHI9 28 | UMWpkJfAIq2xhwgGiy9m5SMSwBqKdT27ObECssUXQomfM4+2py9JGv3wSojwKo49 29 | PyUjw+RfExxHeyck071F1p2dG79Ug3/gqHHSzRCyXs/sZvOm/t/Qjlos7aoGnY8Q 30 | X2k2JjWSD+SPm9qccO2WQVLGUxy4gDqJGz1Z3UyqDu8hJHfdCY5ZlS5GvLfzCItk 31 | +iCqKz/sn9dGSsnwPQ8txENf0UXJemr+9N/WF671NHqF7Rsxi0zmGBjHn1CalAvS 32 | SFGM03YndjuPsJRYj68CR6/8O6yX73ko34Q= 33 | -----END CERTIFICATE----- 34 | -------------------------------------------------------------------------------- /ssl/dev-key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | 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 51 | -----END RSA PRIVATE KEY----- 52 | -------------------------------------------------------------------------------- /test/fixtures/default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Default 8 | 9 | 10 | -------------------------------------------------------------------------------- /test/fixtures/directoryIndexMissing/file.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | A file in a directory with no index.html 8 | 9 | 10 | -------------------------------------------------------------------------------- /test/fixtures/directoryProxied/index.html: -------------------------------------------------------------------------------- 1 | < 2 | 3 | 4 | 5 | 6 | 7 | I am Ron Burgandy? 8 | 9 | 10 | -------------------------------------------------------------------------------- /test/fixtures/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Hello World 8 | 9 | 10 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | var request = require('supertest'); 2 | var webserver = require('../src'); 3 | var File = require('gulp-util').File; 4 | 5 | // Some configuration to enable https testing 6 | process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; 7 | 8 | describe('gulp-webserver', function() { 9 | 10 | var stream; 11 | var proxyStream; 12 | 13 | var rootDir = new File({ 14 | path: __dirname + '/fixtures' 15 | }); 16 | 17 | var directoryIndexMissingDir = new File({ 18 | path: __dirname + '/fixtures/directoryIndexMissing' 19 | }); 20 | 21 | var directoryProxiedDir = new File({ 22 | path: __dirname + '/fixtures/directoryProxied' 23 | }); 24 | 25 | afterEach(function() { 26 | stream.emit('kill'); 27 | if (proxyStream) { 28 | proxyStream.emit('kill'); 29 | proxyStream = undefined; 30 | } 31 | }); 32 | 33 | it('should work with default options', function(done) { 34 | 35 | stream = webserver(); 36 | 37 | stream.write(rootDir); 38 | 39 | request('http://localhost:8000') 40 | .get('/') 41 | .expect(200, /Hello World/) 42 | .end(function(err) { 43 | if (err) return done(err); 44 | done(err); 45 | }); 46 | 47 | }); 48 | 49 | it('should work with custom port', function(done) { 50 | 51 | stream = webserver({ 52 | port: 1111 53 | }); 54 | 55 | stream.write(rootDir); 56 | 57 | request('http://localhost:1111') 58 | .get('/') 59 | .expect(200, /Hello World/) 60 | .end(function(err) { 61 | if (err) return done(err); 62 | done(err); 63 | }); 64 | 65 | }); 66 | 67 | it('should work with custom host', function(done) { 68 | 69 | stream = webserver({ 70 | host: '0.0.0.0' 71 | }); 72 | 73 | stream.write(rootDir); 74 | 75 | request('http://0.0.0.0:8000') 76 | .get('/') 77 | .expect(200, /Hello World/) 78 | .end(function(err) { 79 | if (err) return done(err); 80 | done(err); 81 | }); 82 | 83 | }); 84 | 85 | it('should work with custom path', function(done) { 86 | 87 | stream = webserver({ 88 | path: '/custom/path' 89 | }); 90 | 91 | stream.write(rootDir); 92 | 93 | request('http://localhost:8000/custom/path') 94 | .get('/') 95 | .expect(200, /Hello World/) 96 | .end(function(err) { 97 | if (err) return done(err); 98 | done(err); 99 | }); 100 | 101 | }); 102 | 103 | it('should work with https', function(done) { 104 | 105 | stream = webserver({ 106 | https: true 107 | }); 108 | 109 | stream.write(rootDir); 110 | 111 | request('https://localhost:8000') 112 | .get('/') 113 | .expect(200, /Hello World/) 114 | .end(function(err) { 115 | if (err) return done(err); 116 | done(err); 117 | }); 118 | 119 | }); 120 | 121 | it('should work with https and a custom certificate', function(done) { 122 | 123 | stream = webserver({ 124 | https: { 125 | key: __dirname + '/../ssl/dev-key.pem', 126 | cert: __dirname + '/../ssl/dev-cert.pem' 127 | } 128 | }); 129 | 130 | stream.write(rootDir); 131 | 132 | request('https://localhost:8000') 133 | .get('/') 134 | .expect(200, /Hello World/) 135 | .end(function(err) { 136 | if (err) return done(err); 137 | done(err); 138 | }); 139 | 140 | }); 141 | 142 | it('should fall back to default.html', function(done) { 143 | stream = webserver({ 144 | fallback: 'default.html' 145 | }); 146 | 147 | stream.write(rootDir); 148 | stream.end(); 149 | 150 | request('http://localhost:8000') 151 | .get('/some/random/path/') 152 | .expect(200, /Default/) 153 | .expect('Content-Type', /text\/html; charset=UTF-8/) 154 | .end(function(err) { 155 | if (err) return done(err); 156 | done(err); 157 | }); 158 | 159 | }); 160 | 161 | it('should serve multiple sources even with a fallback', function(done) { 162 | stream = webserver({ 163 | fallback: 'default.html' 164 | }); 165 | 166 | stream.write(rootDir); 167 | stream.write(directoryIndexMissingDir); 168 | stream.end(); 169 | 170 | request('http://localhost:8000') 171 | .get('/file.html') 172 | .expect(200, /file/) 173 | .end(function(err) { 174 | if (err) return done(err); 175 | done(err); 176 | }); 177 | 178 | }); 179 | 180 | it('should show a directory listing when the shorthand setting is enabled', function(done) { 181 | 182 | stream = webserver({ 183 | directoryListing: true 184 | }); 185 | 186 | stream.write(directoryIndexMissingDir); 187 | 188 | request('http://localhost:8000') 189 | .get('/') 190 | .expect(200, /listing directory/) 191 | .end(function(err) { 192 | if (err) return done(err); 193 | done(err); 194 | }); 195 | 196 | }); 197 | 198 | it('should show a directory listing when the shorthand setting is enabled and using custom path', function(done) { 199 | 200 | stream = webserver({ 201 | directoryListing: true, 202 | path: '/custom/path' 203 | }); 204 | 205 | stream.write(directoryIndexMissingDir); 206 | 207 | request('http://localhost:8000/custom/path') 208 | .get('/') 209 | .expect(200, /listing directory/) 210 | .end(function(err) { 211 | if (err) return done(err); 212 | done(err); 213 | }); 214 | 215 | }); 216 | 217 | it('should not show a directory listing when the shorthand setting is disabled', function(done) { 218 | 219 | stream = webserver({ 220 | directoryListing: false 221 | }); 222 | 223 | stream.write(directoryIndexMissingDir); 224 | 225 | request('http://localhost:8000') 226 | .get('/') 227 | .expect(404, /Cannot GET/) 228 | .end(function(err) { 229 | if (err) return done(err); 230 | done(err); 231 | }); 232 | 233 | }); 234 | 235 | it('should start the livereload server when the shorthand setting is enabled', function(done) { 236 | 237 | stream = webserver({ 238 | livereload: true 239 | }); 240 | 241 | stream.write(rootDir); 242 | 243 | request('http://localhost:8000') 244 | .get('/') 245 | .expect(200, /Hello World/) 246 | .end(function(err) { 247 | if (err) return done(err); 248 | }); 249 | request('http://localhost:35729') 250 | .get('/') 251 | .expect(200, /tinylr/) 252 | .end(function(err) { 253 | if (err) return done(err); 254 | done(err); 255 | }); 256 | }); 257 | 258 | it('should start the livereload server with ssl on', function(done) { 259 | 260 | stream = webserver({ 261 | livereload: true, 262 | https: true 263 | }); 264 | 265 | stream.write(rootDir); 266 | 267 | request('https://localhost:8000') 268 | .get('/') 269 | .expect(200, /Hello World/) 270 | .end(function(err) { 271 | if (err) return done(err); 272 | }); 273 | request('https://localhost:35729') 274 | .get('/') 275 | .expect(200, /tinylr/) 276 | .end(function(err) { 277 | if (err) return done(err); 278 | done(err); 279 | }); 280 | }); 281 | 282 | 283 | it('should not start the livereload server when the shorthand setting is disabled', function(done) { 284 | 285 | stream = webserver({ 286 | livereload: false 287 | }); 288 | 289 | stream.write(rootDir); 290 | 291 | request('http://localhost:8000') 292 | .get('/') 293 | .expect(200, /Hello World/) 294 | .end(function(err) { 295 | if (err) return done(err); 296 | }); 297 | request('http://localhost:35729') 298 | .get('/') 299 | .end(function(err) { 300 | if (err && err.code === "ECONNREFUSED") { 301 | done(); 302 | } else { 303 | if (err) { 304 | return done(err); 305 | } else { 306 | done(new Error('livereload should not be started when shorthand middleware setting is set to false')); 307 | } 308 | } 309 | 310 | }); 311 | }); 312 | 313 | 314 | it('should proxy requests to localhost:8001', function(done) { 315 | 316 | stream = webserver({ 317 | proxies: [{ 318 | source: '/proxied', 319 | target: 'http://localhost:8001' 320 | }] 321 | }); 322 | 323 | stream.write(rootDir); 324 | 325 | proxyStream = webserver({ 326 | port: 8001 327 | }); 328 | 329 | proxyStream.write(directoryProxiedDir); 330 | 331 | request('http://localhost:8000') 332 | .get('/') 333 | .expect(200, /Hello World/) 334 | .end(function(err) { 335 | if (err) return done(err); 336 | }); 337 | request('http://localhost:8000') 338 | .get('/proxied') 339 | .expect(200, /I am Ron Burgandy?/) 340 | .end(function(err) { 341 | if (err) return done(err); 342 | done(err); 343 | }); 344 | 345 | }); 346 | 347 | it('should configure proxy with options', function(done) { 348 | 349 | stream = webserver({ 350 | proxies: [{ 351 | source: '/proxied', 352 | target: 'http://localhost:8001', 353 | options: { 354 | headers: { 355 | 'X-forwarded-host': 'localhost:8000' 356 | } 357 | } 358 | }] 359 | }); 360 | 361 | stream.write(rootDir); 362 | 363 | proxyStream = webserver({ 364 | port: 8001 365 | }); 366 | 367 | proxyStream.write(directoryProxiedDir); 368 | 369 | request('http://localhost:8000') 370 | .get('/') 371 | .expect(200, /Hello World/) 372 | .end(function(err) { 373 | if (err) return done(err); 374 | }); 375 | request('http://localhost:8000') 376 | .get('/proxied') 377 | .expect(200, /I am Ron Burgandy?/) 378 | .end(function(err) { 379 | if (err) return done(err); 380 | done(err); 381 | }); 382 | }); 383 | 384 | this.timeout(20); 385 | it('should accept `true` as an open option', function(done){ 386 | stream = webserver({ 387 | open: true 388 | }); 389 | stream.write(rootDir); 390 | setTimeout(done, 15); 391 | }); 392 | 393 | it('should use middleware function', function(done) { 394 | stream = webserver({ 395 | middleware: function(req, res, next) { 396 | if (req.url === '/middleware') { 397 | res.end('middleware'); 398 | } else { 399 | next(); 400 | } 401 | } 402 | }); 403 | 404 | stream.write(rootDir); 405 | 406 | request('http://localhost:8000') 407 | .get('/middleware') 408 | .expect(200, 'middleware') 409 | .end(function(err) { 410 | if (err) return done(err); 411 | done(); 412 | }); 413 | }); 414 | 415 | it('should use middleware array', function(done) { 416 | stream = webserver({ 417 | middleware: [ 418 | function m1(req, res, next) { 419 | if (req.url === '/middleware1') { 420 | res.end('middleware1'); 421 | } else { 422 | next(); 423 | } 424 | }, 425 | function m2(req, res, next) { 426 | if (req.url === '/middleware2') { 427 | res.end('middleware2'); 428 | } else { 429 | next(); 430 | } 431 | }, 432 | ] 433 | }); 434 | 435 | stream.write(rootDir); 436 | 437 | var count = 0; 438 | function end(err) { 439 | if (err) return done(err); 440 | count++; 441 | if (count === 2) done(); 442 | } 443 | 444 | request('http://localhost:8000') 445 | .get('/middleware1') 446 | .expect(200, 'middleware1') 447 | .end(end); 448 | 449 | request('http://localhost:8000') 450 | .get('/middleware2') 451 | .expect(200, 'middleware2') 452 | .end(end); 453 | }); 454 | 455 | }); 456 | --------------------------------------------------------------------------------