Home | History | Annotate | Download | only in mod_pywebsocket
      1 # Copyright 2012, 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.
     29 
     30 
     31 from mod_pywebsocket import common
     32 from mod_pywebsocket import util
     33 from mod_pywebsocket.http_header_util import quote_if_necessary
     34 
     35 
     36 # The list of available server side extension processor classes.
     37 _available_processors = {}
     38 _compression_extension_names = []
     39 
     40 
     41 class ExtensionProcessorInterface(object):
     42 
     43     def __init__(self, request):
     44         self._logger = util.get_class_logger(self)
     45 
     46         self._request = request
     47         self._active = True
     48 
     49     def request(self):
     50         return self._request
     51 
     52     def name(self):
     53         return None
     54 
     55     def check_consistency_with_other_processors(self, processors):
     56         pass
     57 
     58     def set_active(self, active):
     59         self._active = active
     60 
     61     def is_active(self):
     62         return self._active
     63 
     64     def _get_extension_response_internal(self):
     65         return None
     66 
     67     def get_extension_response(self):
     68         if not self._active:
     69             self._logger.debug('Extension %s is deactivated', self.name())
     70             return None
     71 
     72         response = self._get_extension_response_internal()
     73         if response is None:
     74             self._active = False
     75         return response
     76 
     77     def _setup_stream_options_internal(self, stream_options):
     78         pass
     79 
     80     def setup_stream_options(self, stream_options):
     81         if self._active:
     82             self._setup_stream_options_internal(stream_options)
     83 
     84 
     85 def _log_outgoing_compression_ratio(
     86         logger, original_bytes, filtered_bytes, average_ratio):
     87     # Print inf when ratio is not available.
     88     ratio = float('inf')
     89     if original_bytes != 0:
     90         ratio = float(filtered_bytes) / original_bytes
     91 
     92     logger.debug('Outgoing compression ratio: %f (average: %f)' %
     93             (ratio, average_ratio))
     94 
     95 
     96 def _log_incoming_compression_ratio(
     97         logger, received_bytes, filtered_bytes, average_ratio):
     98     # Print inf when ratio is not available.
     99     ratio = float('inf')
    100     if filtered_bytes != 0:
    101         ratio = float(received_bytes) / filtered_bytes
    102 
    103     logger.debug('Incoming compression ratio: %f (average: %f)' %
    104             (ratio, average_ratio))
    105 
    106 
    107 def _parse_window_bits(bits):
    108     """Return parsed integer value iff the given string conforms to the
    109     grammar of the window bits extension parameters.
    110     """
    111 
    112     if bits is None:
    113         raise ValueError('Value is required')
    114 
    115     # For non integer values such as "10.0", ValueError will be raised.
    116     int_bits = int(bits)
    117 
    118     # First condition is to drop leading zero case e.g. "08".
    119     if bits != str(int_bits) or int_bits < 8 or int_bits > 15:
    120         raise ValueError('Invalid value: %r' % bits)
    121 
    122     return int_bits
    123 
    124 
    125 class _AverageRatioCalculator(object):
    126     """Stores total bytes of original and result data, and calculates average
    127     result / original ratio.
    128     """
    129 
    130     def __init__(self):
    131         self._total_original_bytes = 0
    132         self._total_result_bytes = 0
    133 
    134     def add_original_bytes(self, value):
    135         self._total_original_bytes += value
    136 
    137     def add_result_bytes(self, value):
    138         self._total_result_bytes += value
    139 
    140     def get_average_ratio(self):
    141         if self._total_original_bytes != 0:
    142             return (float(self._total_result_bytes) /
    143                     self._total_original_bytes)
    144         else:
    145             return float('inf')
    146 
    147 
    148 class DeflateFrameExtensionProcessor(ExtensionProcessorInterface):
    149     """deflate-frame extension processor.
    150 
    151     Specification:
    152     http://tools.ietf.org/html/draft-tyoshino-hybi-websocket-perframe-deflate
    153     """
    154 
    155     _WINDOW_BITS_PARAM = 'max_window_bits'
    156     _NO_CONTEXT_TAKEOVER_PARAM = 'no_context_takeover'
    157 
    158     def __init__(self, request):
    159         ExtensionProcessorInterface.__init__(self, request)
    160         self._logger = util.get_class_logger(self)
    161 
    162         self._response_window_bits = None
    163         self._response_no_context_takeover = False
    164         self._bfinal = False
    165 
    166         # Calculates
    167         #     (Total outgoing bytes supplied to this filter) /
    168         #     (Total bytes sent to the network after applying this filter)
    169         self._outgoing_average_ratio_calculator = _AverageRatioCalculator()
    170 
    171         # Calculates
    172         #     (Total bytes received from the network) /
    173         #     (Total incoming bytes obtained after applying this filter)
    174         self._incoming_average_ratio_calculator = _AverageRatioCalculator()
    175 
    176     def name(self):
    177         return common.DEFLATE_FRAME_EXTENSION
    178 
    179     def _get_extension_response_internal(self):
    180         # Any unknown parameter will be just ignored.
    181 
    182         window_bits = None
    183         if self._request.has_parameter(self._WINDOW_BITS_PARAM):
    184             window_bits = self._request.get_parameter_value(
    185                 self._WINDOW_BITS_PARAM)
    186             try:
    187                 window_bits = _parse_window_bits(window_bits)
    188             except ValueError, e:
    189                 return None
    190 
    191         no_context_takeover = self._request.has_parameter(
    192             self._NO_CONTEXT_TAKEOVER_PARAM)
    193         if (no_context_takeover and
    194             self._request.get_parameter_value(
    195                 self._NO_CONTEXT_TAKEOVER_PARAM) is not None):
    196             return None
    197 
    198         self._rfc1979_deflater = util._RFC1979Deflater(
    199             window_bits, no_context_takeover)
    200 
    201         self._rfc1979_inflater = util._RFC1979Inflater()
    202 
    203         self._compress_outgoing = True
    204 
    205         response = common.ExtensionParameter(self._request.name())
    206 
    207         if self._response_window_bits is not None:
    208             response.add_parameter(
    209                 self._WINDOW_BITS_PARAM, str(self._response_window_bits))
    210         if self._response_no_context_takeover:
    211             response.add_parameter(
    212                 self._NO_CONTEXT_TAKEOVER_PARAM, None)
    213 
    214         self._logger.debug(
    215             'Enable %s extension ('
    216             'request: window_bits=%s; no_context_takeover=%r, '
    217             'response: window_wbits=%s; no_context_takeover=%r)' %
    218             (self._request.name(),
    219              window_bits,
    220              no_context_takeover,
    221              self._response_window_bits,
    222              self._response_no_context_takeover))
    223 
    224         return response
    225 
    226     def _setup_stream_options_internal(self, stream_options):
    227 
    228         class _OutgoingFilter(object):
    229 
    230             def __init__(self, parent):
    231                 self._parent = parent
    232 
    233             def filter(self, frame):
    234                 self._parent._outgoing_filter(frame)
    235 
    236         class _IncomingFilter(object):
    237 
    238             def __init__(self, parent):
    239                 self._parent = parent
    240 
    241             def filter(self, frame):
    242                 self._parent._incoming_filter(frame)
    243 
    244         stream_options.outgoing_frame_filters.append(
    245             _OutgoingFilter(self))
    246         stream_options.incoming_frame_filters.insert(
    247             0, _IncomingFilter(self))
    248 
    249     def set_response_window_bits(self, value):
    250         self._response_window_bits = value
    251 
    252     def set_response_no_context_takeover(self, value):
    253         self._response_no_context_takeover = value
    254 
    255     def set_bfinal(self, value):
    256         self._bfinal = value
    257 
    258     def enable_outgoing_compression(self):
    259         self._compress_outgoing = True
    260 
    261     def disable_outgoing_compression(self):
    262         self._compress_outgoing = False
    263 
    264     def _outgoing_filter(self, frame):
    265         """Transform outgoing frames. This method is called only by
    266         an _OutgoingFilter instance.
    267         """
    268 
    269         original_payload_size = len(frame.payload)
    270         self._outgoing_average_ratio_calculator.add_original_bytes(
    271                 original_payload_size)
    272 
    273         if (not self._compress_outgoing or
    274             common.is_control_opcode(frame.opcode)):
    275             self._outgoing_average_ratio_calculator.add_result_bytes(
    276                     original_payload_size)
    277             return
    278 
    279         frame.payload = self._rfc1979_deflater.filter(
    280             frame.payload, bfinal=self._bfinal)
    281         frame.rsv1 = 1
    282 
    283         filtered_payload_size = len(frame.payload)
    284         self._outgoing_average_ratio_calculator.add_result_bytes(
    285                 filtered_payload_size)
    286 
    287         _log_outgoing_compression_ratio(
    288                 self._logger,
    289                 original_payload_size,
    290                 filtered_payload_size,
    291                 self._outgoing_average_ratio_calculator.get_average_ratio())
    292 
    293     def _incoming_filter(self, frame):
    294         """Transform incoming frames. This method is called only by
    295         an _IncomingFilter instance.
    296         """
    297 
    298         received_payload_size = len(frame.payload)
    299         self._incoming_average_ratio_calculator.add_result_bytes(
    300                 received_payload_size)
    301 
    302         if frame.rsv1 != 1 or common.is_control_opcode(frame.opcode):
    303             self._incoming_average_ratio_calculator.add_original_bytes(
    304                     received_payload_size)
    305             return
    306 
    307         frame.payload = self._rfc1979_inflater.filter(frame.payload)
    308         frame.rsv1 = 0
    309 
    310         filtered_payload_size = len(frame.payload)
    311         self._incoming_average_ratio_calculator.add_original_bytes(
    312                 filtered_payload_size)
    313 
    314         _log_incoming_compression_ratio(
    315                 self._logger,
    316                 received_payload_size,
    317                 filtered_payload_size,
    318                 self._incoming_average_ratio_calculator.get_average_ratio())
    319 
    320 
    321 _available_processors[common.DEFLATE_FRAME_EXTENSION] = (
    322     DeflateFrameExtensionProcessor)
    323 _compression_extension_names.append(common.DEFLATE_FRAME_EXTENSION)
    324 
    325 _available_processors[common.X_WEBKIT_DEFLATE_FRAME_EXTENSION] = (
    326     DeflateFrameExtensionProcessor)
    327 _compression_extension_names.append(common.X_WEBKIT_DEFLATE_FRAME_EXTENSION)
    328 
    329 
    330 def _parse_compression_method(data):
    331     """Parses the value of "method" extension parameter."""
    332 
    333     return common.parse_extensions(data)
    334 
    335 
    336 def _create_accepted_method_desc(method_name, method_params):
    337     """Creates accepted-method-desc from given method name and parameters"""
    338 
    339     extension = common.ExtensionParameter(method_name)
    340     for name, value in method_params:
    341         extension.add_parameter(name, value)
    342     return common.format_extension(extension)
    343 
    344 
    345 class CompressionExtensionProcessorBase(ExtensionProcessorInterface):
    346     """Base class for perframe-compress and permessage-compress extension."""
    347 
    348     _METHOD_PARAM = 'method'
    349 
    350     def __init__(self, request):
    351         ExtensionProcessorInterface.__init__(self, request)
    352         self._logger = util.get_class_logger(self)
    353         self._compression_method_name = None
    354         self._compression_processor = None
    355         self._compression_processor_hook = None
    356 
    357     def name(self):
    358         return ''
    359 
    360     def _lookup_compression_processor(self, method_desc):
    361         return None
    362 
    363     def _get_compression_processor_response(self):
    364         """Looks up the compression processor based on the self._request and
    365            returns the compression processor's response.
    366         """
    367 
    368         method_list = self._request.get_parameter_value(self._METHOD_PARAM)
    369         if method_list is None:
    370             return None
    371         methods = _parse_compression_method(method_list)
    372         if methods is None:
    373             return None
    374         comression_processor = None
    375         # The current implementation tries only the first method that matches
    376         # supported algorithm. Following methods aren't tried even if the
    377         # first one is rejected.
    378         # TODO(bashi): Need to clarify this behavior.
    379         for method_desc in methods:
    380             compression_processor = self._lookup_compression_processor(
    381                 method_desc)
    382             if compression_processor is not None:
    383                 self._compression_method_name = method_desc.name()
    384                 break
    385         if compression_processor is None:
    386             return None
    387 
    388         if self._compression_processor_hook:
    389             self._compression_processor_hook(compression_processor)
    390 
    391         processor_response = compression_processor.get_extension_response()
    392         if processor_response is None:
    393             return None
    394         self._compression_processor = compression_processor
    395         return processor_response
    396 
    397     def _get_extension_response_internal(self):
    398         processor_response = self._get_compression_processor_response()
    399         if processor_response is None:
    400             return None
    401 
    402         response = common.ExtensionParameter(self._request.name())
    403         accepted_method_desc = _create_accepted_method_desc(
    404                                    self._compression_method_name,
    405                                    processor_response.get_parameters())
    406         response.add_parameter(self._METHOD_PARAM, accepted_method_desc)
    407         self._logger.debug(
    408             'Enable %s extension (method: %s)' %
    409             (self._request.name(), self._compression_method_name))
    410         return response
    411 
    412     def _setup_stream_options_internal(self, stream_options):
    413         if self._compression_processor is None:
    414             return
    415         self._compression_processor.setup_stream_options(stream_options)
    416 
    417     def set_compression_processor_hook(self, hook):
    418         self._compression_processor_hook = hook
    419 
    420     def get_compression_processor(self):
    421         return self._compression_processor
    422 
    423 
    424 class PerMessageDeflateExtensionProcessor(ExtensionProcessorInterface):
    425     """permessage-deflate extension processor. It's also used for
    426     permessage-compress extension when the deflate method is chosen.
    427 
    428     Specification:
    429     http://tools.ietf.org/html/draft-ietf-hybi-permessage-compression-08
    430     """
    431 
    432     _SERVER_MAX_WINDOW_BITS_PARAM = 'server_max_window_bits'
    433     _SERVER_NO_CONTEXT_TAKEOVER_PARAM = 'server_no_context_takeover'
    434     _CLIENT_MAX_WINDOW_BITS_PARAM = 'client_max_window_bits'
    435     _CLIENT_NO_CONTEXT_TAKEOVER_PARAM = 'client_no_context_takeover'
    436 
    437     def __init__(self, request, draft08=True):
    438         """Construct PerMessageDeflateExtensionProcessor
    439 
    440         Args:
    441             draft08: Follow the constraints on the parameters that were not
    442                 specified for permessage-compress but are specified for
    443                 permessage-deflate as on
    444                 draft-ietf-hybi-permessage-compression-08.
    445         """
    446 
    447         ExtensionProcessorInterface.__init__(self, request)
    448         self._logger = util.get_class_logger(self)
    449 
    450         self._preferred_client_max_window_bits = None
    451         self._client_no_context_takeover = False
    452 
    453         self._draft08 = draft08
    454 
    455     def name(self):
    456         return 'deflate'
    457 
    458     def _get_extension_response_internal(self):
    459         if self._draft08:
    460             for name in self._request.get_parameter_names():
    461                 if name not in [self._SERVER_MAX_WINDOW_BITS_PARAM,
    462                                 self._SERVER_NO_CONTEXT_TAKEOVER_PARAM,
    463                                 self._CLIENT_MAX_WINDOW_BITS_PARAM]:
    464                     self._logger.debug('Unknown parameter: %r', name)
    465                     return None
    466         else:
    467             # Any unknown parameter will be just ignored.
    468             pass
    469 
    470         server_max_window_bits = None
    471         if self._request.has_parameter(self._SERVER_MAX_WINDOW_BITS_PARAM):
    472             server_max_window_bits = self._request.get_parameter_value(
    473                     self._SERVER_MAX_WINDOW_BITS_PARAM)
    474             try:
    475                 server_max_window_bits = _parse_window_bits(
    476                     server_max_window_bits)
    477             except ValueError, e:
    478                 self._logger.debug('Bad %s parameter: %r',
    479                                    self._SERVER_MAX_WINDOW_BITS_PARAM,
    480                                    e)
    481                 return None
    482 
    483         server_no_context_takeover = self._request.has_parameter(
    484             self._SERVER_NO_CONTEXT_TAKEOVER_PARAM)
    485         if (server_no_context_takeover and
    486             self._request.get_parameter_value(
    487                 self._SERVER_NO_CONTEXT_TAKEOVER_PARAM) is not None):
    488             self._logger.debug('%s parameter must not have a value: %r',
    489                                self._SERVER_NO_CONTEXT_TAKEOVER_PARAM,
    490                                server_no_context_takeover)
    491             return None
    492 
    493         # client_max_window_bits from a client indicates whether the client can
    494         # accept client_max_window_bits from a server or not.
    495         client_client_max_window_bits = self._request.has_parameter(
    496             self._CLIENT_MAX_WINDOW_BITS_PARAM)
    497         if (self._draft08 and
    498             client_client_max_window_bits and
    499             self._request.get_parameter_value(
    500                 self._CLIENT_MAX_WINDOW_BITS_PARAM) is not None):
    501             self._logger.debug('%s parameter must not have a value in a '
    502                                'client\'s opening handshake: %r',
    503                                self._CLIENT_MAX_WINDOW_BITS_PARAM,
    504                                client_client_max_window_bits)
    505             return None
    506 
    507         self._rfc1979_deflater = util._RFC1979Deflater(
    508             server_max_window_bits, server_no_context_takeover)
    509 
    510         # Note that we prepare for incoming messages compressed with window
    511         # bits upto 15 regardless of the client_max_window_bits value to be
    512         # sent to the client.
    513         self._rfc1979_inflater = util._RFC1979Inflater()
    514 
    515         self._framer = _PerMessageDeflateFramer(
    516             server_max_window_bits, server_no_context_takeover)
    517         self._framer.set_bfinal(False)
    518         self._framer.set_compress_outgoing_enabled(True)
    519 
    520         response = common.ExtensionParameter(self._request.name())
    521 
    522         if server_max_window_bits is not None:
    523             response.add_parameter(
    524                 self._SERVER_MAX_WINDOW_BITS_PARAM,
    525                 str(server_max_window_bits))
    526 
    527         if server_no_context_takeover:
    528             response.add_parameter(
    529                 self._SERVER_NO_CONTEXT_TAKEOVER_PARAM, None)
    530 
    531         if self._preferred_client_max_window_bits is not None:
    532             if self._draft08 and not client_client_max_window_bits:
    533                 self._logger.debug('Processor is configured to use %s but '
    534                                    'the client cannot accept it',
    535                                    self._CLIENT_MAX_WINDOW_BITS_PARAM)
    536                 return None
    537             response.add_parameter(
    538                 self._CLIENT_MAX_WINDOW_BITS_PARAM,
    539                 str(self._preferred_client_max_window_bits))
    540 
    541         if self._client_no_context_takeover:
    542             response.add_parameter(
    543                 self._CLIENT_NO_CONTEXT_TAKEOVER_PARAM, None)
    544 
    545         self._logger.debug(
    546             'Enable %s extension ('
    547             'request: server_max_window_bits=%s; '
    548             'server_no_context_takeover=%r, '
    549             'response: client_max_window_bits=%s; '
    550             'client_no_context_takeover=%r)' %
    551             (self._request.name(),
    552              server_max_window_bits,
    553              server_no_context_takeover,
    554              self._preferred_client_max_window_bits,
    555              self._client_no_context_takeover))
    556 
    557         return response
    558 
    559     def _setup_stream_options_internal(self, stream_options):
    560         self._framer.setup_stream_options(stream_options)
    561 
    562     def set_client_max_window_bits(self, value):
    563         """If this option is specified, this class adds the
    564         client_max_window_bits extension parameter to the handshake response,
    565         but doesn't reduce the LZ77 sliding window size of its inflater.
    566         I.e., you can use this for testing client implementation but cannot
    567         reduce memory usage of this class.
    568 
    569         If this method has been called with True and an offer without the
    570         client_max_window_bits extension parameter is received,
    571         - (When processing the permessage-deflate extension) this processor
    572           declines the request.
    573         - (When processing the permessage-compress extension) this processor
    574           accepts the request.
    575         """
    576 
    577         self._preferred_client_max_window_bits = value
    578 
    579     def set_client_no_context_takeover(self, value):
    580         """If this option is specified, this class adds the
    581         client_no_context_takeover extension parameter to the handshake
    582         response, but doesn't reset inflater for each message. I.e., you can
    583         use this for testing client implementation but cannot reduce memory
    584         usage of this class.
    585         """
    586 
    587         self._client_no_context_takeover = value
    588 
    589     def set_bfinal(self, value):
    590         self._framer.set_bfinal(value)
    591 
    592     def enable_outgoing_compression(self):
    593         self._framer.set_compress_outgoing_enabled(True)
    594 
    595     def disable_outgoing_compression(self):
    596         self._framer.set_compress_outgoing_enabled(False)
    597 
    598 
    599 class _PerMessageDeflateFramer(object):
    600     """A framer for extensions with per-message DEFLATE feature."""
    601 
    602     def __init__(self, deflate_max_window_bits, deflate_no_context_takeover):
    603         self._logger = util.get_class_logger(self)
    604 
    605         self._rfc1979_deflater = util._RFC1979Deflater(
    606             deflate_max_window_bits, deflate_no_context_takeover)
    607 
    608         self._rfc1979_inflater = util._RFC1979Inflater()
    609 
    610         self._bfinal = False
    611 
    612         self._compress_outgoing_enabled = False
    613 
    614         # True if a message is fragmented and compression is ongoing.
    615         self._compress_ongoing = False
    616 
    617         # Calculates
    618         #     (Total outgoing bytes supplied to this filter) /
    619         #     (Total bytes sent to the network after applying this filter)
    620         self._outgoing_average_ratio_calculator = _AverageRatioCalculator()
    621 
    622         # Calculates
    623         #     (Total bytes received from the network) /
    624         #     (Total incoming bytes obtained after applying this filter)
    625         self._incoming_average_ratio_calculator = _AverageRatioCalculator()
    626 
    627     def set_bfinal(self, value):
    628         self._bfinal = value
    629 
    630     def set_compress_outgoing_enabled(self, value):
    631         self._compress_outgoing_enabled = value
    632 
    633     def _process_incoming_message(self, message, decompress):
    634         if not decompress:
    635             return message
    636 
    637         received_payload_size = len(message)
    638         self._incoming_average_ratio_calculator.add_result_bytes(
    639                 received_payload_size)
    640 
    641         message = self._rfc1979_inflater.filter(message)
    642 
    643         filtered_payload_size = len(message)
    644         self._incoming_average_ratio_calculator.add_original_bytes(
    645                 filtered_payload_size)
    646 
    647         _log_incoming_compression_ratio(
    648                 self._logger,
    649                 received_payload_size,
    650                 filtered_payload_size,
    651                 self._incoming_average_ratio_calculator.get_average_ratio())
    652 
    653         return message
    654 
    655     def _process_outgoing_message(self, message, end, binary):
    656         if not binary:
    657             message = message.encode('utf-8')
    658 
    659         if not self._compress_outgoing_enabled:
    660             return message
    661 
    662         original_payload_size = len(message)
    663         self._outgoing_average_ratio_calculator.add_original_bytes(
    664             original_payload_size)
    665 
    666         message = self._rfc1979_deflater.filter(
    667             message, end=end, bfinal=self._bfinal)
    668 
    669         filtered_payload_size = len(message)
    670         self._outgoing_average_ratio_calculator.add_result_bytes(
    671             filtered_payload_size)
    672 
    673         _log_outgoing_compression_ratio(
    674                 self._logger,
    675                 original_payload_size,
    676                 filtered_payload_size,
    677                 self._outgoing_average_ratio_calculator.get_average_ratio())
    678 
    679         if not self._compress_ongoing:
    680             self._outgoing_frame_filter.set_compression_bit()
    681         self._compress_ongoing = not end
    682         return message
    683 
    684     def _process_incoming_frame(self, frame):
    685         if frame.rsv1 == 1 and not common.is_control_opcode(frame.opcode):
    686             self._incoming_message_filter.decompress_next_message()
    687             frame.rsv1 = 0
    688 
    689     def _process_outgoing_frame(self, frame, compression_bit):
    690         if (not compression_bit or
    691             common.is_control_opcode(frame.opcode)):
    692             return
    693 
    694         frame.rsv1 = 1
    695 
    696     def setup_stream_options(self, stream_options):
    697         """Creates filters and sets them to the StreamOptions."""
    698 
    699         class _OutgoingMessageFilter(object):
    700 
    701             def __init__(self, parent):
    702                 self._parent = parent
    703 
    704             def filter(self, message, end=True, binary=False):
    705                 return self._parent._process_outgoing_message(
    706                     message, end, binary)
    707 
    708         class _IncomingMessageFilter(object):
    709 
    710             def __init__(self, parent):
    711                 self._parent = parent
    712                 self._decompress_next_message = False
    713 
    714             def decompress_next_message(self):
    715                 self._decompress_next_message = True
    716 
    717             def filter(self, message):
    718                 message = self._parent._process_incoming_message(
    719                     message, self._decompress_next_message)
    720                 self._decompress_next_message = False
    721                 return message
    722 
    723         self._outgoing_message_filter = _OutgoingMessageFilter(self)
    724         self._incoming_message_filter = _IncomingMessageFilter(self)
    725         stream_options.outgoing_message_filters.append(
    726             self._outgoing_message_filter)
    727         stream_options.incoming_message_filters.append(
    728             self._incoming_message_filter)
    729 
    730         class _OutgoingFrameFilter(object):
    731 
    732             def __init__(self, parent):
    733                 self._parent = parent
    734                 self._set_compression_bit = False
    735 
    736             def set_compression_bit(self):
    737                 self._set_compression_bit = True
    738 
    739             def filter(self, frame):
    740                 self._parent._process_outgoing_frame(
    741                     frame, self._set_compression_bit)
    742                 self._set_compression_bit = False
    743 
    744         class _IncomingFrameFilter(object):
    745 
    746             def __init__(self, parent):
    747                 self._parent = parent
    748 
    749             def filter(self, frame):
    750                 self._parent._process_incoming_frame(frame)
    751 
    752         self._outgoing_frame_filter = _OutgoingFrameFilter(self)
    753         self._incoming_frame_filter = _IncomingFrameFilter(self)
    754         stream_options.outgoing_frame_filters.append(
    755             self._outgoing_frame_filter)
    756         stream_options.incoming_frame_filters.append(
    757             self._incoming_frame_filter)
    758 
    759         stream_options.encode_text_message_to_utf8 = False
    760 
    761 
    762 _available_processors[common.PERMESSAGE_DEFLATE_EXTENSION] = (
    763         PerMessageDeflateExtensionProcessor)
    764 # TODO(tyoshino): Reorganize class names.
    765 _compression_extension_names.append('deflate')
    766 
    767 
    768 class PerMessageCompressExtensionProcessor(
    769     CompressionExtensionProcessorBase):
    770     """permessage-compress extension processor.
    771 
    772     Specification:
    773     http://tools.ietf.org/html/draft-ietf-hybi-permessage-compression
    774     """
    775 
    776     _DEFLATE_METHOD = 'deflate'
    777 
    778     def __init__(self, request):
    779         CompressionExtensionProcessorBase.__init__(self, request)
    780 
    781     def name(self):
    782         return common.PERMESSAGE_COMPRESSION_EXTENSION
    783 
    784     def _lookup_compression_processor(self, method_desc):
    785         if method_desc.name() == self._DEFLATE_METHOD:
    786             return PerMessageDeflateExtensionProcessor(method_desc, False)
    787         return None
    788 
    789 
    790 _available_processors[common.PERMESSAGE_COMPRESSION_EXTENSION] = (
    791     PerMessageCompressExtensionProcessor)
    792 _compression_extension_names.append(common.PERMESSAGE_COMPRESSION_EXTENSION)
    793 
    794 
    795 class MuxExtensionProcessor(ExtensionProcessorInterface):
    796     """WebSocket multiplexing extension processor."""
    797 
    798     _QUOTA_PARAM = 'quota'
    799 
    800     def __init__(self, request):
    801         ExtensionProcessorInterface.__init__(self, request)
    802         self._quota = 0
    803         self._extensions = []
    804 
    805     def name(self):
    806         return common.MUX_EXTENSION
    807 
    808     def check_consistency_with_other_processors(self, processors):
    809         before_mux = True
    810         for processor in processors:
    811             name = processor.name()
    812             if name == self.name():
    813                 before_mux = False
    814                 continue
    815             if not processor.is_active():
    816                 continue
    817             if before_mux:
    818                 # Mux extension cannot be used after extensions
    819                 # that depend on frame boundary, extension data field, or any
    820                 # reserved bits which are attributed to each frame.
    821                 if (name == common.DEFLATE_FRAME_EXTENSION or
    822                     name == common.X_WEBKIT_DEFLATE_FRAME_EXTENSION):
    823                     self.set_active(False)
    824                     return
    825             else:
    826                 # Mux extension should not be applied before any history-based
    827                 # compression extension.
    828                 if (name == common.DEFLATE_FRAME_EXTENSION or
    829                     name == common.X_WEBKIT_DEFLATE_FRAME_EXTENSION or
    830                     name == common.PERMESSAGE_COMPRESSION_EXTENSION or
    831                     name == common.X_WEBKIT_PERMESSAGE_COMPRESSION_EXTENSION):
    832                     self.set_active(False)
    833                     return
    834 
    835     def _get_extension_response_internal(self):
    836         self._active = False
    837         quota = self._request.get_parameter_value(self._QUOTA_PARAM)
    838         if quota is not None:
    839             try:
    840                 quota = int(quota)
    841             except ValueError, e:
    842                 return None
    843             if quota < 0 or quota >= 2 ** 32:
    844                 return None
    845             self._quota = quota
    846 
    847         self._active = True
    848         return common.ExtensionParameter(common.MUX_EXTENSION)
    849 
    850     def _setup_stream_options_internal(self, stream_options):
    851         pass
    852 
    853     def set_quota(self, quota):
    854         self._quota = quota
    855 
    856     def quota(self):
    857         return self._quota
    858 
    859     def set_extensions(self, extensions):
    860         self._extensions = extensions
    861 
    862     def extensions(self):
    863         return self._extensions
    864 
    865 
    866 _available_processors[common.MUX_EXTENSION] = MuxExtensionProcessor
    867 
    868 
    869 def get_extension_processor(extension_request):
    870     """Given an ExtensionParameter representing an extension offer received
    871     from a client, configures and returns an instance of the corresponding
    872     extension processor class.
    873     """
    874 
    875     processor_class = _available_processors.get(extension_request.name())
    876     if processor_class is None:
    877         return None
    878     return processor_class(extension_request)
    879 
    880 
    881 def is_compression_extension(extension_name):
    882     return extension_name in _compression_extension_names
    883 
    884 
    885 # vi:sts=4 sw=4 et
    886