mirror of
https://github.com/servo/servo.git
synced 2025-08-11 08:25:32 +01:00
Update web-platform-tests to revision e92532746b7615dcccdfa060937a87664816b1db
This commit is contained in:
parent
cccca27f4f
commit
726b56aa12
149 changed files with 22796 additions and 1884 deletions
11
tests/wpt/web-platform-tests/tools/pywebsocket/CONTRIBUTING
Normal file
11
tests/wpt/web-platform-tests/tools/pywebsocket/CONTRIBUTING
Normal file
|
@ -0,0 +1,11 @@
|
|||
For instructions for contributing code, please read:
|
||||
https://github.com/google/pywebsocket/wiki/CodeReviewInstruction
|
||||
|
||||
You must complete the Individual Contributor License Agreement.
|
||||
https://cla.developers.google.com/about/google-individual
|
||||
You can do this online, and it only takes a minute.
|
||||
|
||||
If you are contributing on behalf of a corporation, you must fill out the
|
||||
Corporate Contributor License Agreement
|
||||
https://cla.developers.google.com/about/google-corporate
|
||||
and send it to us as described on that page.
|
8
tests/wpt/web-platform-tests/tools/pywebsocket/README.md
Normal file
8
tests/wpt/web-platform-tests/tools/pywebsocket/README.md
Normal file
|
@ -0,0 +1,8 @@
|
|||
|
||||
# pywebsocket #
|
||||
|
||||
The pywebsocket project aims to provide a [WebSocket](https://tools.ietf.org/html/rfc6455) standalone server and a WebSocket extension for [Apache HTTP Server](https://httpd.apache.org/), mod\_pywebsocket.
|
||||
|
||||
pywebsocket is intended for **testing** or **experimental** purposes.
|
||||
|
||||
Please see [Wiki](../../wiki) for more details.
|
|
@ -52,65 +52,35 @@ function getConfig() {
|
|||
// If the size of each message is small, send/receive multiple messages
|
||||
// until the sum of sizes reaches this threshold.
|
||||
minTotal: getIntFromInput('mintotal'),
|
||||
multipliers: getIntArrayFromInput('multipliers'),
|
||||
verifyData: getBoolFromCheckBox('verifydata')
|
||||
multipliers: getFloatArrayFromInput('multipliers'),
|
||||
verifyData: getBoolFromCheckBox('verifydata'),
|
||||
addToLog: addToLog,
|
||||
addToSummary: addToSummary,
|
||||
measureValue: measureValue,
|
||||
notifyAbort: notifyAbort
|
||||
};
|
||||
}
|
||||
|
||||
var worker = new Worker('benchmark.js');
|
||||
worker.onmessage = onMessage;
|
||||
|
||||
function onSendBenchmark() {
|
||||
var config = getConfig();
|
||||
|
||||
if (getBoolFromCheckBox('worker')) {
|
||||
worker.postMessage({type: 'sendBenchmark', config: config});
|
||||
} else {
|
||||
config.addToLog = addToLog;
|
||||
config.addToSummary = addToSummary;
|
||||
config.measureValue = measureValue;
|
||||
sendBenchmark(config);
|
||||
}
|
||||
doAction(config, getBoolFromCheckBox('worker'), 'sendBenchmark');
|
||||
}
|
||||
|
||||
function onReceiveBenchmark() {
|
||||
var config = getConfig();
|
||||
|
||||
if (getBoolFromCheckBox('worker')) {
|
||||
worker.postMessage({type: 'receiveBenchmark', config: config});
|
||||
} else {
|
||||
config.addToLog = addToLog;
|
||||
config.addToSummary = addToSummary;
|
||||
config.measureValue = measureValue;
|
||||
receiveBenchmark(config);
|
||||
}
|
||||
doAction(config, getBoolFromCheckBox('worker'), 'receiveBenchmark');
|
||||
}
|
||||
|
||||
function onBatchBenchmark() {
|
||||
var config = getConfig();
|
||||
|
||||
if (getBoolFromCheckBox('worker')) {
|
||||
worker.postMessage({type: 'batchBenchmark', config: config});
|
||||
} else {
|
||||
config.addToLog = addToLog;
|
||||
config.addToSummary = addToSummary;
|
||||
config.measureValue = measureValue;
|
||||
batchBenchmark(config);
|
||||
}
|
||||
doAction(config, getBoolFromCheckBox('worker'), 'batchBenchmark');
|
||||
}
|
||||
|
||||
function onStop() {
|
||||
var config = getConfig();
|
||||
|
||||
if (getBoolFromCheckBox('worker')) {
|
||||
worker.postMessage({type: 'stop', config: config});
|
||||
} else {
|
||||
config.addToLog = addToLog;
|
||||
config.addToSummary = addToSummary;
|
||||
config.measureValue = measureValue;
|
||||
stop(config);
|
||||
}
|
||||
doAction(config, getBoolFromCheckBox('worker'), 'stop');
|
||||
}
|
||||
|
||||
function init() {
|
||||
addressBox = document.getElementById('address');
|
||||
logBox = document.getElementById('log');
|
||||
|
@ -128,6 +98,8 @@ function init() {
|
|||
if (!('WebSocket' in window)) {
|
||||
addToLog('WebSocket is not available');
|
||||
}
|
||||
|
||||
initWorker('WebSocket', '');
|
||||
}
|
||||
</script>
|
||||
</head>
|
|
@ -32,7 +32,7 @@ function destroyAllSockets() {
|
|||
sockets = [];
|
||||
}
|
||||
|
||||
function sendBenchmarkStep(size, config) {
|
||||
function sendBenchmarkStep(size, config, isWarmUp) {
|
||||
timerID = null;
|
||||
|
||||
var totalSize = 0;
|
||||
|
@ -41,6 +41,7 @@ function sendBenchmarkStep(size, config) {
|
|||
var onMessageHandler = function(event) {
|
||||
if (!verifyAcknowledgement(config, event.data, size)) {
|
||||
destroyAllSockets();
|
||||
config.notifyAbort();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -50,7 +51,8 @@ function sendBenchmarkStep(size, config) {
|
|||
return;
|
||||
}
|
||||
|
||||
calculateAndLogResult(config, size, benchmark.startTimeInMs, totalSize);
|
||||
calculateAndLogResult(config, size, benchmark.startTimeInMs, totalSize,
|
||||
isWarmUp);
|
||||
|
||||
runNextTask(config);
|
||||
};
|
||||
|
@ -89,7 +91,7 @@ function sendBenchmarkStep(size, config) {
|
|||
}
|
||||
}
|
||||
|
||||
function receiveBenchmarkStep(size, config) {
|
||||
function receiveBenchmarkStep(size, config, isWarmUp) {
|
||||
timerID = null;
|
||||
|
||||
var totalSize = 0;
|
||||
|
@ -101,12 +103,14 @@ function receiveBenchmarkStep(size, config) {
|
|||
config.addToLog('Expected ' + size + 'B but received ' +
|
||||
bytesReceived + 'B');
|
||||
destroyAllSockets();
|
||||
config.notifyAbort();
|
||||
return;
|
||||
}
|
||||
|
||||
if (config.verifyData && !verifyArrayBuffer(event.data, 0x61)) {
|
||||
config.addToLog('Response verification failed');
|
||||
destroyAllSockets();
|
||||
config.notifyAbort();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -116,7 +120,8 @@ function receiveBenchmarkStep(size, config) {
|
|||
return;
|
||||
}
|
||||
|
||||
calculateAndLogResult(config, size, benchmark.startTimeInMs, totalSize);
|
||||
calculateAndLogResult(config, size, benchmark.startTimeInMs, totalSize,
|
||||
isWarmUp);
|
||||
|
||||
runNextTask(config);
|
||||
};
|
||||
|
@ -153,12 +158,11 @@ function createSocket(config) {
|
|||
};
|
||||
socket.onclose = function(event) {
|
||||
config.addToLog('Closed');
|
||||
config.notifyAbort();
|
||||
};
|
||||
return socket;
|
||||
}
|
||||
|
||||
var tasks = [];
|
||||
|
||||
function startBenchmark(config) {
|
||||
clearTimeout(timerID);
|
||||
destroyAllSockets();
|
||||
|
@ -180,24 +184,6 @@ function startBenchmark(config) {
|
|||
}
|
||||
}
|
||||
|
||||
function runNextTask(config) {
|
||||
var task = tasks.shift();
|
||||
if (task == undefined) {
|
||||
config.addToLog('Finished');
|
||||
destroyAllSockets();
|
||||
return;
|
||||
}
|
||||
timerID = setTimeout(task, 0);
|
||||
}
|
||||
|
||||
function buildLegendString(config) {
|
||||
var legend = ''
|
||||
if (config.printSize)
|
||||
legend = 'Message size in KiB, Time/message in ms, ';
|
||||
legend += 'Speed in kB/s';
|
||||
return legend;
|
||||
}
|
||||
|
||||
function getConfigString(config) {
|
||||
return '(WebSocket' +
|
||||
', ' + (typeof importScripts !== "undefined" ? 'Worker' : 'Main') +
|
||||
|
@ -209,69 +195,6 @@ function getConfigString(config) {
|
|||
')';
|
||||
}
|
||||
|
||||
function addTasks(config, stepFunc) {
|
||||
for (var i = 0;
|
||||
i < config.numWarmUpIterations + config.numIterations; ++i) {
|
||||
// Ignore the first |config.numWarmUpIterations| iterations.
|
||||
if (i == config.numWarmUpIterations)
|
||||
addResultClearingTask(config);
|
||||
|
||||
var multiplierIndex = 0;
|
||||
for (var size = config.startSize;
|
||||
size <= config.stopThreshold;
|
||||
++multiplierIndex) {
|
||||
var task = stepFunc.bind(
|
||||
null,
|
||||
size,
|
||||
config);
|
||||
tasks.push(task);
|
||||
size *= config.multipliers[
|
||||
multiplierIndex % config.multipliers.length];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function addResultReportingTask(config, title) {
|
||||
tasks.push(function(){
|
||||
timerID = null;
|
||||
config.addToSummary(title);
|
||||
reportAverageData(config);
|
||||
clearAverageData();
|
||||
runNextTask(config);
|
||||
});
|
||||
}
|
||||
|
||||
function addResultClearingTask(config) {
|
||||
tasks.push(function(){
|
||||
timerID = null;
|
||||
clearAverageData();
|
||||
runNextTask(config);
|
||||
});
|
||||
}
|
||||
|
||||
function sendBenchmark(config) {
|
||||
config.addToLog('Send benchmark');
|
||||
config.addToLog(buildLegendString(config));
|
||||
|
||||
tasks = [];
|
||||
clearAverageData();
|
||||
addTasks(config, sendBenchmarkStep);
|
||||
addResultReportingTask(config, 'Send Benchmark ' + getConfigString(config));
|
||||
startBenchmark(config);
|
||||
}
|
||||
|
||||
function receiveBenchmark(config) {
|
||||
config.addToLog('Receive benchmark');
|
||||
config.addToLog(buildLegendString(config));
|
||||
|
||||
tasks = [];
|
||||
clearAverageData();
|
||||
addTasks(config, receiveBenchmarkStep);
|
||||
addResultReportingTask(config,
|
||||
'Receive Benchmark ' + getConfigString(config));
|
||||
startBenchmark(config);
|
||||
}
|
||||
|
||||
function batchBenchmark(config) {
|
||||
config.addToLog('Batch benchmark');
|
||||
config.addToLog(buildLegendString(config));
|
||||
|
@ -286,24 +209,6 @@ function batchBenchmark(config) {
|
|||
startBenchmark(config);
|
||||
}
|
||||
|
||||
function stop(config) {
|
||||
clearTimeout(timerID);
|
||||
timerID = null;
|
||||
config.addToLog('Stopped');
|
||||
function cleanup() {
|
||||
destroyAllSockets();
|
||||
}
|
||||
|
||||
onmessage = function (message) {
|
||||
var config = message.data.config;
|
||||
config.addToLog = workerAddToLog;
|
||||
config.addToSummary = workerAddToSummary;
|
||||
config.measureValue = workerMeasureValue;
|
||||
if (message.data.type === 'sendBenchmark')
|
||||
sendBenchmark(config);
|
||||
else if (message.data.type === 'receiveBenchmark')
|
||||
receiveBenchmark(config);
|
||||
else if (message.data.type === 'batchBenchmark')
|
||||
batchBenchmark(config);
|
||||
else if (message.data.type === 'stop')
|
||||
stop(config);
|
||||
};
|
|
@ -0,0 +1,163 @@
|
|||
<!--
|
||||
Copyright 2015 Google Inc. All rights reserved.
|
||||
|
||||
Use of this source code is governed by a BSD-style
|
||||
license that can be found in the COPYING file or at
|
||||
https://developers.google.com/open-source/licenses/bsd
|
||||
-->
|
||||
<html>
|
||||
<head>
|
||||
<title>Fetch API benchmark</title>
|
||||
<script src="util_main.js"></script>
|
||||
<script src="util.js"></script>
|
||||
<script src="fetch_benchmark.js"></script>
|
||||
<script>
|
||||
var addressBox = null;
|
||||
|
||||
function getConfig() {
|
||||
return {
|
||||
prefixUrl: addressBox.value,
|
||||
printSize: getBoolFromCheckBox('printsize'),
|
||||
numFetches: getIntFromInput('numFetches'),
|
||||
// Initial size of messages.
|
||||
numIterations: getIntFromInput('numiterations'),
|
||||
numWarmUpIterations: getIntFromInput('numwarmupiterations'),
|
||||
startSize: getIntFromInput('startsize'),
|
||||
// Stops benchmark when the size of message exceeds this threshold.
|
||||
stopThreshold: getIntFromInput('stopthreshold'),
|
||||
multipliers: getFloatArrayFromInput('multipliers'),
|
||||
verifyData: getBoolFromCheckBox('verifydata'),
|
||||
addToLog: addToLog,
|
||||
addToSummary: addToSummary,
|
||||
measureValue: measureValue,
|
||||
notifyAbort: notifyAbort
|
||||
};
|
||||
}
|
||||
|
||||
function onSendBenchmark() {
|
||||
var config = getConfig();
|
||||
config.dataType = getStringFromRadioBox('datatyperadio');
|
||||
doAction(config, getBoolFromCheckBox('worker'), 'sendBenchmark');
|
||||
}
|
||||
|
||||
function onReceiveBenchmark() {
|
||||
var config = getConfig();
|
||||
config.dataType = getStringFromRadioBox('datatyperadio');
|
||||
doAction(config, getBoolFromCheckBox('worker'), 'receiveBenchmark');
|
||||
}
|
||||
|
||||
function onBatchBenchmark() {
|
||||
var config = getConfig();
|
||||
doAction(config, getBoolFromCheckBox('worker'), 'batchBenchmark');
|
||||
}
|
||||
|
||||
function onStop() {
|
||||
var config = getConfig();
|
||||
doAction(config, getBoolFromCheckBox('worker'), 'stop');
|
||||
}
|
||||
|
||||
function init() {
|
||||
addressBox = document.getElementById('address');
|
||||
logBox = document.getElementById('log');
|
||||
|
||||
summaryBox = document.getElementById('summary');
|
||||
|
||||
// Special address of pywebsocket for XHR/Fetch API benchmark.
|
||||
addressBox.value = '/073be001e10950692ccbf3a2ad21c245';
|
||||
|
||||
addToLog(window.navigator.userAgent.toLowerCase());
|
||||
addToSummary(window.navigator.userAgent.toLowerCase());
|
||||
|
||||
initWorker('fetch', '');
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
|
||||
<form id="benchmark_form">
|
||||
url prefix <input type="text" id="address" size="40">
|
||||
<input type="button" value="send" onclick="onSendBenchmark()">
|
||||
<input type="button" value="receive" onclick="onReceiveBenchmark()">
|
||||
<input type="button" value="batch" onclick="onBatchBenchmark()">
|
||||
<input type="button" value="stop" onclick="onStop()">
|
||||
|
||||
<br/>
|
||||
|
||||
<input type="checkbox" id="printsize" checked>
|
||||
<label for="printsize">Print size and time per message</label>
|
||||
<input type="checkbox" id="verifydata" checked>
|
||||
<label for="verifydata">Verify data</label>
|
||||
<input type="checkbox" id="worker">
|
||||
<label for="worker">Run on worker</label>
|
||||
|
||||
<br/>
|
||||
|
||||
Parameters:
|
||||
|
||||
<br/>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td>Number of fetch() requests</td>
|
||||
<td><input type="text" id="numFetches" value="1"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Number of iterations</td>
|
||||
<td><input type="text" id="numiterations" value="1"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Number of warm-up iterations</td>
|
||||
<td><input type="text" id="numwarmupiterations" value="0"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Start size</td>
|
||||
<td><input type="text" id="startsize" value="10240"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Stop threshold</td>
|
||||
<td><input type="text" id="stopthreshold" value="102400000"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Multipliers</td>
|
||||
<td><input type="text" id="multipliers" value="5, 2"></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
Set data type
|
||||
<input type="radio"
|
||||
name="datatyperadio"
|
||||
id="datatyperadiotext"
|
||||
value="text"
|
||||
checked><label for="datatyperadiotext">text</label>
|
||||
<input type="radio"
|
||||
name="datatyperadio"
|
||||
id="datatyperadioblob"
|
||||
value="blob"
|
||||
><label for="datatyperadioblob">blob</label>
|
||||
<input type="radio"
|
||||
name="datatyperadio"
|
||||
id="datatyperadioarraybuffer"
|
||||
value="arraybuffer"
|
||||
><label for="datatyperadioarraybuffer">arraybuffer</label>
|
||||
</form>
|
||||
|
||||
<div id="log_div">
|
||||
<textarea
|
||||
id="log" rows="20" style="width: 100%" readonly></textarea>
|
||||
</div>
|
||||
<div id="summary_div">
|
||||
Summary
|
||||
<textarea
|
||||
id="summary" rows="20" style="width: 100%" readonly></textarea>
|
||||
</div>
|
||||
|
||||
<div id="note_div">
|
||||
Note:
|
||||
<ul>
|
||||
<li>Effect of RTT and time spent for ArrayBuffer creation in receive benchmarks are not eliminated.</li>
|
||||
<li>The Stddev column shows NaN when the number of iterations is set to 1.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,225 @@
|
|||
// Copyright 2015 Google Inc. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the COPYING file or at
|
||||
// https://developers.google.com/open-source/licenses/bsd
|
||||
|
||||
var isWorker = typeof importScripts !== "undefined";
|
||||
|
||||
if (isWorker) {
|
||||
// Running on a worker
|
||||
importScripts('util.js', 'util_worker.js');
|
||||
}
|
||||
|
||||
// Namespace for holding globals.
|
||||
var benchmark = {};
|
||||
benchmark.startTimeInMs = 0;
|
||||
|
||||
var timerID = null;
|
||||
|
||||
function sendBenchmarkStep(size, config, isWarmUp) {
|
||||
timerID = null;
|
||||
benchmark.startTimeInMs = null;
|
||||
|
||||
// Prepare data.
|
||||
var dataArray = [];
|
||||
for (var i = 0; i < config.numFetches; ++i) {
|
||||
var data = null;
|
||||
if (config.dataType == 'arraybuffer' ||
|
||||
config.dataType == 'blob') {
|
||||
data = new ArrayBuffer(size);
|
||||
|
||||
fillArrayBuffer(data, 0x61);
|
||||
|
||||
if (config.dataType == 'blob') {
|
||||
data = new Blob([data]);
|
||||
}
|
||||
} else {
|
||||
data = repeatString('a', size);
|
||||
}
|
||||
|
||||
dataArray.push(data);
|
||||
}
|
||||
|
||||
// Start time measuring.
|
||||
benchmark.startTimeInMs = getTimeStamp();
|
||||
|
||||
// Start fetch.
|
||||
var promises = [];
|
||||
for (var i = 0; i < config.numFetches; ++i) {
|
||||
var data = dataArray[i];
|
||||
var promise = fetch(config.prefixUrl + '_send',
|
||||
{method: 'POST', body: data})
|
||||
.then(function (response) {
|
||||
if (response.status != 200) {
|
||||
config.addToLog('Failed (status=' + response.status + ')');
|
||||
return Promise.reject();
|
||||
}
|
||||
// Check and warn if proxy is enabled.
|
||||
if (response.headers.get('Via') !== null) {
|
||||
config.addToLog('WARNING: proxy seems enabled.');
|
||||
}
|
||||
if (config.verifyData) {
|
||||
return response.text()
|
||||
.then(function(text) {
|
||||
if (!verifyAcknowledgement(config, text, size)) {
|
||||
return Promise.reject();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
promises.push(promise);
|
||||
}
|
||||
|
||||
// Finish and report time measuring.
|
||||
Promise.all(promises)
|
||||
.then(function() {
|
||||
if (benchmark.startTimeInMs == null) {
|
||||
config.addToLog('startTimeInMs not set');
|
||||
return Promise.reject();
|
||||
}
|
||||
calculateAndLogResult(config, size, benchmark.startTimeInMs,
|
||||
size * config.numFetches, isWarmUp);
|
||||
runNextTask(config);
|
||||
})
|
||||
.catch(function(e) {
|
||||
config.addToLog("ERROR: " + e);
|
||||
config.notifyAbort();
|
||||
});
|
||||
}
|
||||
|
||||
function receiveBenchmarkStep(size, config, isWarmUp) {
|
||||
timerID = null;
|
||||
benchmark.startTimeInMs = null;
|
||||
|
||||
// Start time measuring.
|
||||
benchmark.startTimeInMs = getTimeStamp();
|
||||
|
||||
// Start fetch.
|
||||
var promises = [];
|
||||
for (var i = 0; i < config.numFetches; ++i) {
|
||||
var request;
|
||||
if (config.methodAndCache === 'GET-NOCACHE') {
|
||||
request = new Request(config.prefixUrl + '_receive_getnocache?' + size,
|
||||
{method: 'GET'});
|
||||
} else if (config.methodAndCache === 'GET-CACHE') {
|
||||
request = new Request(config.prefixUrl + '_receive_getcache?' + size,
|
||||
{method: 'GET'});
|
||||
} else {
|
||||
request = new Request(config.prefixUrl + '_receive',
|
||||
{method: 'POST', body: size + ' none'});
|
||||
}
|
||||
var promise = fetch(request)
|
||||
.then(function(response) {
|
||||
if (response.status != 200) {
|
||||
config.addToLog('Failed (status=' + this.status + ')');
|
||||
return Promise.reject();
|
||||
}
|
||||
// Check and warn if proxy is enabled.
|
||||
if (response.headers.get('Via') !== null) {
|
||||
config.addToLog('WARNING: proxy seems enabled.');
|
||||
}
|
||||
if (config.dataType === 'arraybuffer') {
|
||||
return response.arrayBuffer()
|
||||
.then(function(arrayBuffer) {
|
||||
return [arrayBuffer.byteLength,
|
||||
(!config.verifyData ||
|
||||
verifyArrayBuffer(arrayBuffer, 0x61))];
|
||||
});
|
||||
} else if (config.dataType == 'blob') {
|
||||
return response.blob()
|
||||
.then(function(blob) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
if (config.verifyData) {
|
||||
verifyBlob(config, blob, 0x61,
|
||||
function(receivedSize, verificationResult) {
|
||||
resolve([receivedSize, verificationResult]);
|
||||
});
|
||||
} else {
|
||||
resolve([blob.size, true]);
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
return response.text()
|
||||
.then(function(text) {
|
||||
return [text.length,
|
||||
(!config.verifyData ||
|
||||
text == repeatString('a', text.length))];
|
||||
});
|
||||
}
|
||||
})
|
||||
.then(function(receivedSizeAndVerificationResult) {
|
||||
var receivedSize = receivedSizeAndVerificationResult[0];
|
||||
var verificationResult = receivedSizeAndVerificationResult[1];
|
||||
if (receivedSize !== size) {
|
||||
config.addToLog('Expected ' + size +
|
||||
'B but received ' + receivedSize + 'B');
|
||||
return Promise.reject();
|
||||
}
|
||||
if (!verificationResult) {
|
||||
config.addToLog('Response verification failed');
|
||||
return Promise.reject();
|
||||
}
|
||||
});
|
||||
promises.push(promise);
|
||||
}
|
||||
|
||||
// Finish and report time measuring.
|
||||
Promise.all(promises)
|
||||
.then(function() {
|
||||
if (benchmark.startTimeInMs == null) {
|
||||
config.addToLog('startTimeInMs not set');
|
||||
return Promise.reject();
|
||||
}
|
||||
calculateAndLogResult(config, size, benchmark.startTimeInMs,
|
||||
size * config.numFetches, isWarmUp);
|
||||
runNextTask(config);
|
||||
})
|
||||
.catch(function(e) {
|
||||
config.addToLog("ERROR: " + e);
|
||||
config.notifyAbort();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function getConfigString(config) {
|
||||
return '(' + config.dataType +
|
||||
', verifyData=' + config.verifyData +
|
||||
', ' + (isWorker ? 'Worker' : 'Main') +
|
||||
', numFetches=' + config.numFetches +
|
||||
', numIterations=' + config.numIterations +
|
||||
', numWarmUpIterations=' + config.numWarmUpIterations +
|
||||
')';
|
||||
}
|
||||
|
||||
function startBenchmark(config) {
|
||||
clearTimeout(timerID);
|
||||
|
||||
runNextTask(config);
|
||||
}
|
||||
|
||||
function batchBenchmark(originalConfig) {
|
||||
originalConfig.addToLog('Batch benchmark');
|
||||
|
||||
tasks = [];
|
||||
clearAverageData();
|
||||
|
||||
var dataTypes = ['text', 'blob', 'arraybuffer'];
|
||||
var stepFuncs = [sendBenchmarkStep, receiveBenchmarkStep];
|
||||
var names = ['Send', 'Receive'];
|
||||
for (var i = 0; i < stepFuncs.length; ++i) {
|
||||
for (var j = 0; j < dataTypes.length; ++j) {
|
||||
var config = cloneConfig(originalConfig);
|
||||
config.dataType = dataTypes[j];
|
||||
addTasks(config, stepFuncs[i]);
|
||||
addResultReportingTask(config,
|
||||
names[i] + ' benchmark ' + getConfigString(config));
|
||||
}
|
||||
}
|
||||
|
||||
startBenchmark(config);
|
||||
}
|
||||
|
||||
function cleanup() {
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
<!DOCTYPE html>
|
||||
<head>
|
||||
<script src="util.js"></script>
|
||||
<script src="performance_test_iframe.js"></script>
|
||||
<script src="fetch_benchmark.js"></script>
|
||||
</head>
|
|
@ -0,0 +1,6 @@
|
|||
<!DOCTYPE html>
|
||||
<head>
|
||||
<script src="util.js"></script>
|
||||
<script src="performance_test_iframe.js"></script>
|
||||
<script src="benchmark.js"></script>
|
||||
</head>
|
|
@ -0,0 +1,66 @@
|
|||
function perfTestAddToLog(text) {
|
||||
parent.postMessage({'command': 'log', 'value': text}, '*');
|
||||
}
|
||||
|
||||
function perfTestAddToSummary(text) {
|
||||
}
|
||||
|
||||
function perfTestMeasureValue(value) {
|
||||
parent.postMessage({'command': 'measureValue', 'value': value}, '*');
|
||||
}
|
||||
|
||||
function perfTestNotifyAbort() {
|
||||
parent.postMessage({'command': 'notifyAbort'}, '*');
|
||||
}
|
||||
|
||||
function getConfigForPerformanceTest(connectionType, dataType, async,
|
||||
verifyData, numIterations,
|
||||
numWarmUpIterations) {
|
||||
var prefixUrl;
|
||||
if (connectionType === 'WebSocket') {
|
||||
prefixUrl = 'ws://' + location.host + '/benchmark_helper';
|
||||
} else {
|
||||
// XHR or fetch
|
||||
prefixUrl = 'http://' + location.host + '/073be001e10950692ccbf3a2ad21c245';
|
||||
}
|
||||
|
||||
return {
|
||||
prefixUrl: prefixUrl,
|
||||
printSize: true,
|
||||
numXHRs: 1,
|
||||
numFetches: 1,
|
||||
numSockets: 1,
|
||||
// + 1 is for a warmup iteration by the Telemetry framework.
|
||||
numIterations: numIterations + numWarmUpIterations + 1,
|
||||
numWarmUpIterations: numWarmUpIterations,
|
||||
minTotal: 10240000,
|
||||
startSize: 10240000,
|
||||
stopThreshold: 10240000,
|
||||
multipliers: [2],
|
||||
verifyData: verifyData,
|
||||
dataType: dataType,
|
||||
async: async,
|
||||
addToLog: perfTestAddToLog,
|
||||
addToSummary: perfTestAddToSummary,
|
||||
measureValue: perfTestMeasureValue,
|
||||
notifyAbort: perfTestNotifyAbort
|
||||
};
|
||||
}
|
||||
|
||||
var data;
|
||||
onmessage = function(message) {
|
||||
var action;
|
||||
if (message.data.command === 'start') {
|
||||
data = message.data;
|
||||
initWorker(data.connectionType, 'http://' + location.host);
|
||||
action = data.benchmarkName;
|
||||
} else {
|
||||
action = 'stop';
|
||||
}
|
||||
|
||||
var config = getConfigForPerformanceTest(data.connectionType, data.dataType,
|
||||
data.async, data.verifyData,
|
||||
data.numIterations,
|
||||
data.numWarmUpIterations);
|
||||
doAction(config, data.isWorker, action);
|
||||
};
|
|
@ -76,21 +76,44 @@ function calculateSpeedInKB(size, timeSpentInMs) {
|
|||
return Math.round(size / timeSpentInMs * 1000) / 1000;
|
||||
}
|
||||
|
||||
function calculateAndLogResult(config, size, startTimeInMs, totalSize) {
|
||||
function calculateAndLogResult(config, size, startTimeInMs, totalSize,
|
||||
isWarmUp) {
|
||||
var timeSpentInMs = getTimeStamp() - startTimeInMs;
|
||||
var speed = calculateSpeedInKB(totalSize, timeSpentInMs);
|
||||
var timePerMessageInMs = timeSpentInMs / (totalSize / size);
|
||||
if (!results[size]) {
|
||||
results[size] = {n: 0, sum_t: 0, sum_t2: 0};
|
||||
if (!isWarmUp) {
|
||||
config.measureValue(timePerMessageInMs);
|
||||
if (!results[size]) {
|
||||
results[size] = {n: 0, sum_t: 0, sum_t2: 0};
|
||||
}
|
||||
results[size].n ++;
|
||||
results[size].sum_t += timePerMessageInMs;
|
||||
results[size].sum_t2 += timePerMessageInMs * timePerMessageInMs;
|
||||
}
|
||||
config.measureValue(timePerMessageInMs);
|
||||
results[size].n ++;
|
||||
results[size].sum_t += timePerMessageInMs;
|
||||
results[size].sum_t2 += timePerMessageInMs * timePerMessageInMs;
|
||||
config.addToLog(formatResultInKiB(size, timePerMessageInMs, -1, speed,
|
||||
config.printSize));
|
||||
}
|
||||
|
||||
function repeatString(str, count) {
|
||||
var data = '';
|
||||
var expChunk = str;
|
||||
var remain = count;
|
||||
while (true) {
|
||||
if (remain % 2) {
|
||||
data += expChunk;
|
||||
remain = (remain - 1) / 2;
|
||||
} else {
|
||||
remain /= 2;
|
||||
}
|
||||
|
||||
if (remain == 0)
|
||||
break;
|
||||
|
||||
expChunk = expChunk + expChunk;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
function fillArrayBuffer(buffer, c) {
|
||||
var i;
|
||||
|
||||
|
@ -175,3 +198,130 @@ function cloneConfig(obj) {
|
|||
}
|
||||
return newObj;
|
||||
}
|
||||
|
||||
var tasks = [];
|
||||
|
||||
function runNextTask(config) {
|
||||
var task = tasks.shift();
|
||||
if (task == undefined) {
|
||||
config.addToLog('Finished');
|
||||
cleanup();
|
||||
return;
|
||||
}
|
||||
timerID = setTimeout(task, 0);
|
||||
}
|
||||
|
||||
function buildLegendString(config) {
|
||||
var legend = ''
|
||||
if (config.printSize)
|
||||
legend = 'Message size in KiB, Time/message in ms, ';
|
||||
legend += 'Speed in kB/s';
|
||||
return legend;
|
||||
}
|
||||
|
||||
function addTasks(config, stepFunc) {
|
||||
for (var i = 0;
|
||||
i < config.numWarmUpIterations + config.numIterations; ++i) {
|
||||
var multiplierIndex = 0;
|
||||
for (var size = config.startSize;
|
||||
size <= config.stopThreshold;
|
||||
++multiplierIndex) {
|
||||
var task = stepFunc.bind(
|
||||
null,
|
||||
size,
|
||||
config,
|
||||
i < config.numWarmUpIterations);
|
||||
tasks.push(task);
|
||||
var multiplier = config.multipliers[
|
||||
multiplierIndex % config.multipliers.length];
|
||||
if (multiplier <= 1) {
|
||||
config.addToLog('Invalid multiplier ' + multiplier);
|
||||
config.notifyAbort();
|
||||
throw new Error('Invalid multipler');
|
||||
}
|
||||
size = Math.ceil(size * multiplier);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function addResultReportingTask(config, title) {
|
||||
tasks.push(function(){
|
||||
timerID = null;
|
||||
config.addToSummary(title);
|
||||
reportAverageData(config);
|
||||
clearAverageData();
|
||||
runNextTask(config);
|
||||
});
|
||||
}
|
||||
|
||||
function sendBenchmark(config) {
|
||||
config.addToLog('Send benchmark');
|
||||
config.addToLog(buildLegendString(config));
|
||||
|
||||
tasks = [];
|
||||
clearAverageData();
|
||||
addTasks(config, sendBenchmarkStep);
|
||||
addResultReportingTask(config, 'Send Benchmark ' + getConfigString(config));
|
||||
startBenchmark(config);
|
||||
}
|
||||
|
||||
function receiveBenchmark(config) {
|
||||
config.addToLog('Receive benchmark');
|
||||
config.addToLog(buildLegendString(config));
|
||||
|
||||
tasks = [];
|
||||
clearAverageData();
|
||||
addTasks(config, receiveBenchmarkStep);
|
||||
addResultReportingTask(config,
|
||||
'Receive Benchmark ' + getConfigString(config));
|
||||
startBenchmark(config);
|
||||
}
|
||||
|
||||
function stop(config) {
|
||||
clearTimeout(timerID);
|
||||
timerID = null;
|
||||
tasks = [];
|
||||
config.addToLog('Stopped');
|
||||
cleanup();
|
||||
}
|
||||
|
||||
var worker;
|
||||
|
||||
function initWorker(connectionType, origin) {
|
||||
var scriptPath =
|
||||
connectionType === 'WebSocket' ? '/benchmark.js' :
|
||||
connectionType === 'XHR' ? '/xhr_benchmark.js' :
|
||||
'/fetch_benchmark.js'; // connectionType === 'fetch'
|
||||
worker = new Worker(origin + scriptPath);
|
||||
}
|
||||
|
||||
function doAction(config, isWindowToWorker, action) {
|
||||
if (isWindowToWorker) {
|
||||
worker.onmessage = function(addToLog, addToSummary,
|
||||
measureValue, notifyAbort, message) {
|
||||
if (message.data.type === 'addToLog')
|
||||
addToLog(message.data.data);
|
||||
else if (message.data.type === 'addToSummary')
|
||||
addToSummary(message.data.data);
|
||||
else if (message.data.type === 'measureValue')
|
||||
measureValue(message.data.data);
|
||||
else if (message.data.type === 'notifyAbort')
|
||||
notifyAbort();
|
||||
}.bind(undefined, config.addToLog, config.addToSummary,
|
||||
config.measureValue, config.notifyAbort);
|
||||
config.addToLog = undefined;
|
||||
config.addToSummary = undefined;
|
||||
config.measureValue = undefined;
|
||||
config.notifyAbort = undefined;
|
||||
worker.postMessage({type: action, config: config});
|
||||
} else {
|
||||
if (action === 'sendBenchmark')
|
||||
sendBenchmark(config);
|
||||
else if (action === 'receiveBenchmark')
|
||||
receiveBenchmark(config);
|
||||
else if (action === 'batchBenchmark')
|
||||
batchBenchmark(config);
|
||||
else if (action === 'stop')
|
||||
stop(config);
|
||||
}
|
||||
}
|
|
@ -33,6 +33,12 @@ function addToSummary(log) {
|
|||
function measureValue(value) {
|
||||
}
|
||||
|
||||
// config.notifyAbort is called when the benchmark failed and aborted, and
|
||||
// intended to be used in Performance Tests.
|
||||
// Do nothing here in non-PerformanceTest.
|
||||
function notifyAbort() {
|
||||
}
|
||||
|
||||
function getIntFromInput(id) {
|
||||
return parseInt(document.getElementById(id).value);
|
||||
}
|
||||
|
@ -53,11 +59,7 @@ function getIntArrayFromInput(id) {
|
|||
return strArray.map(function(str) { return parseInt(str, 10); });
|
||||
}
|
||||
|
||||
function onMessage(message) {
|
||||
if (message.data.type === 'addToLog')
|
||||
addToLog(message.data.data);
|
||||
else if (message.data.type === 'addToSummary')
|
||||
addToSummary(message.data.data);
|
||||
else if (message.data.type === 'measureValue')
|
||||
measureValue(message.data.data);
|
||||
function getFloatArrayFromInput(id) {
|
||||
var strArray = document.getElementById(id).value.split(',');
|
||||
return strArray.map(parseFloat);
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
// Copyright 2014 Google Inc. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the COPYING file or at
|
||||
// https://developers.google.com/open-source/licenses/bsd
|
||||
|
||||
// Utilities for example applications (for the worker threads only).
|
||||
|
||||
onmessage = function (message) {
|
||||
var config = message.data.config;
|
||||
config.addToLog = function(text) {
|
||||
postMessage({type: 'addToLog', data: text}); };
|
||||
config.addToSummary = function(text) {
|
||||
postMessage({type: 'addToSummary', data: text}); };
|
||||
config.measureValue = function(value) {
|
||||
postMessage({type: 'measureValue', data: value}); };
|
||||
config.notifyAbort = function() { postMessage({type: 'notifyAbort'}); };
|
||||
|
||||
doAction(config, false, message.data.type);
|
||||
};
|
|
@ -50,70 +50,36 @@ function getConfig() {
|
|||
startSize: getIntFromInput('startsize'),
|
||||
// Stops benchmark when the size of message exceeds this threshold.
|
||||
stopThreshold: getIntFromInput('stopthreshold'),
|
||||
// If the size of each message is small, send/receive multiple messages
|
||||
// until the sum of sizes reaches this threshold.
|
||||
// minTotal: getIntFromInput('mintotal'),
|
||||
// minTotal is not yet implemented on XHR benchmark
|
||||
multipliers: getIntArrayFromInput('multipliers'),
|
||||
verifyData: getBoolFromCheckBox('verifydata')
|
||||
multipliers: getFloatArrayFromInput('multipliers'),
|
||||
verifyData: getBoolFromCheckBox('verifydata'),
|
||||
methodAndCache: getStringFromRadioBox('methodandcache'),
|
||||
addToLog: addToLog,
|
||||
addToSummary: addToSummary,
|
||||
measureValue: measureValue,
|
||||
notifyAbort: notifyAbort
|
||||
};
|
||||
}
|
||||
|
||||
var worker = new Worker('xhr_benchmark.js');
|
||||
worker.onmessage = onMessage;
|
||||
|
||||
function onSendBenchmark() {
|
||||
var config = getConfig();
|
||||
config.dataType = getStringFromRadioBox('datatyperadio');
|
||||
|
||||
if (getBoolFromCheckBox('worker')) {
|
||||
worker.postMessage({type: 'sendBenchmark', config: config});
|
||||
} else {
|
||||
config.addToLog = addToLog;
|
||||
config.addToSummary = addToSummary;
|
||||
config.measureValue = measureValue;
|
||||
sendBenchmark(config);
|
||||
}
|
||||
doAction(config, getBoolFromCheckBox('worker'), 'sendBenchmark');
|
||||
}
|
||||
|
||||
function onReceiveBenchmark() {
|
||||
var config = getConfig();
|
||||
config.dataType = getStringFromRadioBox('datatyperadio');
|
||||
|
||||
if (getBoolFromCheckBox('worker')) {
|
||||
worker.postMessage({type: 'receiveBenchmark', config: config});
|
||||
} else {
|
||||
config.addToLog = addToLog;
|
||||
config.addToSummary = addToSummary;
|
||||
config.measureValue = measureValue;
|
||||
receiveBenchmark(config);
|
||||
}
|
||||
doAction(config, getBoolFromCheckBox('worker'), 'receiveBenchmark');
|
||||
}
|
||||
|
||||
function onBatchBenchmark() {
|
||||
var config = getConfig();
|
||||
|
||||
if (getBoolFromCheckBox('worker')) {
|
||||
worker.postMessage({type: 'batchBenchmark', config: config});
|
||||
} else {
|
||||
config.addToLog = addToLog;
|
||||
config.addToSummary = addToSummary;
|
||||
config.measureValue = measureValue;
|
||||
batchBenchmark(config);
|
||||
}
|
||||
doAction(config, getBoolFromCheckBox('worker'), 'batchBenchmark');
|
||||
}
|
||||
|
||||
function onStop() {
|
||||
var config = getConfig();
|
||||
|
||||
if (getBoolFromCheckBox('worker')) {
|
||||
worker.postMessage({type: 'stop', config: config});
|
||||
} else {
|
||||
config.addToLog = addToLog;
|
||||
config.addToSummary = addToSummary;
|
||||
config.measureValue = measureValue;
|
||||
stop(config);
|
||||
}
|
||||
doAction(config, getBoolFromCheckBox('worker'), 'stop');
|
||||
}
|
||||
|
||||
function init() {
|
||||
|
@ -127,6 +93,8 @@ function init() {
|
|||
|
||||
addToLog(window.navigator.userAgent.toLowerCase());
|
||||
addToSummary(window.navigator.userAgent.toLowerCase());
|
||||
|
||||
initWorker('XHR', '');
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
|
@ -178,10 +146,6 @@ function init() {
|
|||
<td>Stop threshold</td>
|
||||
<td><input type="text" id="stopthreshold" value="102400000"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Minimum total</td>
|
||||
<td><input type="text" id="mintotal" value="102400000"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Multipliers</td>
|
||||
<td><input type="text" id="multipliers" value="5, 2"></td>
|
||||
|
@ -204,6 +168,26 @@ function init() {
|
|||
id="datatyperadioarraybuffer"
|
||||
value="arraybuffer"
|
||||
><label for="datatyperadioarraybuffer">arraybuffer</label>
|
||||
|
||||
<br>
|
||||
Set HTTP method and cache control
|
||||
<input type="radio"
|
||||
name="methodandcache"
|
||||
id="methodandcachePOST"
|
||||
value="POST"
|
||||
checked><label for="methodandcachePOST">POST (No Cache)</label>
|
||||
<input type="radio"
|
||||
name="methodandcache"
|
||||
id="methodandcacheGETNOCACHE"
|
||||
value="GET-NOCACHE"
|
||||
><label for="methodandcacheGETNOCACHE">GET (No Cache)</label>
|
||||
<input type="radio"
|
||||
name="methodandcache"
|
||||
id="methodandcacheGETCACHE"
|
||||
value="GET-CACHE"
|
||||
><label for="methodandcacheGETCACHE">GET (Cache)</label>
|
||||
<br>
|
||||
<span style="font-size: 80%">(Cache control: receive only. Cache is valid for 10 seconds. This config controls Cache-control HTTP response header. Browsers might not cache data e.g. when it is large)</span>
|
||||
</form>
|
||||
|
||||
<div id="log_div">
|
||||
|
@ -216,7 +200,13 @@ function init() {
|
|||
id="summary" rows="20" style="width: 100%" readonly></textarea>
|
||||
</div>
|
||||
|
||||
Note: Effect of RTT and time spent for ArrayBuffer creation in receive benchmarks are not eliminated.
|
||||
<div id="note_div">
|
||||
Note:
|
||||
<ul>
|
||||
<li>Effect of RTT and time spent for ArrayBuffer creation in receive benchmarks are not eliminated.</li>
|
||||
<li>The Stddev column shows NaN when the number of iterations is set to 1.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -33,27 +33,7 @@ function destroyAllXHRs() {
|
|||
// gc() might be needed for Chrome/Blob
|
||||
}
|
||||
|
||||
function repeatString(str, count) {
|
||||
var data = '';
|
||||
var expChunk = str;
|
||||
var remain = count;
|
||||
while (true) {
|
||||
if (remain % 2) {
|
||||
data += expChunk;
|
||||
remain = (remain - 1) / 2;
|
||||
} else {
|
||||
remain /= 2;
|
||||
}
|
||||
|
||||
if (remain == 0)
|
||||
break;
|
||||
|
||||
expChunk = expChunk + expChunk;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
function sendBenchmarkStep(size, config) {
|
||||
function sendBenchmarkStep(size, config, isWarmUp) {
|
||||
timerID = null;
|
||||
|
||||
benchmark.startTimeInMs = null;
|
||||
|
@ -68,12 +48,14 @@ function sendBenchmarkStep(size, config) {
|
|||
if (this.status != 200) {
|
||||
config.addToLog('Failed (status=' + this.status + ')');
|
||||
destroyAllXHRs();
|
||||
config.notifyAbort();
|
||||
return;
|
||||
}
|
||||
|
||||
if (config.verifyData &&
|
||||
!verifyAcknowledgement(config, this.response, size)) {
|
||||
destroyAllXHRs();
|
||||
config.notifyAbort();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -86,10 +68,17 @@ function sendBenchmarkStep(size, config) {
|
|||
if (benchmark.startTimeInMs == null) {
|
||||
config.addToLog('startTimeInMs not set');
|
||||
destroyAllXHRs();
|
||||
config.notifyAbort();
|
||||
return;
|
||||
}
|
||||
|
||||
calculateAndLogResult(config, size, benchmark.startTimeInMs, totalSize);
|
||||
// Check and warn if proxy is enabled.
|
||||
if (this.getResponseHeader('Via') !== null) {
|
||||
config.addToLog('WARNING: proxy seems enabled.');
|
||||
}
|
||||
|
||||
calculateAndLogResult(config, size, benchmark.startTimeInMs, totalSize,
|
||||
isWarmUp);
|
||||
|
||||
destroyAllXHRs();
|
||||
|
||||
|
@ -134,7 +123,7 @@ function sendBenchmarkStep(size, config) {
|
|||
}
|
||||
}
|
||||
|
||||
function receiveBenchmarkStep(size, config) {
|
||||
function receiveBenchmarkStep(size, config, isWarmUp) {
|
||||
timerID = null;
|
||||
|
||||
benchmark.startTimeInMs = null;
|
||||
|
@ -145,6 +134,7 @@ function receiveBenchmarkStep(size, config) {
|
|||
if (!verificationResult) {
|
||||
config.addToLog('Response verification failed');
|
||||
destroyAllXHRs();
|
||||
config.notifyAbort();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -157,10 +147,12 @@ function receiveBenchmarkStep(size, config) {
|
|||
if (benchmark.startTimeInMs == null) {
|
||||
config.addToLog('startTimeInMs not set');
|
||||
destroyAllXHRs();
|
||||
config.notifyAbort();
|
||||
return;
|
||||
}
|
||||
|
||||
calculateAndLogResult(config, size, benchmark.startTimeInMs, totalSize);
|
||||
calculateAndLogResult(config, size, benchmark.startTimeInMs, totalSize,
|
||||
isWarmUp);
|
||||
|
||||
destroyAllXHRs();
|
||||
|
||||
|
@ -175,9 +167,15 @@ function receiveBenchmarkStep(size, config) {
|
|||
if (this.status != 200) {
|
||||
config.addToLog('Failed (status=' + this.status + ')');
|
||||
destroyAllXHRs();
|
||||
config.notifyAbort();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check and warn if proxy is enabled.
|
||||
if (this.getResponseHeader('Via') !== null) {
|
||||
config.addToLog('WARNING: proxy seems enabled.');
|
||||
}
|
||||
|
||||
var bytesReceived = -1;
|
||||
if (this.responseType == 'arraybuffer') {
|
||||
bytesReceived = this.response.byteLength;
|
||||
|
@ -190,6 +188,7 @@ function receiveBenchmarkStep(size, config) {
|
|||
config.addToLog('Expected ' + size +
|
||||
'B but received ' + bytesReceived + 'B');
|
||||
destroyAllXHRs();
|
||||
config.notifyAbort();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -220,9 +219,21 @@ function receiveBenchmarkStep(size, config) {
|
|||
|
||||
for (var i = 0; i < xhrs.length; ++i) {
|
||||
var xhr = xhrs[i];
|
||||
xhr.open('POST', config.prefixUrl + '_receive', config.async);
|
||||
xhr.responseType = config.dataType;
|
||||
xhr.send(size + ' none');
|
||||
if (config.methodAndCache === 'GET-NOCACHE') {
|
||||
xhr.open('GET', config.prefixUrl + '_receive_getnocache?' + size,
|
||||
config.async);
|
||||
xhr.responseType = config.dataType;
|
||||
xhr.send();
|
||||
} else if (config.methodAndCache === 'GET-CACHE') {
|
||||
xhr.open('GET', config.prefixUrl + '_receive_getcache?' + size,
|
||||
config.async);
|
||||
xhr.responseType = config.dataType;
|
||||
xhr.send();
|
||||
} else {
|
||||
xhr.open('POST', config.prefixUrl + '_receive', config.async);
|
||||
xhr.responseType = config.dataType;
|
||||
xhr.send(size + ' none');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -245,94 +256,6 @@ function startBenchmark(config) {
|
|||
runNextTask(config);
|
||||
}
|
||||
|
||||
// TODO(hiroshige): the following code is the same as benchmark.html
|
||||
// and some of them should be merged into e.g. util.js
|
||||
|
||||
var tasks = [];
|
||||
|
||||
function runNextTask(config) {
|
||||
var task = tasks.shift();
|
||||
if (task == undefined) {
|
||||
config.addToLog('Finished');
|
||||
destroyAllXHRs();
|
||||
return;
|
||||
}
|
||||
timerID = setTimeout(task, 0);
|
||||
}
|
||||
|
||||
function buildLegendString(config) {
|
||||
var legend = ''
|
||||
if (config.printSize)
|
||||
legend = 'Message size in KiB, Time/message in ms, ';
|
||||
legend += 'Speed in kB/s';
|
||||
return legend;
|
||||
}
|
||||
|
||||
function addTasks(config, stepFunc) {
|
||||
for (var i = 0;
|
||||
i < config.numWarmUpIterations + config.numIterations; ++i) {
|
||||
// Ignore the first |config.numWarmUpIterations| iterations.
|
||||
if (i == config.numWarmUpIterations)
|
||||
addResultClearingTask(config);
|
||||
|
||||
var multiplierIndex = 0;
|
||||
for (var size = config.startSize;
|
||||
size <= config.stopThreshold;
|
||||
++multiplierIndex) {
|
||||
var task = stepFunc.bind(
|
||||
null,
|
||||
size,
|
||||
config);
|
||||
tasks.push(task);
|
||||
size *= config.multipliers[
|
||||
multiplierIndex % config.multipliers.length];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function addResultReportingTask(config, title) {
|
||||
tasks.push(function(){
|
||||
timerID = null;
|
||||
config.addToSummary(title);
|
||||
reportAverageData(config);
|
||||
clearAverageData();
|
||||
runNextTask(config);
|
||||
});
|
||||
}
|
||||
|
||||
function addResultClearingTask(config) {
|
||||
tasks.push(function(){
|
||||
timerID = null;
|
||||
clearAverageData();
|
||||
runNextTask(config);
|
||||
});
|
||||
}
|
||||
|
||||
// --------------------------------
|
||||
|
||||
function sendBenchmark(config) {
|
||||
config.addToLog('Send benchmark');
|
||||
config.addToLog(buildLegendString(config));
|
||||
|
||||
tasks = [];
|
||||
clearAverageData();
|
||||
addTasks(config, sendBenchmarkStep);
|
||||
addResultReportingTask(config, 'Send Benchmark ' + getConfigString(config));
|
||||
startBenchmark(config);
|
||||
}
|
||||
|
||||
function receiveBenchmark(config) {
|
||||
config.addToLog('Receive benchmark');
|
||||
config.addToLog(buildLegendString(config));
|
||||
|
||||
tasks = [];
|
||||
clearAverageData();
|
||||
addTasks(config, receiveBenchmarkStep);
|
||||
addResultReportingTask(config,
|
||||
'Receive Benchmark ' + getConfigString(config));
|
||||
startBenchmark(config);
|
||||
}
|
||||
|
||||
function batchBenchmark(originalConfig) {
|
||||
originalConfig.addToLog('Batch benchmark');
|
||||
|
||||
|
@ -365,25 +288,5 @@ function batchBenchmark(originalConfig) {
|
|||
startBenchmark(config);
|
||||
}
|
||||
|
||||
|
||||
function stop(config) {
|
||||
destroyAllXHRs();
|
||||
clearTimeout(timerID);
|
||||
timerID = null;
|
||||
config.addToLog('Stopped');
|
||||
function cleanup() {
|
||||
}
|
||||
|
||||
onmessage = function (message) {
|
||||
var config = message.data.config;
|
||||
config.addToLog = workerAddToLog;
|
||||
config.addToSummary = workerAddToSummary;
|
||||
config.measureValue = workerMeasureValue;
|
||||
if (message.data.type === 'sendBenchmark')
|
||||
sendBenchmark(config);
|
||||
else if (message.data.type === 'receiveBenchmark')
|
||||
receiveBenchmark(config);
|
||||
else if (message.data.type === 'batchBenchmark')
|
||||
batchBenchmark(config);
|
||||
else if (message.data.type === 'stop')
|
||||
stop(config);
|
||||
};
|
|
@ -12,15 +12,19 @@ https://developers.google.com/open-source/licenses/bsd
|
|||
<script src="util_main.js"></script>
|
||||
<script>
|
||||
var events = [];
|
||||
var startTime = 0;
|
||||
|
||||
function run() {
|
||||
events = [];
|
||||
startTime = Date.now();
|
||||
|
||||
function pushToLog(type) {
|
||||
var time = Date.now();
|
||||
if (events.length != 0 && type === events[events.length - 1].type) {
|
||||
events[events.length - 1].count += 1;
|
||||
events[events.length - 1].last = time;
|
||||
} else {
|
||||
events.push({type: type, count: 1});
|
||||
events.push({type: type, count: 1, first: time, last: time});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -85,14 +89,23 @@ function print() {
|
|||
var result = '';
|
||||
for (var i = 0; i < events.length; ++i) {
|
||||
var event = events[i];
|
||||
result += event.type + ' * ' + event.count + '\n';
|
||||
var line = '';
|
||||
line += (event.first - startTime) + "ms";
|
||||
if (event.count > 1)
|
||||
line += "-" + (event.last - startTime) + "ms";
|
||||
else
|
||||
line += " ";
|
||||
while(line.length < 15)
|
||||
line += " ";
|
||||
line += ": " + event.type + ' * ' + event.count + '\n';
|
||||
result += line;
|
||||
}
|
||||
document.getElementById('log').value = result;
|
||||
}
|
||||
</script>
|
||||
|
||||
<body>
|
||||
<textarea id="log" rows="10" cols="40" readonly></textarea>
|
||||
<textarea id="log" rows="10" cols="70" readonly></textarea>
|
||||
<br/>
|
||||
Size: <input type="text" id="size" value="65536"><br/>
|
||||
<input type="checkbox" id="chunkedresponse">
|
|
@ -0,0 +1,6 @@
|
|||
<!DOCTYPE html>
|
||||
<head>
|
||||
<script src="util.js"></script>
|
||||
<script src="performance_test_iframe.js"></script>
|
||||
<script src="xhr_benchmark.js"></script>
|
||||
</head>
|
|
@ -807,7 +807,8 @@ class Stream(StreamBase):
|
|||
|
||||
def close_connection(self, code=common.STATUS_NORMAL_CLOSURE, reason='',
|
||||
wait_response=True):
|
||||
"""Closes a WebSocket connection.
|
||||
"""Closes a WebSocket connection. Note that this method blocks until
|
||||
it receives acknowledgement to the closing handshake.
|
||||
|
||||
Args:
|
||||
code: Status code for close frame. If code is None, a close
|
||||
|
@ -824,6 +825,12 @@ class Stream(StreamBase):
|
|||
'Requested close_connection but server is already terminated')
|
||||
return
|
||||
|
||||
# When we receive a close frame, we call _process_close_message().
|
||||
# _process_close_message() immediately acknowledges to the
|
||||
# server-initiated closing handshake and sets server_terminated to
|
||||
# True. So, here we can assume that we haven't received any close
|
||||
# frame. We're initiating a closing handshake.
|
||||
|
||||
if code is None:
|
||||
if reason is not None and len(reason) > 0:
|
||||
raise BadOperationException(
|
|
@ -102,10 +102,8 @@ SEC_WEBSOCKET_LOCATION_HEADER = 'Sec-WebSocket-Location'
|
|||
|
||||
# Extensions
|
||||
DEFLATE_FRAME_EXTENSION = 'deflate-frame'
|
||||
PERMESSAGE_COMPRESSION_EXTENSION = 'permessage-compress'
|
||||
PERMESSAGE_DEFLATE_EXTENSION = 'permessage-deflate'
|
||||
X_WEBKIT_DEFLATE_FRAME_EXTENSION = 'x-webkit-deflate-frame'
|
||||
X_WEBKIT_PERMESSAGE_COMPRESSION_EXTENSION = 'x-webkit-permessage-compress'
|
||||
MUX_EXTENSION = 'mux_DO_NOT_USE'
|
||||
|
||||
# Status codes
|
||||
|
@ -153,9 +151,8 @@ def is_control_opcode(opcode):
|
|||
|
||||
|
||||
class ExtensionParameter(object):
|
||||
"""Holds information about an extension which is exchanged on extension
|
||||
negotiation in opening handshake.
|
||||
"""
|
||||
|
||||
"""This is exchanged on extension negotiation in opening handshake."""
|
||||
|
||||
def __init__(self, name):
|
||||
self._name = name
|
||||
|
@ -166,30 +163,39 @@ class ExtensionParameter(object):
|
|||
self._parameters = []
|
||||
|
||||
def name(self):
|
||||
"""Return the extension name."""
|
||||
return self._name
|
||||
|
||||
def add_parameter(self, name, value):
|
||||
"""Add a parameter."""
|
||||
self._parameters.append((name, value))
|
||||
|
||||
def get_parameters(self):
|
||||
"""Return the parameters."""
|
||||
return self._parameters
|
||||
|
||||
def get_parameter_names(self):
|
||||
"""Return the names of the parameters."""
|
||||
return [name for name, unused_value in self._parameters]
|
||||
|
||||
def has_parameter(self, name):
|
||||
"""Test if a parameter exists."""
|
||||
for param_name, param_value in self._parameters:
|
||||
if param_name == name:
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_parameter_value(self, name):
|
||||
"""Get the value of a specific parameter."""
|
||||
for param_name, param_value in self._parameters:
|
||||
if param_name == name:
|
||||
return param_value
|
||||
|
||||
|
||||
class ExtensionParsingException(Exception):
|
||||
|
||||
"""Exception to handle errors in extension parsing."""
|
||||
|
||||
def __init__(self, name):
|
||||
super(ExtensionParsingException, self).__init__(name)
|
||||
|
||||
|
@ -244,12 +250,11 @@ def _parse_extension(state):
|
|||
|
||||
|
||||
def parse_extensions(data):
|
||||
"""Parses Sec-WebSocket-Extensions header value returns a list of
|
||||
ExtensionParameter objects.
|
||||
"""Parse Sec-WebSocket-Extensions header value.
|
||||
|
||||
Returns a list of ExtensionParameter objects.
|
||||
Leading LWSes must be trimmed.
|
||||
"""
|
||||
|
||||
state = http_header_util.ParsingState(data)
|
||||
|
||||
extension_list = []
|
||||
|
@ -279,8 +284,7 @@ def parse_extensions(data):
|
|||
|
||||
|
||||
def format_extension(extension):
|
||||
"""Formats an ExtensionParameter object."""
|
||||
|
||||
"""Format an ExtensionParameter object."""
|
||||
formatted_params = [extension.name()]
|
||||
for param_name, param_value in extension.get_parameters():
|
||||
if param_value is None:
|
||||
|
@ -292,8 +296,7 @@ def format_extension(extension):
|
|||
|
||||
|
||||
def format_extensions(extension_list):
|
||||
"""Formats a list of ExtensionParameter objects."""
|
||||
|
||||
"""Format a list of ExtensionParameter objects."""
|
||||
formatted_extension_list = []
|
||||
for extension in extension_list:
|
||||
formatted_extension_list.append(format_extension(extension))
|
|
@ -327,103 +327,8 @@ _available_processors[common.X_WEBKIT_DEFLATE_FRAME_EXTENSION] = (
|
|||
_compression_extension_names.append(common.X_WEBKIT_DEFLATE_FRAME_EXTENSION)
|
||||
|
||||
|
||||
def _parse_compression_method(data):
|
||||
"""Parses the value of "method" extension parameter."""
|
||||
|
||||
return common.parse_extensions(data)
|
||||
|
||||
|
||||
def _create_accepted_method_desc(method_name, method_params):
|
||||
"""Creates accepted-method-desc from given method name and parameters"""
|
||||
|
||||
extension = common.ExtensionParameter(method_name)
|
||||
for name, value in method_params:
|
||||
extension.add_parameter(name, value)
|
||||
return common.format_extension(extension)
|
||||
|
||||
|
||||
class CompressionExtensionProcessorBase(ExtensionProcessorInterface):
|
||||
"""Base class for perframe-compress and permessage-compress extension."""
|
||||
|
||||
_METHOD_PARAM = 'method'
|
||||
|
||||
def __init__(self, request):
|
||||
ExtensionProcessorInterface.__init__(self, request)
|
||||
self._logger = util.get_class_logger(self)
|
||||
self._compression_method_name = None
|
||||
self._compression_processor = None
|
||||
self._compression_processor_hook = None
|
||||
|
||||
def name(self):
|
||||
return ''
|
||||
|
||||
def _lookup_compression_processor(self, method_desc):
|
||||
return None
|
||||
|
||||
def _get_compression_processor_response(self):
|
||||
"""Looks up the compression processor based on the self._request and
|
||||
returns the compression processor's response.
|
||||
"""
|
||||
|
||||
method_list = self._request.get_parameter_value(self._METHOD_PARAM)
|
||||
if method_list is None:
|
||||
return None
|
||||
methods = _parse_compression_method(method_list)
|
||||
if methods is None:
|
||||
return None
|
||||
comression_processor = None
|
||||
# The current implementation tries only the first method that matches
|
||||
# supported algorithm. Following methods aren't tried even if the
|
||||
# first one is rejected.
|
||||
# TODO(bashi): Need to clarify this behavior.
|
||||
for method_desc in methods:
|
||||
compression_processor = self._lookup_compression_processor(
|
||||
method_desc)
|
||||
if compression_processor is not None:
|
||||
self._compression_method_name = method_desc.name()
|
||||
break
|
||||
if compression_processor is None:
|
||||
return None
|
||||
|
||||
if self._compression_processor_hook:
|
||||
self._compression_processor_hook(compression_processor)
|
||||
|
||||
processor_response = compression_processor.get_extension_response()
|
||||
if processor_response is None:
|
||||
return None
|
||||
self._compression_processor = compression_processor
|
||||
return processor_response
|
||||
|
||||
def _get_extension_response_internal(self):
|
||||
processor_response = self._get_compression_processor_response()
|
||||
if processor_response is None:
|
||||
return None
|
||||
|
||||
response = common.ExtensionParameter(self._request.name())
|
||||
accepted_method_desc = _create_accepted_method_desc(
|
||||
self._compression_method_name,
|
||||
processor_response.get_parameters())
|
||||
response.add_parameter(self._METHOD_PARAM, accepted_method_desc)
|
||||
self._logger.debug(
|
||||
'Enable %s extension (method: %s)' %
|
||||
(self._request.name(), self._compression_method_name))
|
||||
return response
|
||||
|
||||
def _setup_stream_options_internal(self, stream_options):
|
||||
if self._compression_processor is None:
|
||||
return
|
||||
self._compression_processor.setup_stream_options(stream_options)
|
||||
|
||||
def set_compression_processor_hook(self, hook):
|
||||
self._compression_processor_hook = hook
|
||||
|
||||
def get_compression_processor(self):
|
||||
return self._compression_processor
|
||||
|
||||
|
||||
class PerMessageDeflateExtensionProcessor(ExtensionProcessorInterface):
|
||||
"""permessage-deflate extension processor. It's also used for
|
||||
permessage-compress extension when the deflate method is chosen.
|
||||
"""permessage-deflate extension processor.
|
||||
|
||||
Specification:
|
||||
http://tools.ietf.org/html/draft-ietf-hybi-permessage-compression-08
|
||||
|
@ -434,15 +339,8 @@ class PerMessageDeflateExtensionProcessor(ExtensionProcessorInterface):
|
|||
_CLIENT_MAX_WINDOW_BITS_PARAM = 'client_max_window_bits'
|
||||
_CLIENT_NO_CONTEXT_TAKEOVER_PARAM = 'client_no_context_takeover'
|
||||
|
||||
def __init__(self, request, draft08=True):
|
||||
"""Construct PerMessageDeflateExtensionProcessor
|
||||
|
||||
Args:
|
||||
draft08: Follow the constraints on the parameters that were not
|
||||
specified for permessage-compress but are specified for
|
||||
permessage-deflate as on
|
||||
draft-ietf-hybi-permessage-compression-08.
|
||||
"""
|
||||
def __init__(self, request):
|
||||
"""Construct PerMessageDeflateExtensionProcessor."""
|
||||
|
||||
ExtensionProcessorInterface.__init__(self, request)
|
||||
self._logger = util.get_class_logger(self)
|
||||
|
@ -450,22 +348,18 @@ class PerMessageDeflateExtensionProcessor(ExtensionProcessorInterface):
|
|||
self._preferred_client_max_window_bits = None
|
||||
self._client_no_context_takeover = False
|
||||
|
||||
self._draft08 = draft08
|
||||
|
||||
def name(self):
|
||||
# This method returns "deflate" (not "permessage-deflate") for
|
||||
# compatibility.
|
||||
return 'deflate'
|
||||
|
||||
def _get_extension_response_internal(self):
|
||||
if self._draft08:
|
||||
for name in self._request.get_parameter_names():
|
||||
if name not in [self._SERVER_MAX_WINDOW_BITS_PARAM,
|
||||
self._SERVER_NO_CONTEXT_TAKEOVER_PARAM,
|
||||
self._CLIENT_MAX_WINDOW_BITS_PARAM]:
|
||||
self._logger.debug('Unknown parameter: %r', name)
|
||||
return None
|
||||
else:
|
||||
# Any unknown parameter will be just ignored.
|
||||
pass
|
||||
for name in self._request.get_parameter_names():
|
||||
if name not in [self._SERVER_MAX_WINDOW_BITS_PARAM,
|
||||
self._SERVER_NO_CONTEXT_TAKEOVER_PARAM,
|
||||
self._CLIENT_MAX_WINDOW_BITS_PARAM]:
|
||||
self._logger.debug('Unknown parameter: %r', name)
|
||||
return None
|
||||
|
||||
server_max_window_bits = None
|
||||
if self._request.has_parameter(self._SERVER_MAX_WINDOW_BITS_PARAM):
|
||||
|
@ -494,8 +388,7 @@ class PerMessageDeflateExtensionProcessor(ExtensionProcessorInterface):
|
|||
# accept client_max_window_bits from a server or not.
|
||||
client_client_max_window_bits = self._request.has_parameter(
|
||||
self._CLIENT_MAX_WINDOW_BITS_PARAM)
|
||||
if (self._draft08 and
|
||||
client_client_max_window_bits and
|
||||
if (client_client_max_window_bits and
|
||||
self._request.get_parameter_value(
|
||||
self._CLIENT_MAX_WINDOW_BITS_PARAM) is not None):
|
||||
self._logger.debug('%s parameter must not have a value in a '
|
||||
|
@ -529,7 +422,7 @@ class PerMessageDeflateExtensionProcessor(ExtensionProcessorInterface):
|
|||
self._SERVER_NO_CONTEXT_TAKEOVER_PARAM, None)
|
||||
|
||||
if self._preferred_client_max_window_bits is not None:
|
||||
if self._draft08 and not client_client_max_window_bits:
|
||||
if not client_client_max_window_bits:
|
||||
self._logger.debug('Processor is configured to use %s but '
|
||||
'the client cannot accept it',
|
||||
self._CLIENT_MAX_WINDOW_BITS_PARAM)
|
||||
|
@ -765,33 +658,6 @@ _available_processors[common.PERMESSAGE_DEFLATE_EXTENSION] = (
|
|||
_compression_extension_names.append('deflate')
|
||||
|
||||
|
||||
class PerMessageCompressExtensionProcessor(
|
||||
CompressionExtensionProcessorBase):
|
||||
"""permessage-compress extension processor.
|
||||
|
||||
Specification:
|
||||
http://tools.ietf.org/html/draft-ietf-hybi-permessage-compression
|
||||
"""
|
||||
|
||||
_DEFLATE_METHOD = 'deflate'
|
||||
|
||||
def __init__(self, request):
|
||||
CompressionExtensionProcessorBase.__init__(self, request)
|
||||
|
||||
def name(self):
|
||||
return common.PERMESSAGE_COMPRESSION_EXTENSION
|
||||
|
||||
def _lookup_compression_processor(self, method_desc):
|
||||
if method_desc.name() == self._DEFLATE_METHOD:
|
||||
return PerMessageDeflateExtensionProcessor(method_desc, False)
|
||||
return None
|
||||
|
||||
|
||||
_available_processors[common.PERMESSAGE_COMPRESSION_EXTENSION] = (
|
||||
PerMessageCompressExtensionProcessor)
|
||||
_compression_extension_names.append(common.PERMESSAGE_COMPRESSION_EXTENSION)
|
||||
|
||||
|
||||
class MuxExtensionProcessor(ExtensionProcessorInterface):
|
||||
"""WebSocket multiplexing extension processor."""
|
||||
|
||||
|
@ -825,10 +691,9 @@ class MuxExtensionProcessor(ExtensionProcessorInterface):
|
|||
else:
|
||||
# Mux extension should not be applied before any history-based
|
||||
# compression extension.
|
||||
if (name == common.DEFLATE_FRAME_EXTENSION or
|
||||
name == common.X_WEBKIT_DEFLATE_FRAME_EXTENSION or
|
||||
name == common.PERMESSAGE_COMPRESSION_EXTENSION or
|
||||
name == common.X_WEBKIT_PERMESSAGE_COMPRESSION_EXTENSION):
|
||||
if (name == 'deflate' or
|
||||
name == common.DEFLATE_FRAME_EXTENSION or
|
||||
name == common.X_WEBKIT_DEFLATE_FRAME_EXTENSION):
|
||||
self.set_active(False)
|
||||
return
|
||||
|
|
@ -72,6 +72,7 @@ _PYOPT_ALLOW_DRAFT75_DEFINITION = {'off': False, 'on': True}
|
|||
|
||||
|
||||
class ApacheLogHandler(logging.Handler):
|
||||
|
||||
"""Wrapper logging.Handler to emit log message to apache's error.log."""
|
||||
|
||||
_LEVELS = {
|
||||
|
@ -136,6 +137,7 @@ _LOGGER = logging.getLogger(__name__)
|
|||
|
||||
|
||||
def _parse_option(name, value, definition):
|
||||
"""Return the meaning of a option value."""
|
||||
if value is None:
|
||||
return False
|
||||
|
||||
|
@ -147,6 +149,7 @@ def _parse_option(name, value, definition):
|
|||
|
||||
|
||||
def _create_dispatcher():
|
||||
"""Initialize a dispatch.Dispatcher."""
|
||||
_LOGGER.info('Initializing Dispatcher')
|
||||
|
||||
options = apache.main_server.get_options()
|
||||
|
@ -187,7 +190,6 @@ def headerparserhandler(request):
|
|||
This function is named headerparserhandler because it is the default
|
||||
name for a PythonHeaderParserHandler.
|
||||
"""
|
||||
|
||||
handshake_is_done = False
|
||||
try:
|
||||
# Fallback to default http handler for request paths for which
|
||||
|
@ -234,7 +236,8 @@ def headerparserhandler(request):
|
|||
request._dispatcher = _dispatcher
|
||||
_dispatcher.transfer_data(request)
|
||||
except handshake.AbortedByUserException, e:
|
||||
request.log_error('mod_pywebsocket: Aborted: %s' % e, apache.APLOG_INFO)
|
||||
request.log_error('mod_pywebsocket: Aborted: %s' % e,
|
||||
apache.APLOG_INFO)
|
||||
except Exception, e:
|
||||
# DispatchException can also be thrown if something is wrong in
|
||||
# pywebsocket code. It's caught here, then.
|
|
@ -40,6 +40,7 @@ import sys
|
|||
|
||||
|
||||
class MemorizingFile(object):
|
||||
|
||||
"""MemorizingFile wraps a file and memorizes lines read by readline.
|
||||
|
||||
Note that data read by other methods are not memorized. This behavior
|
||||
|
@ -56,7 +57,6 @@ class MemorizingFile(object):
|
|||
Only the first max_memorized_lines are memorized.
|
||||
Default: sys.maxint.
|
||||
"""
|
||||
|
||||
self._file = file_
|
||||
self._memorized_lines = []
|
||||
self._max_memorized_lines = max_memorized_lines
|
||||
|
@ -64,6 +64,11 @@ class MemorizingFile(object):
|
|||
self._buffered_line = None
|
||||
|
||||
def __getattribute__(self, name):
|
||||
"""Return a file attribute.
|
||||
|
||||
Returns the value overridden by this class for some attributes,
|
||||
and forwards the call to _file for the other attributes.
|
||||
"""
|
||||
if name in ('_file', '_memorized_lines', '_max_memorized_lines',
|
||||
'_buffered', '_buffered_line', 'readline',
|
||||
'get_memorized_lines'):
|
||||
|
@ -77,7 +82,6 @@ class MemorizingFile(object):
|
|||
the whole line will be read out from underlying file object by
|
||||
subsequent readline calls.
|
||||
"""
|
||||
|
||||
if self._buffered:
|
||||
line = self._buffered_line
|
||||
self._buffered = False
|
|
@ -104,23 +104,31 @@ _DROP_CODE_BAD_FRAGMENTATION = 3009
|
|||
|
||||
|
||||
class MuxUnexpectedException(Exception):
|
||||
|
||||
"""Exception in handling multiplexing extension."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
# Temporary
|
||||
class MuxNotImplementedException(Exception):
|
||||
|
||||
"""Raised when a flow enters unimplemented code path."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class LogicalConnectionClosedException(Exception):
|
||||
|
||||
"""Raised when logical connection is gracefully closed."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class PhysicalConnectionError(Exception):
|
||||
|
||||
"""Raised when there is a physical connection error."""
|
||||
|
||||
def __init__(self, drop_code, message=''):
|
||||
super(PhysicalConnectionError, self).__init__(
|
||||
'code=%d, message=%r' % (drop_code, message))
|
||||
|
@ -129,8 +137,11 @@ class PhysicalConnectionError(Exception):
|
|||
|
||||
|
||||
class LogicalChannelError(Exception):
|
||||
|
||||
"""Raised when there is a logical channel error."""
|
||||
|
||||
def __init__(self, channel_id, drop_code, message=''):
|
||||
"""Initialize the error with a status message."""
|
||||
super(LogicalChannelError, self).__init__(
|
||||
'channel_id=%d, code=%d, message=%r' % (
|
||||
channel_id, drop_code, message))
|
||||
|
@ -181,7 +192,7 @@ def _create_drop_channel(channel_id, code=None, message=''):
|
|||
first_byte = _MUX_OPCODE_DROP_CHANNEL << 5
|
||||
block = chr(first_byte) + _encode_channel_id(channel_id)
|
||||
if code is None:
|
||||
block += _encode_number(0) # Reason size
|
||||
block += _encode_number(0) # Reason size
|
||||
else:
|
||||
reason = struct.pack('!H', code) + message
|
||||
reason_size = _encode_number(len(reason))
|
||||
|
@ -209,7 +220,7 @@ def _create_new_channel_slot(slots, send_quota):
|
|||
|
||||
|
||||
def _create_fallback_new_channel_slot():
|
||||
first_byte = (_MUX_OPCODE_NEW_CHANNEL_SLOT << 5) | 1 # Set the F flag
|
||||
first_byte = (_MUX_OPCODE_NEW_CHANNEL_SLOT << 5) | 1 # Set the F flag
|
||||
block = (chr(first_byte) + _encode_number(0) + _encode_number(0))
|
||||
return block
|
||||
|
||||
|
@ -232,7 +243,9 @@ def _parse_request_text(request_text):
|
|||
|
||||
|
||||
class _ControlBlock(object):
|
||||
|
||||
"""A structure that holds parsing result of multiplexing control block.
|
||||
|
||||
Control block specific attributes will be added by _MuxFramePayloadParser.
|
||||
(e.g. encoded_handshake will be added for AddChannelRequest and
|
||||
AddChannelResponse)
|
||||
|
@ -243,6 +256,7 @@ class _ControlBlock(object):
|
|||
|
||||
|
||||
class _MuxFramePayloadParser(object):
|
||||
|
||||
"""A class that parses multiplexed frame payload."""
|
||||
|
||||
def __init__(self, payload):
|
||||
|
@ -251,13 +265,12 @@ class _MuxFramePayloadParser(object):
|
|||
self._logger = util.get_class_logger(self)
|
||||
|
||||
def read_channel_id(self):
|
||||
"""Reads channel id.
|
||||
"""Read channel id.
|
||||
|
||||
Raises:
|
||||
ValueError: when the payload doesn't contain
|
||||
valid channel id.
|
||||
"""
|
||||
|
||||
remaining_length = len(self._data) - self._read_position
|
||||
pos = self._read_position
|
||||
if remaining_length == 0:
|
||||
|
@ -288,12 +301,11 @@ class _MuxFramePayloadParser(object):
|
|||
return channel_id
|
||||
|
||||
def read_inner_frame(self):
|
||||
"""Reads an inner frame.
|
||||
"""Read an inner frame.
|
||||
|
||||
Raises:
|
||||
PhysicalConnectionError: when the inner frame is invalid.
|
||||
"""
|
||||
|
||||
if len(self._data) == self._read_position:
|
||||
raise PhysicalConnectionError(
|
||||
_DROP_CODE_ENCAPSULATED_FRAME_IS_TRUNCATED)
|
||||
|
@ -345,12 +357,11 @@ class _MuxFramePayloadParser(object):
|
|||
return number
|
||||
|
||||
def _read_size_and_contents(self):
|
||||
"""Reads data that consists of followings:
|
||||
"""Read data that consists of the following:
|
||||
- the size of the contents encoded the same way as payload length
|
||||
of the WebSocket Protocol with 1 bit padding at the head.
|
||||
- the contents.
|
||||
"""
|
||||
|
||||
try:
|
||||
size = self._read_number()
|
||||
except ValueError, e:
|
||||
|
@ -455,14 +466,13 @@ class _MuxFramePayloadParser(object):
|
|||
return control_block
|
||||
|
||||
def read_control_blocks(self):
|
||||
"""Reads control block(s).
|
||||
"""Read control block(s).
|
||||
|
||||
Raises:
|
||||
PhysicalConnectionError: when the payload contains invalid control
|
||||
block(s).
|
||||
StopIteration: when no control blocks left.
|
||||
"""
|
||||
|
||||
while self._read_position < len(self._data):
|
||||
first_byte = ord(self._data[self._read_position])
|
||||
self._read_position += 1
|
||||
|
@ -488,17 +498,17 @@ class _MuxFramePayloadParser(object):
|
|||
raise StopIteration
|
||||
|
||||
def remaining_data(self):
|
||||
"""Returns remaining data."""
|
||||
|
||||
"""Return remaining data."""
|
||||
return self._data[self._read_position:]
|
||||
|
||||
|
||||
class _LogicalRequest(object):
|
||||
|
||||
"""Mimics mod_python request."""
|
||||
|
||||
def __init__(self, channel_id, command, path, protocol, headers,
|
||||
connection):
|
||||
"""Constructs an instance.
|
||||
"""Construct an instance.
|
||||
|
||||
Args:
|
||||
channel_id: the channel id of the logical channel.
|
||||
|
@ -507,7 +517,6 @@ class _LogicalRequest(object):
|
|||
headers: HTTP headers.
|
||||
connection: _LogicalConnection instance.
|
||||
"""
|
||||
|
||||
self.channel_id = channel_id
|
||||
self.method = command
|
||||
self.uri = path
|
||||
|
@ -518,14 +527,16 @@ class _LogicalRequest(object):
|
|||
self.client_terminated = False
|
||||
|
||||
def is_https(self):
|
||||
"""Mimics request.is_https(). Returns False because this method is
|
||||
used only by old protocols (hixie and hybi00).
|
||||
"""
|
||||
"""Mimic request.is_https().
|
||||
|
||||
Returns False because this method is used only by old protocols
|
||||
(hixie and hybi00).
|
||||
"""
|
||||
return False
|
||||
|
||||
|
||||
class _LogicalConnection(object):
|
||||
|
||||
"""Mimics mod_python mp_conn."""
|
||||
|
||||
# For details, see the comment of set_read_state().
|
||||
|
@ -534,13 +545,12 @@ class _LogicalConnection(object):
|
|||
STATE_TERMINATED = 3
|
||||
|
||||
def __init__(self, mux_handler, channel_id):
|
||||
"""Constructs an instance.
|
||||
"""Construct an instance.
|
||||
|
||||
Args:
|
||||
mux_handler: _MuxHandler instance.
|
||||
channel_id: channel id of this connection.
|
||||
"""
|
||||
|
||||
self._mux_handler = mux_handler
|
||||
self._channel_id = channel_id
|
||||
self._incoming_data = ''
|
||||
|
@ -555,25 +565,23 @@ class _LogicalConnection(object):
|
|||
|
||||
def get_local_addr(self):
|
||||
"""Getter to mimic mp_conn.local_addr."""
|
||||
|
||||
return self._mux_handler.physical_connection.get_local_addr()
|
||||
local_addr = property(get_local_addr)
|
||||
|
||||
def get_remote_addr(self):
|
||||
"""Getter to mimic mp_conn.remote_addr."""
|
||||
|
||||
return self._mux_handler.physical_connection.get_remote_addr()
|
||||
remote_addr = property(get_remote_addr)
|
||||
|
||||
def get_memorized_lines(self):
|
||||
"""Gets memorized lines. Not supported."""
|
||||
|
||||
"""Get memorized lines. Not supported."""
|
||||
raise MuxUnexpectedException('_LogicalConnection does not support '
|
||||
'get_memorized_lines')
|
||||
|
||||
def write(self, data):
|
||||
"""Writes data. mux_handler sends data asynchronously. The caller will
|
||||
be suspended until write done.
|
||||
"""Write data. mux_handler sends data asynchronously.
|
||||
|
||||
The caller will be suspended until write done.
|
||||
|
||||
Args:
|
||||
data: data to be written.
|
||||
|
@ -582,7 +590,6 @@ class _LogicalConnection(object):
|
|||
MuxUnexpectedException: when called before finishing the previous
|
||||
write.
|
||||
"""
|
||||
|
||||
try:
|
||||
self._write_condition.acquire()
|
||||
if self._waiting_write_completion:
|
||||
|
@ -598,18 +605,18 @@ class _LogicalConnection(object):
|
|||
self._write_condition.release()
|
||||
|
||||
def write_control_data(self, data):
|
||||
"""Writes data via the control channel. Don't wait finishing write
|
||||
because this method can be called by mux dispatcher.
|
||||
"""Write data via the control channel.
|
||||
|
||||
Don't wait finishing write because this method can be called by
|
||||
mux dispatcher.
|
||||
|
||||
Args:
|
||||
data: data to be written.
|
||||
"""
|
||||
|
||||
self._mux_handler.send_control_data(data)
|
||||
|
||||
def on_write_data_done(self):
|
||||
"""Called when sending data is completed."""
|
||||
|
||||
try:
|
||||
self._write_condition.acquire()
|
||||
if not self._waiting_write_completion:
|
||||
|
@ -623,7 +630,6 @@ class _LogicalConnection(object):
|
|||
|
||||
def on_writer_done(self):
|
||||
"""Called by the mux handler when the writer thread has finished."""
|
||||
|
||||
try:
|
||||
self._write_condition.acquire()
|
||||
self._waiting_write_completion = False
|
||||
|
@ -631,23 +637,24 @@ class _LogicalConnection(object):
|
|||
finally:
|
||||
self._write_condition.release()
|
||||
|
||||
|
||||
def append_frame_data(self, frame_data):
|
||||
"""Appends incoming frame data. Called when mux_handler dispatches
|
||||
frame data to the corresponding application.
|
||||
"""Append incoming frame data.
|
||||
|
||||
Called when mux_handler dispatches frame data to the corresponding
|
||||
application.
|
||||
|
||||
Args:
|
||||
frame_data: incoming frame data.
|
||||
"""
|
||||
|
||||
self._read_condition.acquire()
|
||||
self._incoming_data += frame_data
|
||||
self._read_condition.notify()
|
||||
self._read_condition.release()
|
||||
|
||||
def read(self, length):
|
||||
"""Reads data. Blocks until enough data has arrived via physical
|
||||
connection.
|
||||
"""Read data.
|
||||
|
||||
Blocks until enough data has arrived via physical connection.
|
||||
|
||||
Args:
|
||||
length: length of data to be read.
|
||||
|
@ -657,7 +664,6 @@ class _LogicalConnection(object):
|
|||
ConnectionTerminatedException: when the physical connection has
|
||||
closed, or an error is caused on the reader thread.
|
||||
"""
|
||||
|
||||
self._read_condition.acquire()
|
||||
while (self._read_state == self.STATE_ACTIVE and
|
||||
len(self._incoming_data) < length):
|
||||
|
@ -680,9 +686,9 @@ class _LogicalConnection(object):
|
|||
return value
|
||||
|
||||
def set_read_state(self, new_state):
|
||||
"""Sets the state of this connection. Called when an event for this
|
||||
connection has occurred.
|
||||
"""Set the state of this connection.
|
||||
|
||||
Called when an event for this connection has occurred.
|
||||
Args:
|
||||
new_state: state to be set. new_state must be one of followings:
|
||||
- STATE_GRACEFULLY_CLOSED: when closing handshake for this
|
||||
|
@ -690,7 +696,6 @@ class _LogicalConnection(object):
|
|||
- STATE_TERMINATED: when the physical connection has closed or
|
||||
DropChannel of this connection has received.
|
||||
"""
|
||||
|
||||
self._read_condition.acquire()
|
||||
self._read_state = new_state
|
||||
self._read_condition.notify()
|
||||
|
@ -698,8 +703,8 @@ class _LogicalConnection(object):
|
|||
|
||||
|
||||
class _InnerMessage(object):
|
||||
"""Holds the result of _InnerMessageBuilder.build().
|
||||
"""
|
||||
|
||||
"""Hold the result of _InnerMessageBuilder.build()."""
|
||||
|
||||
def __init__(self, opcode, payload):
|
||||
self.opcode = opcode
|
||||
|
@ -707,7 +712,10 @@ class _InnerMessage(object):
|
|||
|
||||
|
||||
class _InnerMessageBuilder(object):
|
||||
"""A class that holds the context of inner message fragmentation and
|
||||
|
||||
"""Class to build an _InnerMessage.
|
||||
|
||||
A class that holds the context of inner message fragmentation and
|
||||
builds a message from fragmented inner frame(s).
|
||||
"""
|
||||
|
||||
|
@ -791,8 +799,10 @@ class _InnerMessageBuilder(object):
|
|||
return _InnerMessage(opcode, payload)
|
||||
|
||||
def build(self, frame):
|
||||
"""Build an inner message. Returns an _InnerMessage instance when
|
||||
the given frame is the last fragmented frame. Returns None otherwise.
|
||||
"""Build an inner message.
|
||||
|
||||
Returns an _InnerMessage instance when the given frame is the last
|
||||
fragmented frame. Returns None otherwise.
|
||||
|
||||
Args:
|
||||
frame: an inner frame.
|
||||
|
@ -801,17 +811,18 @@ class _InnerMessageBuilder(object):
|
|||
receiving non continuation data opcode but the fin flag of
|
||||
the previous inner frame was not set.)
|
||||
"""
|
||||
|
||||
return self._frame_handler(frame)
|
||||
|
||||
|
||||
class _LogicalStream(Stream):
|
||||
"""Mimics the Stream class. This class interprets multiplexed WebSocket
|
||||
frames.
|
||||
|
||||
"""Mimics the Stream class.
|
||||
|
||||
This class interprets multiplexed WebSocket frames.
|
||||
"""
|
||||
|
||||
def __init__(self, request, stream_options, send_quota, receive_quota):
|
||||
"""Constructs an instance.
|
||||
"""Construct an instance.
|
||||
|
||||
Args:
|
||||
request: _LogicalRequest instance.
|
||||
|
@ -819,7 +830,6 @@ class _LogicalStream(Stream):
|
|||
send_quota: Initial send quota.
|
||||
receive_quota: Initial receive quota.
|
||||
"""
|
||||
|
||||
# Physical stream is responsible for masking.
|
||||
stream_options.unmask_receive = False
|
||||
Stream.__init__(self, request, stream_options)
|
||||
|
@ -928,7 +938,6 @@ class _LogicalStream(Stream):
|
|||
|
||||
def replenish_send_quota(self, send_quota):
|
||||
"""Replenish send quota."""
|
||||
|
||||
try:
|
||||
self._send_condition.acquire()
|
||||
if self._send_quota + send_quota > 0x7FFFFFFFFFFFFFFF:
|
||||
|
@ -943,8 +952,7 @@ class _LogicalStream(Stream):
|
|||
self._send_condition.release()
|
||||
|
||||
def consume_receive_quota(self, amount):
|
||||
"""Consumes receive quota. Returns False on failure."""
|
||||
|
||||
"""Consume receive quota. Returns False on failure."""
|
||||
if self._receive_quota < amount:
|
||||
self._logger.debug('Violate quota on channel id %d: %d < %d' %
|
||||
(self._request.channel_id,
|
||||
|
@ -955,7 +963,6 @@ class _LogicalStream(Stream):
|
|||
|
||||
def send_message(self, message, end=True, binary=False):
|
||||
"""Override Stream.send_message."""
|
||||
|
||||
if self._request.server_terminated:
|
||||
raise BadOperationException(
|
||||
'Requested send_message after sending out a closing handshake')
|
||||
|
@ -985,14 +992,13 @@ class _LogicalStream(Stream):
|
|||
self._last_message_was_fragmented = not end
|
||||
|
||||
def _receive_frame(self):
|
||||
"""Overrides Stream._receive_frame.
|
||||
"""Override Stream._receive_frame.
|
||||
|
||||
In addition to call Stream._receive_frame, this method adds the amount
|
||||
of payload to receiving quota and sends FlowControl to the client.
|
||||
We need to do it here because Stream.receive_message() handles
|
||||
control frames internally.
|
||||
"""
|
||||
|
||||
opcode, payload, fin, rsv1, rsv2, rsv3 = Stream._receive_frame(self)
|
||||
amount = len(payload)
|
||||
# Replenish extra one octet when receiving the first fragmented frame.
|
||||
|
@ -1007,9 +1013,7 @@ class _LogicalStream(Stream):
|
|||
return opcode, payload, fin, rsv1, rsv2, rsv3
|
||||
|
||||
def _get_message_from_frame(self, frame):
|
||||
"""Overrides Stream._get_message_from_frame.
|
||||
"""
|
||||
|
||||
"""Override Stream._get_message_from_frame."""
|
||||
try:
|
||||
inner_message = self._inner_message_builder.build(frame)
|
||||
except InvalidFrameException:
|
||||
|
@ -1022,8 +1026,7 @@ class _LogicalStream(Stream):
|
|||
return inner_message.payload
|
||||
|
||||
def receive_message(self):
|
||||
"""Overrides Stream.receive_message."""
|
||||
|
||||
"""Override Stream.receive_message."""
|
||||
# Just call Stream.receive_message(), but catch
|
||||
# LogicalConnectionClosedException, which is raised when the logical
|
||||
# connection has closed gracefully.
|
||||
|
@ -1034,8 +1037,7 @@ class _LogicalStream(Stream):
|
|||
return None
|
||||
|
||||
def _send_closing_handshake(self, code, reason):
|
||||
"""Overrides Stream._send_closing_handshake."""
|
||||
|
||||
"""Override Stream._send_closing_handshake."""
|
||||
body = create_closing_handshake_body(code, reason)
|
||||
self._logger.debug('Sending closing handshake for %d: (%r, %r)' %
|
||||
(self._request.channel_id, code, reason))
|
||||
|
@ -1044,8 +1046,7 @@ class _LogicalStream(Stream):
|
|||
self._request.server_terminated = True
|
||||
|
||||
def send_ping(self, body=''):
|
||||
"""Overrides Stream.send_ping"""
|
||||
|
||||
"""Override Stream.send_ping."""
|
||||
self._logger.debug('Sending ping on logical channel %d: %r' %
|
||||
(self._request.channel_id, body))
|
||||
self._write_inner_frame(common.OPCODE_PING, body, end=True)
|
||||
|
@ -1053,23 +1054,20 @@ class _LogicalStream(Stream):
|
|||
self._ping_queue.append(body)
|
||||
|
||||
def _send_pong(self, body):
|
||||
"""Overrides Stream._send_pong"""
|
||||
|
||||
"""Override Stream._send_pong."""
|
||||
self._logger.debug('Sending pong on logical channel %d: %r' %
|
||||
(self._request.channel_id, body))
|
||||
self._write_inner_frame(common.OPCODE_PONG, body, end=True)
|
||||
|
||||
def close_connection(self, code=common.STATUS_NORMAL_CLOSURE, reason=''):
|
||||
"""Overrides Stream.close_connection."""
|
||||
|
||||
"""Override Stream.close_connection."""
|
||||
# TODO(bashi): Implement
|
||||
self._logger.debug('Closing logical connection %d' %
|
||||
self._request.channel_id)
|
||||
self._request.server_terminated = True
|
||||
|
||||
def stop_sending(self):
|
||||
"""Stops accepting new send operation (_write_inner_frame)."""
|
||||
|
||||
"""Stop accepting new send operation (_write_inner_frame)."""
|
||||
self._send_condition.acquire()
|
||||
self._send_closed = True
|
||||
self._send_condition.notify()
|
||||
|
@ -1077,7 +1075,10 @@ class _LogicalStream(Stream):
|
|||
|
||||
|
||||
class _OutgoingData(object):
|
||||
"""A structure that holds data to be sent via physical connection and
|
||||
|
||||
"""Simple data/channel container.
|
||||
|
||||
A structure that holds data to be sent via physical connection and
|
||||
origin of the data.
|
||||
"""
|
||||
|
||||
|
@ -1087,6 +1088,7 @@ class _OutgoingData(object):
|
|||
|
||||
|
||||
class _PhysicalConnectionWriter(threading.Thread):
|
||||
|
||||
"""A thread that is responsible for writing data to physical connection.
|
||||
|
||||
TODO(bashi): Make sure there is no thread-safety problem when the reader
|
||||
|
@ -1094,12 +1096,11 @@ class _PhysicalConnectionWriter(threading.Thread):
|
|||
"""
|
||||
|
||||
def __init__(self, mux_handler):
|
||||
"""Constructs an instance.
|
||||
"""Construct an instance.
|
||||
|
||||
Args:
|
||||
mux_handler: _MuxHandler instance.
|
||||
"""
|
||||
|
||||
threading.Thread.__init__(self)
|
||||
self._logger = util.get_class_logger(self)
|
||||
self._mux_handler = mux_handler
|
||||
|
@ -1118,16 +1119,14 @@ class _PhysicalConnectionWriter(threading.Thread):
|
|||
self._deque_condition = threading.Condition()
|
||||
|
||||
def put_outgoing_data(self, data):
|
||||
"""Puts outgoing data.
|
||||
"""Put outgoing data.
|
||||
|
||||
Args:
|
||||
data: _OutgoingData instance.
|
||||
|
||||
Raises:
|
||||
BadOperationException: when the thread has been requested to
|
||||
terminate.
|
||||
"""
|
||||
|
||||
try:
|
||||
self._deque_condition.acquire()
|
||||
if self._stop_requested:
|
||||
|
@ -1193,8 +1192,7 @@ class _PhysicalConnectionWriter(threading.Thread):
|
|||
self._mux_handler.notify_writer_done()
|
||||
|
||||
def stop(self, close_code=common.STATUS_NORMAL_CLOSURE):
|
||||
"""Stops the writer thread."""
|
||||
|
||||
"""Stop the writer thread."""
|
||||
self._deque_condition.acquire()
|
||||
self._stop_requested = True
|
||||
self._close_code = close_code
|
||||
|
@ -1203,16 +1201,16 @@ class _PhysicalConnectionWriter(threading.Thread):
|
|||
|
||||
|
||||
class _PhysicalConnectionReader(threading.Thread):
|
||||
|
||||
"""A thread that is responsible for reading data from physical connection.
|
||||
"""
|
||||
|
||||
def __init__(self, mux_handler):
|
||||
"""Constructs an instance.
|
||||
"""Construct an instance.
|
||||
|
||||
Args:
|
||||
mux_handler: _MuxHandler instance.
|
||||
"""
|
||||
|
||||
threading.Thread.__init__(self)
|
||||
self._logger = util.get_class_logger(self)
|
||||
self._mux_handler = mux_handler
|
||||
|
@ -1254,18 +1252,18 @@ class _PhysicalConnectionReader(threading.Thread):
|
|||
|
||||
|
||||
class _Worker(threading.Thread):
|
||||
|
||||
"""A thread that is responsible for running the corresponding application
|
||||
handler.
|
||||
"""
|
||||
|
||||
def __init__(self, mux_handler, request):
|
||||
"""Constructs an instance.
|
||||
"""Construct an instance.
|
||||
|
||||
Args:
|
||||
mux_handler: _MuxHandler instance.
|
||||
request: _LogicalRequest instance.
|
||||
"""
|
||||
|
||||
threading.Thread.__init__(self)
|
||||
self._logger = util.get_class_logger(self)
|
||||
self._mux_handler = mux_handler
|
||||
|
@ -1286,19 +1284,20 @@ class _Worker(threading.Thread):
|
|||
|
||||
|
||||
class _MuxHandshaker(hybi.Handshaker):
|
||||
|
||||
"""Opening handshake processor for multiplexing."""
|
||||
|
||||
_DUMMY_WEBSOCKET_KEY = 'dGhlIHNhbXBsZSBub25jZQ=='
|
||||
|
||||
def __init__(self, request, dispatcher, send_quota, receive_quota):
|
||||
"""Constructs an instance.
|
||||
"""Construct an instance.
|
||||
|
||||
Args:
|
||||
request: _LogicalRequest instance.
|
||||
dispatcher: Dispatcher instance (dispatch.Dispatcher).
|
||||
send_quota: Initial send quota.
|
||||
receive_quota: Initial receive quota.
|
||||
"""
|
||||
|
||||
hybi.Handshaker.__init__(self, request, dispatcher)
|
||||
self._send_quota = send_quota
|
||||
self._receive_quota = receive_quota
|
||||
|
@ -1316,7 +1315,6 @@ class _MuxHandshaker(hybi.Handshaker):
|
|||
|
||||
def _create_stream(self, stream_options):
|
||||
"""Override hybi.Handshaker._create_stream."""
|
||||
|
||||
self._logger.debug('Creating logical stream for %d' %
|
||||
self._request.channel_id)
|
||||
return _LogicalStream(
|
||||
|
@ -1325,7 +1323,6 @@ class _MuxHandshaker(hybi.Handshaker):
|
|||
|
||||
def _create_handshake_response(self, accept):
|
||||
"""Override hybi._create_handshake_response."""
|
||||
|
||||
response = []
|
||||
|
||||
response.append('HTTP/1.1 101 Switching Protocols\r\n')
|
||||
|
@ -1348,7 +1345,6 @@ class _MuxHandshaker(hybi.Handshaker):
|
|||
|
||||
def _send_handshake(self, accept):
|
||||
"""Override hybi.Handshaker._send_handshake."""
|
||||
|
||||
# Don't send handshake response for the default channel
|
||||
if self._request.channel_id == _DEFAULT_CHANNEL_ID:
|
||||
return
|
||||
|
@ -1363,8 +1359,8 @@ class _MuxHandshaker(hybi.Handshaker):
|
|||
|
||||
|
||||
class _LogicalChannelData(object):
|
||||
"""A structure that holds information about logical channel.
|
||||
"""
|
||||
|
||||
"""A structure that holds information about logical channel."""
|
||||
|
||||
def __init__(self, request, worker):
|
||||
self.request = request
|
|
@ -172,6 +172,7 @@ import socket
|
|||
import sys
|
||||
import threading
|
||||
import time
|
||||
import urlparse
|
||||
|
||||
from mod_pywebsocket import common
|
||||
from mod_pywebsocket import dispatch
|
||||
|
@ -732,21 +733,35 @@ class WebSocketRequestHandler(CGIHTTPServer.CGIHTTPRequestHandler):
|
|||
self._logger.info('Request basic authentication')
|
||||
return False
|
||||
|
||||
host, port, resource = http_header_util.parse_uri(self.path)
|
||||
|
||||
# Special paths for XMLHttpRequest benchmark
|
||||
xhr_benchmark_helper_prefix = '/073be001e10950692ccbf3a2ad21c245'
|
||||
if resource == (xhr_benchmark_helper_prefix + '_send'):
|
||||
parsed_path = urlparse.urlsplit(self.path)
|
||||
if parsed_path.path == (xhr_benchmark_helper_prefix + '_send'):
|
||||
xhr_benchmark_handler = XHRBenchmarkHandler(
|
||||
self.headers, self.rfile, self.wfile)
|
||||
xhr_benchmark_handler.do_send()
|
||||
return False
|
||||
if resource == (xhr_benchmark_helper_prefix + '_receive'):
|
||||
if parsed_path.path == (xhr_benchmark_helper_prefix + '_receive'):
|
||||
xhr_benchmark_handler = XHRBenchmarkHandler(
|
||||
self.headers, self.rfile, self.wfile)
|
||||
xhr_benchmark_handler.do_receive()
|
||||
xhr_benchmark_handler.do_receive_and_parse()
|
||||
return False
|
||||
if parsed_path.path == (xhr_benchmark_helper_prefix +
|
||||
'_receive_getnocache'):
|
||||
xhr_benchmark_handler = XHRBenchmarkHandler(
|
||||
self.headers, self.rfile, self.wfile)
|
||||
xhr_benchmark_handler.do_receive(
|
||||
int(parsed_path.query), False, False)
|
||||
return False
|
||||
if parsed_path.path == (xhr_benchmark_helper_prefix +
|
||||
'_receive_getcache'):
|
||||
xhr_benchmark_handler = XHRBenchmarkHandler(
|
||||
self.headers, self.rfile, self.wfile)
|
||||
xhr_benchmark_handler.do_receive(
|
||||
int(parsed_path.query), False, True)
|
||||
return False
|
||||
|
||||
host, port, resource = http_header_util.parse_uri(self.path)
|
||||
if resource is None:
|
||||
self._logger.info('Invalid URI: %r', self.path)
|
||||
self._logger.info('Fallback to CGIHTTPRequestHandler')
|
|
@ -28,8 +28,7 @@
|
|||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
"""This file exports public symbols.
|
||||
"""
|
||||
"""This file exports public symbols."""
|
||||
|
||||
|
||||
from mod_pywebsocket._stream_base import BadOperationException
|
|
@ -28,8 +28,7 @@
|
|||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
"""WebSocket utilities.
|
||||
"""
|
||||
"""WebSocket utilities."""
|
||||
|
||||
|
||||
import array
|
||||
|
@ -69,7 +68,6 @@ def get_stack_trace():
|
|||
TODO: Remove this when we only support Python 2.4 and above.
|
||||
Use traceback.format_exc instead.
|
||||
"""
|
||||
|
||||
out = StringIO.StringIO()
|
||||
traceback.print_exc(file=out)
|
||||
return out.getvalue()
|
||||
|
@ -77,7 +75,6 @@ def get_stack_trace():
|
|||
|
||||
def prepend_message_to_exception(message, exc):
|
||||
"""Prepend message to the exception."""
|
||||
|
||||
exc.args = (message + str(exc),)
|
||||
return
|
||||
|
||||
|
@ -104,7 +101,7 @@ def __translate_interp(interp, cygwin_path):
|
|||
|
||||
|
||||
def get_script_interp(script_path, cygwin_path=None):
|
||||
"""Gets #!-interpreter command line from the script.
|
||||
r"""Get #!-interpreter command line from the script.
|
||||
|
||||
It also fixes command path. When Cygwin Python is used, e.g. in WebKit,
|
||||
it could run "/usr/bin/perl -wT hello.pl".
|
||||
|
@ -133,7 +130,6 @@ def wrap_popen3_for_win(cygwin_path):
|
|||
cygwin_path: path for cygwin binary if command path is needed to be
|
||||
translated. None if no translation required.
|
||||
"""
|
||||
|
||||
__orig_popen3 = os.popen3
|
||||
|
||||
def __wrap_popen3(cmd, mode='t', bufsize=-1):
|
||||
|
@ -151,27 +147,35 @@ def hexify(s):
|
|||
|
||||
|
||||
def get_class_logger(o):
|
||||
"""Return the logging class information."""
|
||||
return logging.getLogger(
|
||||
'%s.%s' % (o.__class__.__module__, o.__class__.__name__))
|
||||
|
||||
|
||||
class NoopMasker(object):
|
||||
"""A masking object that has the same interface as RepeatedXorMasker but
|
||||
just returns the string passed in without making any change.
|
||||
"""A NoOp masking object.
|
||||
|
||||
This has the same interface as RepeatedXorMasker but just returns
|
||||
the string passed in without making any change.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""NoOp."""
|
||||
pass
|
||||
|
||||
def mask(self, s):
|
||||
"""NoOp."""
|
||||
return s
|
||||
|
||||
|
||||
class RepeatedXorMasker(object):
|
||||
"""A masking object that applies XOR on the string given to mask method
|
||||
with the masking bytes given to the constructor repeatedly. This object
|
||||
remembers the position in the masking bytes the last mask method call
|
||||
ended and resumes from that point on the next mask method call.
|
||||
|
||||
"""A masking object that applies XOR on the string.
|
||||
|
||||
Applies XOR on the string given to mask method with the masking bytes
|
||||
given to the constructor repeatedly. This object remembers the position
|
||||
in the masking bytes the last mask method call ended and resumes from
|
||||
that point on the next mask method call.
|
||||
"""
|
||||
|
||||
def __init__(self, masking_key):
|
||||
|
@ -179,6 +183,7 @@ class RepeatedXorMasker(object):
|
|||
self._masking_key_index = 0
|
||||
|
||||
def _mask_using_swig(self, s):
|
||||
"""Perform the mask via SWIG."""
|
||||
masked_data = fast_masking.mask(
|
||||
s, self._masking_key, self._masking_key_index)
|
||||
self._masking_key_index = (
|
||||
|
@ -186,6 +191,7 @@ class RepeatedXorMasker(object):
|
|||
return masked_data
|
||||
|
||||
def _mask_using_array(self, s):
|
||||
"""Perform the mask via python."""
|
||||
result = array.array('B')
|
||||
result.fromstring(s)
|
||||
|
||||
|
@ -355,7 +361,9 @@ class _RFC1979Deflater(object):
|
|||
|
||||
|
||||
class _RFC1979Inflater(object):
|
||||
"""A decompressor class for byte sequence compressed and flushed following
|
||||
"""A decompressor class a la RFC1979.
|
||||
|
||||
A decompressor class for byte sequence compressed and flushed following
|
||||
the algorithm described in the RFC1979 section 2.1.
|
||||
"""
|
||||
|
|
@ -42,12 +42,13 @@ class XHRBenchmarkHandler(object):
|
|||
response_body = '%d' % content_length
|
||||
self.wfile.write(
|
||||
'HTTP/1.1 200 OK\r\n'
|
||||
'Access-Control-Allow-Origin: *\r\n'
|
||||
'Content-Type: text/html\r\n'
|
||||
'Content-Length: %d\r\n'
|
||||
'\r\n%s' % (len(response_body), response_body))
|
||||
self.wfile.flush()
|
||||
|
||||
def do_receive(self):
|
||||
def do_receive_and_parse(self):
|
||||
content_length = int(self.headers.getheader('Content-Length'))
|
||||
request_body = self.rfile.read(content_length)
|
||||
|
||||
|
@ -63,7 +64,6 @@ class XHRBenchmarkHandler(object):
|
|||
except ValueError, e:
|
||||
self._logger.debug('Malformed size parameter: %r', bytes_to_send)
|
||||
return
|
||||
self._logger.debug('Requested to send %s bytes', bytes_to_send)
|
||||
|
||||
# Parse the transfer encoding parameter.
|
||||
chunked_mode = False
|
||||
|
@ -75,10 +75,22 @@ class XHRBenchmarkHandler(object):
|
|||
self._logger.debug('Invalid mode parameter: %r', mode_parameter)
|
||||
return
|
||||
|
||||
self.do_receive(bytes_to_send, chunked_mode, False)
|
||||
|
||||
def do_receive(self, bytes_to_send, chunked_mode, enable_cache):
|
||||
self._logger.debug(
|
||||
'Requested to send %s bytes (chunked: %s, cache: %s)',
|
||||
bytes_to_send, chunked_mode, enable_cache)
|
||||
# Write a header
|
||||
response_header = (
|
||||
'HTTP/1.1 200 OK\r\n'
|
||||
'Access-Control-Allow-Origin: *\r\n'
|
||||
'Content-Type: application/octet-stream\r\n')
|
||||
if enable_cache:
|
||||
response_header += 'Cache-Control: private, max-age=10\r\n'
|
||||
else:
|
||||
response_header += \
|
||||
'Cache-Control: no-cache, no-store, must-revalidate\r\n'
|
||||
if chunked_mode:
|
||||
response_header += 'Transfer-Encoding: chunked\r\n\r\n'
|
||||
else:
|
|
@ -61,7 +61,7 @@ setup(author='Yuzo Fujishima',
|
|||
'mod_pywebsocket is an Apache HTTP Server extension for '
|
||||
'the WebSocket Protocol (RFC 6455). '
|
||||
'See mod_pywebsocket/__init__.py for more detail.'),
|
||||
license='See COPYING',
|
||||
license='See LICENSE',
|
||||
name=_PACKAGE_NAME,
|
||||
packages=[_PACKAGE_NAME, _PACKAGE_NAME + '.handshake'],
|
||||
url='http://code.google.com/p/pywebsocket/',
|
|
@ -1,19 +0,0 @@
|
|||
// Copyright 2014 Google Inc. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the COPYING file or at
|
||||
// https://developers.google.com/open-source/licenses/bsd
|
||||
|
||||
// Utilities for example applications (for the worker threads only).
|
||||
|
||||
function workerAddToLog(text) {
|
||||
postMessage({type: 'addToLog', data: text});
|
||||
}
|
||||
|
||||
function workerAddToSummary(text) {
|
||||
postMessage({type: 'addToSummary', data: text});
|
||||
}
|
||||
|
||||
function workerMeasureValue(value) {
|
||||
postMessage({type: 'measureValue', data: value});
|
||||
}
|
|
@ -69,79 +69,6 @@ class ExtensionsTest(unittest.TestCase):
|
|||
ValueError, extensions._parse_window_bits, '10000000')
|
||||
|
||||
|
||||
class CompressionMethodParameterParserTest(unittest.TestCase):
|
||||
"""A unittest for _parse_compression_method which parses the compression
|
||||
method description used by perframe-compression and permessage-compression
|
||||
extension in their "method" extension parameter.
|
||||
"""
|
||||
|
||||
def test_parse_method_simple(self):
|
||||
method_list = extensions._parse_compression_method('foo')
|
||||
self.assertEqual(1, len(method_list))
|
||||
method = method_list[0]
|
||||
self.assertEqual('foo', method.name())
|
||||
self.assertEqual(0, len(method.get_parameters()))
|
||||
|
||||
def test_parse_method_with_parameter(self):
|
||||
method_list = extensions._parse_compression_method('foo; x; y=10')
|
||||
self.assertEqual(1, len(method_list))
|
||||
method = method_list[0]
|
||||
self.assertEqual('foo', method.name())
|
||||
self.assertEqual(2, len(method.get_parameters()))
|
||||
self.assertTrue(method.has_parameter('x'))
|
||||
self.assertEqual(None, method.get_parameter_value('x'))
|
||||
self.assertTrue(method.has_parameter('y'))
|
||||
self.assertEqual('10', method.get_parameter_value('y'))
|
||||
|
||||
def test_parse_method_with_quoted_parameter(self):
|
||||
method_list = extensions._parse_compression_method(
|
||||
'foo; x="Hello World"; y=10')
|
||||
self.assertEqual(1, len(method_list))
|
||||
method = method_list[0]
|
||||
self.assertEqual('foo', method.name())
|
||||
self.assertEqual(2, len(method.get_parameters()))
|
||||
self.assertTrue(method.has_parameter('x'))
|
||||
self.assertEqual('Hello World', method.get_parameter_value('x'))
|
||||
self.assertTrue(method.has_parameter('y'))
|
||||
self.assertEqual('10', method.get_parameter_value('y'))
|
||||
|
||||
def test_parse_method_multiple(self):
|
||||
method_list = extensions._parse_compression_method('foo, bar')
|
||||
self.assertEqual(2, len(method_list))
|
||||
self.assertEqual('foo', method_list[0].name())
|
||||
self.assertEqual(0, len(method_list[0].get_parameters()))
|
||||
self.assertEqual('bar', method_list[1].name())
|
||||
self.assertEqual(0, len(method_list[1].get_parameters()))
|
||||
|
||||
def test_parse_method_multiple_methods_with_quoted_parameter(self):
|
||||
method_list = extensions._parse_compression_method(
|
||||
'foo; x="Hello World", bar; y=10')
|
||||
self.assertEqual(2, len(method_list))
|
||||
self.assertEqual('foo', method_list[0].name())
|
||||
self.assertEqual(1, len(method_list[0].get_parameters()))
|
||||
self.assertTrue(method_list[0].has_parameter('x'))
|
||||
self.assertEqual('Hello World',
|
||||
method_list[0].get_parameter_value('x'))
|
||||
self.assertEqual('bar', method_list[1].name())
|
||||
self.assertEqual(1, len(method_list[1].get_parameters()))
|
||||
self.assertTrue(method_list[1].has_parameter('y'))
|
||||
self.assertEqual('10', method_list[1].get_parameter_value('y'))
|
||||
|
||||
def test_create_method_desc_simple(self):
|
||||
params = common.ExtensionParameter('foo')
|
||||
desc = extensions._create_accepted_method_desc('foo',
|
||||
params.get_parameters())
|
||||
self.assertEqual('foo', desc)
|
||||
|
||||
def test_create_method_desc_with_parameters(self):
|
||||
params = common.ExtensionParameter('foo')
|
||||
params.add_parameter('x', 'Hello, World')
|
||||
params.add_parameter('y', '10')
|
||||
desc = extensions._create_accepted_method_desc('foo',
|
||||
params.get_parameters())
|
||||
self.assertEqual('foo; x="Hello, World"; y=10', desc)
|
||||
|
||||
|
||||
class DeflateFrameExtensionProcessorParsingTest(unittest.TestCase):
|
||||
"""A unittest for checking that DeflateFrameExtensionProcessor parses given
|
||||
extension parameter correctly.
|
||||
|
@ -345,14 +272,6 @@ class PerMessageDeflateExtensionProcessorBuildingTest(unittest.TestCase):
|
|||
self.assertEqual(0, len(response.get_parameters()))
|
||||
|
||||
|
||||
class PerMessageCompressExtensionProcessorTest(unittest.TestCase):
|
||||
def test_registry(self):
|
||||
processor = extensions.get_extension_processor(
|
||||
common.ExtensionParameter('permessage-compress'))
|
||||
self.assertIsInstance(processor,
|
||||
extensions.PerMessageCompressExtensionProcessor)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
|
@ -247,14 +247,15 @@ class HandshakerTest(unittest.TestCase):
|
|||
def test_do_handshake_with_extensions(self):
|
||||
request_def = _create_good_request_def()
|
||||
request_def.headers['Sec-WebSocket-Extensions'] = (
|
||||
'permessage-compress; method=deflate, unknown')
|
||||
'permessage-deflate; server_no_context_takeover')
|
||||
|
||||
EXPECTED_RESPONSE = (
|
||||
'HTTP/1.1 101 Switching Protocols\r\n'
|
||||
'Upgrade: websocket\r\n'
|
||||
'Connection: Upgrade\r\n'
|
||||
'Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n'
|
||||
'Sec-WebSocket-Extensions: permessage-compress; method=deflate\r\n'
|
||||
'Sec-WebSocket-Extensions: '
|
||||
'permessage-deflate; server_no_context_takeover\r\n'
|
||||
'\r\n')
|
||||
|
||||
request = _create_request(request_def)
|
||||
|
@ -263,32 +264,21 @@ class HandshakerTest(unittest.TestCase):
|
|||
self.assertEqual(EXPECTED_RESPONSE, request.connection.written_data())
|
||||
self.assertEqual(1, len(request.ws_extensions))
|
||||
extension = request.ws_extensions[0]
|
||||
self.assertEqual(common.PERMESSAGE_COMPRESSION_EXTENSION,
|
||||
self.assertEqual(common.PERMESSAGE_DEFLATE_EXTENSION,
|
||||
extension.name())
|
||||
self.assertEqual(['method'], extension.get_parameter_names())
|
||||
self.assertEqual('deflate', extension.get_parameter_value('method'))
|
||||
self.assertEqual(['server_no_context_takeover'],
|
||||
extension.get_parameter_names())
|
||||
self.assertEqual(None,
|
||||
extension.get_parameter_value(
|
||||
'server_no_context_takeover'))
|
||||
self.assertEqual(1, len(request.ws_extension_processors))
|
||||
self.assertEqual(common.PERMESSAGE_COMPRESSION_EXTENSION,
|
||||
request.ws_extension_processors[0].name())
|
||||
|
||||
def test_do_handshake_with_permessage_compress(self):
|
||||
request_def = _create_good_request_def()
|
||||
request_def.headers['Sec-WebSocket-Extensions'] = (
|
||||
'permessage-compress; method=deflate')
|
||||
request = _create_request(request_def)
|
||||
handshaker = _create_handshaker(request)
|
||||
handshaker.do_handshake()
|
||||
self.assertEqual(1, len(request.ws_extensions))
|
||||
self.assertEqual(common.PERMESSAGE_COMPRESSION_EXTENSION,
|
||||
request.ws_extensions[0].name())
|
||||
self.assertEqual(1, len(request.ws_extension_processors))
|
||||
self.assertEqual(common.PERMESSAGE_COMPRESSION_EXTENSION,
|
||||
self.assertEqual('deflate',
|
||||
request.ws_extension_processors[0].name())
|
||||
|
||||
def test_do_handshake_with_quoted_extensions(self):
|
||||
request_def = _create_good_request_def()
|
||||
request_def.headers['Sec-WebSocket-Extensions'] = (
|
||||
'permessage-compress; method=deflate, , '
|
||||
'permessage-deflate, , '
|
||||
'unknown; e = "mc^2"; ma="\r\n \\\rf "; pv=nrt')
|
||||
|
||||
request = _create_request(request_def)
|
||||
|
@ -296,10 +286,7 @@ class HandshakerTest(unittest.TestCase):
|
|||
handshaker.do_handshake()
|
||||
self.assertEqual(2, len(request.ws_requested_extensions))
|
||||
first_extension = request.ws_requested_extensions[0]
|
||||
self.assertEqual('permessage-compress', first_extension.name())
|
||||
self.assertEqual(['method'], first_extension.get_parameter_names())
|
||||
self.assertEqual('deflate',
|
||||
first_extension.get_parameter_value('method'))
|
||||
self.assertEqual('permessage-deflate', first_extension.name())
|
||||
second_extension = request.ws_requested_extensions[1]
|
||||
self.assertEqual('unknown', second_extension.name())
|
||||
self.assertEqual(
|
||||
|
@ -368,11 +355,11 @@ class HandshakerTest(unittest.TestCase):
|
|||
request.ws_extension_processors[1].name())
|
||||
self.assertFalse(hasattr(request, 'mux'))
|
||||
|
||||
def test_do_handshake_with_permessage_compress_and_mux(self):
|
||||
def test_do_handshake_with_permessage_deflate_and_mux(self):
|
||||
request_def = _create_good_request_def()
|
||||
request_def.headers['Sec-WebSocket-Extensions'] = (
|
||||
'%s; method=deflate, %s' % (
|
||||
common.PERMESSAGE_COMPRESSION_EXTENSION,
|
||||
'%s, %s' % (
|
||||
common.PERMESSAGE_DEFLATE_EXTENSION,
|
||||
common.MUX_EXTENSION))
|
||||
request = _create_request(request_def)
|
||||
handshaker = _create_handshaker(request)
|
||||
|
@ -382,7 +369,7 @@ class HandshakerTest(unittest.TestCase):
|
|||
self.assertEqual(common.MUX_EXTENSION,
|
||||
request.ws_extensions[0].name())
|
||||
self.assertEqual(2, len(request.ws_extension_processors))
|
||||
self.assertEqual(common.PERMESSAGE_COMPRESSION_EXTENSION,
|
||||
self.assertEqual('deflate',
|
||||
request.ws_extension_processors[0].name())
|
||||
self.assertEqual(common.MUX_EXTENSION,
|
||||
request.ws_extension_processors[1].name())
|
||||
|
@ -390,27 +377,27 @@ class HandshakerTest(unittest.TestCase):
|
|||
self.assertTrue(request.mux_processor.is_active())
|
||||
mux_extensions = request.mux_processor.extensions()
|
||||
self.assertEqual(1, len(mux_extensions))
|
||||
self.assertEqual(common.PERMESSAGE_COMPRESSION_EXTENSION,
|
||||
self.assertEqual(common.PERMESSAGE_DEFLATE_EXTENSION,
|
||||
mux_extensions[0].name())
|
||||
|
||||
def test_do_handshake_with_mux_and_permessage_compress(self):
|
||||
def test_do_handshake_with_mux_and_permessage_deflate(self):
|
||||
request_def = _create_good_request_def()
|
||||
request_def.headers['Sec-WebSocket-Extensions'] = (
|
||||
'%s, %s; method=deflate' % (
|
||||
'%s, %s' % (
|
||||
common.MUX_EXTENSION,
|
||||
common.PERMESSAGE_COMPRESSION_EXTENSION))
|
||||
common.PERMESSAGE_DEFLATE_EXTENSION))
|
||||
request = _create_request(request_def)
|
||||
handshaker = _create_handshaker(request)
|
||||
handshaker.do_handshake()
|
||||
# mux should be rejected.
|
||||
self.assertEqual(1, len(request.ws_extensions))
|
||||
first_extension = request.ws_extensions[0]
|
||||
self.assertEqual(common.PERMESSAGE_COMPRESSION_EXTENSION,
|
||||
self.assertEqual(common.PERMESSAGE_DEFLATE_EXTENSION,
|
||||
first_extension.name())
|
||||
self.assertEqual(2, len(request.ws_extension_processors))
|
||||
self.assertEqual(common.MUX_EXTENSION,
|
||||
request.ws_extension_processors[0].name())
|
||||
self.assertEqual(common.PERMESSAGE_COMPRESSION_EXTENSION,
|
||||
self.assertEqual('deflate',
|
||||
request.ws_extension_processors[1].name())
|
||||
self.assertFalse(hasattr(request, 'mux_processor'))
|
||||
|
|
@ -44,7 +44,6 @@ import set_sys_path # Update sys.path to locate mod_pywebsocket module.
|
|||
|
||||
from mod_pywebsocket import common
|
||||
from mod_pywebsocket.extensions import DeflateFrameExtensionProcessor
|
||||
from mod_pywebsocket.extensions import PerMessageCompressExtensionProcessor
|
||||
from mod_pywebsocket.extensions import PerMessageDeflateExtensionProcessor
|
||||
from mod_pywebsocket import msgutil
|
||||
from mod_pywebsocket.stream import InvalidUTF8Exception
|
||||
|
@ -81,7 +80,6 @@ def _install_extension_processor(processor, request, stream_options):
|
|||
def _create_request_from_rawdata(
|
||||
read_data,
|
||||
deflate_frame_request=None,
|
||||
permessage_compression_request=None,
|
||||
permessage_deflate_request=None):
|
||||
req = mock.MockRequest(connection=mock.MockConn(''.join(read_data)))
|
||||
req.ws_version = common.VERSION_HYBI_LATEST
|
||||
|
@ -90,9 +88,6 @@ def _create_request_from_rawdata(
|
|||
processor = None
|
||||
if deflate_frame_request is not None:
|
||||
processor = DeflateFrameExtensionProcessor(deflate_frame_request)
|
||||
elif permessage_compression_request is not None:
|
||||
processor = PerMessageCompressExtensionProcessor(
|
||||
permessage_compression_request)
|
||||
elif permessage_deflate_request is not None:
|
||||
processor = PerMessageDeflateExtensionProcessor(
|
||||
permessage_deflate_request)
|
||||
|
@ -732,6 +727,33 @@ class DeflateFrameTest(unittest.TestCase):
|
|||
class PerMessageDeflateTest(unittest.TestCase):
|
||||
"""Tests for permessage-deflate extension."""
|
||||
|
||||
def test_response_parameters(self):
|
||||
extension = common.ExtensionParameter(
|
||||
common.PERMESSAGE_DEFLATE_EXTENSION)
|
||||
extension.add_parameter('server_no_context_takeover', None)
|
||||
processor = PerMessageDeflateExtensionProcessor(extension)
|
||||
response = processor.get_extension_response()
|
||||
self.assertTrue(
|
||||
response.has_parameter('server_no_context_takeover'))
|
||||
self.assertEqual(
|
||||
None, response.get_parameter_value('server_no_context_takeover'))
|
||||
|
||||
extension = common.ExtensionParameter(
|
||||
common.PERMESSAGE_DEFLATE_EXTENSION)
|
||||
extension.add_parameter('client_max_window_bits', None)
|
||||
processor = PerMessageDeflateExtensionProcessor(extension)
|
||||
|
||||
processor.set_client_max_window_bits(8)
|
||||
processor.set_client_no_context_takeover(True)
|
||||
response = processor.get_extension_response()
|
||||
self.assertEqual(
|
||||
'8', response.get_parameter_value('client_max_window_bits'))
|
||||
self.assertTrue(
|
||||
response.has_parameter('client_no_context_takeover'))
|
||||
self.assertEqual(
|
||||
None,
|
||||
response.get_parameter_value('client_no_context_takeover'))
|
||||
|
||||
def test_send_message(self):
|
||||
extension = common.ExtensionParameter(
|
||||
common.PERMESSAGE_DEFLATE_EXTENSION)
|
||||
|
@ -758,16 +780,12 @@ class PerMessageDeflateTest(unittest.TestCase):
|
|||
|
||||
msgutil.send_message(request, '')
|
||||
|
||||
# Payload in binary: 0b00000010 0b00000000
|
||||
# Payload in binary: 0b00000000
|
||||
# From LSB,
|
||||
# - 1 bit of BFINAL (0)
|
||||
# - 2 bits of BTYPE (01 that means fixed Huffman)
|
||||
# - 7 bits of the first code (0000000 that is the code for the
|
||||
# end-of-block)
|
||||
# - 1 bit of BFINAL (0)
|
||||
# - 2 bits of BTYPE (no compression)
|
||||
# - 3 bits of padding
|
||||
self.assertEqual('\xc1\x02\x02\x00',
|
||||
# - 5 bits of padding
|
||||
self.assertEqual('\xc1\x01\x00',
|
||||
request.connection.written_data())
|
||||
|
||||
def test_send_message_with_null_character(self):
|
||||
|
@ -1172,34 +1190,6 @@ class PerMessageDeflateTest(unittest.TestCase):
|
|||
self.assertEqual(None, msgutil.receive_message(request))
|
||||
|
||||
|
||||
class PerMessageCompressTest(unittest.TestCase):
|
||||
"""Tests for checking permessage-compression extension."""
|
||||
|
||||
def test_deflate_response_parameters(self):
|
||||
extension = common.ExtensionParameter(
|
||||
common.PERMESSAGE_COMPRESSION_EXTENSION)
|
||||
extension.add_parameter('method', 'deflate')
|
||||
processor = PerMessageCompressExtensionProcessor(extension)
|
||||
response = processor.get_extension_response()
|
||||
self.assertEqual('deflate',
|
||||
response.get_parameter_value('method'))
|
||||
|
||||
extension = common.ExtensionParameter(
|
||||
common.PERMESSAGE_COMPRESSION_EXTENSION)
|
||||
extension.add_parameter('method', 'deflate')
|
||||
processor = PerMessageCompressExtensionProcessor(extension)
|
||||
|
||||
def _compression_processor_hook(compression_processor):
|
||||
compression_processor.set_client_max_window_bits(8)
|
||||
compression_processor.set_client_no_context_takeover(True)
|
||||
processor.set_compression_processor_hook(
|
||||
_compression_processor_hook)
|
||||
response = processor.get_extension_response()
|
||||
self.assertEqual(
|
||||
'deflate; client_max_window_bits=8; client_no_context_takeover',
|
||||
response.get_parameter_value('method'))
|
||||
|
||||
|
||||
class MessageTestHixie75(unittest.TestCase):
|
||||
"""Tests for draft-hixie-thewebsocketprotocol-76 stream class."""
|
||||
|
|
@ -839,10 +839,10 @@ class MuxHandlerTest(unittest.TestCase):
|
|||
None,
|
||||
dispatcher.channel_events[3].request.ws_protocol)
|
||||
|
||||
def test_add_channel_delta_encoding_permessage_compress(self):
|
||||
# Enable permessage compress extension on the implicitly opened channel.
|
||||
def test_add_channel_delta_encoding_permessage_deflate(self):
|
||||
# Enable permessage deflate extension on the implicitly opened channel.
|
||||
extensions = common.parse_extensions(
|
||||
'%s; method=deflate' % common.PERMESSAGE_COMPRESSION_EXTENSION)
|
||||
common.PERMESSAGE_DEFLATE_EXTENSION)
|
||||
request = _create_mock_request(
|
||||
logical_channel_extensions=extensions)
|
||||
dispatcher = _MuxMockDispatcher()
|
||||
|
@ -892,9 +892,9 @@ class MuxHandlerTest(unittest.TestCase):
|
|||
self.assertEqual(compressed_hello, messages[0])
|
||||
|
||||
def test_add_channel_delta_encoding_remove_extensions(self):
|
||||
# Enable permessage compress extension on the implicitly opened channel.
|
||||
# Enable permessage deflate extension on the implicitly opened channel.
|
||||
extensions = common.parse_extensions(
|
||||
'%s; method=deflate' % common.PERMESSAGE_COMPRESSION_EXTENSION)
|
||||
common.PERMESSAGE_DEFLATE_EXTENSION)
|
||||
request = _create_mock_request(
|
||||
logical_channel_extensions=extensions)
|
||||
dispatcher = _MuxMockDispatcher()
|
||||
|
@ -903,7 +903,7 @@ class MuxHandlerTest(unittest.TestCase):
|
|||
mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS,
|
||||
mux._INITIAL_QUOTA_FOR_CLIENT)
|
||||
|
||||
# Remove permessage compress extension.
|
||||
# Remove permessage deflate extension.
|
||||
delta = ('GET /echo HTTP/1.1\r\n'
|
||||
'Sec-WebSocket-Extensions:\r\n'
|
||||
'\r\n')
|
||||
|
@ -1912,7 +1912,7 @@ class MuxHandlerTest(unittest.TestCase):
|
|||
self.assertEqual(common.STATUS_INTERNAL_ENDPOINT_ERROR,
|
||||
request.connection.server_close_code)
|
||||
|
||||
def test_permessage_compress(self):
|
||||
def test_permessage_deflate(self):
|
||||
request = _create_mock_request()
|
||||
dispatcher = _MuxMockDispatcher()
|
||||
mux_handler = mux._MuxHandler(request, dispatcher)
|
||||
|
@ -1920,9 +1920,8 @@ class MuxHandlerTest(unittest.TestCase):
|
|||
mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS,
|
||||
mux._INITIAL_QUOTA_FOR_CLIENT)
|
||||
|
||||
# Enable permessage compress extension on logical channel 2.
|
||||
extensions = '%s; method=deflate' % (
|
||||
common.PERMESSAGE_COMPRESSION_EXTENSION)
|
||||
# Enable permessage deflate extension on logical channel 2.
|
||||
extensions = common.PERMESSAGE_DEFLATE_EXTENSION
|
||||
encoded_handshake = _create_request_header(path='/echo',
|
||||
extensions=extensions)
|
||||
add_channel_request = _create_add_channel_request_frame(
|
||||
|
@ -1966,9 +1965,9 @@ class MuxHandlerTest(unittest.TestCase):
|
|||
self.assertEqual(compressed_hello2, messages[1])
|
||||
|
||||
|
||||
def test_permessage_compress_fragmented_message(self):
|
||||
def test_permessage_deflate_fragmented_message(self):
|
||||
extensions = common.parse_extensions(
|
||||
'%s; method=deflate' % common.PERMESSAGE_COMPRESSION_EXTENSION)
|
||||
common.PERMESSAGE_DEFLATE_EXTENSION)
|
||||
request = _create_mock_request(
|
||||
logical_channel_extensions=extensions)
|
||||
dispatcher = _MuxMockDispatcher()
|
Loading…
Add table
Add a link
Reference in a new issue