Skip to content

Commit

Permalink
tls: deprecate newSession/resumeSession events
Browse files Browse the repository at this point in the history
Deprecate asynchronous `newSession`/`resumeSession` events, introduce a
synchronous APIs via `newSession`/`resumeSession` option-callback for
`tls.createServer`.

The reason for this transition is rather simple. There is a quite big
amount of code that was added to support this construction, and not that
much users of it. Additionally, that code chunk is running in front of
OpenSSL, so it makes the process of asynchronous session resumption
twice as ineffective.

See: nodejs#1462
  • Loading branch information
indutny committed Mar 18, 2016
1 parent 929b5b9 commit 8b7a3a2
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 42 deletions.
13 changes: 13 additions & 0 deletions doc/api/tls.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,9 @@ established it will be forwarded here.

### Event: 'newSession'

Stability: 0 - Deprecated: Use synchronous `newSession` option-callback of
[`tls.createServer`][] instead.

`function (sessionId, sessionData, callback) { }`

Emitted on creation of TLS session. May be used to store sessions in external
Expand Down Expand Up @@ -243,6 +246,9 @@ certificates.

### Event: 'resumeSession'

Stability: 0 - Deprecated: Use synchronous `resumeSession` option-callback of
[`tls.createServer`][] instead.

`function (sessionId, callback) { }`

Emitted when client wants to resume previous TLS session. Event listener may
Expand Down Expand Up @@ -898,6 +904,13 @@ automatically set as a listener for the [`'secureConnection'`][] event. The
SSL version 3. The possible values depend on your installation of
OpenSSL and are defined in the constant [SSL_METHODS][].

- `newSession`: A callback to invoke when creating new TLS session
(Signature: `newSession(sessionId, sessionData)`).

- `resumeSession`: A callback to invoke when client attempts to resume some
TLS session by id (Signature: `resumeSession(sessionId)`, should return
`undefined` or `Buffer` instance with `sessionData` from `newSession`).

Here is a simple example echo server:

```js
Expand Down
11 changes: 11 additions & 0 deletions lib/_tls_common.js
Original file line number Diff line number Diff line change
Expand Up @@ -173,3 +173,14 @@ exports.translatePeerCertificate = function translatePeerCertificate(c) {
}
return c;
};

exports.deprecateAsyncSessionAPI = function deprecateAsyncSessionAPI(server) {
const asyncSessionWarning = internalUtil.deprecate(() => {},
'server.on(\'newSession\')/server.on(\'resumeSession\') are deprecated.' +
' Please upgrade to synchronous API and pass these callbacks in ' +
'options.newSession (options.resumeSession).');
server.on('newListener', (type) => {
if (/^(new|resume)Session$/.test(type))
asyncSessionWarning();
});
};
29 changes: 24 additions & 5 deletions lib/_tls_wrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,16 @@ function loadSession(self, hello, cb) {
cb(null, ret);
}

if (hello.sessionId.length <= 0 ||
hello.tlsTicket ||
self.server &&
!self.server.emit('resumeSession', hello.sessionId, onSession)) {
if (hello.sessionId.length <= 0 || hello.tlsTicket) {
return cb(null);
}

assert(self.server);
if (self.server.onResumeSession) {
return onSession(null, self.server.onResumeSession(hello.sessionId));
}

if (!self.server.emit('resumeSession', hello.sessionId, onSession)) {
cb(null);
}
}
Expand Down Expand Up @@ -183,6 +189,12 @@ function onnewsession(key, session) {
var once = false;

this._newSessionPending = true;
if (this.server.onNewSession) {
this.server.onNewSession(key, session);
done();
return;
}

if (!this.server.emit('newSession', key, session, done))
done();

Expand Down Expand Up @@ -398,7 +410,9 @@ TLSSocket.prototype._init = function(socket, wrap) {
ssl.handshakes = 0;

if (this.server) {
if (this.server.listenerCount('resumeSession') > 0 ||
if (this.server.onNewSession ||
this.server.onResumeSession ||
this.server.listenerCount('resumeSession') > 0 ||
this.server.listenerCount('newSession') > 0) {
ssl.enableSessionCallbacks();
}
Expand Down Expand Up @@ -828,6 +842,8 @@ function Server(/* [options], listener */) {
if (listener) {
this.on('secureConnection', listener);
}

common.deprecateAsyncSessionAPI(this);
}

util.inherits(Server, net.Server);
Expand Down Expand Up @@ -902,6 +918,9 @@ Server.prototype.setOptions = function(options) {
.digest('hex')
.slice(0, 32);
}

if (options.newSession) this.onNewSession = options.newSession;
if (options.resumeSession) this.onResumeSession = options.resumeSession;
};

// SNI Contexts High-Level API
Expand Down
95 changes: 58 additions & 37 deletions test/parallel/test-tls-session-cache.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,7 @@ if (!common.hasCrypto) {
return;
}

doTest({ tickets: false }, function() {
doTest({ tickets: true }, function() {
console.error('all done');
});
});

function doTest(testOptions, callback) {
const doTest = (testOptions, callback) => {
var assert = require('assert');
var tls = require('tls');
var fs = require('fs');
Expand All @@ -38,8 +32,23 @@ function doTest(testOptions, callback) {
var resumeCount = 0;
var session;

var server = tls.createServer(options, function(cleartext) {
cleartext.on('error', function(er) {
if (testOptions.sync) {
options.newSession = (id, data) => {
assert.ok(!session);
session = { id: id, data: data };
};

options.resumeSession = (id) => {
++resumeCount;
assert.ok(session);
assert.equal(session.id.toString('hex'), id.toString('hex'));

return session.data;
};
}

var server = tls.createServer(options, (cleartext) => {
cleartext.on('error', (er) => {
// We're ok with getting ECONNRESET in this test, but it's
// timing-dependent, and thus unreliable. Any other errors
// are just failures, though.
Expand All @@ -49,27 +58,30 @@ function doTest(testOptions, callback) {
++requestCount;
cleartext.end();
});
server.on('newSession', function(id, data, cb) {
// Emulate asynchronous store
setTimeout(function() {
assert.ok(!session);
session = {
id: id,
data: data
};
cb();
}, 1000);
});
server.on('resumeSession', function(id, callback) {
++resumeCount;
assert.ok(session);
assert.equal(session.id.toString('hex'), id.toString('hex'));

// Just to check that async really works there
setTimeout(function() {
callback(null, session.data);
}, 100);
});

if (!testOptions.sync) {
server.on('newSession', (id, data, cb) => {
// Emulate asynchronous store
setTimeout(() => {
assert.ok(!session);
session = {
id: id,
data: data
};
cb();
}, 1000);
});
server.on('resumeSession', (id, callback) => {
++resumeCount;
assert.ok(session);
assert.equal(session.id.toString('hex'), id.toString('hex'));

// Just to check that async really works there
setTimeout(() => {
callback(null, session.data);
}, 100);
});
}

var args = [
's_client',
Expand All @@ -85,25 +97,25 @@ function doTest(testOptions, callback) {
if (common.isWindows)
args.push('-no_rand_screen');

server.listen(common.PORT, function() {
server.listen(common.PORT, () => {
var client = spawn(common.opensslCli, args, {
stdio: [ 0, 1, 'pipe' ]
stdio: [ 0, 'ignore', 'pipe' ]
});
var err = '';
client.stderr.setEncoding('utf8');
client.stderr.on('data', function(chunk) {
client.stderr.on('data', (chunk) => {
err += chunk;
});
client.on('exit', function(code) {
client.on('exit', (code) => {
console.error('done');
assert.equal(code, 0);
server.close(function() {
server.close(() => {
setTimeout(callback, 100);
});
});
});

process.on('exit', function() {
process.on('exit', () => {
if (testOptions.tickets) {
assert.equal(requestCount, 6);
assert.equal(resumeCount, 0);
Expand All @@ -114,4 +126,13 @@ function doTest(testOptions, callback) {
assert.equal(resumeCount, 5);
}
});
}
};

doTest({ tickets: false, sync: false }, () => {
doTest({ tickets: true, sync: false }, () => {
doTest({ tickets: false, sync: true }, () => {
console.error('all done');
});
});
});

0 comments on commit 8b7a3a2

Please sign in to comment.