From 50f0368a728b21e1602dde4b8aa1eecb8dc00e3e Mon Sep 17 00:00:00 2001 From: Spencer Alger Date: Thu, 12 Jun 2014 15:51:06 -0700 Subject: [PATCH] moved devServer into a module so that it can be used programmatically --- package.json | 10 +- tasks/config/connect.js | 75 ------------- tasks/maybe_connect_dev.js | 2 +- tasks/server.js | 9 +- .../utils/dev_server/_amd_rapper.js | 0 .../utils/dev_server/_instrumentation.js | 0 test/utils/dev_server/index.js | 106 ++++++++++++++++++ 7 files changed, 120 insertions(+), 82 deletions(-) delete mode 100644 tasks/config/connect.js rename tasks/utils/amd_rapper.js => test/utils/dev_server/_amd_rapper.js (100%) rename tasks/utils/instrumentation.js => test/utils/dev_server/_instrumentation.js (100%) create mode 100644 test/utils/dev_server/index.js diff --git a/package.json b/package.json index cc9e824373fd..daeacd2d9d85 100644 --- a/package.json +++ b/package.json @@ -6,22 +6,22 @@ "main": "Gulpfile.js", "dependencies": {}, "devDependencies": { + "bluebird": "~2.0.7", + "connect": "~2.19.5", + "event-stream": "~3.1.5", "expect.js": "~0.2.0", "grunt": "~0.4.2", - "grunt-cli": "~0.1.13", - "grunt-contrib-connect": "~0.6.0", "grunt-contrib-jade": "~0.10.0", "grunt-contrib-jshint": "~0.8.0", "grunt-contrib-less": "~0.10.0", "grunt-contrib-requirejs": "~0.4.4", "grunt-contrib-watch": "~0.5.3", "grunt-mocha": "~0.4.10", + "http-proxy": "~1.1.4", "istanbul": "~0.2.4", "load-grunt-config": "~0.7.0", "lodash": "~2.4.1", - "bluebird": "~1.2.4", - "mocha": "~1.17.1", - "event-stream": "~3.1.5" + "mocha": "~1.17.1" }, "scripts": { "test": "grunt test", diff --git a/tasks/config/connect.js b/tasks/config/connect.js deleted file mode 100644 index 644829cff22b..000000000000 --- a/tasks/config/connect.js +++ /dev/null @@ -1,75 +0,0 @@ -module.exports = function (grunt) { - var instrumentationMiddleware = require('../utils/instrumentation'); - var amdRapperMiddleware = require('../utils/amd_rapper'); - - return { - dev: { - options: { - hostname: '0.0.0.0', - middleware: function (connect, options, stack) { - stack = stack || []; - - var root = grunt.config.get('root'); - - // when a request for an intrumented file comes in (?instrument=true) - // and it is included in `pattern`, it will be handled - // by this middleware - stack.push(instrumentationMiddleware({ - // root that files should be served from - root: root, - - // make file names easier to read - displayRoot: grunt.config.get('src'), - - // filter the filenames that will be served - filter: function (filename) { - // return true if the filename should be - // included in the coverage report (results are cached) - return grunt.file.isMatch([ - '**/src/**/*.js', - '!**/src/bower_components/**/*', - '!**/src/kibana/utils/{event_emitter,next_tick}.js' - ], filename); - } - })); - - // minimize code duplication (especially in the istanbul reporter) - // by allowing node_modules to be requested in an AMD rapper - stack.push(amdRapperMiddleware({ - root: root - })); - - // standard static middleware reading from the root - stack.push(connect.static(root)); - - stack.push(function (req, res, next) { - if (req.method !== 'HEAD' || req.url !== '/') return next(); - res.statusCode === 200; - res.setHeader('Pong', 'Kibana 4 Dev Server'); - res.end(); - }); - - // redirect requests for '/' to '/src/' - stack.push(function (req, res, next) { - if (req.url !== '/') return next(); - res.statusCode = 303; - res.setHeader('Location', '/src/'); - res.end(); - }); - - // allow browsing directories - stack.push( - function (req, res, next) { - // prevent chrome's stupid "this page is in spanish" - res.setHeader('Content-Language', 'en'); - next(); - }, - connect.directory(root) - ); - - return stack; - } - } - } - }; -}; \ No newline at end of file diff --git a/tasks/maybe_connect_dev.js b/tasks/maybe_connect_dev.js index baadbe481688..c8ed73a24866 100644 --- a/tasks/maybe_connect_dev.js +++ b/tasks/maybe_connect_dev.js @@ -19,7 +19,7 @@ module.exports = function (grunt) { } function onError() { - grunt.task.run(['connect:dev']); + grunt.task.run(['server']); done(); } diff --git a/tasks/server.js b/tasks/server.js index 00dc30c9c792..7cffc39c4a75 100644 --- a/tasks/server.js +++ b/tasks/server.js @@ -1,3 +1,10 @@ module.exports = function (grunt) { - grunt.registerTask('server', ['connect:dev:keepalive']); + grunt.registerTask('server', function () { + var done = this.async(); + var DevServer = require('../test/utils/dev_server'); + var server = new DevServer(); + server.listen(8000, function () { + console.log('visit http://localhost:8000'); + }); + }); }; \ No newline at end of file diff --git a/tasks/utils/amd_rapper.js b/test/utils/dev_server/_amd_rapper.js similarity index 100% rename from tasks/utils/amd_rapper.js rename to test/utils/dev_server/_amd_rapper.js diff --git a/tasks/utils/instrumentation.js b/test/utils/dev_server/_instrumentation.js similarity index 100% rename from tasks/utils/instrumentation.js rename to test/utils/dev_server/_instrumentation.js diff --git a/test/utils/dev_server/index.js b/test/utils/dev_server/index.js new file mode 100644 index 000000000000..8315143f471a --- /dev/null +++ b/test/utils/dev_server/index.js @@ -0,0 +1,106 @@ +/* jshint node:true */ + +var connect = require('connect'); +var http = require('http'); +var Promise = require('bluebird'); + +var instrumentationMiddleware = require('./_instrumentation'); +var amdRapperMiddleware = require('./_amd_rapper'); +var rel = require('path').join.bind(null, __dirname); + +var proxy = require('http-proxy').createProxyServer({}); +var ROOT = rel('../../../'); +var SRC = rel('../../../src'); + +module.exports = function DevServer(opts) { + opts = opts || {}; + + var server = this; + var app = connect(); + var httpServer = http.createServer(app); + + app.use(instrumentationMiddleware({ + root: ROOT, + displayRoot: SRC, + filter: function (filename) { + return filename.match(/.*\/src\/.*\.js$/) + && !filename.match(/.*\/src\/bower_components\/.*\.js$/) + && !filename.match(/.*\/src\/kibana\/utils\/(event_emitter|next_tick)\.js$/); + } + })); + + app.use(amdRapperMiddleware({ + root: ROOT + })); + + app.use('/es-proxy', function (req, res) { + req.url = req.url.replace(/^\/es-proxy/, ''); + proxy.web(req, res, { target: 'http://localhost:' + (process.env.ES_PORT || 9200) }); + }); + + app.use(connect.static(ROOT)); + + // respond to the "maybe_start_server" pings + app.use(function (req, res, next) { + if (req.method !== 'HEAD' || req.url !== '/') return next(); + res.statusCode === 200; + res.setHeader('Pong', 'Kibana 4 Dev Server'); + res.end(); + }); + + app.use(function (req, res, next) { + if (req.url !== '/') return next(); + res.statusCode = 303; + res.setHeader('Location', '/src/'); + res.end(); + }); + + // prevent chrome's stupid "this page is in spanish" on the directories page + app.use(function (req, res, next) { + res.setHeader('Content-Language', 'en'); + next(); + }); + + // allow browsing directories + app.use(connect.directory(ROOT)); + + server.listenOnFirstOpenPort = function (ports) { + var options = ports.slice(0); + + // wrap this logic in an IIFE so that we can call it again later + return (function attempt() { + var port = options.shift(); + if (!port) return Promise.reject(new Error('None of the supplied options succeeded')); + + return server.listen(port) + // filter out EADDRINUSE errors and call attempt again + .catch(function (err) { + if (err.code === 'EADDRINUSE') return attempt(); + throw err; + }); + })(); + }; + + server.listen = function (port) { + return new Promise(function (resolve, reject) { + var done = function (err) { + httpServer.removeListener('error', done); + httpServer.removeListener('listening', done); + + // pass the error along + if (err) return reject(err); + + resolve(server.port = httpServer.address().port); + }; + + // call done with an error + httpServer.on('error', done, true); + // call done without any args + httpServer.on('listening', done, true); + + httpServer.listen(port); + }); + }; + + server.close = httpServer.close.bind(httpServer); +}; \ No newline at end of file