1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/testing/mochitest/pywebsocket/mod_pywebsocket/extensions.py Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,265 @@ 1.4 +# Copyright 2011, Google Inc. 1.5 +# All rights reserved. 1.6 +# 1.7 +# Redistribution and use in source and binary forms, with or without 1.8 +# modification, are permitted provided that the following conditions are 1.9 +# met: 1.10 +# 1.11 +# * Redistributions of source code must retain the above copyright 1.12 +# notice, this list of conditions and the following disclaimer. 1.13 +# * Redistributions in binary form must reproduce the above 1.14 +# copyright notice, this list of conditions and the following disclaimer 1.15 +# in the documentation and/or other materials provided with the 1.16 +# distribution. 1.17 +# * Neither the name of Google Inc. nor the names of its 1.18 +# contributors may be used to endorse or promote products derived from 1.19 +# this software without specific prior written permission. 1.20 +# 1.21 +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 1.22 +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 1.23 +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 1.24 +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 1.25 +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 1.26 +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 1.27 +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 1.28 +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 1.29 +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 1.30 +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 1.31 +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 1.32 + 1.33 + 1.34 +from mod_pywebsocket import common 1.35 +from mod_pywebsocket import util 1.36 + 1.37 + 1.38 +_available_processors = {} 1.39 + 1.40 + 1.41 +class ExtensionProcessorInterface(object): 1.42 + 1.43 + def get_extension_response(self): 1.44 + return None 1.45 + 1.46 + def setup_stream_options(self, stream_options): 1.47 + pass 1.48 + 1.49 + 1.50 +class DeflateStreamExtensionProcessor(ExtensionProcessorInterface): 1.51 + """WebSocket DEFLATE stream extension processor.""" 1.52 + 1.53 + def __init__(self, request): 1.54 + self._logger = util.get_class_logger(self) 1.55 + 1.56 + self._request = request 1.57 + 1.58 + def get_extension_response(self): 1.59 + if len(self._request.get_parameter_names()) != 0: 1.60 + return None 1.61 + 1.62 + self._logger.debug( 1.63 + 'Enable %s extension', common.DEFLATE_STREAM_EXTENSION) 1.64 + 1.65 + return common.ExtensionParameter(common.DEFLATE_STREAM_EXTENSION) 1.66 + 1.67 + def setup_stream_options(self, stream_options): 1.68 + stream_options.deflate_stream = True 1.69 + 1.70 + 1.71 +_available_processors[common.DEFLATE_STREAM_EXTENSION] = ( 1.72 + DeflateStreamExtensionProcessor) 1.73 + 1.74 + 1.75 +class DeflateFrameExtensionProcessor(ExtensionProcessorInterface): 1.76 + """WebSocket Per-frame DEFLATE extension processor.""" 1.77 + 1.78 + _WINDOW_BITS_PARAM = 'max_window_bits' 1.79 + _NO_CONTEXT_TAKEOVER_PARAM = 'no_context_takeover' 1.80 + 1.81 + def __init__(self, request): 1.82 + self._logger = util.get_class_logger(self) 1.83 + 1.84 + self._request = request 1.85 + 1.86 + self._response_window_bits = None 1.87 + self._response_no_context_takeover = False 1.88 + 1.89 + # Counters for statistics. 1.90 + 1.91 + # Total number of outgoing bytes supplied to this filter. 1.92 + self._total_outgoing_payload_bytes = 0 1.93 + # Total number of bytes sent to the network after applying this filter. 1.94 + self._total_filtered_outgoing_payload_bytes = 0 1.95 + 1.96 + # Total number of bytes received from the network. 1.97 + self._total_incoming_payload_bytes = 0 1.98 + # Total number of incoming bytes obtained after applying this filter. 1.99 + self._total_filtered_incoming_payload_bytes = 0 1.100 + 1.101 + def get_extension_response(self): 1.102 + # Any unknown parameter will be just ignored. 1.103 + 1.104 + window_bits = self._request.get_parameter_value( 1.105 + self._WINDOW_BITS_PARAM) 1.106 + no_context_takeover = self._request.has_parameter( 1.107 + self._NO_CONTEXT_TAKEOVER_PARAM) 1.108 + if (no_context_takeover and 1.109 + self._request.get_parameter_value( 1.110 + self._NO_CONTEXT_TAKEOVER_PARAM) is not None): 1.111 + return None 1.112 + 1.113 + if window_bits is not None: 1.114 + try: 1.115 + window_bits = int(window_bits) 1.116 + except ValueError, e: 1.117 + return None 1.118 + if window_bits < 8 or window_bits > 15: 1.119 + return None 1.120 + 1.121 + self._deflater = util._RFC1979Deflater( 1.122 + window_bits, no_context_takeover) 1.123 + 1.124 + self._inflater = util._RFC1979Inflater() 1.125 + 1.126 + self._compress_outgoing = True 1.127 + 1.128 + response = common.ExtensionParameter(self._request.name()) 1.129 + 1.130 + if self._response_window_bits is not None: 1.131 + response.add_parameter( 1.132 + self._WINDOW_BITS_PARAM, str(self._response_window_bits)) 1.133 + if self._response_no_context_takeover: 1.134 + response.add_parameter( 1.135 + self._NO_CONTEXT_TAKEOVER_PARAM, None) 1.136 + 1.137 + self._logger.debug( 1.138 + 'Enable %s extension (' 1.139 + 'request: window_bits=%s; no_context_takeover=%r, ' 1.140 + 'response: window_wbits=%s; no_context_takeover=%r)' % 1.141 + (self._request.name(), 1.142 + window_bits, 1.143 + no_context_takeover, 1.144 + self._response_window_bits, 1.145 + self._response_no_context_takeover)) 1.146 + 1.147 + return response 1.148 + 1.149 + def setup_stream_options(self, stream_options): 1.150 + 1.151 + class _OutgoingFilter(object): 1.152 + 1.153 + def __init__(self, parent): 1.154 + self._parent = parent 1.155 + 1.156 + def filter(self, frame): 1.157 + self._parent._outgoing_filter(frame) 1.158 + 1.159 + class _IncomingFilter(object): 1.160 + 1.161 + def __init__(self, parent): 1.162 + self._parent = parent 1.163 + 1.164 + def filter(self, frame): 1.165 + self._parent._incoming_filter(frame) 1.166 + 1.167 + stream_options.outgoing_frame_filters.append( 1.168 + _OutgoingFilter(self)) 1.169 + stream_options.incoming_frame_filters.insert( 1.170 + 0, _IncomingFilter(self)) 1.171 + 1.172 + def set_response_window_bits(self, value): 1.173 + self._response_window_bits = value 1.174 + 1.175 + def set_response_no_context_takeover(self, value): 1.176 + self._response_no_context_takeover = value 1.177 + 1.178 + def enable_outgoing_compression(self): 1.179 + self._compress_outgoing = True 1.180 + 1.181 + def disable_outgoing_compression(self): 1.182 + self._compress_outgoing = False 1.183 + 1.184 + def _outgoing_filter(self, frame): 1.185 + """Transform outgoing frames. This method is called only by 1.186 + an _OutgoingFilter instance. 1.187 + """ 1.188 + 1.189 + original_payload_size = len(frame.payload) 1.190 + self._total_outgoing_payload_bytes += original_payload_size 1.191 + 1.192 + if (not self._compress_outgoing or 1.193 + common.is_control_opcode(frame.opcode)): 1.194 + self._total_filtered_outgoing_payload_bytes += ( 1.195 + original_payload_size) 1.196 + return 1.197 + 1.198 + frame.payload = self._deflater.filter(frame.payload) 1.199 + frame.rsv1 = 1 1.200 + 1.201 + filtered_payload_size = len(frame.payload) 1.202 + self._total_filtered_outgoing_payload_bytes += filtered_payload_size 1.203 + 1.204 + # Print inf when ratio is not available. 1.205 + ratio = float('inf') 1.206 + average_ratio = float('inf') 1.207 + if original_payload_size != 0: 1.208 + ratio = float(filtered_payload_size) / original_payload_size 1.209 + if self._total_outgoing_payload_bytes != 0: 1.210 + average_ratio = ( 1.211 + float(self._total_filtered_outgoing_payload_bytes) / 1.212 + self._total_outgoing_payload_bytes) 1.213 + self._logger.debug( 1.214 + 'Outgoing compress ratio: %f (average: %f)' % 1.215 + (ratio, average_ratio)) 1.216 + 1.217 + def _incoming_filter(self, frame): 1.218 + """Transform incoming frames. This method is called only by 1.219 + an _IncomingFilter instance. 1.220 + """ 1.221 + 1.222 + received_payload_size = len(frame.payload) 1.223 + self._total_incoming_payload_bytes += received_payload_size 1.224 + 1.225 + if frame.rsv1 != 1 or common.is_control_opcode(frame.opcode): 1.226 + self._total_filtered_incoming_payload_bytes += ( 1.227 + received_payload_size) 1.228 + return 1.229 + 1.230 + frame.payload = self._inflater.filter(frame.payload) 1.231 + frame.rsv1 = 0 1.232 + 1.233 + filtered_payload_size = len(frame.payload) 1.234 + self._total_filtered_incoming_payload_bytes += filtered_payload_size 1.235 + 1.236 + # Print inf when ratio is not available. 1.237 + ratio = float('inf') 1.238 + average_ratio = float('inf') 1.239 + if received_payload_size != 0: 1.240 + ratio = float(received_payload_size) / filtered_payload_size 1.241 + if self._total_filtered_incoming_payload_bytes != 0: 1.242 + average_ratio = ( 1.243 + float(self._total_incoming_payload_bytes) / 1.244 + self._total_filtered_incoming_payload_bytes) 1.245 + self._logger.debug( 1.246 + 'Incoming compress ratio: %f (average: %f)' % 1.247 + (ratio, average_ratio)) 1.248 + 1.249 + 1.250 +_available_processors[common.DEFLATE_FRAME_EXTENSION] = ( 1.251 + DeflateFrameExtensionProcessor) 1.252 + 1.253 + 1.254 +# Adding vendor-prefixed deflate-frame extension. 1.255 +# TODO(bashi): Remove this after WebKit stops using vender prefix. 1.256 +_available_processors[common.X_WEBKIT_DEFLATE_FRAME_EXTENSION] = ( 1.257 + DeflateFrameExtensionProcessor) 1.258 + 1.259 + 1.260 +def get_extension_processor(extension_request): 1.261 + global _available_processors 1.262 + processor_class = _available_processors.get(extension_request.name()) 1.263 + if processor_class is None: 1.264 + return None 1.265 + return processor_class(extension_request) 1.266 + 1.267 + 1.268 +# vi:sts=4 sw=4 et