Add support for Unix Domain Sockets to HTTP
authorMark Cavage <mark.cavage@joyent.com>
Mon, 25 Apr 2011 23:04:07 +0000 (16:04 -0700)
committerRyan Dahl <ry@tinyclouds.org>
Mon, 25 Apr 2011 23:52:31 +0000 (16:52 -0700)
fixes #979.

doc/api/http.markdown
lib/http.js
lib/https.js
test/simple/test-http-unix-socket.js [new file with mode: 0644]

index 09efede..6500a5c 100644 (file)
@@ -368,6 +368,7 @@ Options:
 
 - `host`: A domain name or IP address of the server to issue the request to.
 - `port`: Port of remote server.
+- `socketPath`: Unix Domain Socket (use one of host:port or socketPath)
 - `method`: A string specifying the HTTP request method. Possible values:
   `'GET'` (default), `'POST'`, `'PUT'`, and `'DELETE'`.
 - `path`: Request path. Should include query string and fragments if any.
@@ -443,13 +444,19 @@ Example:
 
 
 ## http.Agent
-## http.getAgent(host, port)
+## http.getAgent(options)
 
 `http.request()` uses a special `Agent` for managing multiple connections to
 an HTTP server. Normally `Agent` instances should not be exposed to user
 code, however in certain situations it's useful to check the status of the
 agent. The `http.getAgent()` function allows you to access the agents.
 
+Options:
+
+- `host`: A domain name or IP address of the server to issue the request to.
+- `port`: Port of remote server.
+- `socketPath`: Unix Domain Socket (use one of host:port or socketPath)
+
 ### Event: 'upgrade'
 
 `function (request, socket, head)`
index a444728..9339dd6 100644 (file)
@@ -1118,10 +1118,12 @@ function Agent(options) {
   this.options = options;
   this.host = options.host;
   this.port = options.port || this.defaultPort;
+  this.socketPath = options.socketPath;
 
   this.queue = [];
   this.sockets = [];
   this.maxSockets = Agent.defaultMaxSockets;
+
 }
 util.inherits(Agent, EventEmitter);
 exports.Agent = Agent;
@@ -1161,7 +1163,7 @@ Agent.prototype._establishNewConnection = function() {
 
   // Grab a new "socket". Depending on the implementation of _getConnection
   // this could either be a raw TCP socket or a TLS stream.
-  var socket = this._getConnection(this.host, this.port, function() {
+  var socket = this._getConnection(self, function() {
     socket._httpConnecting = false;
     self.emit('connect'); // mostly for the shim.
     debug('Agent _getConnection callback');
@@ -1342,9 +1344,18 @@ Agent.prototype._establishNewConnection = function() {
 
 // Sub-classes can overwrite this method with e.g. something that supplies
 // TLS streams.
-Agent.prototype._getConnection = function(host, port, cb) {
+Agent.prototype._getConnection = function(options, cb) {
   debug('Agent connected!');
-  var c = net.createConnection(port, host);
+
+  var c;
+
+  if (options.host) {
+    c = net.createConnection(options.port, options.host);
+  } else if (options.socketPath) {
+    c = net.createConnection(options.socketPath);
+  } else {
+    c = net.createConnection(options.port);
+  }
   c.on('connect', cb);
   return c;
 };
@@ -1404,14 +1415,41 @@ Agent.prototype._cycle = function() {
 // to remove it?
 var agents = {};
 
+// Backwards compatible with legacy getAgent(host, port);
+function getAgent(options) {
+  var agent;
+  var host;
+  var id;
+  var port;
+
+  var _opts = {};
+
+  if (options instanceof String) {
+    port = arguments[1] || 80;
+    id = options + ':' + port;
+    _opts.host = options;
+    _opts.port = port;
+  } else if (options instanceof Object) {
+    if (options.port || options.host) {
+      host = options.host || 'localhost';
+      port = options.port || 80;
+      id = host + port;
+      _opts.host = host;
+      _opts.port = port;
+    } else if (options.socketPath) {
+      id = options.socketPath;
+      _opts.socketPath = options.socketPath;
+    } else {
+      throw new TypeError('Invalid options specification to getAgent');
+    }
+  } else {
+    throw new TypeError('Invalid argument to getAgent');
+  }
 
-function getAgent(host, port) {
-  port = port || 80;
-  var id = host + ':' + port;
-  var agent = agents[id];
+  agent = agents[id];
 
   if (!agent) {
-    agent = agents[id] = new Agent({ host: host, port: port });
+    agent = agents[id] = new Agent(_opts);
   }
 
   return agent;
@@ -1429,7 +1467,7 @@ exports._requestFromAgent = function(options, cb) {
 
 exports.request = function(options, cb) {
   if (options.agent === undefined) {
-    options.agent = getAgent(options.host, options.port);
+    options.agent = getAgent(options);
   } else if (options.agent === false) {
     options.agent = new Agent(options);
   }
index 7f4aaff..1036ea3 100644 (file)
@@ -63,12 +63,12 @@ inherits(Agent, http.Agent);
 Agent.prototype.defaultPort = 443;
 
 
-Agent.prototype._getConnection = function(host, port, cb) {
+Agent.prototype._getConnection = function(options, cb) {
   if (NPN_ENABLED && !this.options.NPNProtocols) {
     this.options.NPNProtocols = ['http/1.1', 'http/1.0'];
   }
 
-  var s = tls.connect(port, host, this.options, function() {
+  var s = tls.connect(options.port, options.host, this.options, function() {
     // do other checks here?
     if (cb) cb();
   });
diff --git a/test/simple/test-http-unix-socket.js b/test/simple/test-http-unix-socket.js
new file mode 100644 (file)
index 0000000..2184663
--- /dev/null
@@ -0,0 +1,78 @@
+// Copyright Joyent, Inc. and other Node contributors.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to permit
+// persons to whom the Software is furnished to do so, subject to the
+// following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+// USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+var common = require('../common');
+var assert = require('assert');
+var fs = require('fs');
+var http = require('http');
+
+var SOCKET = common.tmpDir + '/http.sock';
+
+var server = http.createServer(function(req, res) {
+  res.writeHead(200, {'Content-Type': 'text/plain',
+                      'Connection': 'close'
+                     });
+  res.write('hello ');
+  res.write('world\n');
+  res.end();
+});
+
+server.listen(SOCKET, function() {
+
+  var options = {
+    socketPath: SOCKET,
+    path: '/'
+  };
+
+  var req = http.get(options, function(res) {
+    assert.equal(res.statusCode, 200);
+    assert.equal(res.headers['content-type'], 'text/plain');
+    res.body = '';
+    res.setEncoding('utf8');
+    res.on('data', function (chunk) {
+      res.body += chunk;
+    });
+    res.on('end', function() {
+      assert.equal(res.body, 'hello world\n');
+      server.close();
+    });
+  });
+
+  req.on('error', function(e) {
+    console.log(e.stack);
+    process.exit(1);
+  });
+
+  req.end();
+
+});
+
+server.on('close', function() {
+  try {
+    fs.unlinkSync(SOCKET);
+  } catch (e) {}
+});
+
+process.on('exit', function() {
+  try {
+    server.close();
+  } catch (e) {}
+});