Commit 0665b8d7 authored by yhirano@chromium.org's avatar yhirano@chromium.org

[WebSocket] Add tests for permessage deflate split frames.

Upgrade pywebsocket to r804.

BUG=NONE
R=tyoshino

Review URL: https://codereview.chromium.org/314043002

git-svn-id: svn://svn.chromium.org/blink/trunk@175544 bbb929c8-8fbe-4397-9dbb-9b2b20218538
parent 23fb6337
Deflated message is split to multiple frames.
On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
onmessage: HelloHello
onclose
PASS successfullyParsed is true
TEST COMPLETE
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<html>
<head>
<script src="/js-test-resources/js-test.js"></script>
</head>
<body>
<script type="text/javascript">
description("Deflated message is split to multiple frames.");
window.jsTestIsAsync = true;
var ws = new WebSocket('ws://localhost:8880/permessage-deflate-split-frames');
ws.onopen = function()
{
ws.send('kick');
};
ws.onmessage = function(e)
{
debug('onmessage: ' + e.data);
}
ws.onclose = function(e)
{
debug('onclose');
finishJSTest();
};
</script>
</body>
</html>
# Copyright 2014 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
from mod_pywebsocket import common
from mod_pywebsocket.extensions import PerMessageDeflateExtensionProcessor
from mod_pywebsocket.stream import create_header
def _get_permessage_deflate_extension_processor(request):
for extension_processor in request.ws_extension_processors:
if isinstance(extension_processor,
PerMessageDeflateExtensionProcessor):
return extension_processor
return None
def web_socket_do_extra_handshake(request):
processor = _get_permessage_deflate_extension_processor(request)
assert processor is not None
# Remove extension processors other than
# PerMessageDeflateExtensionProcessor to avoid conflict.
request.ws_extension_processors = [processor]
def web_socket_transfer_data(request):
line = request.ws_stream.receive_message()
# Hello
payload = b'\xf2\x48\xcd\xc9\xc9\x07\x00\x00\x00\xff\xff'
# Strip \x00\x00\xff\xff
stripped = payload[:-4]
header = create_header(common.OPCODE_TEXT, len(payload),
fin=0, rsv1=1, rsv2=0, rsv3=0, mask=False)
request.ws_stream._write(header + payload)
header = create_header(common.OPCODE_CONTINUATION, len(stripped),
fin=1, rsv1=0, rsv2=0, rsv3=0, mask=False)
request.ws_stream._write(header + stripped)
# vi:sts=4 sw=4 et
...@@ -292,6 +292,31 @@ TEST(WebSocketPerMessageDeflateTest, TestInflate) ...@@ -292,6 +292,31 @@ TEST(WebSocketPerMessageDeflateTest, TestInflate)
EXPECT_TRUE(f3.final); EXPECT_TRUE(f3.final);
} }
TEST(WebSocketPerMessageDeflateTest, TestInflateMultipleBlocksOverMultipleFrames)
{
WebSocketPerMessageDeflate c;
c.enable(8, WebSocketDeflater::TakeOverContext);
WebSocketFrame::OpCode opcode = WebSocketFrame::OpCodeText;
WebSocketFrame::OpCode continuation = WebSocketFrame::OpCodeContinuation;
std::string expected = "HelloHello";
std::string actual;
WebSocketFrame f1(opcode, "\xf2\x48\xcd\xc9\xc9\x07\x00\x00\x00\xff\xff", 11, WebSocketFrame::Compress);
WebSocketFrame f2(continuation, "\xf2\x00\x11\x00\x00", 5, WebSocketFrame::Final);
ASSERT_TRUE(c.inflate(f1));
EXPECT_FALSE(f1.compress);
EXPECT_FALSE(f1.final);
actual += std::string(f1.payload, f1.payloadLength);
c.resetInflateBuffer();
ASSERT_TRUE(c.inflate(f2));
EXPECT_FALSE(f2.compress);
EXPECT_TRUE(f2.final);
actual += std::string(f2.payload, f2.payloadLength);
EXPECT_EQ(expected, actual);
}
TEST(WebSocketPerMessageDeflateTest, TestInflateEmptyFrame) TEST(WebSocketPerMessageDeflateTest, TestInflateEmptyFrame)
{ {
WebSocketPerMessageDeflate c; WebSocketPerMessageDeflate c;
......
...@@ -102,7 +102,6 @@ SEC_WEBSOCKET_LOCATION_HEADER = 'Sec-WebSocket-Location' ...@@ -102,7 +102,6 @@ SEC_WEBSOCKET_LOCATION_HEADER = 'Sec-WebSocket-Location'
# Extensions # Extensions
DEFLATE_FRAME_EXTENSION = 'deflate-frame' DEFLATE_FRAME_EXTENSION = 'deflate-frame'
PERFRAME_COMPRESSION_EXTENSION = 'perframe-compress'
PERMESSAGE_COMPRESSION_EXTENSION = 'permessage-compress' PERMESSAGE_COMPRESSION_EXTENSION = 'permessage-compress'
PERMESSAGE_DEFLATE_EXTENSION = 'permessage-deflate' PERMESSAGE_DEFLATE_EXTENSION = 'permessage-deflate'
X_WEBKIT_DEFLATE_FRAME_EXTENSION = 'x-webkit-deflate-frame' X_WEBKIT_DEFLATE_FRAME_EXTENSION = 'x-webkit-deflate-frame'
...@@ -195,7 +194,7 @@ class ExtensionParsingException(Exception): ...@@ -195,7 +194,7 @@ class ExtensionParsingException(Exception):
super(ExtensionParsingException, self).__init__(name) super(ExtensionParsingException, self).__init__(name)
def _parse_extension_param(state, definition, allow_quoted_string): def _parse_extension_param(state, definition):
param_name = http_header_util.consume_token(state) param_name = http_header_util.consume_token(state)
if param_name is None: if param_name is None:
...@@ -209,11 +208,8 @@ def _parse_extension_param(state, definition, allow_quoted_string): ...@@ -209,11 +208,8 @@ def _parse_extension_param(state, definition, allow_quoted_string):
http_header_util.consume_lwses(state) http_header_util.consume_lwses(state)
if allow_quoted_string: # TODO(tyoshino): Add code to validate that parsed param_value is token
# TODO(toyoshim): Add code to validate that parsed param_value is token param_value = http_header_util.consume_token_or_quoted_string(state)
param_value = http_header_util.consume_token_or_quoted_string(state)
else:
param_value = http_header_util.consume_token(state)
if param_value is None: if param_value is None:
raise ExtensionParsingException( raise ExtensionParsingException(
'No valid parameter value found on the right-hand side of ' 'No valid parameter value found on the right-hand side of '
...@@ -222,7 +218,7 @@ def _parse_extension_param(state, definition, allow_quoted_string): ...@@ -222,7 +218,7 @@ def _parse_extension_param(state, definition, allow_quoted_string):
definition.add_parameter(param_name, param_value) definition.add_parameter(param_name, param_value)
def _parse_extension(state, allow_quoted_string): def _parse_extension(state):
extension_token = http_header_util.consume_token(state) extension_token = http_header_util.consume_token(state)
if extension_token is None: if extension_token is None:
return None return None
...@@ -238,7 +234,7 @@ def _parse_extension(state, allow_quoted_string): ...@@ -238,7 +234,7 @@ def _parse_extension(state, allow_quoted_string):
http_header_util.consume_lwses(state) http_header_util.consume_lwses(state)
try: try:
_parse_extension_param(state, extension, allow_quoted_string) _parse_extension_param(state, extension)
except ExtensionParsingException, e: except ExtensionParsingException, e:
raise ExtensionParsingException( raise ExtensionParsingException(
'Failed to parse parameter for %r (%r)' % 'Failed to parse parameter for %r (%r)' %
...@@ -247,7 +243,7 @@ def _parse_extension(state, allow_quoted_string): ...@@ -247,7 +243,7 @@ def _parse_extension(state, allow_quoted_string):
return extension return extension
def parse_extensions(data, allow_quoted_string=False): def parse_extensions(data):
"""Parses Sec-WebSocket-Extensions header value returns a list of """Parses Sec-WebSocket-Extensions header value returns a list of
ExtensionParameter objects. ExtensionParameter objects.
...@@ -258,7 +254,7 @@ def parse_extensions(data, allow_quoted_string=False): ...@@ -258,7 +254,7 @@ def parse_extensions(data, allow_quoted_string=False):
extension_list = [] extension_list = []
while True: while True:
extension = _parse_extension(state, allow_quoted_string) extension = _parse_extension(state)
if extension is not None: if extension is not None:
extension_list.append(extension) extension_list.append(extension)
......
...@@ -330,7 +330,7 @@ _compression_extension_names.append(common.X_WEBKIT_DEFLATE_FRAME_EXTENSION) ...@@ -330,7 +330,7 @@ _compression_extension_names.append(common.X_WEBKIT_DEFLATE_FRAME_EXTENSION)
def _parse_compression_method(data): def _parse_compression_method(data):
"""Parses the value of "method" extension parameter.""" """Parses the value of "method" extension parameter."""
return common.parse_extensions(data, allow_quoted_string=True) return common.parse_extensions(data)
def _create_accepted_method_desc(method_name, method_params): def _create_accepted_method_desc(method_name, method_params):
...@@ -421,32 +421,6 @@ class CompressionExtensionProcessorBase(ExtensionProcessorInterface): ...@@ -421,32 +421,6 @@ class CompressionExtensionProcessorBase(ExtensionProcessorInterface):
return self._compression_processor return self._compression_processor
class PerFrameCompressExtensionProcessor(CompressionExtensionProcessorBase):
"""perframe-compress processor.
Specification:
http://tools.ietf.org/html/draft-ietf-hybi-websocket-perframe-compression
"""
_DEFLATE_METHOD = 'deflate'
def __init__(self, request):
CompressionExtensionProcessorBase.__init__(self, request)
def name(self):
return common.PERFRAME_COMPRESSION_EXTENSION
def _lookup_compression_processor(self, method_desc):
if method_desc.name() == self._DEFLATE_METHOD:
return DeflateFrameExtensionProcessor(method_desc)
return None
_available_processors[common.PERFRAME_COMPRESSION_EXTENSION] = (
PerFrameCompressExtensionProcessor)
_compression_extension_names.append(common.PERFRAME_COMPRESSION_EXTENSION)
class PerMessageDeflateExtensionProcessor(ExtensionProcessorInterface): class PerMessageDeflateExtensionProcessor(ExtensionProcessorInterface):
"""permessage-deflate extension processor. It's also used for """permessage-deflate extension processor. It's also used for
permessage-compress extension when the deflate method is chosen. permessage-compress extension when the deflate method is chosen.
...@@ -690,7 +664,7 @@ class _PerMessageDeflateFramer(object): ...@@ -690,7 +664,7 @@ class _PerMessageDeflateFramer(object):
original_payload_size) original_payload_size)
message = self._rfc1979_deflater.filter( message = self._rfc1979_deflater.filter(
message, flush=end, bfinal=self._bfinal) message, end=end, bfinal=self._bfinal)
filtered_payload_size = len(message) filtered_payload_size = len(message)
self._outgoing_average_ratio_calculator.add_result_bytes( self._outgoing_average_ratio_calculator.add_result_bytes(
...@@ -844,16 +818,14 @@ class MuxExtensionProcessor(ExtensionProcessorInterface): ...@@ -844,16 +818,14 @@ class MuxExtensionProcessor(ExtensionProcessorInterface):
# Mux extension cannot be used after extensions # Mux extension cannot be used after extensions
# that depend on frame boundary, extension data field, or any # that depend on frame boundary, extension data field, or any
# reserved bits which are attributed to each frame. # reserved bits which are attributed to each frame.
if (name == common.PERFRAME_COMPRESSION_EXTENSION or if (name == common.DEFLATE_FRAME_EXTENSION or
name == common.DEFLATE_FRAME_EXTENSION or
name == common.X_WEBKIT_DEFLATE_FRAME_EXTENSION): name == common.X_WEBKIT_DEFLATE_FRAME_EXTENSION):
self.set_active(False) self.set_active(False)
return return
else: else:
# Mux extension should not be applied before any history-based # Mux extension should not be applied before any history-based
# compression extension. # compression extension.
if (name == common.PERFRAME_COMPRESSION_EXTENSION or if (name == common.DEFLATE_FRAME_EXTENSION or
name == common.DEFLATE_FRAME_EXTENSION or
name == common.X_WEBKIT_DEFLATE_FRAME_EXTENSION or name == common.X_WEBKIT_DEFLATE_FRAME_EXTENSION or
name == common.PERMESSAGE_COMPRESSION_EXTENSION or name == common.PERMESSAGE_COMPRESSION_EXTENSION or
name == common.X_WEBKIT_PERMESSAGE_COMPRESSION_EXTENSION): name == common.X_WEBKIT_PERMESSAGE_COMPRESSION_EXTENSION):
......
...@@ -69,13 +69,10 @@ from mod_pywebsocket import util ...@@ -69,13 +69,10 @@ from mod_pywebsocket import util
_SEC_WEBSOCKET_KEY_REGEX = re.compile('^[+/0-9A-Za-z]{21}[AQgw]==$') _SEC_WEBSOCKET_KEY_REGEX = re.compile('^[+/0-9A-Za-z]{21}[AQgw]==$')
# Defining aliases for values used frequently. # Defining aliases for values used frequently.
_VERSION_HYBI08 = common.VERSION_HYBI08
_VERSION_HYBI08_STRING = str(_VERSION_HYBI08)
_VERSION_LATEST = common.VERSION_HYBI_LATEST _VERSION_LATEST = common.VERSION_HYBI_LATEST
_VERSION_LATEST_STRING = str(_VERSION_LATEST) _VERSION_LATEST_STRING = str(_VERSION_LATEST)
_SUPPORTED_VERSIONS = [ _SUPPORTED_VERSIONS = [
_VERSION_LATEST, _VERSION_LATEST,
_VERSION_HYBI08,
] ]
...@@ -150,9 +147,6 @@ class Handshaker(object): ...@@ -150,9 +147,6 @@ class Handshaker(object):
self._request.ws_version = self._check_version() self._request.ws_version = self._check_version()
# This handshake must be based on latest hybi. We are responsible to
# fallback to HTTP on handshake failure as latest hybi handshake
# specifies.
try: try:
self._get_origin() self._get_origin()
self._set_protocol() self._set_protocol()
...@@ -286,10 +280,7 @@ class Handshaker(object): ...@@ -286,10 +280,7 @@ class Handshaker(object):
raise e raise e
def _get_origin(self): def _get_origin(self):
if self._request.ws_version is _VERSION_HYBI08: origin_header = common.ORIGIN_HEADER
origin_header = common.SEC_WEBSOCKET_ORIGIN_HEADER
else:
origin_header = common.ORIGIN_HEADER
origin = self._request.headers_in.get(origin_header) origin = self._request.headers_in.get(origin_header)
if origin is None: if origin is None:
self._logger.debug('Client request does not have origin header') self._logger.debug('Client request does not have origin header')
...@@ -298,8 +289,6 @@ class Handshaker(object): ...@@ -298,8 +289,6 @@ class Handshaker(object):
def _check_version(self): def _check_version(self):
version = get_mandatory_header(self._request, version = get_mandatory_header(self._request,
common.SEC_WEBSOCKET_VERSION_HEADER) common.SEC_WEBSOCKET_VERSION_HEADER)
if version == _VERSION_HYBI08_STRING:
return _VERSION_HYBI08
if version == _VERSION_LATEST_STRING: if version == _VERSION_LATEST_STRING:
return _VERSION_LATEST return _VERSION_LATEST
...@@ -335,13 +324,9 @@ class Handshaker(object): ...@@ -335,13 +324,9 @@ class Handshaker(object):
self._request.ws_requested_extensions = None self._request.ws_requested_extensions = None
return return
if self._request.ws_version is common.VERSION_HYBI08:
allow_quoted_string=False
else:
allow_quoted_string=True
try: try:
self._request.ws_requested_extensions = common.parse_extensions( self._request.ws_requested_extensions = common.parse_extensions(
extensions_header, allow_quoted_string=allow_quoted_string) extensions_header)
except common.ExtensionParsingException, e: except common.ExtensionParsingException, e:
raise HandshakeException( raise HandshakeException(
'Failed to parse Sec-WebSocket-Extensions header: %r' % e) 'Failed to parse Sec-WebSocket-Extensions header: %r' % e)
......
...@@ -36,6 +36,7 @@ Use this file to launch pywebsocket without Apache HTTP Server. ...@@ -36,6 +36,7 @@ Use this file to launch pywebsocket without Apache HTTP Server.
BASIC USAGE BASIC USAGE
===========
Go to the src directory and run Go to the src directory and run
...@@ -61,10 +62,13 @@ For trouble shooting, adding "--log_level debug" might help you. ...@@ -61,10 +62,13 @@ For trouble shooting, adding "--log_level debug" might help you.
TRY DEMO TRY DEMO
========
Go to the src directory and run Go to the src directory and run standalone.py with -d option to set the
document root to the directory containing example HTMLs and handlers like this:
$ python standalone.py -d example $ cd src
$ PYTHONPATH=. python mod_pywebsocket/standalone.py -d example
to launch pywebsocket with the sample handler and html on port 80. Open to launch pywebsocket with the sample handler and html on port 80. Open
http://localhost/console.html, click the connect button, type something into http://localhost/console.html, click the connect button, type something into
...@@ -72,24 +76,48 @@ the text box next to the send button and click the send button. If everything ...@@ -72,24 +76,48 @@ the text box next to the send button and click the send button. If everything
is working, you'll see the message you typed echoed by the server. is working, you'll see the message you typed echoed by the server.
SUPPORTING TLS USING TLS
=========
To support TLS, run standalone.py with -t, -k, and -c options. To run the standalone server with TLS support, run it with -t, -k, and -c
options. When TLS is enabled, the standalone server accepts only TLS connection.
Note that when ssl module is used and the key/cert location is incorrect, Note that when ssl module is used and the key/cert location is incorrect,
TLS connection silently fails while pyOpenSSL fails on startup. TLS connection silently fails while pyOpenSSL fails on startup.
Example:
$ PYTHONPATH=. python mod_pywebsocket/standalone.py \
-d example \
-p 10443 \
-t \
-c ../test/cert/cert.pem \
-k ../test/cert/key.pem \
Note that when passing a relative path to -c and -k option, it will be resolved
using the document root directory as the base.
SUPPORTING CLIENT AUTHENTICATION USING CLIENT AUTHENTICATION
===========================
To support client authentication with TLS, run standalone.py with -t, -k, -c, To run the standalone server with TLS client authentication support, run it with
and --tls-client-auth, and --tls-client-ca options. --tls-client-auth and --tls-client-ca options in addition to ones required for
TLS support.
E.g., $./standalone.py -d ../example -p 10443 -t -c ../test/cert/cert.pem -k Example:
../test/cert/key.pem --tls-client-auth --tls-client-ca=../test/cert/cacert.pem
$ PYTHONPATH=. python mod_pywebsocket/standalone.py -d example -p 10443 -t \
-c ../test/cert/cert.pem -k ../test/cert/key.pem \
--tls-client-auth \
--tls-client-ca=../test/cert/cacert.pem
Note that when passing a relative path to --tls-client-ca option, it will be
resolved using the document root directory as the base.
CONFIGURATION FILE CONFIGURATION FILE
==================
You can also write a configuration file and use it by specifying the path to You can also write a configuration file and use it by specifying the path to
the configuration file by --config option. Please write a configuration file the configuration file by --config option. Please write a configuration file
...@@ -113,12 +141,14 @@ configuration file. ...@@ -113,12 +141,14 @@ configuration file.
THREADING THREADING
=========
This server is derived from SocketServer.ThreadingMixIn. Hence a thread is This server is derived from SocketServer.ThreadingMixIn. Hence a thread is
used for each request. used for each request.
SECURITY WARNING SECURITY WARNING
================
This uses CGIHTTPServer and CGIHTTPServer is not secure. This uses CGIHTTPServer and CGIHTTPServer is not secure.
It may execute arbitrary Python code or external programs. It should not be It may execute arbitrary Python code or external programs. It should not be
...@@ -149,6 +179,7 @@ from mod_pywebsocket import handshake ...@@ -149,6 +179,7 @@ from mod_pywebsocket import handshake
from mod_pywebsocket import http_header_util from mod_pywebsocket import http_header_util
from mod_pywebsocket import memorizingfile from mod_pywebsocket import memorizingfile
from mod_pywebsocket import util from mod_pywebsocket import util
from mod_pywebsocket.xhr_benchmark_handler import XHRBenchmarkHandler
_DEFAULT_LOG_MAX_BYTES = 1024 * 256 _DEFAULT_LOG_MAX_BYTES = 1024 * 256
...@@ -662,99 +693,6 @@ class WebSocketRequestHandler(CGIHTTPServer.CGIHTTPRequestHandler): ...@@ -662,99 +693,6 @@ class WebSocketRequestHandler(CGIHTTPServer.CGIHTTPRequestHandler):
CGIHTTPServer.CGIHTTPRequestHandler.__init__( CGIHTTPServer.CGIHTTPRequestHandler.__init__(
self, request, client_address, server) self, request, client_address, server)
def _xhr_send_benchmark_helper(self):
content_length = int(self.headers.getheader('Content-Length'))
self._logger.debug('Requested to receive %s bytes', content_length)
RECEIVE_BLOCK_SIZE = 1024 * 1024
bytes_to_receive = content_length
while bytes_to_receive > 0:
bytes_to_receive_in_this_loop = bytes_to_receive
if bytes_to_receive_in_this_loop > RECEIVE_BLOCK_SIZE:
bytes_to_receive_in_this_loop = RECEIVE_BLOCK_SIZE
received_data = self.rfile.read(bytes_to_receive_in_this_loop)
for c in received_data:
if c != 'a':
self._logger.debug('Request body verification failed')
return
bytes_to_receive -= len(received_data)
if bytes_to_receive < 0:
self._logger.debug('Received %d more bytes than expected' %
(-bytes_to_receive))
return
# Return the number of received bytes back to the client.
response_body = '%d' % content_length
self.wfile.write(
'HTTP/1.1 200 OK\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 _xhr_receive_benchmark_helper(self):
content_length = self.headers.getheader('Content-Length')
request_body = self.rfile.read(int(content_length))
request_array = request_body.split(' ')
if len(request_array) < 2:
self._logger.debug('Malformed request body: %r', request_body)
return
# Parse the size parameter.
bytes_to_send = request_array[0]
try:
bytes_to_send = int(bytes_to_send)
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
mode_parameter = request_array[1]
if mode_parameter == 'chunked':
self._logger.debug('Requested chunked transfer encoding')
chunked_mode = True
elif mode_parameter != 'none':
self._logger.debug('Invalid mode parameter: %r', mode_parameter)
return
# Write a header
response_header = (
'HTTP/1.1 200 OK\r\n'
'Content-Type: application/octet-stream\r\n')
if chunked_mode:
response_header += 'Transfer-Encoding: chunked\r\n\r\n'
else:
response_header += (
'Content-Length: %d\r\n\r\n' % bytes_to_send)
self.wfile.write(response_header)
self.wfile.flush()
# Write a body
SEND_BLOCK_SIZE = 1024 * 1024
while bytes_to_send > 0:
bytes_to_send_in_this_loop = bytes_to_send
if bytes_to_send_in_this_loop > SEND_BLOCK_SIZE:
bytes_to_send_in_this_loop = SEND_BLOCK_SIZE
if chunked_mode:
self.wfile.write('%x\r\n' % bytes_to_send_in_this_loop)
self.wfile.write('a' * bytes_to_send_in_this_loop)
if chunked_mode:
self.wfile.write('\r\n')
self.wfile.flush()
bytes_to_send -= bytes_to_send_in_this_loop
if chunked_mode:
self.wfile.write('0\r\n\r\n')
self.wfile.flush()
def parse_request(self): def parse_request(self):
"""Override BaseHTTPServer.BaseHTTPRequestHandler.parse_request. """Override BaseHTTPServer.BaseHTTPRequestHandler.parse_request.
...@@ -791,10 +729,14 @@ class WebSocketRequestHandler(CGIHTTPServer.CGIHTTPRequestHandler): ...@@ -791,10 +729,14 @@ class WebSocketRequestHandler(CGIHTTPServer.CGIHTTPRequestHandler):
# Special paths for XMLHttpRequest benchmark # Special paths for XMLHttpRequest benchmark
xhr_benchmark_helper_prefix = '/073be001e10950692ccbf3a2ad21c245' xhr_benchmark_helper_prefix = '/073be001e10950692ccbf3a2ad21c245'
if resource == (xhr_benchmark_helper_prefix + '_send'): if resource == (xhr_benchmark_helper_prefix + '_send'):
self._xhr_send_benchmark_helper() xhr_benchmark_handler = XHRBenchmarkHandler(
self.headers, self.rfile, self.wfile)
xhr_benchmark_handler.do_send()
return False return False
if resource == (xhr_benchmark_helper_prefix + '_receive'): if resource == (xhr_benchmark_helper_prefix + '_receive'):
self._xhr_receive_benchmark_helper() xhr_benchmark_handler = XHRBenchmarkHandler(
self.headers, self.rfile, self.wfile)
xhr_benchmark_handler.do_receive()
return False return False
if resource is None: if resource is None:
......
...@@ -331,8 +331,8 @@ class _RFC1979Deflater(object): ...@@ -331,8 +331,8 @@ class _RFC1979Deflater(object):
self._window_bits = window_bits self._window_bits = window_bits
self._no_context_takeover = no_context_takeover self._no_context_takeover = no_context_takeover
def filter(self, bytes, flush=True, bfinal=False): def filter(self, bytes, end=True, bfinal=False):
if self._deflater is None or (self._no_context_takeover and flush): if self._deflater is None:
self._deflater = _Deflater(self._window_bits) self._deflater = _Deflater(self._window_bits)
if bfinal: if bfinal:
...@@ -341,11 +341,17 @@ class _RFC1979Deflater(object): ...@@ -341,11 +341,17 @@ class _RFC1979Deflater(object):
result = result + chr(0) result = result + chr(0)
self._deflater = None self._deflater = None
return result return result
if flush:
result = self._deflater.compress_and_flush(bytes)
if end:
# Strip last 4 octets which is LEN and NLEN field of a # Strip last 4 octets which is LEN and NLEN field of a
# non-compressed block added for Z_SYNC_FLUSH. # non-compressed block added for Z_SYNC_FLUSH.
return self._deflater.compress_and_flush(bytes)[:-4] result = result[:-4]
return self._deflater.compress(bytes)
if self._no_context_takeover and end:
self._deflater = None
return result
class _RFC1979Inflater(object): class _RFC1979Inflater(object):
......
# 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
from mod_pywebsocket import util
class XHRBenchmarkHandler(object):
def __init__(self, headers, rfile, wfile):
self._logger = util.get_class_logger(self)
self.headers = headers
self.rfile = rfile
self.wfile = wfile
def do_send(self):
content_length = int(self.headers.getheader('Content-Length'))
self._logger.debug('Requested to receive %s bytes', content_length)
RECEIVE_BLOCK_SIZE = 1024 * 1024
bytes_to_receive = content_length
while bytes_to_receive > 0:
bytes_to_receive_in_this_loop = bytes_to_receive
if bytes_to_receive_in_this_loop > RECEIVE_BLOCK_SIZE:
bytes_to_receive_in_this_loop = RECEIVE_BLOCK_SIZE
received_data = self.rfile.read(bytes_to_receive_in_this_loop)
for c in received_data:
if c != 'a':
self._logger.debug('Request body verification failed')
return
bytes_to_receive -= len(received_data)
if bytes_to_receive < 0:
self._logger.debug('Received %d more bytes than expected' %
(-bytes_to_receive))
return
# Return the number of received bytes back to the client.
response_body = '%d' % content_length
self.wfile.write(
'HTTP/1.1 200 OK\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):
content_length = int(self.headers.getheader('Content-Length'))
request_body = self.rfile.read(content_length)
request_array = request_body.split(' ')
if len(request_array) < 2:
self._logger.debug('Malformed request body: %r', request_body)
return
# Parse the size parameter.
bytes_to_send = request_array[0]
try:
bytes_to_send = int(bytes_to_send)
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
mode_parameter = request_array[1]
if mode_parameter == 'chunked':
self._logger.debug('Requested chunked transfer encoding')
chunked_mode = True
elif mode_parameter != 'none':
self._logger.debug('Invalid mode parameter: %r', mode_parameter)
return
# Write a header
response_header = (
'HTTP/1.1 200 OK\r\n'
'Content-Type: application/octet-stream\r\n')
if chunked_mode:
response_header += 'Transfer-Encoding: chunked\r\n\r\n'
else:
response_header += (
'Content-Length: %d\r\n\r\n' % bytes_to_send)
self.wfile.write(response_header)
self.wfile.flush()
# Write a body
SEND_BLOCK_SIZE = 1024 * 1024
while bytes_to_send > 0:
bytes_to_send_in_this_loop = bytes_to_send
if bytes_to_send_in_this_loop > SEND_BLOCK_SIZE:
bytes_to_send_in_this_loop = SEND_BLOCK_SIZE
if chunked_mode:
self.wfile.write('%x\r\n' % bytes_to_send_in_this_loop)
self.wfile.write('a' * bytes_to_send_in_this_loop)
if chunked_mode:
self.wfile.write('\r\n')
self.wfile.flush()
bytes_to_send -= bytes_to_send_in_this_loop
if chunked_mode:
self.wfile.write('0\r\n\r\n')
self.wfile.flush()
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment