Permalink
Browse files

Rewrite UNIX Domain Socket support into 2.33.1. Add test.

  • Loading branch information...
1 parent 650e64b commit 54e6dfb77d57757d4006982f813ebaab9e005cd5 Ben Patterson committed Feb 7, 2014
Showing with 131 additions and 3 deletions.
  1. +12 โˆ’0 README.md
  2. +88 โˆ’3 request.js
  3. +31 โˆ’0 tests/test-unix.js
View
@@ -81,6 +81,18 @@ http.createServer(function (req, resp) {
You can still use intermediate proxies, the requests will still follow HTTP forwards, etc.
+## UNIX Socket
+
+`request` supports the `unix://` protocol for all requests. The path is assumed to be absolute to the root of the host file system.
+
+HTTP paths are extracted from the supplied URL by testing each level of the full URL against net.connect for a socket response.
+
+Thus the following request will GET `/httppath` from the HTTP server listening on `/tmp/unix.socket`
+
+```javascript
+request.get('unix://tmp/unix.socket/httppath')
+```
+
## Forms
`request` supports `application/x-www-form-urlencoded` and `multipart/form-data` form uploads. For `multipart/related` refer to the `multipart` API.
View
@@ -27,6 +27,7 @@ var optional = require('./lib/optional')
, copy = require('./lib/copy')
, debug = require('./lib/debug')
, getSafe = require('./lib/getSafe')
+ , net = require('net')
;
function safeStringify (obj) {
@@ -37,7 +38,7 @@ function safeStringify (obj) {
}
var globalPool = {}
-var isUrl = /^https?:/i
+var isUrl = /^https?:|^unix:/
// Hacky fix for pre-0.4.4 https
@@ -163,7 +164,7 @@ Request.prototype.init = function (options) {
if (!self.uri.pathname) {self.uri.pathname = '/'}
- if (!self.uri.host) {
+ if (!self.uri.host && !self.protocol=='unix:') {
// Invalid URI: it may generate lot of bad errors, like "TypeError: Cannot call method 'indexOf' of undefined" in CookieJar
// Detect and reject it as soon as possible
var faultyUri = url.format(self.uri)
@@ -241,6 +242,9 @@ Request.prototype.init = function (options) {
}
}
+ self._buildRequest = function(){
+ var self = this;
+
if (options.form) {
self.form(options.form)
}
@@ -324,7 +328,7 @@ Request.prototype.init = function (options) {
}
var protocol = self.proxy && !self.tunnel ? self.proxy.protocol : self.uri.protocol
- , defaultModules = {'http:':http, 'https:':https}
+ , defaultModules = {'http:':http, 'https:':https, 'unix:':http}
, httpModules = self.httpModules || {}
;
self.httpModule = httpModules[protocol] || defaultModules[protocol]
@@ -415,6 +419,87 @@ Request.prototype.init = function (options) {
}
self.ntick = true
})
+
+ } // End _buildRequest
+
+ self._handleUnixSocketURI = function(self){
+ // Parse URI and extract a socket path (tested as a valid socket using net.connect), and a http style path suffix
+ // Thus http requests can be made to a socket using the uri unix://tmp/my.socket/urlpath
+ // and a request for '/urlpath' will be sent to the unix socket at /tmp/my.socket
+
+ self.unixsocket = true;
+
+ var full_path = self.uri.href.replace(self.uri.protocol+'/', '');
+
+ var lookup = full_path.split('/');
+ var error_connecting = true;
+
+ var lookup_table = {};
+ do { lookup_table[lookup.join('/')]={} } while(lookup.pop())
+ for (r in lookup_table){
+ try_next(r);
+ }
+
+ function try_next(table_row){
+ var client = net.connect( table_row );
+ client.path = table_row
+ client.on('error', function(){ lookup_table[this.path].error_connecting=true; this.end(); });
+ client.on('connect', function(){ lookup_table[this.path].error_connecting=false; this.end(); });
+ table_row.client = client;
+ }
+
+ wait_for_socket_response();
+
+ response_counter = 0;
+
+ function wait_for_socket_response(){
+ setImmediate(function(){
+ // counter to prevent infinite blocking waiting for an open socket to be found.
+ response_counter++;
+ var trying = false;
+ for (r in lookup_table){
+ //console.log(r, lookup_table[r], lookup_table[r].error_connecting)
+ if('undefined' == typeof lookup_table[r].error_connecting)
+ trying = true;
+ }
+ if(trying && response_counter<1000)
+ wait_for_socket_response()
+ else
+ set_socket_properties();
+ })
+ }
+
+ function set_socket_properties(){
+ var host;
+ for (r in lookup_table){
+ if(lookup_table[r].error_connecting === false){
+ host = r
+ }
+ }
+ if(!host){
+ self.emit('error', new Error("Failed to connect to any socket in "+full_path))
+ }
+ var path = full_path.replace(host, '')
+
+ self.socketPath = host
+ self.uri.pathname = path
+ self.uri.href = path
+ self.uri.path = path
+ self.host = ''
+ self.hostname = ''
+ delete self.host
+ delete self.hostname
+ self._buildRequest();
+ }
+ }
+
+ // Intercept UNIX protocol requests to change properties to match socket
+ if(/^unix:/.test(self.uri.protocol)){
+ self._handleUnixSocketURI(self);
+ } else {
+ self._buildRequest();
+ }
+
}
// Must call this when following a redirect from https to http or vice versa
View
@@ -0,0 +1,31 @@
+var assert = require('assert')
+ , request = require('../index')
+ , http = require('http')
+ , fs = require('fs')
+ ;
+
+var path = [null, 'test', 'path'].join('/');
+var socket = [__dirname, 'tmp-socket'].join('/');
+var body = 'connected';
+var statusCode = 200;
+
+var s = http.createServer(function(req, res) {
+ // Assert requested path is sent to server
+ assert.equal(req.url, path);
+ res.statusCode = statusCode;
+ res.end(body);
+}).listen(socket, function () {
+
+ request(['unix://', socket, path].join(''), function (error, response, response_body) {
+ // Assert no error in connection
+ assert.equal(error, null);
+ // Assert http success status code
+ assert.equal(response.statusCode, statusCode);
+ // Assert expected response body is recieved
+ assert.equal(response_body, body);
+ // clean up
+ s.close();
+ fs.unlink(socket, function(){});
+ })
+
+})

0 comments on commit 54e6dfb

Please sign in to comment.