Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

http, https: add new Agents for tunneling

http.overHttpAgent(): for HTTP over HTTP tunnels.
http.overHttpsAgent(): for HTTP over HTTPS tunnels.
https.overHttpAgent(): for HTTPS over HTTP tunnels.
https.overHttpsAgent(): for HTTPS over HTTPS tunnels.

Fixes #2474.
  • Loading branch information...
commit a635acbe67f7a41518c305588a9328649059d9af 1 parent 47adb2e
@koichik authored
View
67 doc/api/http.markdown
@@ -538,9 +538,13 @@ Alternatively, you could just opt out of pooling entirely using `agent:false`:
// Do stuff
})
-## http.globalAgent
+### new http.Agent([options])
-Global instance of Agent which is used as the default for all http client requests.
+Creates a new Agent object.
+The `options` argument has the following options:
+
+- maxSockets: Determines how many concurrent sockets the agent can have open
+ per host. Defaults to `5`.
### agent.maxSockets
@@ -557,6 +561,65 @@ modify.
An object which contains queues of requests that have not yet been assigned to
sockets. Do not modify.
+## http.globalAgent
+
+Global instance of Agent which is used as the default for all http client requests.
+
+## http.overHttpAgent(options)
+
+Creates and returns a new Agent using HTTP over HTTP tunneling proxy.
+The `options` must have a `proxy` in addition to some options same as a
+[http.Agent()](#new_http.Agent).
+The `proxy` is an object with information to connect to the proxy.
+It is similar to [http.request()](#http.request)'s `options` argument without
+`method`, `path` and `agent`.
+
+Example:
+
+ http.get({
+ host: 'www.google.com',
+ agent: http.overHttpAgent({
+ maxSockets: 2,
+ proxy: { // settings for the tunneling proxy
+ host: 'localhost',
+ port: 3128,
+
+ // This is necessary only if the proxy requires Basic Authentication
+ auth: 'user:password'
+ }
+ })
+ }, function(res) {
+ //...
+ });
+
+## http.overHttpsAgent(options)
+
+Creates and returns a new Agent using HTTP over HTTPS tunneling proxy.
+The `options` must have a `proxy` in addition to some options same as a
+[http.Agent()](#new_http.Agent).
+The `proxy` is an object with information to connect to the proxy.
+It is similar to [https.request()](https.html#https.request)'s `options`
+argument without `method`, `path` and `agent`.
+
+Example:
+
+ http.get({
+ host: 'www.google.com',
+ agent: http.overHttpsAgent({
+ maxSockets: 2,
+ proxy: { // settings for the tunneling proxy
+ host: 'localhost',
+ port: 3128,
+
+ // These are necessary only if the proxy requires client certification
+ key: fs.readFileSync('client-key.pem'),
+ cert: fs.readFileSync('client-cert.pem')
+ }
+ })
+ }, function(res) {
+ //...
+ });
+
## http.ClientRequest
View
77 doc/api/https.markdown
@@ -61,7 +61,7 @@ Example:
console.error(e);
});
-The options argument has the following options
+The `options` argument has the following options
- host: IP or domain of host to make request to. Defaults to `'localhost'`.
- port: port of host to request to. Defaults to 443.
@@ -99,7 +99,8 @@ specified. However, a [globalAgent](#https.globalAgent) silently ignores these.
fails. Verification happens at the connection level, *before* the HTTP
request is sent. Default `false`.
-In order to specify these options, use a custom `Agent`.
+In order to specify these options, pass them to a custom
+[Agent](#new_https.Agent).
Example:
@@ -108,10 +109,11 @@ Example:
port: 443,
path: '/',
method: 'GET',
- key: fs.readFileSync('test/fixtures/keys/agent2-key.pem'),
- cert: fs.readFileSync('test/fixtures/keys/agent2-cert.pem')
+ agent: new https.Agent({
+ key: fs.readFileSync('test/fixtures/keys/agent2-key.pem'),
+ cert: fs.readFileSync('test/fixtures/keys/agent2-cert.pem')
+ })
};
- options.agent = new https.Agent(options);
var req = https.request(options, function(res) {
...
@@ -161,8 +163,73 @@ Example:
An Agent object for HTTPS similar to [http.Agent](http.html#http.Agent).
See [https.request()](#https.request) for more information.
+### new https.Agent([options])
+
+Creates a new Agent object.
+The `options` argument has the following options:
+
+- maxSockets: Determines how many concurrent sockets the agent can have open
+ per host. Defaults to `5`.
+
+The options from [tls.connect()](tls.html#tls.connect) can also be specified.
+See [https.request()](#https.request) for more information.
## https.globalAgent
Global instance of [https.Agent](#https.Agent) which is used as the default
for all HTTPS client requests.
+
+## https.overHttpAgent(options)
+
+Creates and returns a new Agent using HTTPS over HTTP tunneling proxy.
+The `options` must have a `proxy` in addition to some options same as a
+[https.Agent()](#new_https.Agent).
+The `proxy` is an object with information to connect to the proxy.
+It is similar to [https.request()](#https.request)'s `options` argument without
+`method`, `path` and `agent`.
+
+Example:
+
+ https.get({
+ host: 'github.com',
+ agent: https.overHttpAgent({
+ maxSockets: 2,
+ proxy: { // settings for the tunneling proxy
+ host: 'localhost',
+ port: 3128,
+
+ // This is necessary only if the proxy requires Basic Authentication
+ auth: 'user:password'
+ }
+ })
+ }, function(res) {
+ //...
+ });
+
+## https.overHttpsAgent(options)
+
+Creates and returns a new Agent using HTTPS over HTTPS tunneling proxy.
+The `options` must have a `proxy` in addition to some options same as a
+[https.Agent()](#new_https.Agent).
+The `proxy` is an object with information to connect to the proxy.
+It is similar to [https.request()](https.html#https.request)'s `options`
+argument without `method`, `path` and `agent`.
+
+Example:
+
+ https.get({
+ host: 'github.com',
+ agent: https.overHttpsAgent({
+ maxSockets: 2,
+ proxy: { // settings for the tunneling proxy
+ host: 'localhost',
+ port: 3128,
+
+ // These are necessary only if the proxy requires client certification
+ key: fs.readFileSync('client2-key.pem'),
+ cert: fs.readFileSync('client2-cert.pem')
+ }
+ })
+ }, function(res) {
+ //...
+ });
View
120 lib/http.js
@@ -943,12 +943,9 @@ function Agent(options) {
self.maxSockets = self.options.maxSockets || Agent.defaultMaxSockets;
self.on('free', function(socket, host, port) {
var name = host + ':' + port;
- if (self.requests[name] && self.requests[name].length) {
- self.requests[name].shift().onSocket(socket);
- if (self.requests[name].length === 0) {
- // don't leak
- delete self.requests[name];
- }
+ var req = self.takeRequest(name);
+ if (req) {
+ req.onSocket(socket);
} else {
// If there are no pending requests just destroy the
// socket and it will get removed from the pool. This
@@ -965,6 +962,7 @@ exports.Agent = Agent;
Agent.defaultMaxSockets = 5;
Agent.prototype.defaultPort = 80;
+
Agent.prototype.addRequest = function(req, host, port) {
var name = host + ':' + port;
if (!this.sockets[name]) {
@@ -972,7 +970,7 @@ Agent.prototype.addRequest = function(req, host, port) {
}
if (this.sockets[name].length < this.maxSockets) {
// If we are under maxSockets create a new one.
- req.onSocket(this.createSocket(name, host, port));
+ this.assignNewSocket(req, name, host, port);
} else {
// We are over limit so we'll add it to the queue.
if (!this.requests[name]) {
@@ -981,13 +979,34 @@ Agent.prototype.addRequest = function(req, host, port) {
this.requests[name].push(req);
}
};
+
+Agent.prototype.takeRequest = function(name) {
+ if (!this.requests[name]) {
+ return;
+ }
+ var req = this.requests[name].shift();
+ if (this.requests[name].length === 0) {
+ // don't leak
+ delete this.requests[name];
+ }
+ return req;
+};
+
+Agent.prototype.assignNewSocket = function(req, name, host, port) {
+ req.onSocket(this.createSocket(name, host, port));
+};
+
Agent.prototype.createSocket = function(name, host, port) {
- var self = this;
- var s = self.createConnection(port, host, self.options);
- if (!self.sockets[name]) {
- self.sockets[name] = [];
+ var s = this.createConnection(port, host, this.options);
+ if (!this.sockets[name]) {
+ this.sockets[name] = [];
}
this.sockets[name].push(s);
+ return this.setupSocket(s, name, host, port);
+};
+
+Agent.prototype.setupSocket = function(s, name, host, port) {
+ var self = this;
var onFree = function() {
self.emit('free', s, host, port);
}
@@ -1011,6 +1030,7 @@ Agent.prototype.createSocket = function(name, host, port) {
s.on('agentRemove', onRemove);
return s;
};
+
Agent.prototype.removeSocket = function(s, name, host, port) {
if (this.sockets[name]) {
var index = this.sockets[name].indexOf(s);
@@ -1022,10 +1042,11 @@ Agent.prototype.removeSocket = function(s, name, host, port) {
}
}
}
- if (this.requests[name] && this.requests[name].length) {
+ var req = this.takeRequest(name);
+ if (req) {
// If we have pending requests and a socket gets closed a new one
// needs to be created to take over in the pool for the one that closed.
- this.createSocket(name, host, port).emit('free');
+ this.assignNewSocket(req, name, host, port);
}
};
@@ -1033,6 +1054,79 @@ var globalAgent = new Agent();
exports.globalAgent = globalAgent;
+function _TunnelingAgent(options) {
+ Agent.call(this, options);
+ this._proxyOptions = this.options.proxy || {};
+}
+util.inherits(_TunnelingAgent, Agent);
+exports._TunnelingAgent = _TunnelingAgent; // exports for https module
+
+_TunnelingAgent.prototype.assignNewSocket = function(req, name, host, port) {
+ var self = this;
+
+ // It is necessary to keep the space in a connection pool
+ // that maxSocket may not be exceeded, until a connection is established.
+ var placeholder = {connecting: true};
+ if (!this.sockets[name]) {
+ this.sockets[name] = [];
+ }
+ this.sockets[name].push(placeholder);
+
+ var connectReq = this.request(util.mergeOptions({}, this._proxyOptions, {
+ method: 'CONNECT',
+ path: name,
+ agent: false
+ }));
+
+ var connectListener = function(res, socket, head) {
+ connectReq.removeListener('error', errorListener);
+
+ if (res.statusCode === 200) {
+ assert.equal(head.length, 0);
+ socket = self.setupSocket(socket, name, host, port);
+ var index = self.sockets[name].indexOf(placeholder);
+ self.sockets[name][index] = socket;
+ req.onSocket(socket);
+ } else {
+ debug('HTTP TUNNELING SOCKET ERROR, statusCode=' + res.statusCode);
+ var error = new Error('tunneling socket could not be established, ' +
+ 'sutatusCode=' + res.statusCode);
+ error.code = 'ECONNRESET';
+ req.emit('error', error);
+ self.removeSocket(placeholder, name, host, port);
+ }
+ };
+ connectReq.once('connect', connectListener);
+
+ var errorListener = function(cause) {
+ connectReq.removeListener('connect', connectListener);
+ debug('HTTP TUNNELING SOCKET ERROR: ' + cause.message + '\n' + cause.stack);
+ var error = new Error('tunneling socket could not be established, ' +
+ 'cause=' + cause.message);
+ error.code = 'ECONNRESET';
+ req.emit('error', error);
+ self.removeSocket(placeholder, name, host, port);
+ };
+ connectReq.once('error', errorListener);
+
+ connectReq.end();
+};
+
+// HTTP over HTTP
+exports.overHttpAgent = function(options) {
+ var agent = new _TunnelingAgent(options);
+ agent.request = exports.request;
+ return agent;
+};
+
+// HTTP over HTTPS
+exports.overHttpsAgent = function(options) {
+ var agent = new _TunnelingAgent(options);
+ agent.request = require('https').request; // lazy load
+ return agent;
+};
+
+
function ClientRequest(options, cb) {
var self = this;
OutgoingMessage.call(self);
View
35 lib/https.js
@@ -21,7 +21,7 @@
var tls = require('tls');
var http = require('http');
-var inherits = require('util').inherits;
+var util = require('util');
function Server(opts, requestListener) {
if (!(this instanceof Server)) return new Server(opts, requestListener);
@@ -38,7 +38,7 @@ function Server(opts, requestListener) {
this.addListener('request', requestListener);
}
}
-inherits(Server, tls.Server);
+util.inherits(Server, tls.Server);
exports.Server = Server;
@@ -55,13 +55,13 @@ function createConnection(port, host, options) {
options.port = port;
options.host = host;
return tls.connect(options);
-};
+}
function Agent(options) {
http.Agent.call(this, options);
this.createConnection = createConnection;
-};
-inherits(Agent, http.Agent);
+}
+util.inherits(Agent, http.Agent);
Agent.prototype.defaultPort = 443;
var globalAgent = new Agent();
@@ -69,6 +69,31 @@ var globalAgent = new Agent();
exports.globalAgent = globalAgent;
exports.Agent = Agent;
+var httpAgentSetupSocket = http.Agent.prototype.setupSocket;
+
+function setupSocket(socket, name, host, port) {
+ var secureSocket = tls.connect(util.mergeOptions({}, this.options, {
+ socket: socket
+ }));
+ return httpAgentSetupSocket.call(this, secureSocket, name, host, port);
+}
+
+// HTTPS over HTTP
+exports.overHttpAgent = function(options) {
+ var agent = new http._TunnelingAgent(options);
+ agent.request = http.request;
+ agent.setupSocket = setupSocket;
+ return agent;
+};
+
+// HTTPS over HTTPS
+exports.overHttpsAgent = function(options) {
+ var agent = new http._TunnelingAgent(options);
+ agent.request = exports.request;
+ agent.setupSocket = setupSocket;
+ return agent;
+};
+
exports.request = function(options, cb) {
if (options.protocol && options.protocol !== 'https:') {
throw new Error('Protocol:' + options.protocol + ' not supported.');
View
104 test/simple/test-http-over-http.js
@@ -0,0 +1,104 @@
+// 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 http = require('http');
+var net = require('net');
+
+var poolSize = 3;
+var N = 10;
+var serverConnect = 0;
+var proxyConnect = 0;
+var clientConnect = 0;
+var agent;
+
+var server = http.createServer(function(req, res) {
+ common.debug('Server got request');
+ ++serverConnect;
+
+ res.writeHead(200);
+ res.end('Hello' + req.url);
+});
+server.listen(common.PORT, function() {
+ var proxy = http.createServer(function(req, res) {
+ assert(false);
+ });
+ proxy.on('connect', function(req, clientSocket, head) {
+ common.debug('Proxy got CONNECT request');
+ assert.equal(req.method, 'CONNECT');
+ assert.equal(req.url, 'localhost:' + common.PORT);
+ ++proxyConnect;
+
+ var serverSocket = net.connect(common.PORT, function() {
+ clientSocket.write('HTTP/1.1 200 Connection established\r\n\r\n');
+ clientSocket.pipe(serverSocket);
+ serverSocket.write(head);
+ serverSocket.pipe(clientSocket);
+ // workaround, see #2524
+ serverSocket.on('end', function() {
+ clientSocket.end();
+ });
+ });
+ });
+ proxy.listen(common.PORT + 1, function() {
+ agent = http.overHttpAgent({
+ maxSockets: poolSize,
+ proxy: {
+ port: common.PORT + 1
+ }
+ });
+
+ for (var i = 0; i < N; ++i) {
+ (function(i) {
+ var req = http.get({
+ port: common.PORT,
+ path: '/' + i,
+ agent: agent
+ }, function(res) {
+ common.debug('Client got response');
+ ++clientConnect;
+
+ res.setEncoding('utf8');
+ res.on('data', function(data) {
+ assert.equal(data, 'Hello/' + i);
+ });
+ res.on('end', function() {
+ if (clientConnect === N) {
+ proxy.close();
+ server.close();
+ }
+ });
+ });
+ })(i);
+ }
+ });
+});
+
+process.on('exit', function() {
+ assert.equal(serverConnect, N);
+ assert.equal(proxyConnect, poolSize);
+ assert.equal(clientConnect, N);
+
+ var name = 'localhost:' + common.PORT;
+ assert(!agent.sockets.hasOwnProperty(name));
+ assert(!agent.requests.hasOwnProperty(name));
+});
View
120 test/simple/test-http-over-https.js
@@ -0,0 +1,120 @@
+// 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 http = require('http');
+var https = require('https');
+var net = require('net');
+var fs = require('fs');
+var path = require('path');
+
+var poolSize = 3;
+var N = 10;
+var serverConnect = 0;
+var proxyConnect = 0;
+var clientConnect = 0;
+var agent;
+
+function readPem(file) {
+ return fs.readFileSync(path.join(common.fixturesDir, 'keys', file + '.pem'));
+}
+
+var server = http.createServer(function(req, res) {
+ common.debug('Server got request');
+ ++serverConnect;
+
+ res.writeHead(200);
+ res.end('Hello' + req.url);
+});
+server.listen(common.PORT, function() {
+ var proxy = https.createServer({
+ key: readPem('agent4-key'),
+ cert: readPem('agent4-cert'),
+ ca: [readPem('ca2-cert')], // ca for agent3
+ requestCert: true,
+ rejectUnauthorized: true
+ }, function(req, res) {
+ assert(false);
+ });
+ proxy.on('connect', function(req, clientSocket, head) {
+ common.debug('Proxy got CONNECT request');
+ assert.equal(req.method, 'CONNECT');
+ assert.equal(req.url, 'localhost:' + common.PORT);
+ ++proxyConnect;
+
+ var serverSocket = net.connect(common.PORT, function() {
+ clientSocket.write('HTTP/1.1 200 Connection established\r\n\r\n');
+ clientSocket.pipe(serverSocket);
+ serverSocket.write(head);
+ serverSocket.pipe(clientSocket);
+ // workaround, see #2524
+ serverSocket.on('end', function() {
+ clientSocket.end();
+ });
+ });
+ });
+ proxy.listen(common.PORT + 1, function() {
+ agent = http.overHttpsAgent({
+ maxSockets: poolSize,
+ proxy: {
+ port: common.PORT + 1,
+ // client certification for proxy
+ key: readPem('agent3-key'),
+ cert: readPem('agent3-cert')
+ }
+ });
+
+ for (var i = 0; i < N; ++i) {
+ (function(i) {
+ var req = http.get({
+ port: common.PORT,
+ path: '/' + i,
+ agent: agent
+ }, function(res) {
+ common.debug('Client got response');
+ ++clientConnect;
+
+ res.setEncoding('utf8');
+ res.on('data', function(data) {
+ assert.equal(data, 'Hello/' + i);
+ });
+ res.on('end', function() {
+ if (clientConnect === N) {
+ proxy.close();
+ server.close();
+ }
+ });
+ });
+ })(i);
+ }
+ });
+});
+
+process.on('exit', function() {
+ assert.equal(serverConnect, N);
+ assert.equal(proxyConnect, poolSize);
+ assert.equal(clientConnect, N);
+
+ var name = 'localhost:' + common.PORT;
+ assert(!agent.sockets.hasOwnProperty(name));
+ assert(!agent.requests.hasOwnProperty(name));
+});
View
120 test/simple/test-https-over-http.js
@@ -0,0 +1,120 @@
+// 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 http = require('http');
+var https = require('https');
+var net = require('net');
+var fs = require('fs');
+var path = require('path');
+
+var poolSize = 3;
+var N = 10;
+var serverConnect = 0;
+var proxyConnect = 0;
+var clientConnect = 0;
+var agent;
+
+function readPem(file) {
+ return fs.readFileSync(path.join(common.fixturesDir, 'keys', file + '.pem'));
+}
+
+var server = https.createServer({
+ key: readPem('agent2-key'),
+ cert: readPem('agent2-cert'),
+ ca: [readPem('ca1-cert')], // ca for agent1
+ requestCert: true,
+ rejectUnauthorized: true
+}, function(req, res) {
+ common.debug('Server got request');
+ ++serverConnect;
+
+ res.writeHead(200);
+ res.end('Hello' + req.url);
+});
+server.listen(common.PORT, function() {
+ var proxy = http.createServer(function(req, res) {
+ assert(false);
+ });
+ proxy.on('connect', function(req, clientSocket, head) {
+ common.debug('Proxy got CONNECT request');
+ assert.equal(req.method, 'CONNECT');
+ assert.equal(req.url, 'localhost:' + common.PORT);
+ ++proxyConnect;
+
+ var serverSocket = net.connect(common.PORT, function() {
+ clientSocket.write('HTTP/1.1 200 Connection established\r\n\r\n');
+ clientSocket.pipe(serverSocket);
+ serverSocket.write(head);
+ serverSocket.pipe(clientSocket);
+ // workaround, see #2524
+ serverSocket.on('end', function() {
+ clientSocket.end();
+ });
+ });
+ });
+ proxy.listen(common.PORT + 1, function() {
+ agent = https.overHttpAgent({
+ maxSockets: poolSize,
+ // client certification for origin server
+ key: readPem('agent1-key'),
+ cert: readPem('agent1-cert'),
+ proxy: {
+ port: common.PORT + 1
+ }
+ });
+
+ for (var i = 0; i < N; ++i) {
+ (function(i) {
+ var req = https.get({
+ port: common.PORT,
+ path: '/' + i,
+ agent: agent
+ }, function(res) {
+ common.debug('Client got response');
+ ++clientConnect;
+
+ res.setEncoding('utf8');
+ res.on('data', function(data) {
+ assert.equal(data, 'Hello/' + i);
+ });
+ res.on('end', function() {
+ if (clientConnect === N) {
+ proxy.close();
+ server.close();
+ }
+ });
+ });
+ })(i);
+ }
+ });
+});
+
+process.on('exit', function() {
+ assert.equal(serverConnect, N);
+ assert.equal(proxyConnect, poolSize);
+ assert.equal(clientConnect, N);
+
+ var name = 'localhost:' + common.PORT;
+ assert(!agent.sockets.hasOwnProperty(name));
+ assert(!agent.requests.hasOwnProperty(name));
+});
View
150 test/simple/test-https-over-https-error.js
@@ -0,0 +1,150 @@
+// 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 http = require('http');
+var https = require('https');
+var net = require('net');
+var fs = require('fs');
+var path = require('path');
+
+var serverConnect = 0;
+var proxyConnect = 0;
+var clientConnect = 0;
+var clientError = 0;
+
+function readPem(file) {
+ return fs.readFileSync(path.join(common.fixturesDir, 'keys', file + '.pem'));
+}
+
+var server = https.createServer({
+ key: readPem('agent2-key'),
+ cert: readPem('agent2-cert'),
+ ca: [ readPem('ca1-cert') ], // ca for agent1
+ requestCert: true,
+ rejectUnauthorized: true
+}, function(req, res) {
+ common.debug('Server got request');
+ ++serverConnect;
+
+ res.writeHead(200);
+ res.end('Hello, ' + serverConnect);
+});
+server.listen(common.PORT, function() {
+ var proxy = https.createServer({
+ key: readPem('agent4-key'),
+ cert: readPem('agent4-cert'),
+ ca: [ readPem('ca2-cert') ], // ca for agent3
+ requestCert: true,
+ rejectUnauthorized: true
+ }, function(req, res) {
+ assert(false);
+ });
+ proxy.on('connect', function(req, clientSocket, head) {
+ common.debug('Proxy got CONNECT request');
+ assert.equal(req.method, 'CONNECT');
+ assert.equal(req.url, 'localhost:' + common.PORT);
+ ++proxyConnect;
+
+ var serverSocket = net.connect(common.PORT, function() {
+ clientSocket.write('HTTP/1.1 200 Connection established\r\n\r\n');
+ clientSocket.pipe(serverSocket);
+ serverSocket.write(head);
+ serverSocket.pipe(clientSocket);
+ // workaround, see #2524
+ serverSocket.on('end', function() {
+ clientSocket.end();
+ });
+ });
+ });
+ proxy.listen(common.PORT + 1, function() {
+ function doRequest(agent) {
+ var req = https.get({
+ port: common.PORT,
+ agent: agent
+ }, function(res) {
+ common.debug('Client got response');
+ ++clientConnect;
+ req.emit('finish');
+ });
+ req.on('error', function(err) {
+ common.debug('Client got error: ' + err.message);
+ ++clientError;
+ req.emit('finish');
+ });
+ req.on('finish', function() {
+ if (clientConnect + clientError === 4) {
+ proxy.close();
+ server.close();
+ }
+ });
+ }
+
+ doRequest(https.overHttpsAgent({ // invalid
+ maxSockets: 1,
+ // no certificate for origin server
+ proxy: {
+ port: common.PORT + 1
+ // no certificate for proxy
+ }
+ }));
+ doRequest(https.overHttpsAgent({ // invalid
+ maxSockets: 1,
+ // client certification for origin server
+ key: readPem('agent1-key'),
+ cert: readPem('agent1-cert'),
+ proxy: {
+ port: common.PORT + 1
+ // no certificate for proxy
+ }
+ }));
+ doRequest(https.overHttpsAgent({ // invalid
+ maxSockets: 1,
+ // no certificate for origin server
+ proxy: {
+ port: common.PORT + 1,
+ // client certification for proxy
+ key: readPem('agent3-key'),
+ cert: readPem('agent3-cert')
+ }
+ }));
+ doRequest(https.overHttpsAgent({ // valid
+ maxSockets: 1,
+ // client certification for origin server
+ key: readPem('agent1-key'),
+ cert: readPem('agent1-cert'),
+ proxy: {
+ port: common.PORT + 1,
+ // client certification for proxy
+ key: readPem('agent3-key'),
+ cert: readPem('agent3-cert')
+ }
+ }));
+ });
+});
+
+process.on('exit', function() {
+ assert.equal(serverConnect, 1);
+ assert.equal(proxyConnect, 2);
+ assert.equal(clientConnect, 1);
+ assert.equal(clientError, 3);
+});
View
129 test/simple/test-https-over-https.js
@@ -0,0 +1,129 @@
+// 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 http = require('http');
+var https = require('https');
+var net = require('net');
+var fs = require('fs');
+var path = require('path');
+
+var poolSize = 3;
+var N = 5;
+var serverConnect = 0;
+var proxyConnect = 0;
+var clientConnect = 0;
+var agent;
+
+function readPem(file) {
+ return fs.readFileSync(path.join(common.fixturesDir, 'keys', file + '.pem'));
+}
+
+var server = https.createServer({
+ key: readPem('agent2-key'),
+ cert: readPem('agent2-cert'),
+ ca: [readPem('ca1-cert')], // ca for agent1
+ requestCert: true,
+ rejectUnauthorized: true
+}, function(req, res) {
+ common.debug('Server got request');
+ ++serverConnect;
+
+ res.writeHead(200);
+ res.end('Hello' + req.url);
+});
+server.listen(common.PORT, function() {
+ var proxy = https.createServer({
+ key: readPem('agent4-key'),
+ cert: readPem('agent4-cert'),
+ ca: [readPem('ca2-cert')], // ca for agent3
+ requestCert: true,
+ rejectUnauthorized: true
+ }, function(req, res) {
+ assert(false);
+ });
+ proxy.on('connect', function(req, clientSocket, head) {
+ common.debug('Proxy got CONNECT request');
+ assert.equal(req.method, 'CONNECT');
+ assert.equal(req.url, 'localhost:' + common.PORT);
+ ++proxyConnect;
+
+ var serverSocket = net.connect(common.PORT, function() {
+ clientSocket.write('HTTP/1.1 200 Connection established\r\n\r\n');
+ clientSocket.pipe(serverSocket);
+ serverSocket.write(head);
+ serverSocket.pipe(clientSocket);
+ // workaround, see #2524
+ serverSocket.on('end', function() {
+ clientSocket.end();
+ });
+ });
+ });
+ proxy.listen(common.PORT + 1, function() {
+ agent = https.overHttpsAgent({
+ maxSockets: poolSize,
+ // client certification for origin server
+ key: readPem('agent1-key'),
+ cert: readPem('agent1-cert'),
+ proxy: {
+ port: common.PORT + 1,
+ // client certification for proxy
+ key: readPem('agent3-key'),
+ cert: readPem('agent3-cert')
+ }
+ });
+
+ for (var i = 0; i < N; ++i) {
+ (function(i) {
+ var req = https.get({
+ port: common.PORT,
+ path: '/' + i,
+ agent: agent
+ }, function(res) {
+ common.debug('Client got response');
+ ++clientConnect;
+
+ res.setEncoding('utf8');
+ res.on('data', function(data) {
+ assert.equal(data, 'Hello/' + i);
+ });
+ res.on('end', function() {
+ if (clientConnect === N) {
+ proxy.close();
+ server.close();
+ }
+ });
+ });
+ })(i);
+ }
+ });
+});
+
+process.on('exit', function() {
+ assert.equal(serverConnect, N);
+ assert.equal(proxyConnect, poolSize);
+ assert.equal(clientConnect, N);
+
+ var name = 'localhost:' + common.PORT;
+ assert(!agent.sockets.hasOwnProperty(name));
+ assert(!agent.requests.hasOwnProperty(name));
+});
Please sign in to comment.
Something went wrong with that request. Please try again.