testing/mochitest/pywebsocket/mod_pywebsocket/extensions.py

changeset 0
6474c204b198
     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

mercurial