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