3 Commits

Author SHA1 Message Date
James Coglan 7c7b9a9e21 Remove an unnecessary 'use strict'. 2015-04-10 08:29:48 +01:00
James Coglan 0de9386f81 Make the build run on Node < 0.12. 2015-04-10 08:25:58 +01:00
James Coglan ef6a4a2ecc Convert the codebase to ES6. 2015-04-10 08:18:55 +01:00
23 changed files with 406 additions and 6333 deletions
+1
View File
@@ -1 +1,2 @@
lib
node_modules
-1
View File
@@ -2,6 +2,5 @@
.gitignore
.npmignore
.travis.yml
examples
node_modules
spec
+6 -6
View File
@@ -1,12 +1,12 @@
sudo: false
language: node_js
node_js:
- "0.8"
- "0.10"
- "0.12"
- "iojs-1"
- "iojs-2"
- "iojs-3"
- "4"
- "5"
- "iojs"
before_install:
- '[ "${TRAVIS_NODE_VERSION}" != "0.8" ] || npm install -g npm@~1.4.0'
script: make test
-14
View File
@@ -1,17 +1,3 @@
### 0.1.5 / 2016-02-24
* Catch errors thrown by `close()` on zlib streams
### 0.1.4 / 2015-11-06
* The server does not send `server_max_window_bits` if the client does not ask
for it; this works around an issue in Firefox.
### 0.1.3 / 2015-04-10
* Fix a race condition causing some fragments of deflate output to be dropped
* Make sure to emit minimal output on all Node versions
### 0.1.2 / 2014-12-18
* Don't allow configure() to be called with unrecognized options
-4
View File
@@ -1,4 +0,0 @@
# Code of Conduct
All projects under the [Faye](https://github.com/faye) umbrella are covered by
the [Code of Conduct](https://github.com/faye/code-of-conduct).
+19
View File
@@ -0,0 +1,19 @@
SHELL := /bin/bash
PATH := node_modules/.bin:$(PATH)
source_files := $(shell find src -name '*.js')
target_files := $(source_files:src/%.js=lib/%.js)
.PHONY: all clean test
all: $(target_files)
lib/%.js: src/%.js
mkdir -p $(dir $@)
babel $< --out-file $@ --source-maps true
clean:
rm -rf lib
test: all
jstest spec/runner.js
+1 -1
View File
@@ -72,7 +72,7 @@ can be used to set the local session's behaviour and control that of the peer:
(The MIT License)
Copyright (c) 2014-2016 James Coglan
Copyright (c) 2014-2015 James Coglan
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
-5809
View File
File diff suppressed because it is too large Load Diff
-52
View File
@@ -1,52 +0,0 @@
var fs = require('fs'),
deflate = require('..');
var records = fs.readFileSync(__dirname + '/bad.out.log', 'utf8')
.replace(/\s*$/g, '')
.split(/\n/)
.map(JSON.parse);
var client = deflate.createClientSession(),
offer = client.generateOffer(),
server = deflate.createServerSession([offer]),
response = server.generateResponse(),
compressed = [],
size = [0, 0];
client.activate(response);
function compress(index) {
var record = records[index];
if (!record) {
console.log(size, size[0] / size[1]);
return decompress(0);
}
var message = {data: new Buffer(record[3], 'base64')};
size[0] += message.data.length;
server.processOutgoingMessage(message, function(error, message) {
compressed[index] = message;
size[1] += message.data.length;
compress(index + 1);
});
}
function decompress(index) {
var record = records[index];
if (!record) return;
var payload = record[3],
message = compressed[index];
client.processIncomingMessage(message, function(error, message) {
var output = message.data.toString('base64');
if (output !== payload) {
console.error('Failed on', record, message);
process.exit(1);
}
decompress(index + 1);
});
}
compress(0);
-77
View File
@@ -1,77 +0,0 @@
'use strict';
var util = require('util'),
common = require('./common'),
Session = require('./session');
var ClientSession = function(options) {
Session.call(this, options);
};
util.inherits(ClientSession, Session);
ClientSession.validParams = function(params) {
if (!common.validParams(params)) return false;
if (params.hasOwnProperty('client_max_window_bits')) {
if (common.VALID_WINDOW_BITS.indexOf(params.client_max_window_bits) < 0)
return false;
}
return true;
};
ClientSession.prototype.generateOffer = function() {
var offer = {};
if (this._acceptNoContextTakeover)
offer.client_no_context_takeover = true;
if (this._acceptMaxWindowBits !== undefined) {
if (common.VALID_WINDOW_BITS.indexOf(this._acceptMaxWindowBits) < 0) {
throw new Error('Invalid value for maxWindowBits');
}
offer.client_max_window_bits = this._acceptMaxWindowBits;
} else {
offer.client_max_window_bits = true;
}
if (this._requestNoContextTakeover)
offer.server_no_context_takeover = true;
if (this._requestMaxWindowBits !== undefined) {
if (common.VALID_WINDOW_BITS.indexOf(this._requestMaxWindowBits) < 0) {
throw new Error('Invalid valud for requestMaxWindowBits');
}
offer.server_max_window_bits = this._requestMaxWindowBits;
}
return offer;
};
ClientSession.prototype.activate = function(params) {
if (!ClientSession.validParams(params)) return false;
if (this._acceptMaxWindowBits && params.client_max_window_bits) {
if (params.client_max_window_bits > this._acceptMaxWindowBits) return false;
}
if (this._requestNoContextTakeover && !params.server_no_context_takeover)
return false;
if (this._requestMaxWindowBits) {
if (!params.server_max_window_bits) return false;
if (params.server_max_window_bits > this._requestMaxWindowBits) return false;
}
this._ownContextTakeover = !(this._acceptNoContextTakeover || params.client_no_context_takeover);
this._ownWindowBits = Math.min(
this._acceptMaxWindowBits || common.MAX_WINDOW_BITS,
params.client_max_window_bits || common.MAX_WINDOW_BITS
);
this._peerContextTakeover = !params.server_no_context_takeover;
this._peerWindowBits = params.server_max_window_bits || common.MAX_WINDOW_BITS;
return true;
};
module.exports = ClientSession;
-58
View File
@@ -1,58 +0,0 @@
'use strict';
var common = {
VALID_PARAMS: [
'server_no_context_takeover',
'client_no_context_takeover',
'server_max_window_bits',
'client_max_window_bits'
],
MAX_WINDOW_BITS: 15,
VALID_WINDOW_BITS: [8, 9, 10, 11, 12, 13, 14, 15],
concat: function(buffers, length) {
var buffer = new Buffer(length),
offset = 0;
for (var i = 0, n = buffers.length; i < n; i++) {
buffers[i].copy(buffer, offset);
offset += buffers[i].length;
}
return buffer;
},
fetch: function(options, key, _default) {
if (options.hasOwnProperty(key))
return options[key];
else
return _default;
},
validateOptions: function(options, validKeys) {
for (var key in options) {
if (validKeys.indexOf(key) < 0)
throw new Error('Unrecognized option: ' + key);
}
},
validParams: function(params) {
var keys = Object.keys(params), i = keys.length;
while (i--) {
if (this.VALID_PARAMS.indexOf(keys[i]) < 0) return false;
if (params[keys[i]] instanceof Array) return false;
}
if (params.hasOwnProperty('server_no_context_takeover')) {
if (params.server_no_context_takeover !== true) return false;
}
if (params.hasOwnProperty('client_no_context_takeover')) {
if (params.client_no_context_takeover !== true) return false;
}
if (params.hasOwnProperty('server_max_window_bits')) {
if (this.VALID_WINDOW_BITS.indexOf(params.server_max_window_bits) < 0) return false;
}
return true;
}
};
module.exports = common;
-44
View File
@@ -1,44 +0,0 @@
'use strict';
var ClientSession = require('./client_session'),
ServerSession = require('./server_session'),
common = require('./common');
var VALID_OPTIONS = [
'level',
'memLevel',
'strategy',
'noContextTakeover',
'maxWindowBits',
'requestNoContextTakeover',
'requestMaxWindowBits'
];
var PermessageDeflate = {
configure: function(options) {
common.validateOptions(options, VALID_OPTIONS);
var opts = this._options || {};
for (var key in opts) options[key] = opts[key];
return Object.create(this, {_options: {value: options}});
},
createClientSession: function() {
return new ClientSession(this._options || {});
},
createServerSession: function(offers) {
for (var i = 0, n = offers.length; i < n; i++) {
if (ServerSession.validParams(offers[i]))
return new ServerSession(this._options || {}, offers[i]);
}
return null;
},
name: 'permessage-deflate',
type: 'permessage',
rsv1: true,
rsv2: false,
rsv3: false
};
module.exports = PermessageDeflate;
-66
View File
@@ -1,66 +0,0 @@
'use strict';
var util = require('util'),
common = require('./common'),
Session = require('./session');
var ServerSession = function(options, params) {
Session.call(this, options);
this._params = params;
};
util.inherits(ServerSession, Session);
ServerSession.validParams = function(params) {
if (!common.validParams(params)) return false;
if (params.hasOwnProperty('client_max_window_bits')) {
if ([true].concat(common.VALID_WINDOW_BITS).indexOf(params.client_max_window_bits) < 0)
return false;
}
return true;
};
ServerSession.prototype.generateResponse = function() {
var response = {};
// https://tools.ietf.org/html/draft-ietf-hybi-permessage-compression#section-8.1.1.1
this._ownContextTakeover = !this._acceptNoContextTakeover &&
!this._params.server_no_context_takeover;
if (!this._ownContextTakeover) response.server_no_context_takeover = true;
// https://tools.ietf.org/html/draft-ietf-hybi-permessage-compression#section-8.1.1.2
this._peerContextTakeover = !this._requestNoContextTakeover &&
!this._params.client_no_context_takeover;
if (!this._peerContextTakeover) response.client_no_context_takeover = true;
// https://tools.ietf.org/html/draft-ietf-hybi-permessage-compression#section-8.1.2.1
this._ownWindowBits = Math.min(this._acceptMaxWindowBits || common.MAX_WINDOW_BITS,
this._params.server_max_window_bits || common.MAX_WINDOW_BITS);
// In violation of the spec, Firefox closes the connection if it does not
// send server_max_window_bits but the server includes this in its response
if (this._ownWindowBits < common.MAX_WINDOW_BITS && this._params.server_max_window_bits)
response.server_max_window_bits = this._ownWindowBits;
// https://tools.ietf.org/html/draft-ietf-hybi-permessage-compression#section-8.1.2.2
var clientMax = this._params.client_max_window_bits, requestMax;
if (clientMax) {
if (clientMax === true) clientMax = common.MAX_WINDOW_BITS;
this._peerWindowBits = Math.min(this._requestMaxWindowBits || common.MAX_WINDOW_BITS, clientMax);
} else {
this._peerWindowBits = common.MAX_WINDOW_BITS;
}
if (this._peerWindowBits < common.MAX_WINDOW_BITS)
response.client_max_window_bits = this._peerWindowBits;
return response;
};
module.exports = ServerSession;
-167
View File
@@ -1,167 +0,0 @@
'use strict';
var zlib = require('zlib'),
common = require('./common');
var Session = function(options) {
this._level = common.fetch(options, 'level', zlib.Z_DEFAULT_LEVEL);
this._memLevel = common.fetch(options, 'memLevel', zlib.Z_DEFAULT_MEMLEVEL);
this._strategy = common.fetch(options, 'strategy', zlib.Z_DEFAULT_STRATEGY);
this._acceptNoContextTakeover = common.fetch(options, 'noContextTakeover', false);
this._acceptMaxWindowBits = common.fetch(options, 'maxWindowBits', undefined);
this._requestNoContextTakeover = common.fetch(options, 'requestNoContextTakeover', false);
this._requestMaxWindowBits = common.fetch(options, 'requestMaxWindowBits', undefined);
this._queueIn = [];
this._queueOut = [];
};
Session.prototype.processIncomingMessage = function(message, callback) {
if (!message.rsv1) return callback(null, message);
if (this._lockIn) return this._queueIn.push([message, callback]);
var inflate = this._getInflate(),
chunks = [],
length = 0,
self = this;
if (this._inflate) this._lockIn = true;
var return_ = function(error, message) {
return_ = function() {};
inflate.removeListener('data', onData);
inflate.removeListener('error', onError);
if (!self._inflate) self._close(inflate);
self._lockIn = false;
var next = self._queueIn.shift();
if (next) self.processIncomingMessage.apply(self, next);
callback(error, message);
};
var onData = function(data) {
chunks.push(data);
length += data.length;
};
var onError = function(error) {
return_(error, null);
};
inflate.on('data', onData);
inflate.on('error', onError);
inflate.write(message.data);
inflate.write(new Buffer([0x00, 0x00, 0xff, 0xff]));
inflate.flush(function() {
message.data = common.concat(chunks, length);
return_(null, message);
});
};
Session.prototype.processOutgoingMessage = function(message, callback) {
if (this._lockOut) return this._queueOut.push([message, callback]);
var deflate = this._getDeflate(),
chunks = [],
length = 0,
self = this;
if (this._deflate) this._lockOut = true;
var return_ = function(error, message) {
return_ = function() {};
deflate.removeListener('data', onData);
deflate.removeListener('error', onError);
if (!self._deflate) self._close(deflate);
self._lockOut = false;
var next = self._queueOut.shift();
if (next) self.processOutgoingMessage.apply(self, next);
callback(error, message);
};
var onData = function(data) {
chunks.push(data);
length += data.length;
};
var onError = function(error) {
return_(error, null);
};
deflate.on('data', onData);
deflate.on('error', onError);
deflate.write(message.data);
var onFlush = function() {
var data = common.concat(chunks, length);
message.data = data.slice(0, data.length - 4);
message.rsv1 = true;
return_(null, message);
};
if (deflate.params !== undefined)
deflate.flush(zlib.Z_SYNC_FLUSH, onFlush);
else
deflate.flush(onFlush);
};
Session.prototype.close = function() {
this._close(this._inflate);
this._inflate = null;
this._close(this._deflate);
this._deflate = null;
};
Session.prototype._getInflate = function() {
if (this._inflate) return this._inflate;
var inflate = zlib.createInflateRaw({windowBits: this._peerWindowBits});
if (this._peerContextTakeover) this._inflate = inflate;
return inflate;
};
Session.prototype._getDeflate = function() {
if (this._deflate) return this._deflate;
var deflate = zlib.createDeflateRaw({
windowBits: this._ownWindowBits,
level: this._level,
memLevel: this._memLevel,
strategy: this._strategy
});
var flush = deflate.flush;
// This monkey-patch is needed to make Node 0.10 produce optimal output.
// Without this it uses Z_FULL_FLUSH and effectively drops all its context
// state on every flush.
if (deflate._flushFlag !== undefined && deflate.params === undefined)
deflate.flush = function(callback) {
var ws = this._writableState;
if (ws.ended || ws.ending || ws.needDrain) {
flush.call(this, callback);
} else {
this._flushFlag = zlib.Z_SYNC_FLUSH;
this.write(new Buffer(0), '', callback);
}
};
if (this._ownContextTakeover) this._deflate = deflate;
return deflate;
};
Session.prototype._close = function(codec) {
if (!codec || !codec.close) return;
try { codec.close() } catch (error) {}
};
module.exports = Session;
+5 -7
View File
@@ -1,20 +1,18 @@
{ "name" : "permessage-deflate"
, "description" : "Per-message DEFLATE compression extension for WebSocket connections"
, "homepage" : "https://github.com/faye/permessage-deflate-node"
, "homepage" : "http://github.com/faye/permessage-deflate-node"
, "author" : "James Coglan <jcoglan@gmail.com> (http://jcoglan.com/)"
, "keywords" : ["websocket", "compression", "deflate"]
, "license" : "MIT"
, "version" : "0.1.5"
, "engines" : {"node": ">=0.8.0"}
, "version" : "0.1.3"
, "engines" : {"node": ">=0.6.0"}
, "main" : "./lib/permessage_deflate"
, "devDependencies" : {"jstest": ""}
, "scripts" : {"test": "jstest spec/runner.js"}
, "devDependencies" : {"babel": "", "jstest": ""}
, "repository" : { "type" : "git"
, "url" : "git://github.com/faye/permessage-deflate-node.git"
}
, "bugs" : "https://github.com/faye/permessage-deflate-node/issues"
, "bugs" : "http://github.com/faye/permessage-deflate-node/issues"
}
-12
View File
@@ -90,12 +90,6 @@ test.describe("ClientSession", function() { with(this) {
processIncomingMessage()
processIncomingMessage()
}})
it("catches errors thrown by zlib", function() { with(this) {
activate()
stub(zlib, "createInflateRaw").returning(inflate)
assertNothingThrown(function() { processIncomingMessage() })
}})
}})
describe("when the response includes client_no_context_takeover", function() { with(this) {
@@ -112,12 +106,6 @@ test.describe("ClientSession", function() { with(this) {
processOutgoingMessage()
processOutgoingMessage()
}})
it("catches errors thrown by zlib", function() { with(this) {
activate()
stub(zlib, "createDeflateRaw").returning(deflate)
assertNothingThrown(function() { processOutgoingMessage() })
}})
}})
describe("when the response includes server_max_window_bits", function() { with(this) {
+2
View File
@@ -1,2 +1,4 @@
require('babel/polyfill');
require('./client_session_spec')
require('./server_session_spec')
+3 -15
View File
@@ -2,7 +2,7 @@ var PermessageDeflate = require('../lib/permessage_deflate'),
zlib = require('zlib'),
test = require('jstest').Test
test.describe("ServerSession", function() { with(this) {
test.describe("ClientSession", function() { with(this) {
before(function() { with(this) {
this.ext = PermessageDeflate.configure(options)
this.session = ext.createServerSession([offer])
@@ -81,12 +81,6 @@ test.describe("ServerSession", function() { with(this) {
processOutgoingMessage()
processOutgoingMessage()
}})
it("catches errors thrown by zlib", function() { with(this) {
response()
stub(zlib, "createDeflateRaw").returning(deflate)
assertNothingThrown(function() { processOutgoingMessage() })
}})
}})
describe("when the offer includes client_no_context_takeover", function() { with(this) {
@@ -103,12 +97,6 @@ test.describe("ServerSession", function() { with(this) {
processIncomingMessage()
processIncomingMessage()
}})
it("catches errors thrown by zlib", function() { with(this) {
response()
stub(zlib, "createInflateRaw").returning(inflate)
assertNothingThrown(function() { processIncomingMessage() })
}})
}})
describe("when the offer includes server_max_window_bits", function() { with(this) {
@@ -195,8 +183,8 @@ test.describe("ServerSession", function() { with(this) {
define("options", {maxWindowBits: 12})
describe("with an empty offer", function() { with(this) {
it("does not include server_max_window_bits in the response", function() { with(this) {
assertEqual( {}, response() )
it("includes server_max_window_bits in the response", function() { with(this) {
assertEqual( {server_max_window_bits: 12}, response() )
}})
it("uses context takeover and 12 window bits for deflating outgoing messages", function() { with(this) {
+69
View File
@@ -0,0 +1,69 @@
import Session from './session';
import * as common from './common';
export default class ClientSession extends Session {
static validParams(params) {
if (!common.validParams(params)) return false;
if (params.hasOwnProperty('client_max_window_bits')) {
if (common.VALID_WINDOW_BITS.indexOf(params.client_max_window_bits) < 0)
return false;
}
return true;
}
generateOffer() {
let offer = {};
if (this._acceptNoContextTakeover)
offer.client_no_context_takeover = true;
if (this._acceptMaxWindowBits !== undefined) {
if (common.VALID_WINDOW_BITS.indexOf(this._acceptMaxWindowBits) < 0) {
throw new Error('Invalid value for maxWindowBits');
}
offer.client_max_window_bits = this._acceptMaxWindowBits;
} else {
offer.client_max_window_bits = true;
}
if (this._requestNoContextTakeover)
offer.server_no_context_takeover = true;
if (this._requestMaxWindowBits !== undefined) {
if (common.VALID_WINDOW_BITS.indexOf(this._requestMaxWindowBits) < 0) {
throw new Error('Invalid valud for requestMaxWindowBits');
}
offer.server_max_window_bits = this._requestMaxWindowBits;
}
return offer;
}
activate(params) {
if (!ClientSession.validParams(params)) return false;
if (this._acceptMaxWindowBits && params.client_max_window_bits) {
if (params.client_max_window_bits > this._acceptMaxWindowBits) return false;
}
if (this._requestNoContextTakeover && !params.server_no_context_takeover)
return false;
if (this._requestMaxWindowBits) {
if (!params.server_max_window_bits) return false;
if (params.server_max_window_bits > this._requestMaxWindowBits) return false;
}
this._ownContextTakeover = !(this._acceptNoContextTakeover || params.client_no_context_takeover);
this._ownWindowBits = Math.min(
this._acceptMaxWindowBits || common.DEFAULT_MAX_WINDOW_BITS,
params.client_max_window_bits || common.DEFAULT_MAX_WINDOW_BITS
);
this._peerContextTakeover = !params.server_no_context_takeover;
this._peerWindowBits = params.server_max_window_bits || common.DEFAULT_MAX_WINDOW_BITS;
return true;
}
}
+44
View File
@@ -0,0 +1,44 @@
const VALID_PARAMS = [
'server_no_context_takeover',
'client_no_context_takeover',
'server_max_window_bits',
'client_max_window_bits'
];
export const DEFAULT_MAX_WINDOW_BITS = 15;
export const VALID_WINDOW_BITS = [8, 9, 10, 11, 12, 13, 14, 15];
export function concat(buffers, length) {
let buffer = new Buffer(length),
offset = 0;
for (let buf of buffers) {
buf.copy(buffer, offset);
offset += buf.length;
}
return buffer;
}
export function validateOptions(options, validKeys) {
for (let key in options) {
if (validKeys.indexOf(key) < 0)
throw new Error(`Unrecognized option: ${key}`);
}
}
export function validParams(params) {
for (let key of Object.keys(params)) {
if (VALID_PARAMS.indexOf(key) < 0) return false;
if (params[key] instanceof Array) return false;
}
if (params.hasOwnProperty('server_no_context_takeover')) {
if (params.server_no_context_takeover !== true) return false;
}
if (params.hasOwnProperty('client_no_context_takeover')) {
if (params.client_no_context_takeover !== true) return false;
}
if (params.hasOwnProperty('server_max_window_bits')) {
if (this.VALID_WINDOW_BITS.indexOf(params.server_max_window_bits) < 0) return false;
}
return true;
}
+42
View File
@@ -0,0 +1,42 @@
import ClientSession from './client_session';
import ServerSession from './server_session';
import { validateOptions } from './common';
const VALID_OPTIONS = [
'level',
'memLevel',
'strategy',
'noContextTakeover',
'maxWindowBits',
'requestNoContextTakeover',
'requestMaxWindowBits'
];
const PermessageDeflate = {
configure(options) {
validateOptions(options, VALID_OPTIONS);
let opts = this._options || {};
for (let key in opts) options[key] = opts[key];
return Object.create(this, {_options: {value: options}});
},
createClientSession() {
return new ClientSession(this._options || {});
},
createServerSession(offers) {
for (let offer of offers) {
if (ServerSession.validParams(offer))
return new ServerSession(this._options || {}, offer);
}
return null;
},
name: 'permessage-deflate',
type: 'permessage',
rsv1: true,
rsv2: false,
rsv3: false
};
export default PermessageDeflate;
+58
View File
@@ -0,0 +1,58 @@
import Session from './session';
import * as common from './common';
export default class ServerSession extends Session {
constructor(options, params) {
super(options);
this._params = params;
}
static validParams(params) {
if (!common.validParams(params)) return false;
if (params.hasOwnProperty('client_max_window_bits')) {
if ([true].concat(common.VALID_WINDOW_BITS).indexOf(params.client_max_window_bits) < 0)
return false;
}
return true;
}
generateResponse() {
let params = {};
// https://tools.ietf.org/html/draft-ietf-hybi-permessage-compression#section-8.1.1.1
if (this._acceptNoContextTakeover || this._params.server_no_context_takeover)
params.server_no_context_takeover = true;
// https://tools.ietf.org/html/draft-ietf-hybi-permessage-compression#section-8.1.1.2
if (this._requestNoContextTakeover || this._params.client_no_context_takeover)
params.client_no_context_takeover = true;
// https://tools.ietf.org/html/draft-ietf-hybi-permessage-compression#section-8.1.2.1
let acceptMax, serverMax;
if (this._acceptMaxWindowBits || this._params.server_max_window_bits) {
acceptMax = this._acceptMaxWindowBits || common.DEFAULT_MAX_WINDOW_BITS;
serverMax = this._params.server_max_window_bits || common.DEFAULT_MAX_WINDOW_BITS;
params.server_max_window_bits = Math.min(acceptMax, serverMax);
}
// https://tools.ietf.org/html/draft-ietf-hybi-permessage-compression#section-8.1.2.2
let clientMax = this._params.client_max_window_bits, requestMax;
if (clientMax) {
if (clientMax === true) {
if (this._requestMaxWindowBits) params.client_max_window_bits = this._requestMaxWindowBits;
} else {
requestMax = this._requestMaxWindowBits || common.DEFAULT_MAX_WINDOW_BITS;
params.client_max_window_bits = Math.min(requestMax, clientMax);
}
}
this._ownContextTakeover = !params.server_no_context_takeover;
this._ownWindowBits = params.server_max_window_bits || common.DEFAULT_MAX_WINDOW_BITS;
this._peerContextTakeover = !params.client_no_context_takeover;
this._peerWindowBits = params.client_max_window_bits || common.DEFAULT_MAX_WINDOW_BITS;
return params;
}
}
+156
View File
@@ -0,0 +1,156 @@
import zlib from 'zlib';
import { concat } from './common';
export default class Session {
constructor({ level = zlib.Z_DEFAULT_LEVEL,
memLevel = zlib.Z_DEFAULT_MEMLEVEL,
strategy = zlib.Z_DEFAULT_STRATEGY,
noContextTakeover = false,
maxWindowBits,
requestNoContextTakeover = false,
requestMaxWindowBits }) {
this._level = level;
this._memLevel = memLevel;
this._strategy = strategy;
this._acceptNoContextTakeover = noContextTakeover;
this._acceptMaxWindowBits = maxWindowBits;
this._requestNoContextTakeover = requestNoContextTakeover;
this._requestMaxWindowBits = requestMaxWindowBits;
this._queueIn = [];
this._queueOut = [];
}
processIncomingMessage(message, callback) {
if (!message.rsv1) return callback(null, message);
if (this._lockIn) return this._queueIn.push([message, callback]);
let [inflate, chunks, length] = [this._getInflate(), [], 0];
if (this._inflate) this._lockIn = true;
let return_ = (error, message) => {
return_ = () => {};
inflate.removeListener('data', onData);
inflate.removeListener('error', onError);
if (!this._inflate && inflate.close) inflate.close();
this._lockIn = false;
let next = this._queueIn.shift();
if (next) this.processIncomingMessage.apply(this, next);
callback(error, message);
};
let onData = (data) => {
chunks.push(data);
length += data.length;
};
let onError = (error) => return_(error, null);
inflate.on('data', onData);
inflate.on('error', onError);
inflate.write(message.data);
inflate.write(new Buffer([0x00, 0x00, 0xff, 0xff]));
inflate.flush(() => {
message.data = concat(chunks, length);
return_(null, message);
});
}
processOutgoingMessage(message, callback) {
if (this._lockOut) return this._queueOut.push([message, callback]);
let [deflate, chunks, length] = [this._getDeflate(), [], 0];
if (this._deflate) this._lockOut = true;
let return_ = (error, message) => {
return_ = () => {};
deflate.removeListener('data', onData);
deflate.removeListener('error', onError);
if (!this._deflate && deflate.close) deflate.close();
this._lockOut = false;
let next = this._queueOut.shift();
if (next) this.processOutgoingMessage.apply(this, next);
callback(error, message);
};
let onData = (data) => {
chunks.push(data);
length += data.length;
};
let onError = (error) => return_(error, null);
deflate.on('data', onData);
deflate.on('error', onError);
deflate.write(message.data);
let onFlush = () => {
let data = concat(chunks, length);
message.data = data.slice(0, data.length - 4);
message.rsv1 = true;
return_(null, message);
};
if (deflate.params !== undefined)
deflate.flush(zlib.Z_SYNC_FLUSH, onFlush);
else
deflate.flush(onFlush);
}
close() {
if (this._inflate && this._inflate.close) this._inflate.close();
this._inflate = null;
if (this._deflate && this._deflate.close) this._deflate.close();
this._deflate = null;
}
_getInflate() {
if (this._inflate) return this._inflate;
let inflate = zlib.createInflateRaw({windowBits: this._peerWindowBits});
if (this._peerContextTakeover) this._inflate = inflate;
return inflate;
}
_getDeflate() {
if (this._deflate) return this._deflate;
let deflate = zlib.createDeflateRaw({
windowBits: this._ownWindowBits,
level: this._level,
memLevel: this._memLevel,
strategy: this._strategy
});
let flush = deflate.flush;
// This monkey-patch is needed to make Node 0.10 produce optimal output.
// Without this it uses Z_FULL_FLUSH and effectively drops all its context
// state on every flush.
if (deflate._flushFlag !== undefined && deflate.params === undefined)
deflate.flush = function(callback) {
let ws = this._writableState;
if (ws.ended || ws.ending || ws.needDrain) {
flush.call(this, callback);
} else {
this._flushFlag = zlib.Z_SYNC_FLUSH;
this.write(new Buffer(0), '', callback);
}
};
if (this._ownContextTakeover) this._deflate = deflate;
return deflate;
}
}