diff --git a/.jshintrc b/.jshintrc
index 11ec1bc..36b6c1e 100644
--- a/.jshintrc
+++ b/.jshintrc
@@ -116,9 +116,10 @@
//"iLoveDouglas"
],
"indent" : 4, // Specify indentation spacing
- "mocha": true,
"globals": {
+ //"console": true,
"WebSocket": false,
+ "WorkerGlobalScope": false,
"Buffer": false,
"require": false,
"exports": false,
diff --git a/HISTORY.md b/HISTORY.md
index b8ceb74..1353010 100644
--- a/HISTORY.md
+++ b/HISTORY.md
@@ -1,6 +1,9 @@
Version history
===============
+### 0.2.1x (2018-12-xx)
+* Add Websocket connection support inside Worker thread to accelerate decompresion and free Window Rendering thread.
+
### 0.2.15 (2018-12-06)
* Fix possible cache hit on POST|PUT|DELETE (via #118)
diff --git a/README.md b/README.md
index 560d591..ef10e27 100644
--- a/README.md
+++ b/README.md
@@ -92,12 +92,47 @@ In full
The integration tests require Java JDK 8 be installed.
```bash
-npm i
+npm install
npm run build
```
+### Browser Compatibility
+
+| Feature/Browser | Chrome | Firefox | Safari | Internet Explorer |
+|-----------------|--------|---------|--------|-------------------|
+| http2 over ws | x | x | x | x (IE 10+) |
+| ws in worker | x | x | x | x (IE 11+) |
+| arraybuffer | x | x | x | x (Edge) |
+
+### Native Browser Implementations
+
+The example directory contains a simple Web App which tests whether the browser
+supports native HTTP2 push with SPEC compliant caching.
+
+
+Start origin
+```
+http-server -c-1
+```
+
+Start data server
+```
+ node server.js
+```
+
+Visit page `https://localhost:8080/` (Note: need to trust TLS cert)
+
+
### Integration Tests
-```bash
-npm run test:browser
+
+Run integration tests on client side:
```
+npm run integration
+```
+
+Run client worker integration tests"
+```
+npm run integration:worker
+```
+
diff --git a/index.js b/index.js
index 50711bc..e3d11f9 100644
--- a/index.js
+++ b/index.js
@@ -1,2 +1 @@
-
require('./lib/http2-cache');
\ No newline at end of file
diff --git a/integration-test/.jshintrc b/integration-test/.jshintrc
new file mode 100644
index 0000000..c9d3e68
--- /dev/null
+++ b/integration-test/.jshintrc
@@ -0,0 +1,3 @@
+{
+ "mocha": true
+}
diff --git a/integration-test/http2-worker-itest.js b/integration-test/http2-worker-itest.js
new file mode 100644
index 0000000..60fd21e
--- /dev/null
+++ b/integration-test/http2-worker-itest.js
@@ -0,0 +1,445 @@
+var assert = chai.assert;
+
+var largeRequestCharSize = 1024 * 1024 * 1; // ~ 1MB
+//var largeRequestCharSize = 1024 * 1024 * 1; // ~ 5MB
+//var largeRequestCharSize = 1024 * 1000; // ~ 1000Kb
+var hostname = window.location.hostname;
+describe('http2-cache', function () {
+
+ it('proxy() with empty params throws exception', function () {
+ assert.throws(function () {
+ XMLHttpRequest.proxy();
+ });
+ });
+
+ it('proxy() with no arrays throws exception', function () {
+ assert.throws(function () {
+ XMLHttpRequest.proxy("http://url");
+ }
+ );
+ });
+
+ it('proxy() with invalid params throws exception', function () {
+ assert.throws(function () {
+ XMLHttpRequest.proxy([1]);
+ });
+ });
+
+ describe('http-cache regular (no worker)', function () {
+
+ describe('Pure XHR', function () {
+
+ it('should proxy GET request small', function (done) {
+
+ var xhr = new XMLHttpRequest();
+
+ xhr.onloadstart = function () {
+ xhr.onprogress = function () {
+ xhr.onload = function () {
+ assert.equal(xhr.status, 200);
+ assert.equal(typeof xhr.response, 'string');
+ assert.notEqual(xhr.response.lentgh, 0);
+ assert.equal(typeof JSON.parse(xhr.response), 'object');
+ assert.equal(xhr.getResponseHeader('content-type'), 'application/json');
+ done();
+ };
+ };
+ };
+
+ xhr.onerror = function (err) {
+ throw new TypeError('Network request failed');
+ };
+ xhr.open('GET', 'http://' + hostname + ':7080/config', true);
+ xhr.send(null);
+ });
+
+ it('should proxy GET request large (string)', function (done) {
+
+ var xhr = new XMLHttpRequest();
+
+ xhr.onloadstart = function () {
+ xhr.onprogress = function () {
+ xhr.onload = function () {
+ assert.equal(xhr.status, 200);
+ //assert.equal(typeof xhr.response, 'string');
+ assert.notEqual(xhr.response.lentgh, 0);
+ assert.equal(xhr.getResponseHeader('content-type'), 'text/plain; charset=utf-8');
+ done();
+ };
+ };
+ };
+
+ xhr.onerror = function (err) {
+ throw new TypeError('Network request failed');
+ };
+ xhr.open('GET', 'http://' + hostname + ':7080/charof' + largeRequestCharSize, true);
+ xhr.send(null);
+ });
+
+ it('should proxy GET request large (gzip+string)', function (done) {
+
+ var xhr = new XMLHttpRequest();
+
+ xhr.onloadstart = function () {
+ xhr.onprogress = function () {
+ xhr.onload = function () {
+ assert.equal(xhr.status, 200);
+ //assert.equal(typeof xhr.response, 'string');
+ assert.notEqual(xhr.response.lentgh, 0);
+ assert.equal(xhr.getResponseHeader('content-type'), 'text/plain; charset=utf-8');
+ done();
+ };
+ };
+ };
+
+ xhr.onerror = function (err) {
+ throw new TypeError('Network request failed');
+ };
+ xhr.open('GET', 'http://' + hostname + ':7080/gzip/charof' + largeRequestCharSize, true);
+ xhr.send(null);
+ });
+
+ it('should proxy GET request large (arraybuffer)', function (done) {
+
+ var xhr = new XMLHttpRequest();
+
+ xhr.responseType = 'arraybuffer';
+ xhr.onloadstart = function () {
+ xhr.onprogress = function () {
+ xhr.onload = function () {
+ assert.equal(xhr.status, 200);
+ assert.equal(typeof xhr.response, 'object');
+ assert.notEqual(xhr.response.lentgh, 0);
+ assert.equal(xhr.getResponseHeader('content-type'), 'text/plain; charset=utf-8');
+ done();
+ };
+ };
+ };
+
+ xhr.onerror = function (err) {
+ throw new TypeError('Network request failed');
+ };
+ xhr.open('GET', 'http://' + hostname + ':7080/charof' + largeRequestCharSize, true);
+ xhr.send(null);
+ });
+ });
+
+ describe('HTTP2.js XHR', function () {
+
+ before('configure http2 proxy, and worker (wait 250)', function (done) {
+ XMLHttpRequest.configuration.useWorker = false;
+ XMLHttpRequest.configuration.terminateWorker(true);
+ XMLHttpRequest.proxy(["http://" + hostname + ":7080/config"]);
+ setTimeout(done, 250);
+ });
+
+ it('should proxy GET request small', function (done) {
+
+ var xhr = new XMLHttpRequest();
+
+ xhr.onloadstart = function () {
+ xhr.onprogress = function () {
+ xhr.onload = function () {
+ assert.equal(xhr.status, 200);
+ assert.equal(typeof xhr.response, 'string');
+ assert.notEqual(xhr.response.lentgh, 0);
+ assert.equal(xhr.getResponseHeader('content-type'), 'text/plain; charset=utf-8');
+ done();
+ };
+ };
+ };
+
+ xhr.onerror = function (err) {
+ throw new TypeError('Network request failed');
+ };
+ xhr.open('GET', 'http://cache-endpoint/charof10', true);
+ xhr.send(null);
+ });
+
+ it('should proxy GET request large (string)', function (done) {
+
+ var xhr = new XMLHttpRequest();
+
+ xhr.onloadstart = function () {
+ xhr.onprogress = function () {
+ xhr.onload = function () {
+ assert.equal(xhr.status, 200);
+ assert.equal(typeof xhr.response, 'string');
+ assert.notEqual(xhr.response.lentgh, 0);
+ assert.equal(xhr.getResponseHeader('content-type'), 'text/plain; charset=utf-8');
+ done();
+ };
+ };
+ };
+
+ xhr.onerror = function (err) {
+ throw new TypeError('Network request failed');
+ };
+ xhr.open('GET', 'http://cache-endpoint/charof' + largeRequestCharSize, true);
+ xhr.send(null);
+ });
+
+ it('should proxy GET request large (gzip+string)', function (done) {
+
+ var xhr = new XMLHttpRequest();
+
+ xhr.onloadstart = function () {
+ xhr.onprogress = function () {
+ xhr.onload = function () {
+ assert.equal(xhr.status, 200);
+ assert.equal(typeof xhr.response, 'string');
+ assert.notEqual(xhr.response.lentgh, 0);
+ assert.equal(xhr.getResponseHeader('content-type'), 'text/plain; charset=utf-8');
+ done();
+ };
+ };
+ };
+
+ xhr.onerror = function (err) {
+ throw new TypeError('Network request failed');
+ };
+ xhr.open('GET', 'http://cache-endpoint/gzip/charof' + largeRequestCharSize, true);
+
+ // not required to work, and cause
+ // http2-cache.js:2059 Refused to set unsafe header "accept-encoding"
+ //xhr.setRequestHeader('accept-encoding','gzip');
+ xhr.send(null);
+ });
+
+ it('should proxy GET request large (arraybuffer)', function (done) {
+
+ var xhr = new XMLHttpRequest();
+ xhr.responseType = 'arraybuffer';
+
+ xhr.onloadstart = function () {
+ xhr.onprogress = function () {
+ xhr.onload = function () {
+ assert.equal(xhr.status, 200);
+ assert.equal(typeof xhr.response, 'object');
+ assert.notEqual(xhr.response.lentgh, 0);
+ assert.equal(xhr.getResponseHeader('content-type'), 'text/plain; charset=utf-8');
+ done();
+ };
+ };
+ };
+
+ xhr.onerror = function (err) {
+ throw new TypeError('Network request failed');
+ };
+ xhr.open('GET', 'http://cache-endpoint/charof' + largeRequestCharSize, true);
+ xhr.send(null);
+ });
+ });
+ });
+
+ describe('HTTP2.js using Worker', function () {
+
+ before('configure http2 proxy, and worker (wait 250)', function (done) {
+ XMLHttpRequest.configuration.useTransferable = false;
+ XMLHttpRequest.configuration.useWorker = true;
+ XMLHttpRequest.configuration.terminateWorker(true);
+ XMLHttpRequest.proxy(["http://" + hostname + ":7080/config"]);
+ setTimeout(done, 250);
+ });
+
+ it('should proxy GET request small', function (done) {
+
+ var xhr = new XMLHttpRequest();
+
+ xhr.onloadstart = function () {
+ xhr.onprogress = function () {
+ xhr.onload = function () {
+ assert.equal(xhr.status, 200);
+ assert.equal(typeof xhr.response, 'string');
+ assert.notEqual(xhr.response.lentgh, 0);
+ assert.equal(xhr.getResponseHeader('content-type'), 'text/plain; charset=utf-8');
+ done();
+ };
+ };
+ };
+
+ xhr.onerror = function (err) {
+ throw new TypeError('Network request failed');
+ };
+ xhr.open('GET', 'http://cache-endpoint/charof10', true);
+ xhr.send(null);
+ });
+
+ it('should proxy GET request large (string)', function (done) {
+
+ var xhr = new XMLHttpRequest();
+
+ xhr.onloadstart = function () {
+ xhr.onprogress = function () {
+ xhr.onload = function () {
+ assert.equal(xhr.status, 200);
+ assert.equal(typeof xhr.response, 'string');
+ assert.notEqual(xhr.response.lentgh, 0);
+ assert.equal(xhr.getResponseHeader('content-type'), 'text/plain; charset=utf-8');
+ done();
+ };
+ };
+ };
+
+ xhr.onerror = function (err) {
+ throw new TypeError('Network request failed');
+ };
+ xhr.open('GET', 'http://cache-endpoint/charof' + largeRequestCharSize, true);
+ xhr.send(null);
+ });
+
+ it('should proxy GET request large (gzip+string)', function (done) {
+
+ var xhr = new XMLHttpRequest();
+
+ xhr.onloadstart = function () {
+ xhr.onprogress = function () {
+ xhr.onload = function () {
+ assert.equal(xhr.status, 200);
+ assert.equal(typeof xhr.response, 'string');
+ assert.notEqual(xhr.response.lentgh, 0);
+ assert.equal(xhr.getResponseHeader('content-type'), 'text/plain; charset=utf-8');
+ done();
+ };
+ };
+ };
+
+ xhr.onerror = function (err) {
+ throw new TypeError('Network request failed');
+ };
+ xhr.open('GET', 'http://cache-endpoint/gzip/charof' + largeRequestCharSize, true);
+ xhr.send(null);
+ });
+
+ it('should proxy GET request large (arraybuffer)', function (done) {
+
+ var xhr = new XMLHttpRequest();
+ xhr.responseType = 'arraybuffer';
+
+ xhr.onloadstart = function () {
+ xhr.onprogress = function () {
+ xhr.onload = function () {
+ assert.equal(xhr.status, 200);
+ assert.equal(typeof xhr.response, 'object');
+ assert.notEqual(xhr.response.lentgh, 0);
+ assert.equal(xhr.getResponseHeader('content-type'), 'text/plain; charset=utf-8');
+ done();
+ };
+ };
+ };
+
+ xhr.onerror = function (err) {
+ throw new TypeError('Network request failed');
+ };
+ xhr.open('GET', 'http://cache-endpoint/charof' + largeRequestCharSize, true);
+ xhr.send(null);
+ });
+ });
+
+ describe('HTTP2.js using Worker (Transferable ArrayBuffer)', function () {
+
+ before('configure http2 proxy, and worker (wait 250)', function (done) {
+ XMLHttpRequest.configuration.useTransferable = true;
+ XMLHttpRequest.configuration.useWorker = true;
+ XMLHttpRequest.configuration.terminateWorker(true);
+ XMLHttpRequest.proxy(["http://" + hostname + ":7080/config"]);
+ setTimeout(done, 250);
+ });
+
+ it('should proxy GET request small', function (done) {
+
+ var xhr = new XMLHttpRequest();
+
+ xhr.onloadstart = function () {
+ xhr.onprogress = function () {
+ xhr.onload = function () {
+ assert.equal(xhr.status, 200);
+ assert.equal(typeof xhr.response, 'string');
+ assert.notEqual(xhr.response.lentgh, 0);
+ assert.equal(xhr.getResponseHeader('content-type'), 'text/plain; charset=utf-8');
+ done();
+ };
+ };
+ };
+
+ xhr.onerror = function (err) {
+ throw new TypeError('Network request failed');
+ };
+ xhr.open('GET', 'http://cache-endpoint/charof10', true);
+ xhr.send(null);
+ });
+
+ it('should proxy GET request large (string)', function (done) {
+
+ var xhr = new XMLHttpRequest();
+ XMLHttpRequest.configuration.useTransferable = true;
+
+ xhr.onloadstart = function () {
+ xhr.onprogress = function () {
+ xhr.onload = function () {
+ assert.equal(xhr.status, 200);
+ assert.equal(typeof xhr.response, 'string');
+ assert.notEqual(xhr.response.lentgh, 0);
+ assert.equal(xhr.getResponseHeader('content-type'), 'text/plain; charset=utf-8');
+ done();
+ };
+ };
+ };
+
+ xhr.onerror = function (err) {
+ throw new TypeError('Network request failed');
+ };
+ xhr.open('GET', 'http://cache-endpoint/charof' + largeRequestCharSize, true);
+ xhr.send(null);
+ });
+
+ it('should proxy GET request large (string+gzip)', function (done) {
+
+ var xhr = new XMLHttpRequest();
+ XMLHttpRequest.configuration.useTransferable = true;
+
+ xhr.onloadstart = function () {
+ xhr.onprogress = function () {
+ xhr.onload = function () {
+ assert.equal(xhr.status, 200);
+ assert.equal(typeof xhr.response, 'string');
+ assert.notEqual(xhr.response.lentgh, 0);
+ assert.equal(xhr.getResponseHeader('content-type'), 'text/plain; charset=utf-8');
+ done();
+ };
+ };
+ };
+
+ xhr.onerror = function (err) {
+ throw new TypeError('Network request failed');
+ };
+ xhr.open('GET', 'http://cache-endpoint/gzip/charof' + largeRequestCharSize, true);
+ xhr.send(null);
+ });
+
+ it('should proxy GET request large (arraybuffer)', function (done) {
+
+ var xhr = new XMLHttpRequest();
+ XMLHttpRequest.configuration.useTransferable = true;
+ xhr.responseType = 'arraybuffer';
+
+ xhr.onloadstart = function () {
+ xhr.onprogress = function () {
+ xhr.onload = function () {
+ assert.equal(xhr.status, 200);
+ assert.equal(typeof xhr.response, 'object');
+ assert.notEqual(xhr.response.lentgh, 0);
+ assert.equal(xhr.getResponseHeader('content-type'), 'text/plain; charset=utf-8');
+ done();
+ };
+ };
+ };
+
+ xhr.onerror = function (err) {
+ throw new TypeError('Network request failed');
+ };
+ xhr.open('GET', 'http://cache-endpoint/charof' + largeRequestCharSize, true);
+ xhr.send(null);
+ });
+ });
+});
diff --git a/integration-test/run-in-worker.html b/integration-test/run-in-worker.html
new file mode 100644
index 0000000..5c2209b
--- /dev/null
+++ b/integration-test/run-in-worker.html
@@ -0,0 +1,15 @@
+
+
+ http2-cache worker tests
+
+
+
+
+
+
+
+
diff --git a/integration-test/run-worker.html b/integration-test/run-worker.html
index 5c2209b..1cca5a2 100644
--- a/integration-test/run-worker.html
+++ b/integration-test/run-worker.html
@@ -1,11 +1,48 @@
- http2-cache worker tests
+ http2-cache tests
+
+
+
+
+
+
diff --git a/integration-test/run-worker.js b/integration-test/run-worker.js
index da949ee..fc3670d 100644
--- a/integration-test/run-worker.js
+++ b/integration-test/run-worker.js
@@ -1,8 +1,7 @@
/* globals self, importScripts, console, mocha:true */
-
self.global = self;
-delete self['global'];
+delete self.global;
importScripts('../../../node_modules/mocha/mocha.js');
importScripts('../../../node_modules/chai/chai.js');
@@ -37,7 +36,7 @@ mocha.setup({
ignoreLeaks: false
});
-importScripts('./http-cache-test.js');
+importScripts('./http2-worker-itest.js');
mocha.run();
diff --git a/integration-test/run.html b/integration-test/run.html
index e1c5e3a..e2f901e 100644
--- a/integration-test/run.html
+++ b/integration-test/run.html
@@ -10,7 +10,7 @@
mocha.setup({
allowUncaught: true,
ui: 'bdd',
- slow: 150,
+ slow: 1,
timeout: 15000,
bail: false,
ignoreLeaks: false
diff --git a/integration-test/test-server.js b/integration-test/test-server.js
index 5eec6a5..b775b61 100644
--- a/integration-test/test-server.js
+++ b/integration-test/test-server.js
@@ -1,33 +1,25 @@
-var http = require('http'),
- getSocketServer = require('./../test/test-utils.js').getSocketServer,
+var getSocketTestServer = require('./../test/test-utils.js').getSocketTestServer,
getConfigServer = require('./../test/test-utils.js').getConfigServer;
+var socketServerOps = {
+ //hostname: '192.168.6.143',
+ hostname: 'localhost',
+ port: 7081
+};
var configServerOps = {
config: {
- 'transport': 'ws://localhost:7081/path',
- 'worker': true,
+ 'transport': 'ws://' + socketServerOps.hostname + ':' + socketServerOps.port + '/path',
'proxy': [
- 'http://cache-endpoint/',
- 'http://localhost:7080/path/proxy',
+ 'http://cache-endpoint/'
]
},
- port: 7080
-};
-
-getConfigServer(configServerOps);
-
-var socketServerOps = {
- port: 7081
+ port: 7080
};
+// Start test websocket+http2 server
+getSocketTestServer(socketServerOps);
-var message = "Hello, Dave. You're looking well today.";
-getSocketServer(socketServerOps, function (request, response) {
- response.setHeader('Content-Type', 'text/html');
- response.setHeader('Content-Length', message.length);
- response.setHeader('Cache-Control', 'private, max-age=0');
- response.write(message);
- response.end();
-});
\ No newline at end of file
+// Start config http
+getConfigServer(configServerOps);
diff --git a/lib/configuration.js b/lib/configuration.js
index f3187a3..dc1078d 100644
--- a/lib/configuration.js
+++ b/lib/configuration.js
@@ -10,18 +10,35 @@ var websocket = require('websocket-stream'),
Agent = require('./agent').Agent,
logger = require('./logger'),
parseUrl = require('./utils').parseUrl,
+ dataToType = require('./utils').dataToType,
getOrigin = require('./utils.js').getOrigin,
mergeTypedArrays = require('./utils.js').mergeTypedArrays,
defaultPort = require('./utils.js').defaultPort,
+ runningInWorker = require('./utils.js').runningInWorker,
CacheListener = require('./cache-listener').CacheListener;
//////////////////////////////////////////// Configuration ////////////////////////////////////////////
+// Save global self to worker
+/* global self:true */
+var runInWorker = runningInWorker(),
+ worker = runInWorker ? self : null;
+/* global self:false */
+
+// Detect current script to be injected in worker, null in worker.
+var currentScript = (function () {
+ if (typeof document !== 'undefined' && runInWorker !== true) {
+ return (document.currentScript || (function () {
+ var scripts = document.getElementsByTagName('script');
+ return scripts[scripts.length - 1];
+ }()));
+ }
+}());
function Configuration(options) {
- var that = this;
+ var self = this;
// Init default options
- that.options = assign({}, that.defaultOptions);
+ self.options = assign({}, self.defaultOptions);
// Set options
this.setOptions(options);
@@ -29,35 +46,42 @@ function Configuration(options) {
EventEmitter.call(this);
// Map of Url to transport
- that._proxyMap = {};
+ self._proxyMap = {};
// Map of PushUrl ro PushRequest
- that._pushRequests = {};
+ self._pushRequests = {};
- that._activeConfigurationCnt = 0;
- that._activeTransportConnections = {};
+ self._activeConfigurationCnt = 0;
+ self._activeTransportConnections = {};
// Init debug/log
- that._log = logger.consoleLogger;
- that.setDebugLevel(that.options.clientLogLevel || that.options.debug);
+ self._log = logger.consoleLogger;
+ self.setDebugLevel(self.options.clientLogLevel || self.options.debug);
// Init Cache
- that.cache = options.cache || new Cache({
- debug: that.debug,
- log: that._log
+ self.cache = options.cache || new Cache({
+ debug: self.debug,
+ log: self._log
});
- that.cacheListener = new CacheListener(that);
+ self.cacheListener = new CacheListener(self);
- that.agent = new Agent({
- log: that._log
+ self.agent = new Agent({
+ log: self._log
});
+
+ // Init worker listener
+ if (self.runInWorker === true) {
+ self.initWorker();
+ }
}
util.inherits(Configuration, EventEmitter);
var confProto = Configuration.prototype;
+confProto.runInWorker = runInWorker;
+
confProto.defaultOptions = {
// Logger debugLevel true='info' or (info|debug|trace)
debug: false,
@@ -71,13 +95,13 @@ confProto.defaultOptions = {
accelerationStrategy: 'always',
};
+confProto.debug = false;
+
confProto.setOptions = function (options) {
assign(this.options, options);
return this;
};
-confProto.debug = false;
-
confProto.setDebugLevel = function (level) {
var that = this;
@@ -411,6 +435,9 @@ confProto.parseConfig = function (config) {
return config;
};
+confProto.useWorker = typeof Worker !== 'undefined';
+confProto.useTransferable = typeof ArrayBuffer !== 'undefined';
+
// add config by json
/*
{
@@ -437,27 +464,320 @@ confProto.parseConfig = function (config) {
*/
confProto.addConfig = function (config) {
- var that = this;
+ var self = this;
// Decode config
- config = that.parseConfig(config);
+ config = self.parseConfig(config);
// Legacy with warning
if (config.pushURL) {
- that._log.warn('XMLHttpRequest Http2-Cache configuration "pushURL" is now "push"');
+ self._log.warn('XMLHttpRequest Http2-Cache configuration "pushURL" is now "push"');
config.push = config.pushURL;
delete config.pushURL;
}
// Update clientLogLevel
if (config.hasOwnProperty('clientLogLevel')) {
- that.setDebugLevel(config.clientLogLevel);
+ self.setDebugLevel(config.clientLogLevel);
// Update option to reflect client state
- that.options.clientLogLevel = config.clientLogLevel;
+ self.options.clientLogLevel = config.clientLogLevel;
+ }
+
+ config.worker = typeof config.worker === 'undefined' ?
+ self.useWorker : config.worker;
+
+ // Lookup for defaultOptions keys in config
+ keys(self.defaultOptions).forEach(function (configOption) {
+ if (config.hasOwnProperty(configOption)) {
+ // Create config.options if do not exit only if mapping match once at least
+ config.options = config.options || {};
+ config.options[configOption] = config[configOption];
+ }
+ });
+
+ // Merge config options
+ if (config.hasOwnProperty('options') && config.options) {
+ self.setOptions(config.options);
}
+ // Install transport
+ if (config.hasOwnProperty('transport') && config.transport) {
+
+ // Validate transport
+ try {
+
+ var transportUri = parseUrl(config.transport);
+
+
+ if (
+ config.worker !== false &&
+ self.runInWorker === false
+ ) {
+ // Create worker if missing
+ self.registerWorker(config.worker);
+
+ self.configuring();
+
+ // Call addConfig
+ self.channel.port1.postMessage({
+ method: 'addConfig',
+ params: {
+ config: config
+ }
+ });
+
+ // Add transport proxyfied urls
+ if (config.hasOwnProperty('proxy')) {
+ self.addTransportUrls(config.proxy, transportUri);
+ }
+ } else {
+
+ if (config.channel) {
+ self.channel = config.channel;
+ }
+
+ // Add transport proxyfied urls
+ if (config.hasOwnProperty('proxy')) {
+ self.addTransportUrls(config.proxy, transportUri);
+ }
+
+ // Connect transport to push url
+ if (config.hasOwnProperty('push') && config.push) {
+ self.openH2StreamForPush(parseUrl(config.push), transportUri);
+ }
+ }
+
+ } catch (err) {
+ self._log.error('XMLHttpRequest Http2-Cache configuration "transport" error', err);
+ }
+ }
+};
+
+
+confProto.terminateWorker = function () {
+ var self = this;
+ if (typeof self.worker !== 'undefined') {
+ self.worker.terminate();
+ delete self.worker;
+ }
+
+ if (typeof self.channel !== 'undefined') {
+ self.channel.port1.close();
+ self.channel.port2.close();
+ delete self.channel;
+ }
+};
+
+confProto.registerWorker = function (worker) {
+ var self = this;
+
+ // Only one
+ this.terminateWorker();
+
+ // TODO detect location
+ if (typeof Worker === 'undefined') {
+ throw new Error('Worker not supported');
+ } else if (worker instanceof Worker) {
+ self.worker = worker;
+ } else if (typeof worker === 'string') {
+ worker = self.worker = new Worker(worker);
+ } else if (typeof worker === 'boolean') {
+ if (
+ currentScript !== null &&
+ typeof currentScript.src !== 'undefined'
+ ) {
+ worker = self.worker = new Worker(currentScript.src);
+ } else {
+ throw new Error('Unable to detect http2-cache script location');
+ }
+ } else {
+ throw new Error('Invalid worker options.');
+ }
+
+ if (typeof self.channel !== 'undefined') {
+ throw new Error('Channel already open.');
+ }
+
+ // TODO close MessageChannel
+ var channel = self.channel = new MessageChannel();
+
+ // TODO add push event support
+
+ channel.port1.onmessage = function (event) {
+ //console.log('channel onmessage', event);
+ var data = event.data;
+ if (
+ typeof data === 'object' &&
+ typeof data.method === 'string'
+ ) {
+ if (data.method === 'configured') {
+ self.configured();
+ } else {
+ throw new Error('Unknow method: ' + data.method);
+ }
+ } else {
+ throw new Error('Unknow event:' + event);
+ }
+ };
+
+ worker.postMessage({
+ method: 'registerWorkerPort',
+ params: {
+ port: channel.port2
+ }
+ }, [channel.port2]);
+};
+
+confProto.initWorker = function () {
+ //console.log('initWorker');
+ var self = this;
+ worker.addEventListener('message', function (event) {
+ //console.log('initWorker message', event);
+ var data = event.data;
+ if (
+ typeof data === 'object' &&
+ typeof data.method === 'string'
+ ) {
+ if (data.method === 'registerWorkerPort') {
+ self.registerWorkerPort(data.params.port);
+ } else {
+ throw new Error('Unknow method: ' + data.method);
+ }
+ } else {
+ throw new Error('Unknow event:' + event);
+ }
+ });
+};
+
+/**
+ * XmlHttpRequest's getAllResponseHeaders() method returns a string of response
+ * headers according to the format described here:
+ * http://www.w3.org/TR/XMLHttpRequest/#the-getallresponseheaders-method
+ * This method parses that string into a user-friendly key/value pair object.
+ */
+function parseResponseHeaders(headerStr) {
+ var headers = {};
+ if (!headerStr) {
+ return headers;
+ }
+ var headerPairs = headerStr.split('\u000d\u000a');
+ for (var i = 0, len = headerPairs.length; i < len; i++) {
+ var headerPair = headerPairs[i];
+ var index = headerPair.indexOf('\u003a\u0020');
+ if (index > 0) {
+ var key = headerPair.substring(0, index);
+ var val = headerPair.substring(index + 2);
+ headers[key] = val;
+ }
+ }
+ return headers;
+}
+
+confProto.registerWorkerPort = function (port) {
+ // TODO check typeof port
+ //console.log('registerWorkerPort', port);
+ var self = this; // TODO avoid self due worker ?
+ port.onmessage = function (event) {
+ //console.log('registerWorkerPort onmessage', event);
+ var data = event.data;
+ if (
+ typeof data === 'object' &&
+ typeof data.method === 'string'
+ ) {
+ if (data.method === 'addConfig') {
+ var result = self.addConfig(data.params.config);
+ port.postMessage({
+ method: 'configured',
+ params: data.params
+ });
+ } else if (data.method === 'sendViaChannel') {
+
+ // TODO map not only on _sendViaHttp2 but pure xhr in worker also
+ var xhr = new XMLHttpRequest();
+
+ xhr.responsetype = event.data.params.responseType;
+
+ // Share worker client
+ xhr.addEventListener('readystatechange', function () {
+ var state = xhr.readyState,
+ options = {
+ response: {}
+ };
+
+ if (state === XMLHttpRequest.HEADERS_RECEIVED) {
+ options.response.statusCode = parseInt(xhr.status, 10);
+ }
+
+ if (state === XMLHttpRequest.DONE) {
+ options.response.headers = (xhr.responseRaw ?
+ xhr.responseRaw.headers :
+ parseResponseHeaders(xhr.getAllResponseHeaders() || ""));
+ }
+
+ if (state >= XMLHttpRequest.LOADING) {
+ options.response.data = (xhr.responseRaw ?
+ xhr.responseRaw.data : xhr.response);
+ }
+
+ // Use Sharebuffer to prevent copy
+ var transferable = [],
+ // TODO Enable via config ?
+ useTransferable = !!self.useTransferable; // Disabled for now
+
+ // DONE fix Uncaught DataCloneError: Failed to execute 'postMessage' on 'Worker': Value at index 0 does not have a transferable type.
+ // - https://chromium.googlesource.com/chromium/blink/+/72fef91ac1ef679207f51def8133b336a6f6588f/LayoutTests/fast/events/message-port-clone.html
+ // DONE fix Uncaught (in promise) DOMException: Failed to execute 'postMessage' on 'MessagePort': An ArrayBuffer is neutered and could not be cloned.
+ // - https://stackoverflow.com/questions/38169672/why-are-transfered-buffers-neutered-in-javascript/38283644
+ // DONE fix Firefox InvalidStateError: An attempt was made to use an object that is not, or is no longer, usable (http://localhost:8086/dist/http2-cache.js:942)
+ // - Parse header only when XMLHttpRequest.DONE
+ if (
+ useTransferable &&
+ options.response &&
+ options.response.data
+ ) {
+ options.response.buffer = (options.response.data instanceof ArrayBuffer) ?
+ options.response.data : dataToType(options.response.data, 'arraybuffer');
+ delete options.response.data;
+
+ transferable.push(options.response.buffer);
+ }
+
+ data.params.port.postMessage({
+ method: '_changeState',
+ params: {
+ state: state,
+ options: options
+ }
+ }, transferable);
+ });
+
+ if (event.data.params.headers) {
+ event.data.params.headers.forEach(function (value, key) {
+ self.setRequestHeader(key, value);
+ }, self);
+ }
+
+ xhr.open(event.data.params.method, event.data.params.url);
+ xhr.send(event.data.params.body);
+
+ data.params.port.postMessage({
+ method: 'willSendViaChannel',
+ params: event.data.params.url
+ });
+
+ } else {
+ throw new Error('Unknow method: ' + data.method);
+ }
+ } else {
+ throw new Error('Unknow event:' + event);
+ }
+ };
+};
+
+confProto.addConfigTransportUrls = function (config) {
+ var self = this;
// Lookup for defaultOptions keys in config
- keys(that.defaultOptions).forEach(function (configOption) {
+ keys(self.defaultOptions).forEach(function (configOption) {
if (config.hasOwnProperty(configOption)) {
// Create config.options if do not exit only if mapping match once at least
config.options = config.options || {};
@@ -467,7 +787,7 @@ confProto.addConfig = function (config) {
// Merge config options
if (config.hasOwnProperty('options') && config.options) {
- that.setOptions(config.options);
+ self.setOptions(config.options);
}
// Install transport
@@ -480,16 +800,16 @@ confProto.addConfig = function (config) {
// Add transport proxyfied urls
if (config.hasOwnProperty('proxy')) {
- that.addTransportUrls(config.proxy, transportUri);
+ self.addTransportUrls(config.proxy, transportUri);
}
// Connect transport to push url
if (config.hasOwnProperty('push') && config.push) {
- that.openH2StreamForPush(parseUrl(config.push), transportUri);
+ self.openH2StreamForPush(parseUrl(config.push), transportUri);
}
} catch (err) {
- that._log.error('XMLHttpRequest Http2-Cache configuration "transport" error', err);
+ self._log.error('XMLHttpRequest Http2-Cache configuration "transport" error', err);
}
}
};
diff --git a/lib/utils.js b/lib/utils.js
index 754e729..13a9298 100644
--- a/lib/utils.js
+++ b/lib/utils.js
@@ -2,7 +2,6 @@ var url = require('url'),
util = require("util"),
InvalidStateError = require('./errors').InvalidStateError;
-
var resolvePort = function (u) {
u = (u instanceof url.constructor) ? u : url.parse(u);
var port = u.port;
@@ -62,11 +61,6 @@ var redefine = function (obj, prop, value) {
var defineGetter = function (obj, prop, getter) {
- if (obj[prop]) {
- // TODO, consider erasing scope/hiding (enumerable: false)
- obj["_" + prop] = obj[prop];
- }
-
Object.defineProperty(obj, prop, {
enumerable: true,
configurable: true,
@@ -194,7 +188,31 @@ var mergeTypedArrays = function (a, b) {
return c;
};
+var addition = Math.pow(2, 16) -1;
+function arrayBufferToString(buffer){
+
+ var bufView = new Uint8Array(buffer);
+ var length = bufView.length;
+ var result = '';
+
+ for(var i = 0;i length){
+ addition = length - i;
+ }
+ result += String.fromCharCode.apply(null, bufView.subarray(i,i+addition));
+ }
+
+ return result;
+
+}
+
var toArrayBuffer = function (buf) {
+ if (buf instanceof Uint8Array) {
+ // Fix is neutered and could not be clone due parent reference.
+ return buf.buffer.slice(0);
+ }
+
var ab = new ArrayBuffer(buf.length),
view = new Uint8Array(ab);
@@ -212,8 +230,10 @@ var dataToType = function (data, type) {
case "text":
if (data instanceof Uint8Array) {
return Utf8ArrayToStr(data);
- }
- return data;
+ } else if (data instanceof ArrayBuffer) {
+ return arrayBufferToString(data);
+ }
+ break;
case "json":
return JSON.parse(data);
case "arraybuffer":
@@ -309,7 +329,15 @@ var serializeXhrBody = function (headers, body) {
return body;
};
+var runningInWorker = function () {
+ /* global self:true */
+ return typeof window === 'undefined' &&
+ typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope;
+ /* global self:false */
+};
+
module.exports = {
+ runningInWorker: runningInWorker,
parseUrl: parseUrl,
redefine: redefine,
defineGetter: defineGetter,
@@ -324,5 +352,3 @@ module.exports = {
mergeTypedArrays: mergeTypedArrays,
Utf8ArrayToStr: Utf8ArrayToStr
};
-
-
diff --git a/lib/xhr.js b/lib/xhr.js
index 4ff1587..591583d 100644
--- a/lib/xhr.js
+++ b/lib/xhr.js
@@ -65,6 +65,7 @@ function enableXHROverH2(XMLHttpRequest, configuration) {
var xhrInfo = new XhrInfo();
definePublic(XMLHttpRequest, 'configuration', configuration);
+
definePublic(XMLHttpRequest, 'proxy', function (configs) {
return configuration.configure(configs);
});
@@ -83,6 +84,32 @@ function enableXHROverH2(XMLHttpRequest, configuration) {
});
}
+ // Expose response and responseText with cached dataToType
+ function defineXhrResponse(xhr, response) {
+
+ // TODO find a way to expose internaly
+ defineGetter(xhr, 'responseRaw', function () {
+ return response;
+ });
+
+ defineGetter(xhr, 'response', function () {
+ // Render as responseType
+ var responseType = xhrInfo.get(xhr, 'responseType') || '';
+ return dataToType(response.data, responseType);
+ });
+
+ defineGetter(xhr, 'responseText', function () {
+
+ var responseType = xhrInfo.get(xhr, 'responseType') || '';
+ if (responseType !== '' && responseType !== 'text') {
+ throw new InvalidStateError("Failed to read the 'responseText' property from 'XMLHttpRequest': The value is only accessible if the object's 'responseType' is '' or 'text' (was '" + responseType + "')");
+ }
+
+ // Force text rendering
+ return dataToType(response.data, 'text');
+ });
+ }
+
redefineProtoInfo(xhrProto, xhrInfo, "responseType", '');
redefineProtoInfo(xhrProto, xhrInfo, "readyState", 0);
redefineProtoInfo(xhrProto, xhrInfo, "timeout");
@@ -212,28 +239,7 @@ function enableXHROverH2(XMLHttpRequest, configuration) {
}
this.__dispatchEvent(new ProgressEvent('readystatechange'));
});
-
- // Expose response and responseText with cached dataToType
- function defineXhrResponse(xhr, response) {
-
- defineGetter(xhr, 'response', function () {
- // Render as responseType
- var responseType = xhrInfo.get(xhr, 'responseType') || '';
- return dataToType(response.data, responseType);
- });
-
- defineGetter(xhr, 'responseText', function () {
-
- var responseType = xhrInfo.get(xhr, 'responseType') || '';
- if (responseType !== '' && responseType !== 'text') {
- throw new InvalidStateError("Failed to read the 'responseText' property from 'XMLHttpRequest': The value is only accessible if the object's 'responseType' is '' or 'text' (was '" + responseType + "')");
- }
-
- // Force text rendering
- return dataToType(response.data, 'text');
- });
- }
-
+
definePrivate(xhrProto, '_sendViaHttp2', function (destination, body, proxyTransportUrl) {
var self = this,
@@ -341,7 +347,7 @@ function enableXHROverH2(XMLHttpRequest, configuration) {
var request = http2.raw.request({
agent: configuration.agent,
// protocol has already been matched by getting transport url
- // protocol: destination.protocol,
+ //protocol: destination.protocol,
hostname: destination.hostname,
port: destination.port,
path: destination.path,
@@ -484,8 +490,7 @@ function enableXHROverH2(XMLHttpRequest, configuration) {
self.send();
}
});
-
- // Update cache when receive pushRequest
+
request.on('push', function(respo) {
configuration.onPush(respo);
});
@@ -497,6 +502,73 @@ function enableXHROverH2(XMLHttpRequest, configuration) {
});
});
+ definePrivate(xhrProto, '_sendViaChannel', function (body) {
+
+ var self = this,
+ cache = configuration.cache,
+ channel = configuration.channel,
+ requestUrl = xhrInfo.get(self, 'url'),
+ requestMethod = xhrInfo.get(self, 'method'),
+ responseType = xhrInfo.get(self, 'responseType'),
+ requestHeaders = xhrInfo.get(self, 'headers'),
+ requestInfo = new RequestInfo(requestMethod, requestUrl, requestHeaders);
+
+ xhrInfo.put(self, 'channel');
+
+ // TODO close MessageChannel
+ var xhrChannel = new MessageChannel(),
+ xhrChannelResponse = null;
+
+ xhrChannel.port1.onmessage = function (event) {
+ //console.log('sendViaHttp2 channel onmessage', event);
+ var data = event.data;
+ if (
+ typeof data === 'object' &&
+ typeof data.method === 'string'
+ ) {
+ if (data.method === 'willSendViaChannel') {
+ self.__dispatchEvent(new ProgressEvent('loadstart'));
+ } else if (data.method === '_changeState') {
+
+ xhrChannelResponse = data.params.options.response;
+ if (xhrChannelResponse) {
+
+ // Use buffer has data
+ if (xhrChannelResponse.buffer) {
+ xhrChannelResponse.data = xhrChannelResponse.buffer;
+ }
+
+ defineXhrResponse(self, xhrChannelResponse);
+ }
+
+ self._changeState(
+ data.params.state,
+ data.params.options
+ );
+ } else {
+ throw new Error('Unknow method: ' + data.method);
+ }
+ } else {
+ throw new Error('Unknow event: ' + event);
+ }
+ };
+
+ channel.port1.postMessage({
+ method: 'sendViaChannel',
+ params: {
+ port: xhrChannel.port2,
+ url: requestUrl,
+ responseType: responseType,
+ method: requestMethod,
+ headers: requestHeaders
+ }
+ }, [xhrChannel.port2]);
+
+ // Set requestInfo revalidate state
+ cache.revalidate(requestInfo);
+
+ });
+
redefine(xhrProto, 'send', function (body) {
var self = this,
@@ -532,7 +604,12 @@ function enableXHROverH2(XMLHttpRequest, configuration) {
body = serializeXhrBody(headers, body);
}
- self._sendViaHttp2(destination, body, proxyTransportUrl);
+ // Use channel
+ if (configuration.channel) {
+ self._sendViaChannel(destination, body, proxyTransportUrl);
+ } else {
+ self._sendViaHttp2(destination, body, proxyTransportUrl);
+ }
} else {
@@ -550,16 +627,18 @@ function enableXHROverH2(XMLHttpRequest, configuration) {
// Restore responseType
self._responseType = xhrInfo.get(self, 'responseType') || '';
-
// Fix support for http2.js only if FormData is not defined and not null
if (body && typeof FormData === "undefined") {
body = serializeXhrBody(headers, body);
}
-
+
// Reset Headers
- headers.forEach(function (value, key) {
- self._setRequestHeader(key, value);
- }, self);
+ var requestHeaders = xhrInfo.get(self, 'headers');
+ if (requestHeaders) {
+ requestHeaders.forEach(function (value, key) {
+ self._setRequestHeader(key, value);
+ }, self);
+ }
self._send(body);
}
@@ -641,4 +720,4 @@ function enableXHROverH2(XMLHttpRequest, configuration) {
module.exports = {
enableXHROverH2: enableXHROverH2
-};
+};
\ No newline at end of file
diff --git a/package.json b/package.json
index f9c26a0..40ec3c8 100644
--- a/package.json
+++ b/package.json
@@ -28,8 +28,10 @@
"test:karma-travis": "npm run build:integration && karma start --no-auto-watch --single-run --browsers=Chrome_travis_ci",
"test:karma-dev": "karma start --auto-watch --no-single-run --capture",
"test:browser": "npm run build:integration && concurrently \"http-server -p 8086\" \"open http://localhost:8086/integration-test/run.html\"",
+ "test:browser-worker": "concurrently \"node integration-test/test-server\" \"http-server -p 8086\" \"open http://localhost:8086/integration-test/run-worker.html\"",
"test:jasmine": "npm run test:browser",
"integration": "npm run test:browser",
+ "integration:worker": "npm run test:browser-worker",
"build": "npm run build:browserify && npm run build:uglify",
"build:browserify": "browserify -p [ browserify-banner --template '<%= pkg.name %> v<%= pkg.version %>' ] -r ./lib/http2-cache.js -s http2-cache > ./dist/http2-cache.js",
"build:uglify": "uglifyjs --comments '/^/*!/' dist/http2-cache.js -c > ./dist/http2-cache.min.js",
diff --git a/test/.jshintrc b/test/.jshintrc
new file mode 100644
index 0000000..5164d79
--- /dev/null
+++ b/test/.jshintrc
@@ -0,0 +1,3 @@
+{
+ "mocha": true
+}
\ No newline at end of file
diff --git a/test/test-utils.js b/test/test-utils.js
index 3e68e6e..1f661e0 100644
--- a/test/test-utils.js
+++ b/test/test-utils.js
@@ -109,6 +109,61 @@ var defaultResponseHeaders = {
};
+function generateRandAlphaNumStr(len) {
+ var rdmString = "";
+ while (rdmString.length < len) {
+ rdmString += Math.random().toString(36).substr(2);
+ }
+ return rdmString;
+}
+
+var UTF8_BYTES_REG = /%[89ABab]/g;
+function lengthInUtf8Bytes(str) {
+ // Matches only the 10.. bytes that are non-initial characters in a multi-byte sequence.
+ var m = encodeURIComponent(str).match(UTF8_BYTES_REG);
+ return str.length + (m ? m.length : 0);
+}
+
+
+function sendResponse(request, response, body) {
+ response.writeHead(200, Object.assign({
+ "Content-Type": 'text/plain; charset=utf-8',
+ // TODO 'Content-Length' via lengthInUtf8Bytes ?
+ }, defaultResponseHeaders));
+ var buf = Buffer.from(body, 'utf8');
+ response.write(buf);
+ response.end();
+}
+
+var zlib = require('zlib');
+function sendGzipResponse(request, response, body) {
+
+ var acceptEncoding = request.headers['accept-encoding'];
+ if (!acceptEncoding) {
+ acceptEncoding = '';
+ }
+
+ // Note: this is not a conformant accept-encoding parser.
+ // See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.3
+ if (acceptEncoding.match(/\bdeflate\b/)) {
+ response.writeHead(200, Object.assign({
+ "Content-Type": 'text/plain; charset=utf-8',
+ "content-encoding": 'deflate'
+ }, defaultResponseHeaders));
+ response.write(Buffer.from(zlib.deflateSync(body)));
+ response.end();
+ } else if (acceptEncoding.match(/\bgzip\b/)) {
+ response.writeHead(200, Object.assign({
+ "Content-Type": 'text/plain; charset=utf-8',
+ "content-encoding": 'gzip'
+ }, defaultResponseHeaders));
+ response.write(Buffer.from(zlib.gzipSync(body)));
+ response.end();
+ } else {
+ send(request, response, body);
+ }
+}
+
function _getConfigServer(options, onStart) {
return http.createServer(function (request, response) {
@@ -152,13 +207,23 @@ function _getConfigServer(options, onStart) {
data: Date.now()
}));
}
+ } else if (path.startsWith("/charof")) {
+ var charSize = parseInt(request.url.replace("/charof", ""), 10) || 8192;
+ var charBody = generateRandAlphaNumStr(charSize);
+ var charLength = lengthInUtf8Bytes(charBody);
+ sendResponse(request, response, charBody);
+
+ } else if (path.startsWith("/gzip/charof")) {
+ var charSize = parseInt(request.url.replace("/gzip/charof", ""), 10) || 8192;
+ var charBody = generateRandAlphaNumStr(charSize);
+ var charLength = lengthInUtf8Bytes(charBody);
+ sendGzipResponse(request, response, charBody);
} else {
- console.warn("Request for unknown path: " + path);
response.writeHead(404);
response.end("Not Found");
}
- }).listen(options.port, function (){
+ }).listen(options.port, function () {
console.log("Listening on " + options.port);
if (typeof onStart === 'function') {
onStart();
@@ -180,21 +245,6 @@ function unicodeStringToTypedArray(s) {
return ua;
}
-function generateRandAlphaNumStr(len) {
- var rdmString = "";
- while (rdmString.length < len) {
- rdmString += Math.random().toString(36).substr(2);
- }
- return rdmString;
-}
-
-var UTF8_BYTES_REG = /%[89ABab]/g;
-function lengthInUtf8Bytes(str) {
- // Matches only the 10.. bytes that are non-initial characters in a multi-byte sequence.
- var m = encodeURIComponent(str).match(UTF8_BYTES_REG);
- return str.length + (m ? m.length : 0);
-}
-
function getConfigServer(options, onStart) {
if (
typeof window !== 'undefined' ||
@@ -206,6 +256,7 @@ function getConfigServer(options, onStart) {
}
}
+
function getSocketServer(options, onRequest, onStart) {
if (
typeof window !== 'undefined' ||
@@ -217,10 +268,33 @@ function getSocketServer(options, onRequest, onStart) {
}
}
+function getSocketTestServer(options, onStart) {
+ return getSocketServer(options, function (request, response) {
+
+ if (request.url.startsWith("/charof")) {
+ var charSize = parseInt(request.url.replace("/charof", ""), 10) || 8192,
+ charBody = generateRandAlphaNumStr(charSize),
+ charLength = lengthInUtf8Bytes(charBody);
+ sendResponse(request, response, charBody);
+ } else if (request.url.startsWith("/gzip/charof")) {
+ var charGzipSize = parseInt(request.url.replace("/charof", ""), 10) || 8192,
+ charGzipBody = generateRandAlphaNumStr(charGzipSize);
+ //send(request, response, charBody);
+ sendGzipResponse(request, response, charGzipBody);
+ } else {
+ response.writeHead(404);
+ response.end("Not Found");
+ }
+ }, onStart);
+}
+
module.exports = {
+ sendResponse: sendResponse,
+ sendGzipResponse: sendGzipResponse,
generateRandAlphaNumStr: generateRandAlphaNumStr,
lengthInUtf8Bytes: lengthInUtf8Bytes,
unicodeStringToTypedArray: unicodeStringToTypedArray,
getConfigServer: getConfigServer,
+ getSocketTestServer: getSocketTestServer,
getSocketServer: getSocketServer
};
\ No newline at end of file
diff --git a/test/utils-test.js b/test/utils-test.js
index abaa4a4..234a811 100644
--- a/test/utils-test.js
+++ b/test/utils-test.js
@@ -1,11 +1,13 @@
/* global console, global */
var mergeTypedArrays = require('../lib/utils').mergeTypedArrays,
- Utf8ArrayToStr = require('../lib/utils').Utf8ArrayToStr,
- parseUrl = require('../lib/utils').parseUrl,
- FormData = require('../lib/form-data').FormData,
- serializeXhrBody = require('../lib/utils').serializeXhrBody,
+ Utf8ArrayToStr = require('../lib/utils').Utf8ArrayToStr,
+ parseUrl = require('../lib/utils').parseUrl,
+ runningInWorker = require('../lib/utils').runningInWorker,
+ FormData = require('../lib/form-data').FormData,
+ serializeXhrBody = require('../lib/utils').serializeXhrBody,
unicodeStringToTypedArray = require('./test-utils').unicodeStringToTypedArray,
generateRandAlphaNumStr = require('./test-utils').generateRandAlphaNumStr;
+
var assert = require('assert');
@@ -18,82 +20,86 @@ require("../lib/http2-cache");
describe('utils', function () {
- describe('parseUrl', function () {
- it('should parse url with custom port', function () {
- var url = "https://example.com:8080/path?query=1",
- uri = parseUrl(url);
- assert.equal(uri.port, 8080);
- assert.equal(uri.host, uri.hostname + ":" + uri.port);
- assert.equal(uri.href, url);
- });
-
- it('should parse url with default https port', function () {
- var url = "https://example.com/path?query=1",
- uri = parseUrl(url);
- assert.equal(uri.port, 443);
- assert.equal(uri.host, uri.hostname + ":" + uri.port);
- assert.equal(uri.href, url.replace(uri.hostname, uri.host));
- });
-
- it('should parse url with default http port', function () {
- var url = "http://example.com/path?query=1",
- uri = parseUrl(url);
-
- assert.equal(uri.port, 80);
- assert.equal(uri.host, uri.hostname + ":" + uri.port);
- assert.equal(uri.href, url.replace(uri.hostname, uri.host));
- });
-
-
- it('should add defult protocol, hostname, and port', function () {
-
- global.window = {
- location: {
- hostname: 'example.com',
- protocol: 'https:',
- port: '8080'
- }
- };
-
- var url = "/path?query=1",
- uri = parseUrl(url);
- assert.equal(uri.port, 8080);
- assert.equal(uri.host, 'example.com:8080');
- assert.equal(uri.href, "https://example.com:8080/path?query=1");
- });
- });
-
- describe('Utf8ArrayToStr', function () {
- it('should convert Utf8Array to string', function () {
- var aStr = generateRandAlphaNumStr(2500),
- a = unicodeStringToTypedArray(aStr);
- assert.equal(Utf8ArrayToStr(a), aStr);
- });
- it('should handle 4+ byte sequences', function () {
- assert.equal(Utf8ArrayToStr([240,159,154,133]), '🚅');
- assert.equal(Utf8ArrayToStr([226,152,131]), '☃');
- });
- });
-
- describe('mergeTypedArrays', function () {
- it('should merge Utf8Array', function () {
- var aStr = generateRandAlphaNumStr(2500),
- bStr = generateRandAlphaNumStr(2500),
- a = unicodeStringToTypedArray(aStr),
- b = unicodeStringToTypedArray(bStr),
- c = unicodeStringToTypedArray(aStr + bStr);
- assert.equal(Utf8ArrayToStr(mergeTypedArrays(a, b)), Utf8ArrayToStr(c));
- });
- });
+ describe('runningInWorker', function () {
+ assert.equal(runningInWorker(), false);
+ });
+
+ describe('parseUrl', function () {
+ it('should parse url with custom port', function () {
+ var url = "https://example.com:8080/path?query=1",
+ uri = parseUrl(url);
+ assert.equal(uri.port, 8080);
+ assert.equal(uri.host, uri.hostname + ":" + uri.port);
+ assert.equal(uri.href, url);
+ });
+
+ it('should parse url with default https port', function () {
+ var url = "https://example.com/path?query=1",
+ uri = parseUrl(url);
+ assert.equal(uri.port, 443);
+ assert.equal(uri.host, uri.hostname + ":" + uri.port);
+ assert.equal(uri.href, url.replace(uri.hostname, uri.host));
+ });
+
+ it('should parse url with default http port', function () {
+ var url = "http://example.com/path?query=1",
+ uri = parseUrl(url);
+
+ assert.equal(uri.port, 80);
+ assert.equal(uri.host, uri.hostname + ":" + uri.port);
+ assert.equal(uri.href, url.replace(uri.hostname, uri.host));
+ });
+
+
+ it('should add defult protocol, hostname, and port', function () {
+
+ global.window = {
+ location: {
+ hostname: 'example.com',
+ protocol: 'https:',
+ port: '8080'
+ }
+ };
+
+ var url = "/path?query=1",
+ uri = parseUrl(url);
+ assert.equal(uri.port, 8080);
+ assert.equal(uri.host, 'example.com:8080');
+ assert.equal(uri.href, "https://example.com:8080/path?query=1");
+ });
+ });
+
+ describe('Utf8ArrayToStr', function () {
+ it('should convert Utf8Array to string', function () {
+ var aStr = generateRandAlphaNumStr(2500),
+ a = unicodeStringToTypedArray(aStr);
+ assert.equal(Utf8ArrayToStr(a), aStr);
+ });
+ it('should handle 4+ byte sequences', function () {
+ assert.equal(Utf8ArrayToStr([240,159,154,133]), '🚅');
+ assert.equal(Utf8ArrayToStr([226,152,131]), '☃');
+ });
+ });
+
+ describe('mergeTypedArrays', function () {
+ it('should merge Utf8Array', function () {
+ var aStr = generateRandAlphaNumStr(2500),
+ bStr = generateRandAlphaNumStr(2500),
+ a = unicodeStringToTypedArray(aStr),
+ b = unicodeStringToTypedArray(bStr),
+ c = unicodeStringToTypedArray(aStr + bStr);
+ assert.equal(Utf8ArrayToStr(mergeTypedArrays(a, b)), Utf8ArrayToStr(c));
+ });
+ });
describe('serializeXhrBody', function () {
it('should merge serialize Xhr Body', function () {
+ var headers = new Map();
var formData = new FormData();
formData.append('username', 'Chris');
formData.append('username', 'Bob');
formData.append('gender', 'male');
- var headers = new Map();
var seed = formData._TestBoundary = (+(new Date())).toString(16);
assert.equal(serializeXhrBody(headers, formData),