diff --git a/MANUAL.md b/MANUAL.md index 46699a2..b5c2af4 100644 --- a/MANUAL.md +++ b/MANUAL.md @@ -381,6 +381,8 @@ request with the header `Host: pow`. The available endpoints are: environment, which is inherited by each application worker. * `/config.json`: A JSON-encoded object representing the running server's [configuration](http://pow.cx/docs/configuration.html). +* `/apps.json`: A JSON-encoded object representing the running + server's running applications. Example of requesting an endpoint with `curl`: @@ -463,6 +465,9 @@ looks like. redirection is now handled by the pf packet filter. * Upgrades bundled Node version to 0.10.32. +* **0.4.4** (September 16, 2014): + * Added support for `http://pow/apps.json` endpoint. + * **0.4.3** (April 3, 2014): * Upgrade Nack to 0.16 to fix issues when requiring the json library. diff --git a/lib/http_server.js b/lib/http_server.js index b727d8c..680d05a 100644 --- a/lib/http_server.js +++ b/lib/http_server.js @@ -104,6 +104,16 @@ }; }; + HttpServer.prototype.runningApps = function() { + var apps, rootPath; + + apps = []; + for (rootPath in this.rackApplications) { + apps.push(this.rackApplications[rootPath]); + } + return apps; + }; + HttpServer.prototype.logRequest = function(req, res, next) { this.accessLog.info("[" + req.socket.remoteAddress + "] " + req.method + " " + req.headers.host + " " + req.url); this.requestCount++; @@ -134,6 +144,9 @@ case "/status.json": res.writeHead(200); return res.end(JSON.stringify(this)); + case "/apps.json": + res.writeHead(200); + return res.end(JSON.stringify(this.runningApps())); default: return this.handleLocationNotFound(req, res, next); } diff --git a/lib/rack_application.js b/lib/rack_application.js index 643b1d8..62b2094 100644 --- a/lib/rack_application.js +++ b/lib/rack_application.js @@ -23,6 +23,20 @@ this.statCallbacks = []; } + RackApplication.prototype.toJSON = function() { + return { + root: this.root, + host: this.firstHost, + startedAt: Math.round(this.started / 1000), + lastRestartedAt: Math.round(this.mtime / 1000), + lastRequestAt: Math.round(this.lastRequestTime / 1000), + requestCount: this.requestCount, + timeout: Math.round(this.timeout / 1000), + mightIdleAt: Math.round((this.lastRequestTime + this.timeout) / 1000), + status: ((+(new Date)) - this.lastRequestTime) > this.timeout ? 'idle' : 'active' + }; + }; + RackApplication.prototype.ready = function(callback) { if (this.state === "ready") { return callback(); @@ -145,6 +159,7 @@ RackApplication.prototype.initialize = function() { var _this = this; + this.requestCount = 0; if (this.state) { if (this.state === "terminating") { this.quit(function() { @@ -164,10 +179,12 @@ _this.logger.error("stderr: " + err.stderr); } else { _this.state = "ready"; + _this.started = +(new Date); + _this.timeout = ((_ref2 = env != null ? env.POW_TIMEOUT : void 0) != null ? _ref2 : _this.configuration.timeout) * 1000; _this.pool = nack.createPool(join(_this.root, "config.ru"), { env: env, - size: (_ref2 = env != null ? env.POW_WORKERS : void 0) != null ? _ref2 : _this.configuration.workers, - idle: ((_ref3 = env != null ? env.POW_TIMEOUT : void 0) != null ? _ref3 : _this.configuration.timeout) * 1000 + size: (_ref3 = env != null ? env.POW_WORKERS : void 0) != null ? _ref3 : _this.configuration.workers, + idle: _this.timeout }); bufferLines(_this.pool.stdout, function(line) { return _this.logger.info(line); @@ -229,6 +246,8 @@ SERVER_PORT: _this.configuration.dstPort.toString() }; try { + _this.lastRequestTime = +(new Date); + _this.requestCount++; return _this.pool.proxy(req, res, function(err) { if (err) { _this.quit(); diff --git a/src/http_server.coffee b/src/http_server.coffee index dfc853f..52aac77 100644 --- a/src/http_server.coffee +++ b/src/http_server.coffee @@ -83,6 +83,14 @@ module.exports = class HttpServer extends connect.HTTPServer version: version requestCount: @requestCount + # Gets an array of objects describing the server's currently + # running applications passed to `JSON.stringify`. + runningApps: -> + apps = [] + for rootPath of @rackApplications + apps.push @rackApplications[rootPath] + apps + # The first middleware in the stack logs each incoming request's # source address, method, hostname, and path to the access log # (`~/Library/Logs/Pow/access.log` by default). @@ -109,6 +117,8 @@ module.exports = class HttpServer extends connect.HTTPServer # applications inherit. # * `/status.json`: Returns information about the current server # version, number of requests handled, and process ID. + # * `/apps.json`: Returns information about the currently running + # applications from the `rackApplications` instance. # # Third-party utilities may use these endpoints to inspect a running # Pow server. @@ -125,6 +135,9 @@ module.exports = class HttpServer extends connect.HTTPServer when "/status.json" res.writeHead 200 res.end JSON.stringify this + when "/apps.json" + res.writeHead 200 + res.end JSON.stringify @runningApps() else @handleLocationNotFound req, res, next diff --git a/src/rack_application.coffee b/src/rack_application.coffee index d8bd0bf..c43f0ce 100644 --- a/src/rack_application.coffee +++ b/src/rack_application.coffee @@ -41,6 +41,19 @@ module.exports = class RackApplication @quitCallbacks = [] @statCallbacks = [] + # Gets an object describing the server's current status that can be + # passed to `JSON.stringify`. + toJSON: -> + root: @root + host: @firstHost + startedAt: Math.round(@started / 1000) + lastRestartedAt: Math.round(@mtime / 1000) + lastRequestAt: Math.round(@lastRequestTime / 1000) + requestCount: @requestCount + timeout: Math.round(@timeout / 1000) + mightIdleAt: Math.round((@lastRequestTime + @timeout) / 1000) + status: if ((+new Date) - @lastRequestTime) > @timeout then 'idle' else 'active' + # Queue `callback` to be invoked when the application becomes ready, # then start the initialization process. If the application's state # is ready, the callback is invoked immediately. @@ -138,6 +151,7 @@ module.exports = class RackApplication # uninitialized state. (If the application is terminating, queue a # call to `initialize` after all workers have exited.) initialize: -> + @requestCount = 0 if @state if @state is "terminating" @quit => @initialize() @@ -160,11 +174,12 @@ module.exports = class RackApplication # the application's environment or the global configuration. else @state = "ready" - + @started = +new Date + @timeout = (env?.POW_TIMEOUT ? @configuration.timeout) * 1000 @pool = nack.createPool join(@root, "config.ru"), env: env size: env?.POW_WORKERS ? @configuration.workers - idle: (env?.POW_TIMEOUT ? @configuration.timeout) * 1000 + idle: @timeout # Log the workers' stderr and stdout, and log each worker's # PID as it spawns and exits. @@ -214,6 +229,8 @@ module.exports = class RackApplication req.proxyMetaVariables = SERVER_PORT: @configuration.dstPort.toString() try + @lastRequestTime = +new Date + @requestCount++ @pool.proxy req, res, (err) => @quit() if err next err diff --git a/test/test_http_server.coffee b/test/test_http_server.coffee index 2ee994c..c0386a7 100644 --- a/test/test_http_server.coffee +++ b/test/test_http_server.coffee @@ -262,3 +262,11 @@ module.exports = testCase test.same 200, response.statusCode test.same server.toJSON(), JSON.parse body done -> test.done() + + "http://pow/apps.json": (test) -> + test.expect 2 + serveRoot "apps", (request, done, server) -> + request "GET", "/apps.json", host: "pow", (body, response) -> + test.same 200, response.statusCode + test.same server.runningApps(), JSON.parse body + done -> test.done() \ No newline at end of file