michael@0: # Copyright 2011, Google Inc. michael@0: # All rights reserved. michael@0: # michael@0: # Redistribution and use in source and binary forms, with or without michael@0: # modification, are permitted provided that the following conditions are michael@0: # met: michael@0: # michael@0: # * Redistributions of source code must retain the above copyright michael@0: # notice, this list of conditions and the following disclaimer. michael@0: # * Redistributions in binary form must reproduce the above michael@0: # copyright notice, this list of conditions and the following disclaimer michael@0: # in the documentation and/or other materials provided with the michael@0: # distribution. michael@0: # * Neither the name of Google Inc. nor the names of its michael@0: # contributors may be used to endorse or promote products derived from michael@0: # this software without specific prior written permission. michael@0: # michael@0: # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS michael@0: # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT michael@0: # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR michael@0: # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT michael@0: # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, michael@0: # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT michael@0: # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, michael@0: # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY michael@0: # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT michael@0: # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE michael@0: # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. michael@0: michael@0: michael@0: from mod_pywebsocket import common michael@0: from mod_pywebsocket import util michael@0: michael@0: michael@0: _available_processors = {} michael@0: michael@0: michael@0: class ExtensionProcessorInterface(object): michael@0: michael@0: def get_extension_response(self): michael@0: return None michael@0: michael@0: def setup_stream_options(self, stream_options): michael@0: pass michael@0: michael@0: michael@0: class DeflateStreamExtensionProcessor(ExtensionProcessorInterface): michael@0: """WebSocket DEFLATE stream extension processor.""" michael@0: michael@0: def __init__(self, request): michael@0: self._logger = util.get_class_logger(self) michael@0: michael@0: self._request = request michael@0: michael@0: def get_extension_response(self): michael@0: if len(self._request.get_parameter_names()) != 0: michael@0: return None michael@0: michael@0: self._logger.debug( michael@0: 'Enable %s extension', common.DEFLATE_STREAM_EXTENSION) michael@0: michael@0: return common.ExtensionParameter(common.DEFLATE_STREAM_EXTENSION) michael@0: michael@0: def setup_stream_options(self, stream_options): michael@0: stream_options.deflate_stream = True michael@0: michael@0: michael@0: _available_processors[common.DEFLATE_STREAM_EXTENSION] = ( michael@0: DeflateStreamExtensionProcessor) michael@0: michael@0: michael@0: class DeflateFrameExtensionProcessor(ExtensionProcessorInterface): michael@0: """WebSocket Per-frame DEFLATE extension processor.""" michael@0: michael@0: _WINDOW_BITS_PARAM = 'max_window_bits' michael@0: _NO_CONTEXT_TAKEOVER_PARAM = 'no_context_takeover' michael@0: michael@0: def __init__(self, request): michael@0: self._logger = util.get_class_logger(self) michael@0: michael@0: self._request = request michael@0: michael@0: self._response_window_bits = None michael@0: self._response_no_context_takeover = False michael@0: michael@0: # Counters for statistics. michael@0: michael@0: # Total number of outgoing bytes supplied to this filter. michael@0: self._total_outgoing_payload_bytes = 0 michael@0: # Total number of bytes sent to the network after applying this filter. michael@0: self._total_filtered_outgoing_payload_bytes = 0 michael@0: michael@0: # Total number of bytes received from the network. michael@0: self._total_incoming_payload_bytes = 0 michael@0: # Total number of incoming bytes obtained after applying this filter. michael@0: self._total_filtered_incoming_payload_bytes = 0 michael@0: michael@0: def get_extension_response(self): michael@0: # Any unknown parameter will be just ignored. michael@0: michael@0: window_bits = self._request.get_parameter_value( michael@0: self._WINDOW_BITS_PARAM) michael@0: no_context_takeover = self._request.has_parameter( michael@0: self._NO_CONTEXT_TAKEOVER_PARAM) michael@0: if (no_context_takeover and michael@0: self._request.get_parameter_value( michael@0: self._NO_CONTEXT_TAKEOVER_PARAM) is not None): michael@0: return None michael@0: michael@0: if window_bits is not None: michael@0: try: michael@0: window_bits = int(window_bits) michael@0: except ValueError, e: michael@0: return None michael@0: if window_bits < 8 or window_bits > 15: michael@0: return None michael@0: michael@0: self._deflater = util._RFC1979Deflater( michael@0: window_bits, no_context_takeover) michael@0: michael@0: self._inflater = util._RFC1979Inflater() michael@0: michael@0: self._compress_outgoing = True michael@0: michael@0: response = common.ExtensionParameter(self._request.name()) michael@0: michael@0: if self._response_window_bits is not None: michael@0: response.add_parameter( michael@0: self._WINDOW_BITS_PARAM, str(self._response_window_bits)) michael@0: if self._response_no_context_takeover: michael@0: response.add_parameter( michael@0: self._NO_CONTEXT_TAKEOVER_PARAM, None) michael@0: michael@0: self._logger.debug( michael@0: 'Enable %s extension (' michael@0: 'request: window_bits=%s; no_context_takeover=%r, ' michael@0: 'response: window_wbits=%s; no_context_takeover=%r)' % michael@0: (self._request.name(), michael@0: window_bits, michael@0: no_context_takeover, michael@0: self._response_window_bits, michael@0: self._response_no_context_takeover)) michael@0: michael@0: return response michael@0: michael@0: def setup_stream_options(self, stream_options): michael@0: michael@0: class _OutgoingFilter(object): michael@0: michael@0: def __init__(self, parent): michael@0: self._parent = parent michael@0: michael@0: def filter(self, frame): michael@0: self._parent._outgoing_filter(frame) michael@0: michael@0: class _IncomingFilter(object): michael@0: michael@0: def __init__(self, parent): michael@0: self._parent = parent michael@0: michael@0: def filter(self, frame): michael@0: self._parent._incoming_filter(frame) michael@0: michael@0: stream_options.outgoing_frame_filters.append( michael@0: _OutgoingFilter(self)) michael@0: stream_options.incoming_frame_filters.insert( michael@0: 0, _IncomingFilter(self)) michael@0: michael@0: def set_response_window_bits(self, value): michael@0: self._response_window_bits = value michael@0: michael@0: def set_response_no_context_takeover(self, value): michael@0: self._response_no_context_takeover = value michael@0: michael@0: def enable_outgoing_compression(self): michael@0: self._compress_outgoing = True michael@0: michael@0: def disable_outgoing_compression(self): michael@0: self._compress_outgoing = False michael@0: michael@0: def _outgoing_filter(self, frame): michael@0: """Transform outgoing frames. This method is called only by michael@0: an _OutgoingFilter instance. michael@0: """ michael@0: michael@0: original_payload_size = len(frame.payload) michael@0: self._total_outgoing_payload_bytes += original_payload_size michael@0: michael@0: if (not self._compress_outgoing or michael@0: common.is_control_opcode(frame.opcode)): michael@0: self._total_filtered_outgoing_payload_bytes += ( michael@0: original_payload_size) michael@0: return michael@0: michael@0: frame.payload = self._deflater.filter(frame.payload) michael@0: frame.rsv1 = 1 michael@0: michael@0: filtered_payload_size = len(frame.payload) michael@0: self._total_filtered_outgoing_payload_bytes += filtered_payload_size michael@0: michael@0: # Print inf when ratio is not available. michael@0: ratio = float('inf') michael@0: average_ratio = float('inf') michael@0: if original_payload_size != 0: michael@0: ratio = float(filtered_payload_size) / original_payload_size michael@0: if self._total_outgoing_payload_bytes != 0: michael@0: average_ratio = ( michael@0: float(self._total_filtered_outgoing_payload_bytes) / michael@0: self._total_outgoing_payload_bytes) michael@0: self._logger.debug( michael@0: 'Outgoing compress ratio: %f (average: %f)' % michael@0: (ratio, average_ratio)) michael@0: michael@0: def _incoming_filter(self, frame): michael@0: """Transform incoming frames. This method is called only by michael@0: an _IncomingFilter instance. michael@0: """ michael@0: michael@0: received_payload_size = len(frame.payload) michael@0: self._total_incoming_payload_bytes += received_payload_size michael@0: michael@0: if frame.rsv1 != 1 or common.is_control_opcode(frame.opcode): michael@0: self._total_filtered_incoming_payload_bytes += ( michael@0: received_payload_size) michael@0: return michael@0: michael@0: frame.payload = self._inflater.filter(frame.payload) michael@0: frame.rsv1 = 0 michael@0: michael@0: filtered_payload_size = len(frame.payload) michael@0: self._total_filtered_incoming_payload_bytes += filtered_payload_size michael@0: michael@0: # Print inf when ratio is not available. michael@0: ratio = float('inf') michael@0: average_ratio = float('inf') michael@0: if received_payload_size != 0: michael@0: ratio = float(received_payload_size) / filtered_payload_size michael@0: if self._total_filtered_incoming_payload_bytes != 0: michael@0: average_ratio = ( michael@0: float(self._total_incoming_payload_bytes) / michael@0: self._total_filtered_incoming_payload_bytes) michael@0: self._logger.debug( michael@0: 'Incoming compress ratio: %f (average: %f)' % michael@0: (ratio, average_ratio)) michael@0: michael@0: michael@0: _available_processors[common.DEFLATE_FRAME_EXTENSION] = ( michael@0: DeflateFrameExtensionProcessor) michael@0: michael@0: michael@0: # Adding vendor-prefixed deflate-frame extension. michael@0: # TODO(bashi): Remove this after WebKit stops using vender prefix. michael@0: _available_processors[common.X_WEBKIT_DEFLATE_FRAME_EXTENSION] = ( michael@0: DeflateFrameExtensionProcessor) michael@0: michael@0: michael@0: def get_extension_processor(extension_request): michael@0: global _available_processors michael@0: processor_class = _available_processors.get(extension_request.name()) michael@0: if processor_class is None: michael@0: return None michael@0: return processor_class(extension_request) michael@0: michael@0: michael@0: # vi:sts=4 sw=4 et