master
1#!/usr/bin/env node
2
3"use strict";
4
5var crypto = require('crypto');
6var fs = require('fs');
7var http = require("http");
8var nopt = require('nopt');
9var url = require("url");
10
11var util = require('./lib/util');
12
13function respond(response, statusCode, contents) {
14 response.writeHead(statusCode, {"Content-Type": "application/json"});
15 if (contents) {
16 response.write(JSON.stringify(contents));
17 }
18 response.end();
19}
20
21function respondError(response, responseCode, errorMessage) {
22 var contents = {
23 "error": errorMessage
24 };
25 respond(response, responseCode, contents);
26}
27
28var DownTime = function () {
29 this.startTime = Date.now();
30};
31DownTime.prototype.end = function () {
32 this.endTime = Date.now();
33};
34DownTime.prototype.duration = function () {
35 var endTime = this.endTime || Date.now()
36 return (endTime - this.startTime);
37};
38
39var QueueServer = function (responseDelay, allowedInFlight, maxQueueLength) {
40 this.responseDelay = responseDelay;
41 this.allowedInFlight = allowedInFlight;
42 this.maxQueueLength = maxQueueLength;
43 this.inFlight = 0;
44 this.queued = [];
45 this.downtimes = [];
46};
47QueueServer.prototype.pushRequest = function (reqData) {
48 if (this.queued.length >= this.maxQueueLength) {
49 respondError(reqData.response, "500", "Server is falling over from the load.");
50 } else {
51 this.queued.push(reqData);
52 }
53};
54QueueServer.prototype.popRequest = function () {
55 if (this.queued.length === 0) {
56 this.inFlight -= 1;
57 this.downtimes.push(new DownTime());
58 return;
59 }
60 var value = this.queued[0];
61 this.queued.splice(0, 1);
62 this.handleRequest(value);
63};
64QueueServer.prototype.handleRequest = function (reqData) {
65 if (reqData.closed) {
66 process.nextTick(this.popRequest.bind(this));
67 return;
68 }
69 var parsed = url.parse(reqData.request.url, true),
70 nonce = parsed.query.nonce,
71 contents,
72 hmac;
73 if (nonce === undefined) {
74 respondError(reqData.response, "400", "No nonce was provided.");
75 process.nextTick(this.popRequest.bind(this));
76 return;
77 }
78 hmac = util.sign(nonce, this.secret);
79 contents = {
80 "hmac": hmac
81 };
82 setTimeout(this.waitAndRespond.bind(this), this.responseDelay, reqData, contents);
83};
84QueueServer.prototype.waitAndRespond = function (reqData, contents) {
85 if (reqData.closed) {
86 console.log("Client timed out while waiting for us.");
87 } else {
88 respond(reqData.response, 200, contents);
89 }
90 this.popRequest();
91};
92
93var RequestData = function (request, response) {
94 this.request = request;
95 this.response = response;
96 this.closed = false;
97};
98
99function handleGET(queue, request, response) {
100 var reqData = new RequestData(request, response);
101 response.on('close', function () {
102 reqData.closed = true;
103 });
104 if (queue.inFlight >= queue.allowedInFlight) {
105 // Save this request for later
106 queue.pushRequest(reqData);
107 } else {
108 // Handle now
109 if (queue.downtimes.length != 0) {
110 queue.downtimes[queue.downtimes.length - 1].end();
111 }
112 queue.inFlight += 1;
113 queue.handleRequest(reqData);
114 }
115}
116
117function handleHEAD(validPath, request, response) {
118 var parsed = url.parse(request.url);
119 if (parsed['pathname'] == "/" + validPath) {
120 respond(response, 200);
121 } else {
122 respond(response, 400);
123 }
124}
125
126function main() {
127 var opts = {
128 "secret": String,
129 "in-socket": String,
130 "in-port": String,
131 };
132 var parsed = nopt(opts),
133 secret = parsed.secret || "defaultsecret",
134 secretHash = util.hash(secret),
135 listenTarget;
136
137 if (parsed['in-socket'] !== undefined && parsed['in-port'] !== undefined) {
138 console.log("Cannot specify both an in-port and an in-socket. Exiting.");
139 process.exit(1);
140 } else if (parsed['in-socket']) {
141 listenTarget = parsed['in-socket'];
142 } else {
143 // Default: listen on port 3001
144 listenTarget = parsed['in-port'] || '3001';
145 }
146 // Response delay in ms, Allowed in flight connections, Allowed queue length
147 var queue = new QueueServer(75, 2, 4);
148 queue.secret = secret;
149 var server = http.createServer(function (request, response) {
150 switch (request.method) {
151 case "GET":
152 handleGET(queue, request, response);
153 break;
154 case "HEAD":
155 handleHEAD(secretHash, request, response);
156 break;
157 default:
158 respondUserError(response, "Unsupported HTTP method " + request.method);
159 }
160 });
161
162 server.on("listening", function () {
163 if (parsed['in-socket'] !== undefined) {
164 fs.chmodSync(parsed['in-socket'], "0666");
165 }
166 })
167
168 console.log("The backend is up and listening.");
169 server.listen(listenTarget);
170}
171
172main();