testing/mochitest/pywebsocket/mod_pywebsocket/extensions.py

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

michael@0 1 # Copyright 2011, Google Inc.
michael@0 2 # All rights reserved.
michael@0 3 #
michael@0 4 # Redistribution and use in source and binary forms, with or without
michael@0 5 # modification, are permitted provided that the following conditions are
michael@0 6 # met:
michael@0 7 #
michael@0 8 # * Redistributions of source code must retain the above copyright
michael@0 9 # notice, this list of conditions and the following disclaimer.
michael@0 10 # * Redistributions in binary form must reproduce the above
michael@0 11 # copyright notice, this list of conditions and the following disclaimer
michael@0 12 # in the documentation and/or other materials provided with the
michael@0 13 # distribution.
michael@0 14 # * Neither the name of Google Inc. nor the names of its
michael@0 15 # contributors may be used to endorse or promote products derived from
michael@0 16 # this software without specific prior written permission.
michael@0 17 #
michael@0 18 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
michael@0 19 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
michael@0 20 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
michael@0 21 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
michael@0 22 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
michael@0 23 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
michael@0 24 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
michael@0 25 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
michael@0 26 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
michael@0 27 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
michael@0 28 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
michael@0 29
michael@0 30
michael@0 31 from mod_pywebsocket import common
michael@0 32 from mod_pywebsocket import util
michael@0 33
michael@0 34
michael@0 35 _available_processors = {}
michael@0 36
michael@0 37
michael@0 38 class ExtensionProcessorInterface(object):
michael@0 39
michael@0 40 def get_extension_response(self):
michael@0 41 return None
michael@0 42
michael@0 43 def setup_stream_options(self, stream_options):
michael@0 44 pass
michael@0 45
michael@0 46
michael@0 47 class DeflateStreamExtensionProcessor(ExtensionProcessorInterface):
michael@0 48 """WebSocket DEFLATE stream extension processor."""
michael@0 49
michael@0 50 def __init__(self, request):
michael@0 51 self._logger = util.get_class_logger(self)
michael@0 52
michael@0 53 self._request = request
michael@0 54
michael@0 55 def get_extension_response(self):
michael@0 56 if len(self._request.get_parameter_names()) != 0:
michael@0 57 return None
michael@0 58
michael@0 59 self._logger.debug(
michael@0 60 'Enable %s extension', common.DEFLATE_STREAM_EXTENSION)
michael@0 61
michael@0 62 return common.ExtensionParameter(common.DEFLATE_STREAM_EXTENSION)
michael@0 63
michael@0 64 def setup_stream_options(self, stream_options):
michael@0 65 stream_options.deflate_stream = True
michael@0 66
michael@0 67
michael@0 68 _available_processors[common.DEFLATE_STREAM_EXTENSION] = (
michael@0 69 DeflateStreamExtensionProcessor)
michael@0 70
michael@0 71
michael@0 72 class DeflateFrameExtensionProcessor(ExtensionProcessorInterface):
michael@0 73 """WebSocket Per-frame DEFLATE extension processor."""
michael@0 74
michael@0 75 _WINDOW_BITS_PARAM = 'max_window_bits'
michael@0 76 _NO_CONTEXT_TAKEOVER_PARAM = 'no_context_takeover'
michael@0 77
michael@0 78 def __init__(self, request):
michael@0 79 self._logger = util.get_class_logger(self)
michael@0 80
michael@0 81 self._request = request
michael@0 82
michael@0 83 self._response_window_bits = None
michael@0 84 self._response_no_context_takeover = False
michael@0 85
michael@0 86 # Counters for statistics.
michael@0 87
michael@0 88 # Total number of outgoing bytes supplied to this filter.
michael@0 89 self._total_outgoing_payload_bytes = 0
michael@0 90 # Total number of bytes sent to the network after applying this filter.
michael@0 91 self._total_filtered_outgoing_payload_bytes = 0
michael@0 92
michael@0 93 # Total number of bytes received from the network.
michael@0 94 self._total_incoming_payload_bytes = 0
michael@0 95 # Total number of incoming bytes obtained after applying this filter.
michael@0 96 self._total_filtered_incoming_payload_bytes = 0
michael@0 97
michael@0 98 def get_extension_response(self):
michael@0 99 # Any unknown parameter will be just ignored.
michael@0 100
michael@0 101 window_bits = self._request.get_parameter_value(
michael@0 102 self._WINDOW_BITS_PARAM)
michael@0 103 no_context_takeover = self._request.has_parameter(
michael@0 104 self._NO_CONTEXT_TAKEOVER_PARAM)
michael@0 105 if (no_context_takeover and
michael@0 106 self._request.get_parameter_value(
michael@0 107 self._NO_CONTEXT_TAKEOVER_PARAM) is not None):
michael@0 108 return None
michael@0 109
michael@0 110 if window_bits is not None:
michael@0 111 try:
michael@0 112 window_bits = int(window_bits)
michael@0 113 except ValueError, e:
michael@0 114 return None
michael@0 115 if window_bits < 8 or window_bits > 15:
michael@0 116 return None
michael@0 117
michael@0 118 self._deflater = util._RFC1979Deflater(
michael@0 119 window_bits, no_context_takeover)
michael@0 120
michael@0 121 self._inflater = util._RFC1979Inflater()
michael@0 122
michael@0 123 self._compress_outgoing = True
michael@0 124
michael@0 125 response = common.ExtensionParameter(self._request.name())
michael@0 126
michael@0 127 if self._response_window_bits is not None:
michael@0 128 response.add_parameter(
michael@0 129 self._WINDOW_BITS_PARAM, str(self._response_window_bits))
michael@0 130 if self._response_no_context_takeover:
michael@0 131 response.add_parameter(
michael@0 132 self._NO_CONTEXT_TAKEOVER_PARAM, None)
michael@0 133
michael@0 134 self._logger.debug(
michael@0 135 'Enable %s extension ('
michael@0 136 'request: window_bits=%s; no_context_takeover=%r, '
michael@0 137 'response: window_wbits=%s; no_context_takeover=%r)' %
michael@0 138 (self._request.name(),
michael@0 139 window_bits,
michael@0 140 no_context_takeover,
michael@0 141 self._response_window_bits,
michael@0 142 self._response_no_context_takeover))
michael@0 143
michael@0 144 return response
michael@0 145
michael@0 146 def setup_stream_options(self, stream_options):
michael@0 147
michael@0 148 class _OutgoingFilter(object):
michael@0 149
michael@0 150 def __init__(self, parent):
michael@0 151 self._parent = parent
michael@0 152
michael@0 153 def filter(self, frame):
michael@0 154 self._parent._outgoing_filter(frame)
michael@0 155
michael@0 156 class _IncomingFilter(object):
michael@0 157
michael@0 158 def __init__(self, parent):
michael@0 159 self._parent = parent
michael@0 160
michael@0 161 def filter(self, frame):
michael@0 162 self._parent._incoming_filter(frame)
michael@0 163
michael@0 164 stream_options.outgoing_frame_filters.append(
michael@0 165 _OutgoingFilter(self))
michael@0 166 stream_options.incoming_frame_filters.insert(
michael@0 167 0, _IncomingFilter(self))
michael@0 168
michael@0 169 def set_response_window_bits(self, value):
michael@0 170 self._response_window_bits = value
michael@0 171
michael@0 172 def set_response_no_context_takeover(self, value):
michael@0 173 self._response_no_context_takeover = value
michael@0 174
michael@0 175 def enable_outgoing_compression(self):
michael@0 176 self._compress_outgoing = True
michael@0 177
michael@0 178 def disable_outgoing_compression(self):
michael@0 179 self._compress_outgoing = False
michael@0 180
michael@0 181 def _outgoing_filter(self, frame):
michael@0 182 """Transform outgoing frames. This method is called only by
michael@0 183 an _OutgoingFilter instance.
michael@0 184 """
michael@0 185
michael@0 186 original_payload_size = len(frame.payload)
michael@0 187 self._total_outgoing_payload_bytes += original_payload_size
michael@0 188
michael@0 189 if (not self._compress_outgoing or
michael@0 190 common.is_control_opcode(frame.opcode)):
michael@0 191 self._total_filtered_outgoing_payload_bytes += (
michael@0 192 original_payload_size)
michael@0 193 return
michael@0 194
michael@0 195 frame.payload = self._deflater.filter(frame.payload)
michael@0 196 frame.rsv1 = 1
michael@0 197
michael@0 198 filtered_payload_size = len(frame.payload)
michael@0 199 self._total_filtered_outgoing_payload_bytes += filtered_payload_size
michael@0 200
michael@0 201 # Print inf when ratio is not available.
michael@0 202 ratio = float('inf')
michael@0 203 average_ratio = float('inf')
michael@0 204 if original_payload_size != 0:
michael@0 205 ratio = float(filtered_payload_size) / original_payload_size
michael@0 206 if self._total_outgoing_payload_bytes != 0:
michael@0 207 average_ratio = (
michael@0 208 float(self._total_filtered_outgoing_payload_bytes) /
michael@0 209 self._total_outgoing_payload_bytes)
michael@0 210 self._logger.debug(
michael@0 211 'Outgoing compress ratio: %f (average: %f)' %
michael@0 212 (ratio, average_ratio))
michael@0 213
michael@0 214 def _incoming_filter(self, frame):
michael@0 215 """Transform incoming frames. This method is called only by
michael@0 216 an _IncomingFilter instance.
michael@0 217 """
michael@0 218
michael@0 219 received_payload_size = len(frame.payload)
michael@0 220 self._total_incoming_payload_bytes += received_payload_size
michael@0 221
michael@0 222 if frame.rsv1 != 1 or common.is_control_opcode(frame.opcode):
michael@0 223 self._total_filtered_incoming_payload_bytes += (
michael@0 224 received_payload_size)
michael@0 225 return
michael@0 226
michael@0 227 frame.payload = self._inflater.filter(frame.payload)
michael@0 228 frame.rsv1 = 0
michael@0 229
michael@0 230 filtered_payload_size = len(frame.payload)
michael@0 231 self._total_filtered_incoming_payload_bytes += filtered_payload_size
michael@0 232
michael@0 233 # Print inf when ratio is not available.
michael@0 234 ratio = float('inf')
michael@0 235 average_ratio = float('inf')
michael@0 236 if received_payload_size != 0:
michael@0 237 ratio = float(received_payload_size) / filtered_payload_size
michael@0 238 if self._total_filtered_incoming_payload_bytes != 0:
michael@0 239 average_ratio = (
michael@0 240 float(self._total_incoming_payload_bytes) /
michael@0 241 self._total_filtered_incoming_payload_bytes)
michael@0 242 self._logger.debug(
michael@0 243 'Incoming compress ratio: %f (average: %f)' %
michael@0 244 (ratio, average_ratio))
michael@0 245
michael@0 246
michael@0 247 _available_processors[common.DEFLATE_FRAME_EXTENSION] = (
michael@0 248 DeflateFrameExtensionProcessor)
michael@0 249
michael@0 250
michael@0 251 # Adding vendor-prefixed deflate-frame extension.
michael@0 252 # TODO(bashi): Remove this after WebKit stops using vender prefix.
michael@0 253 _available_processors[common.X_WEBKIT_DEFLATE_FRAME_EXTENSION] = (
michael@0 254 DeflateFrameExtensionProcessor)
michael@0 255
michael@0 256
michael@0 257 def get_extension_processor(extension_request):
michael@0 258 global _available_processors
michael@0 259 processor_class = _available_processors.get(extension_request.name())
michael@0 260 if processor_class is None:
michael@0 261 return None
michael@0 262 return processor_class(extension_request)
michael@0 263
michael@0 264
michael@0 265 # vi:sts=4 sw=4 et

mercurial