Skip to content
This repository has been archived by the owner on Oct 23, 2023. It is now read-only.

Add support for sending events through proxy #397

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 74 additions & 4 deletions lib/transports.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@ var timeoutReq = require('timed-out');

var http = require('http');
var https = require('https');

var agentOptions = {keepAlive: true, maxSockets: 100};
var tunnel = require('tunnel-agent');
var agentOptions = {
keepAlive: true,
maxSockets: 100
};
var httpAgent = new http.Agent(agentOptions);
var httpsAgent = new https.Agent(agentOptions);

Expand All @@ -31,14 +34,45 @@ HTTPTransport.prototype.send = function(client, message, headers, eventId, cb) {
ca: client.ca,
agent: this.agent
};

// set path apprpriately when using http endpoint + proxy, set proxy headers appropriately when using https endpoint + proxy
if (this.options.hasOwnProperty('proxyHost')) {
if (client.dsn.protocol === 'http') {
this.options.path =
'http://' +
client.dsn.host +
':' +
client.dsn.port +
client.dsn.path +
'api/' +
client.dsn.project_id +
'/store/';
delete options.hostname; // only 'host' should be set when using proxy
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

slightly better comment here for what will happen if you send hostname or a link to a resource that justifies why?


if (this.options.proxyAuth) {
// might be able to use httpOverHttp agent
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could this be commented to be more explanatory? not sure what these two branches mean

this.options.headers['Proxy-Authorization'] =
'Basic ' + Buffer.from(this.options.proxyAuth).toString('base64');
}
} else {
this.options.agent.proxyOptions.headers = {
'Content-Type': 'application/octet-stream',
host: client.dsn.host + ':' + client.dsn.port
};
}
}

for (var key in this.options) {
if (this.options.hasOwnProperty(key)) {
options[key] = this.options[key];
}
}

// prevent off heap memory explosion
var _name = this.agent.getName({host: client.dsn.host, port: client.dsn.port});
var _name = this.agent.getName({
host: client.dsn.host,
port: client.dsn.port
});
var _requests = this.agent.requests[_name];
if (_requests && Object.keys(_requests).length > client.maxReqQueueCount) {
// other feedback strategy
Expand All @@ -63,7 +97,6 @@ HTTPTransport.prototype.send = function(client, message, headers, eventId, cb) {
client.emit('error', e);
cb && cb(e);
}

// force the socket to drain
var noop = function() {};
res.on('data', noop);
Expand All @@ -89,10 +122,47 @@ function HTTPSTransport(options) {
this.options = options || {};
this.agent = httpsAgent;
}

function HTTPProxyTransport(options) {
this.defaultPort = 80;
this.transport = http;
this.options = options || {};
this.agent = httpAgent;
this.options.host = options.proxyHost;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about just using host port etc. instead? If we pass options object to this constructor, it's by definition mentioned to be used by proxy, therefore using a prefix proxyHost seems redundant.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or it may be useful to distinguish http from httpProxy options. It's up to you really.

this.options.port = options.proxyPort;

if (options.proxyUser && options.proxyPassword) {
this.options.proxyAuth = options.proxyUser + ':' + options.proxyPassword;
}
}

function HTTPSProxyTransport(options) {
this.transport = https;
this.options = options || {};
this.agent = httpsAgent;
this.options.agent = tunnel.httpsOverHttp({
proxy: {
host: options.proxyHost,
port: options.proxyPort,
proxyAuth:
options.proxyUser && options.proxyPassword
? options.proxyUser + ':' + options.proxyPassword
: null
},
keepAlive: agentOptions.keepAlive,
maxSockets: agentOptions.maxSockets
});
}

util.inherits(HTTPSTransport, HTTPTransport);
util.inherits(HTTPProxyTransport, HTTPTransport);
util.inherits(HTTPSProxyTransport, HTTPTransport);

module.exports.http = new HTTPTransport();
module.exports.https = new HTTPSTransport();
module.exports.Transport = Transport;
module.exports.HTTPTransport = HTTPTransport;
module.exports.HTTPSTransport = HTTPSTransport;

module.exports.HTTPProxyTransport = HTTPProxyTransport;
module.exports.HTTPSProxyTransport = HTTPSProxyTransport;
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"lsmod": "1.0.0",
"stack-trace": "0.0.9",
"timed-out": "4.0.1",
"tunnel-agent": "^0.6.0",
"uuid": "3.0.0"
},
"devDependencies": {
Expand Down
86 changes: 86 additions & 0 deletions test/raven.transports.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,92 @@ describe('transports', function() {
https.agent.maxSockets.should.equal(100);
});

it('should set HTTPS proxy transport when proxy config is specified and request is sent', function(done) {
var option = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/option/options/g

proxyHost: 'localhost',
proxyPort: '8080'
};

// HTTPS
var httpsProxyTransport = new transports.HTTPSProxyTransport(option);
httpsProxyTransport.options.agent.proxyOptions.should.exist;

var _cachedAgent = httpsProxyTransport.agent;
var requests = {};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't seem to use this object anywhere in tests

for (var i = 0; i < 10; i++) {
requests[i] = 'req';
}

httpsProxyTransport.agent = Object.assign({}, _cachedAgent, {
getName: function() {
return 'foo:123';
},
requests: {
'foo:123': {}
}
});

httpsProxyTransport.send(
{
dsn: {
host: 'foo',
port: 123
},
emit: function() {}
},
null,
null,
null,
function() {
httpsProxyTransport.options.agent.proxyOptions.headers.host.should.exist;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could also check that it has been created correctly and assert on foo:123

done();
}
);
});

it('should set HTTP proxy transport when proxy config is specified and request is sent', function(done) {
var option = {
proxyHost: 'localhost',
proxyPort: '8080'
};

// HTTP
var httpProxyTransport = new transports.HTTPProxyTransport(option);
httpProxyTransport.options.host.should.equal('localhost');
httpProxyTransport.options.port.should.equal('8080');

var _cachedAgent = httpProxyTransport.agent;
httpProxyTransport.agent = Object.assign({}, _cachedAgent, {
getName: function() {
return 'foo:123';
},
requests: {
'foo:123': {}
}
});

httpProxyTransport.options.agent = _cachedAgent;
httpProxyTransport.send(
{
dsn: {
host: 'foo',
port: 123,
protocol: 'http',
path: '/',
project_id: '1'
},
emit: function() {}
},
null,
null,
null,
function() {
httpProxyTransport.options.path.should.equal('http://foo:123/api/1/store/');
done();
}
);
});

it('should emit error when requests queued over the limit', function(done) {
var http = transports.http;
var _cachedAgent = http.options.agent;
Expand Down