From 84072ddbb61e86c41ae99db8b692106c581653db Mon Sep 17 00:00:00 2001 From: Justin Silver Date: Thu, 26 Jan 2017 22:47:22 -0800 Subject: [PATCH 1/6] Optionally respond with CORS headers --- lib/options.js | 1 + lib/server.js | 29 +++++++++++++++++++++++++---- test/server_test.js | 1 + 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/lib/options.js b/lib/options.js index 72a0a0e..3d964a7 100644 --- a/lib/options.js +++ b/lib/options.js @@ -4,6 +4,7 @@ module.exports = { noBrowser: false, host: '127.0.0.1', secure: false, + cors: false, static: [], port: 1307, pathIndex: '', diff --git a/lib/server.js b/lib/server.js index 79c8a7f..594d399 100644 --- a/lib/server.js +++ b/lib/server.js @@ -132,6 +132,10 @@ module.exports = class Server extends EventEmitter { { short: 'st', long: 'static' + }, + { + short: 'c', + long: 'cors' } ]; @@ -204,6 +208,8 @@ module.exports = class Server extends EventEmitter { threshold: '1kb' })); + this._initCors(); + this.opts.static.forEach((p) => { this._app.use(express.static(p, { index: false @@ -215,22 +221,37 @@ module.exports = class Server extends EventEmitter { this._app.get(/.+/, (req, res) => this._sendIndex(req, res)); } + _initCors() { + if (this.opts.cors) { + this._app.use(function(req, res, next) { + res.setHeader('Access-Control-Allow-Origin', req.headers.origin || '*'); + res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE'); + res.setHeader('Access-Control-Allow-Headers', 'Authorization,X-Requested-With,Content-Type'); + res.setHeader('Access-Control-Allow-Credentials', true); + if (req.method === 'OPTIONS') { + return res.status(200).end(); + } + return next(); + }); + } + } + _initProxy() { if (this.opts.proxy) { this._app.all(this.opts.proxyWhen, (req, res) => { this.emit('proxy', {req: req}); this._proxyServer.web(req, res); - }) + }); this._proxyServer.on('proxyReq', (proxyReq, req) => { req._proxyReq = proxyReq; - }) + }); - this._proxyServer.on('error', (err, req, res) => { + this._proxyServer.on('error', (err, req, res) => { if (req.socket.destroyed && err.code === 'ECONNRESET') { req._proxyReq.abort(); } - }) + }); } } diff --git a/test/server_test.js b/test/server_test.js index af6cd85..070466b 100644 --- a/test/server_test.js +++ b/test/server_test.js @@ -43,6 +43,7 @@ describe('server', () => { expect(_server.opts.host).to.equal('127.0.0.1'); expect(_server.opts.port).to.equal(1307); expect(_server.opts.secure).to.equal(false); + expect(_server.opts.cors).to.be.false; expect(_server.opts.quiet).to.be.false; expect(_server.opts.pathIndex).to.equal(''); expect(_server.opts.noBrowser).to.equal(false); From 4088d002e3c55cb079af61008afe4daf0380dd87 Mon Sep 17 00:00:00 2001 From: Justin Silver Date: Thu, 26 Jan 2017 22:56:39 -0800 Subject: [PATCH 2/6] Refactor and add test coverage --- lib/server.js | 22 ++++++++++++---------- test/server_test.js | 9 +++++++-- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/lib/server.js b/lib/server.js index 594d399..d5210dc 100644 --- a/lib/server.js +++ b/lib/server.js @@ -223,17 +223,19 @@ module.exports = class Server extends EventEmitter { _initCors() { if (this.opts.cors) { - this._app.use(function(req, res, next) { - res.setHeader('Access-Control-Allow-Origin', req.headers.origin || '*'); - res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE'); - res.setHeader('Access-Control-Allow-Headers', 'Authorization,X-Requested-With,Content-Type'); - res.setHeader('Access-Control-Allow-Credentials', true); - if (req.method === 'OPTIONS') { - return res.status(200).end(); - } - return next(); - }); + this._app.use(this._cors); + } + } + + _cors(req, res, next) { + res.setHeader('Access-Control-Allow-Origin', req.headers.origin || '*'); + res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE'); + res.setHeader('Access-Control-Allow-Headers', 'Authorization,X-Requested-With,Content-Type'); + res.setHeader('Access-Control-Allow-Credentials', true); + if (req.method === 'OPTIONS') { + return res.status(200).end(); } + return next(); } _initProxy() { diff --git a/test/server_test.js b/test/server_test.js index 070466b..cae9f79 100644 --- a/test/server_test.js +++ b/test/server_test.js @@ -561,16 +561,21 @@ describe('server', () => { }); describe('options', function() { - it('should open the browser', () => { - let _server = new Server(); + it('should open the browser and use CORS', () => { + let _server = new Server({ + cors: true, + }); let _openStub = sinon.stub(_server, '_open', () => {}); + let _corsStub = sinon.stub(_server, '_cors', () => {}); _server.start(); expect(_server._open).to.have.been.called; + expect(_server._cors).to.have.been.called; _openStub.restore(); + _corsStub.restore(); }); }); From 4961013253ecba0970edf0cb569aace1aab5b80d Mon Sep 17 00:00:00 2001 From: Justin Silver Date: Thu, 26 Jan 2017 23:02:11 -0800 Subject: [PATCH 3/6] =?UTF-8?q?Don=E2=80=99t=20stub=20the=20cors=20method?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/server_test.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/server_test.js b/test/server_test.js index cae9f79..ebf6d6a 100644 --- a/test/server_test.js +++ b/test/server_test.js @@ -567,7 +567,6 @@ describe('server', () => { }); let _openStub = sinon.stub(_server, '_open', () => {}); - let _corsStub = sinon.stub(_server, '_cors', () => {}); _server.start(); @@ -575,7 +574,6 @@ describe('server', () => { expect(_server._cors).to.have.been.called; _openStub.restore(); - _corsStub.restore(); }); }); From d37a96beab369fc1292dc01c88772892557c6061 Mon Sep 17 00:00:00 2001 From: Justin Silver Date: Thu, 26 Jan 2017 23:18:16 -0800 Subject: [PATCH 4/6] Improve test coverage by validating that the core header was added --- test/index.html | 0 test/server_test.js | 9 ++++++++- 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 test/index.html diff --git a/test/index.html b/test/index.html new file mode 100644 index 0000000..e69de29 diff --git a/test/server_test.js b/test/server_test.js index ebf6d6a..cbcdc3f 100644 --- a/test/server_test.js +++ b/test/server_test.js @@ -561,15 +561,22 @@ describe('server', () => { }); describe('options', function() { - it('should open the browser and use CORS', () => { + it('should open the browser and use CORS', (done) => { let _server = new Server({ cors: true, + quiet: true, + pathIndex: 'test/' }); let _openStub = sinon.stub(_server, '_open', () => {}); _server.start(); + http.get(`http://${_server.opts.host}:${_server.opts.port}/`, function(res) { + expect(res.headers['access-control-allow-origin']).to.not.be.undefined; + return done(); + }) + expect(_server._open).to.have.been.called; expect(_server._cors).to.have.been.called; From ad47b7438e35cddadbd83703cbe1da93a2f95bc9 Mon Sep 17 00:00:00 2001 From: Justin Silver Date: Fri, 27 Jan 2017 11:47:22 -0800 Subject: [PATCH 5/6] Update options tests and README --- README.md | 2 ++ test/options_test.js | 1 + 2 files changed, 3 insertions(+) diff --git a/README.md b/README.md index 1ba3f14..b4313d0 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,7 @@ Oh, do you want some specific stuff? Checkout the available o --port, --p change port --host, --h change the host name --secure, --s use https/wss +--cors, --c respond to requests with CORS headers --quiet, --q no logging whatsoever --noBrowser, --nb won't open the browser automagically --only, --o will only watch for changes in the given path/glob/regex/array @@ -116,6 +117,7 @@ new Server({quiet: true}).start(); --port is 1307 --host is 127.0.0.1 --secure is false +--cors is false --quiet is false --only is ".", which means it'll watch everything --ignore is ^(.git|node_modules|bower_components|jspm_packages|test|typings|coverage|unit_coverage) diff --git a/test/options_test.js b/test/options_test.js index d7baa75..85f659b 100644 --- a/test/options_test.js +++ b/test/options_test.js @@ -10,6 +10,7 @@ describe('options', () => { expect(options.pathIndex).to.equal(''); expect(options.noBrowser).to.be.false; expect(options.secure).to.be.false; + expect(options.cors).to.be.false; expect(options.static).to.deep.equal([]); expect(options.quiet).to.be.false; expect(options.proxy).to.be.false; From f7a8da98fa823e1c2ec51522ef7950d99fdfae07 Mon Sep 17 00:00:00 2001 From: Justin Silver Date: Fri, 27 Jan 2017 22:05:33 -0800 Subject: [PATCH 6/6] Allow CORS headers to be overridden, include test coverage and docs. --- README.md | 7 ++++++- lib/server.js | 26 ++++++++++++++++---------- test/server_test.js | 8 ++++++-- 3 files changed, 28 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index b4313d0..b6816b6 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ Oh, do you want some specific stuff? Checkout the available o --port, --p change port --host, --h change the host name --secure, --s use https/wss ---cors, --c respond to requests with CORS headers +--cors, --c respond to requests with CORS headers, use true or object to override defaults --quiet, --q no logging whatsoever --noBrowser, --nb won't open the browser automagically --only, --o will only watch for changes in the given path/glob/regex/array @@ -88,6 +88,11 @@ All the options being used on the `CLI` can be added to t { "port": 9999, "quiet": true, + "cors": { + "headers": "Content-Type, Custom-Header", + "methods": "GET, OPTIONS", + "credentials": false + }, "pathIndex": "src/", "only": ["src/**/*"], "proxy": true, diff --git a/lib/server.js b/lib/server.js index d5210dc..3015bb0 100644 --- a/lib/server.js +++ b/lib/server.js @@ -222,20 +222,26 @@ module.exports = class Server extends EventEmitter { } _initCors() { - if (this.opts.cors) { - this._app.use(this._cors); + if (!!this.opts.cors) { + this._app.use(this._cors()); } } - _cors(req, res, next) { - res.setHeader('Access-Control-Allow-Origin', req.headers.origin || '*'); - res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE'); - res.setHeader('Access-Control-Allow-Headers', 'Authorization,X-Requested-With,Content-Type'); - res.setHeader('Access-Control-Allow-Credentials', true); - if (req.method === 'OPTIONS') { - return res.status(200).end(); + _cors() { + const corsOptions = Object.assign({ + methods: 'GET, POST, OPTIONS, PUT, PATCH, DELETE', + headers: 'Authorization,X-Requested-With,Content-Type', + credentials: true, + }, this.opts.cors || {}); + + return function(req, res, next) { + res.setHeader('Access-Control-Allow-Origin', req.headers.origin || '*'); + res.setHeader('Access-Control-Allow-Methods', corsOptions.methods); + res.setHeader('Access-Control-Allow-Headers', corsOptions.headers); + res.setHeader('Access-Control-Allow-Credentials', corsOptions.credentials); + + return req.method === 'OPTIONS' ? res.status(200).end() : next(); } - return next(); } _initProxy() { diff --git a/test/server_test.js b/test/server_test.js index cbcdc3f..549620d 100644 --- a/test/server_test.js +++ b/test/server_test.js @@ -561,9 +561,11 @@ describe('server', () => { }); describe('options', function() { - it('should open the browser and use CORS', (done) => { + it('should open the browser and use CORS with custom access-control-allow-headers', (done) => { let _server = new Server({ - cors: true, + cors: { + headers: 'test-header', + }, quiet: true, pathIndex: 'test/' }); @@ -574,6 +576,8 @@ describe('server', () => { http.get(`http://${_server.opts.host}:${_server.opts.port}/`, function(res) { expect(res.headers['access-control-allow-origin']).to.not.be.undefined; + expect(res.headers['access-control-allow-headers']).to.equal('test-header'); + expect(res.headers['access-control-allow-credentials']).to.equal('true'); return done(); })