Bug 752776 - Upgrade pywebsocket to v631. r=mcmanus
authorJason Duell <jduell.mcbugs@gmail.com>
Wed, 16 May 2012 17:04:15 -0700
changeset 94157 6aa80954981cb9865327116f30552364b30dd11c
parent 94156 722e163ecc1e5440f1e74842fc3f28b67762705b
child 94158 deb1fd74e10b5d129081ca5dab8defec05e64403
push id9499
push userjduell@mozilla.com
push dateThu, 17 May 2012 00:05:27 +0000
treeherdermozilla-inbound@c567e28272d5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmcmanus
bugs752776
milestone15.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 752776 - Upgrade pywebsocket to v631. r=mcmanus
testing/mochitest/pywebsocket/README-MOZILLA
testing/mochitest/pywebsocket/mod_pywebsocket/_stream_hixie75.py
testing/mochitest/pywebsocket/mod_pywebsocket/_stream_hybi.py
testing/mochitest/pywebsocket/mod_pywebsocket/common.py
testing/mochitest/pywebsocket/mod_pywebsocket/dispatch.py
testing/mochitest/pywebsocket/mod_pywebsocket/extensions.py
testing/mochitest/pywebsocket/mod_pywebsocket/handshake/__init__.py
testing/mochitest/pywebsocket/mod_pywebsocket/handshake/_base.py
testing/mochitest/pywebsocket/mod_pywebsocket/handshake/hybi.py
testing/mochitest/pywebsocket/mod_pywebsocket/handshake/hybi00.py
testing/mochitest/pywebsocket/mod_pywebsocket/msgutil.py
testing/mochitest/pywebsocket/mod_pywebsocket/util.py
testing/mochitest/pywebsocket/standalone.py
--- a/testing/mochitest/pywebsocket/README-MOZILLA
+++ b/testing/mochitest/pywebsocket/README-MOZILLA
@@ -15,17 +15,17 @@ STEPS TO UPDATE MOZILLA TO NEWER PYWEBSO
 
 - Export a version w/o SVN files:
 
     svn export src dist
 
 - rsync new version into our tree, deleting files that aren't needed any more
   (NOTE: this will blow away this file!  hg revert it or keep a copy.)
 
-    rsync -r --delete dist/ $MOZ_SRC/testing/mochitest/pywebsocket
+    rsync -rv --delete dist/ $MOZ_SRC/testing/mochitest/pywebsocket
 
 - Get rid of examples/test directory and some cruft:
 
     rm -rf example test setup.py MANIFEST.in
 
 - Manually move the 'standalone.py' file from the mmod_pywebsocket/ directory to
   the parent directory (not sure why we moved it: probably no reason)
 
--- a/testing/mochitest/pywebsocket/mod_pywebsocket/_stream_hixie75.py
+++ b/testing/mochitest/pywebsocket/mod_pywebsocket/_stream_hixie75.py
@@ -23,32 +23,37 @@
 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
-"""Stream of WebSocket protocol with the framing used by IETF HyBi 00 and
-Hixie 75. For Hixie 75 this stream doesn't perform closing handshake.
+"""This file provides a class for parsing/building frames of the WebSocket
+protocol version HyBi 00 and Hixie 75.
+
+Specification:
+http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00
 """
 
 
 from mod_pywebsocket import common
 from mod_pywebsocket._stream_base import BadOperationException
 from mod_pywebsocket._stream_base import ConnectionTerminatedException
 from mod_pywebsocket._stream_base import InvalidFrameException
 from mod_pywebsocket._stream_base import StreamBase
 from mod_pywebsocket._stream_base import UnsupportedFrameException
 from mod_pywebsocket import util
 
 
 class StreamHixie75(StreamBase):
-    """Stream of WebSocket messages."""
+    """A class for parsing/building frames of the WebSocket protocol version
+    HyBi 00 and Hixie 75.
+    """
 
     def __init__(self, request, enable_closing_handshake=False):
         """Construct an instance.
 
         Args:
             request: mod_python request.
             enable_closing_handshake: to let StreamHixie75 perform closing
                                       handshake as specified in HyBi 00, set
--- a/testing/mochitest/pywebsocket/mod_pywebsocket/_stream_hybi.py
+++ b/testing/mochitest/pywebsocket/mod_pywebsocket/_stream_hybi.py
@@ -1,9 +1,9 @@
-# Copyright 2011, Google Inc.
+# Copyright 2012, Google Inc.
 # All rights reserved.
 #
 # Redistribution and use in source and binary forms, with or without
 # modification, are permitted provided that the following conditions are
 # met:
 #
 #     * Redistributions of source code must retain the above copyright
 # notice, this list of conditions and the following disclaimer.
@@ -23,17 +23,21 @@
 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
-"""Stream class for IETF HyBi latest WebSocket protocol.
+"""This file provides classes and helper functions for parsing/building frames
+of the WebSocket protocol (RFC 6455).
+
+Specification:
+http://tools.ietf.org/html/rfc6455
 """
 
 
 from collections import deque
 import os
 import struct
 
 from mod_pywebsocket import common
@@ -233,17 +237,19 @@ class StreamOptions(object):
         self.outgoing_frame_filters = []
         self.incoming_frame_filters = []
 
         self.mask_send = False
         self.unmask_receive = True
 
 
 class Stream(StreamBase):
-    """Stream of WebSocket messages."""
+    """A class for parsing/building frames of the WebSocket protocol
+    (RFC 6455).
+    """
 
     def __init__(self, request, options):
         """Constructs an instance.
 
         Args:
             request: mod_python request.
         """
 
@@ -348,18 +354,18 @@ class Stream(StreamBase):
         """Send message.
 
         Args:
             message: text in unicode or binary in str to send.
             binary: send message as binary frame.
 
         Raises:
             BadOperationException: when called on a server-terminated
-                connection or called with inconsistent message type or binary
-                parameter.
+                connection or called with inconsistent message type or
+                binary parameter.
         """
 
         if self._request.server_terminated:
             raise BadOperationException(
                 'Requested send_message after sending out a closing handshake')
 
         if binary and isinstance(message, unicode):
             raise BadOperationException(
@@ -477,17 +483,21 @@ class Stream(StreamBase):
                 self._request.client_terminated = True
 
                 # Status code is optional. We can have status reason only if we
                 # have status code. Status reason can be empty string. So,
                 # allowed cases are
                 # - no application data: no code no reason
                 # - 2 octet of application data: has code but no reason
                 # - 3 or more octet of application data: both code and reason
-                if len(message) == 1:
+                if len(message) == 0:
+                    self._logger.debug('Received close frame (empty body)')
+                    self._request.ws_close_code = (
+                        common.STATUS_NO_STATUS_RECEIVED)
+                elif len(message) == 1:
                     raise InvalidFrameException(
                         'If a close frame has status code, the length of '
                         'status code must be 2 octet')
                 elif len(message) >= 2:
                     self._request.ws_close_code = struct.unpack(
                         '!H', message[0:2])[0]
                     self._request.ws_close_reason = message[2:].decode(
                         'utf-8', 'replace')
@@ -496,32 +506,38 @@ class Stream(StreamBase):
                         self._request.ws_close_code,
                         self._request.ws_close_reason)
 
                 # Drain junk data after the close frame if necessary.
                 self._drain_received_data()
 
                 if self._request.server_terminated:
                     self._logger.debug(
-                        'Received ack for server-initiated closing '
-                        'handshake')
+                        'Received ack for server-initiated closing handshake')
                     return None
 
                 self._logger.debug(
                     'Received client-initiated closing handshake')
 
-                code = common.STATUS_NORMAL
+                code = common.STATUS_NORMAL_CLOSURE
                 reason = ''
                 if hasattr(self._request, '_dispatcher'):
                     dispatcher = self._request._dispatcher
                     code, reason = dispatcher.passive_closing_handshake(
                         self._request)
+                    if code is None and reason is not None and len(reason) > 0:
+                        self._logger.warning(
+                            'Handler specified reason despite code being None')
+                        reason = ''
+                    if reason is None:
+                        reason = ''
                 self._send_closing_handshake(code, reason)
                 self._logger.debug(
-                    'Sent ack for client-initiated closing handshake')
+                    'Sent ack for client-initiated closing handshake '
+                    '(code=%r, reason=%r)', code, reason)
                 return None
             elif self._original_opcode == common.OPCODE_PING:
                 try:
                     handler = self._request.on_ping_handler
                     if handler:
                         handler(self._request, message)
                         continue
                 except AttributeError, e:
@@ -560,44 +576,67 @@ class Stream(StreamBase):
                     pass
 
                 continue
             else:
                 raise UnsupportedFrameException(
                     'Opcode %d is not supported' % self._original_opcode)
 
     def _send_closing_handshake(self, code, reason):
-        if code >= (1 << 16) or code < 0:
-            raise BadOperationException('Status code is out of range')
-
-        encoded_reason = reason.encode('utf-8')
-        if len(encoded_reason) + 2 > 125:
-            raise BadOperationException(
-                'Application data size of close frames must be 125 bytes or '
-                'less')
+        body = ''
+        if code is not None:
+            if code >= (1 << 16) or code < 0:
+                raise BadOperationException('Status code is out of range')
+            encoded_reason = reason.encode('utf-8')
+            if len(encoded_reason) + 2 > 125:
+                raise BadOperationException(
+                    'Application data size of close frames must be 125 bytes '
+                    'or less')
+            body = struct.pack('!H', code) + encoded_reason
 
         frame = create_close_frame(
-            struct.pack('!H', code) + encoded_reason,
+            body,
             self._options.mask_send,
             self._options.outgoing_frame_filters)
 
         self._request.server_terminated = True
 
         self._write(frame)
 
-    def close_connection(self, code=common.STATUS_NORMAL, reason=''):
-        """Closes a WebSocket connection."""
+    def close_connection(self, code=common.STATUS_NORMAL_CLOSURE, reason=''):
+        """Closes a WebSocket connection.
+
+        Args:
+            code: Status code for close frame. If code is None, a close
+                frame with empty body will be sent.
+            reason: string representing close reason.
+        Raises:
+            BadOperationException: when reason is specified with code None
+            or reason is not an instance of both str and unicode.
+        """
 
         if self._request.server_terminated:
             self._logger.debug(
                 'Requested close_connection but server is already terminated')
             return
 
+        if code is None:
+            if reason is not None and len(reason) > 0:
+                raise BadOperationException(
+                    'close reason must not be specified if code is None')
+            reason = ''
+        else:
+            if not isinstance(reason, str) and not isinstance(reason, unicode):
+                raise BadOperationException(
+                    'close reason must be an instance of str or unicode')
+
         self._send_closing_handshake(code, reason)
-        self._logger.debug('Sent server-initiated closing handshake')
+        self._logger.debug(
+            'Sent server-initiated closing handshake (code=%r, reason=%r)',
+            code, reason)
 
         if (code == common.STATUS_GOING_AWAY or
             code == common.STATUS_PROTOCOL_ERROR):
             # It doesn't make sense to wait for a close frame if the reason is
             # protocol error or that the server is going away. For some of
             # other reasons, it might not make sense to wait for a close frame,
             # but it's not clear, yet.
             return
--- a/testing/mochitest/pywebsocket/mod_pywebsocket/common.py
+++ b/testing/mochitest/pywebsocket/mod_pywebsocket/common.py
@@ -1,9 +1,9 @@
-# Copyright 2011, Google Inc.
+# Copyright 2012, Google Inc.
 # All rights reserved.
 #
 # Redistribution and use in source and binary forms, with or without
 # modification, are permitted provided that the following conditions are
 # met:
 #
 #     * Redistributions of source code must retain the above copyright
 # notice, this list of conditions and the following disclaimer.
@@ -88,31 +88,51 @@ SEC_WEBSOCKET_EXTENSIONS_HEADER = 'Sec-W
 SEC_WEBSOCKET_DRAFT_HEADER = 'Sec-WebSocket-Draft'
 SEC_WEBSOCKET_KEY1_HEADER = 'Sec-WebSocket-Key1'
 SEC_WEBSOCKET_KEY2_HEADER = 'Sec-WebSocket-Key2'
 SEC_WEBSOCKET_LOCATION_HEADER = 'Sec-WebSocket-Location'
 
 # Extensions
 DEFLATE_STREAM_EXTENSION = 'deflate-stream'
 DEFLATE_FRAME_EXTENSION = 'deflate-frame'
+X_WEBKIT_DEFLATE_FRAME_EXTENSION = 'x-webkit-deflate-frame'
 
 # Status codes
-# Code STATUS_CODE_NOT_AVAILABLE should not be used in actual frames. This code
-# is exposed to JavaScript API as pseudo status code which represent actual
-# frame does not have status code.
-STATUS_NORMAL = 1000
+# Code STATUS_NO_STATUS_RECEIVED, STATUS_ABNORMAL_CLOSURE, and
+# STATUS_TLS_HANDSHAKE are pseudo codes to indicate specific error cases.
+# Could not be used for codes in actual closing frames.
+# Application level errors must use codes in the range
+# STATUS_USER_REGISTERED_BASE to STATUS_USER_PRIVATE_MAX. The codes in the
+# range STATUS_USER_REGISTERED_BASE to STATUS_USER_REGISTERED_MAX are managed
+# by IANA. Usually application must define user protocol level errors in the
+# range STATUS_USER_PRIVATE_BASE to STATUS_USER_PRIVATE_MAX.
+STATUS_NORMAL_CLOSURE = 1000
 STATUS_GOING_AWAY = 1001
 STATUS_PROTOCOL_ERROR = 1002
-STATUS_UNSUPPORTED = 1003
-STATUS_CODE_NOT_AVAILABLE = 1005
-STATUS_ABNORMAL_CLOSE = 1006
-STATUS_INVALID_FRAME_PAYLOAD = 1007
+STATUS_UNSUPPORTED_DATA = 1003
+STATUS_NO_STATUS_RECEIVED = 1005
+STATUS_ABNORMAL_CLOSURE = 1006
+STATUS_INVALID_FRAME_PAYLOAD_DATA = 1007
 STATUS_POLICY_VIOLATION = 1008
 STATUS_MESSAGE_TOO_BIG = 1009
-STATUS_MANDATORY_EXT = 1010
+STATUS_MANDATORY_EXTENSION = 1010
+STATUS_INTERNAL_SERVER_ERROR = 1011
+STATUS_TLS_HANDSHAKE = 1015
+STATUS_USER_REGISTERED_BASE = 3000
+STATUS_USER_REGISTERED_MAX = 3999
+STATUS_USER_PRIVATE_BASE = 4000
+STATUS_USER_PRIVATE_MAX = 4999
+# Following definitions are aliases to keep compatibility. Applications must
+# not use these obsoleted definitions anymore.
+STATUS_NORMAL = STATUS_NORMAL_CLOSURE
+STATUS_UNSUPPORTED = STATUS_UNSUPPORTED_DATA
+STATUS_CODE_NOT_AVAILABLE = STATUS_NO_STATUS_RECEIVED
+STATUS_ABNORMAL_CLOSE = STATUS_ABNORMAL_CLOSURE
+STATUS_INVALID_FRAME_PAYLOAD = STATUS_INVALID_FRAME_PAYLOAD_DATA
+STATUS_MANDATORY_EXT = STATUS_MANDATORY_EXTENSION
 
 # HTTP status codes
 HTTP_STATUS_BAD_REQUEST = 400
 HTTP_STATUS_FORBIDDEN = 403
 HTTP_STATUS_NOT_FOUND = 404
 
 
 def is_control_opcode(opcode):
--- a/testing/mochitest/pywebsocket/mod_pywebsocket/dispatch.py
+++ b/testing/mochitest/pywebsocket/mod_pywebsocket/dispatch.py
@@ -1,9 +1,9 @@
-# Copyright 2011, Google Inc.
+# Copyright 2012, Google Inc.
 # All rights reserved.
 #
 # Redistribution and use in source and binary forms, with or without
 # modification, are permitted provided that the following conditions are
 # met:
 #
 #     * Redistributions of source code must retain the above copyright
 # notice, this list of conditions and the following disclaimer.
@@ -57,17 +57,17 @@ class DispatchException(Exception):
     def __init__(self, name, status=common.HTTP_STATUS_NOT_FOUND):
         super(DispatchException, self).__init__(name)
         self.status = status
 
 
 def _default_passive_closing_handshake_handler(request):
     """Default web_socket_passive_closing_handshake handler."""
 
-    return common.STATUS_NORMAL, ''
+    return common.STATUS_NORMAL_CLOSURE, ''
 
 
 def _normalize_path(path):
     """Normalize path.
 
     Args:
         path: the path to normalize.
 
@@ -287,29 +287,29 @@ class Dispatcher(object):
             if not request.server_terminated:
                 request.ws_stream.close_connection()
         # Catch non-critical exceptions the handler didn't handle.
         except handshake.AbortedByUserException, e:
             self._logger.debug('%s', e)
             raise
         except msgutil.BadOperationException, e:
             self._logger.debug('%s', e)
-            request.ws_stream.close_connection(common.STATUS_ABNORMAL_CLOSE)
+            request.ws_stream.close_connection(common.STATUS_ABNORMAL_CLOSURE)
         except msgutil.InvalidFrameException, e:
             # InvalidFrameException must be caught before
             # ConnectionTerminatedException that catches InvalidFrameException.
             self._logger.debug('%s', e)
             request.ws_stream.close_connection(common.STATUS_PROTOCOL_ERROR)
         except msgutil.UnsupportedFrameException, e:
             self._logger.debug('%s', e)
-            request.ws_stream.close_connection(common.STATUS_UNSUPPORTED)
+            request.ws_stream.close_connection(common.STATUS_UNSUPPORTED_DATA)
         except stream.InvalidUTF8Exception, e:
             self._logger.debug('%s', e)
             request.ws_stream.close_connection(
-                common.STATUS_INVALID_FRAME_PAYLOAD)
+                common.STATUS_INVALID_FRAME_PAYLOAD_DATA)
         except msgutil.ConnectionTerminatedException, e:
             self._logger.debug('%s', e)
         except Exception, e:
             util.prepend_message_to_exception(
                 '%s raised exception for %s: ' % (
                     _TRANSFER_DATA_HANDLER_NAME, request.ws_resource),
                 e)
             raise
--- a/testing/mochitest/pywebsocket/mod_pywebsocket/extensions.py
+++ b/testing/mochitest/pywebsocket/mod_pywebsocket/extensions.py
@@ -67,27 +67,39 @@ class DeflateStreamExtensionProcessor(Ex
 
 _available_processors[common.DEFLATE_STREAM_EXTENSION] = (
     DeflateStreamExtensionProcessor)
 
 
 class DeflateFrameExtensionProcessor(ExtensionProcessorInterface):
     """WebSocket Per-frame DEFLATE extension processor."""
 
-    _WINDOW_BITS_PARAM = 'window_bits'
+    _WINDOW_BITS_PARAM = 'max_window_bits'
     _NO_CONTEXT_TAKEOVER_PARAM = 'no_context_takeover'
 
     def __init__(self, request):
         self._logger = util.get_class_logger(self)
 
         self._request = request
 
         self._response_window_bits = None
         self._response_no_context_takeover = False
 
+        # Counters for statistics.
+
+        # Total number of outgoing bytes supplied to this filter.
+        self._total_outgoing_payload_bytes = 0
+        # Total number of bytes sent to the network after applying this filter.
+        self._total_filtered_outgoing_payload_bytes = 0
+
+        # Total number of bytes received from the network.
+        self._total_incoming_payload_bytes = 0
+        # Total number of incoming bytes obtained after applying this filter.
+        self._total_filtered_incoming_payload_bytes = 0
+
     def get_extension_response(self):
         # Any unknown parameter will be just ignored.
 
         window_bits = self._request.get_parameter_value(
             self._WINDOW_BITS_PARAM)
         no_context_takeover = self._request.has_parameter(
             self._NO_CONTEXT_TAKEOVER_PARAM)
         if (no_context_takeover and
@@ -105,30 +117,30 @@ class DeflateFrameExtensionProcessor(Ext
 
         self._deflater = util._RFC1979Deflater(
             window_bits, no_context_takeover)
 
         self._inflater = util._RFC1979Inflater()
 
         self._compress_outgoing = True
 
-        response = common.ExtensionParameter(common.DEFLATE_FRAME_EXTENSION)
+        response = common.ExtensionParameter(self._request.name())
 
         if self._response_window_bits is not None:
             response.add_parameter(
                 self._WINDOW_BITS_PARAM, str(self._response_window_bits))
         if self._response_no_context_takeover:
             response.add_parameter(
                 self._NO_CONTEXT_TAKEOVER_PARAM, None)
 
         self._logger.debug(
             'Enable %s extension ('
             'request: window_bits=%s; no_context_takeover=%r, '
             'response: window_wbits=%s; no_context_takeover=%r)' %
-            (common.DEFLATE_STREAM_EXTENSION,
+            (self._request.name(),
              window_bits,
              no_context_takeover,
              self._response_window_bits,
              self._response_no_context_takeover))
 
         return response
 
     def setup_stream_options(self, stream_options):
@@ -166,39 +178,87 @@ class DeflateFrameExtensionProcessor(Ext
     def disable_outgoing_compression(self):
         self._compress_outgoing = False
 
     def _outgoing_filter(self, frame):
         """Transform outgoing frames. This method is called only by
         an _OutgoingFilter instance.
         """
 
+        original_payload_size = len(frame.payload)
+        self._total_outgoing_payload_bytes += original_payload_size
+
         if (not self._compress_outgoing or
             common.is_control_opcode(frame.opcode)):
+            self._total_filtered_outgoing_payload_bytes += (
+                original_payload_size)
             return
 
         frame.payload = self._deflater.filter(frame.payload)
         frame.rsv1 = 1
 
+        filtered_payload_size = len(frame.payload)
+        self._total_filtered_outgoing_payload_bytes += filtered_payload_size
+
+        # Print inf when ratio is not available.
+        ratio = float('inf')
+        average_ratio = float('inf')
+        if original_payload_size != 0:
+            ratio = float(filtered_payload_size) / original_payload_size
+        if self._total_outgoing_payload_bytes != 0:
+            average_ratio = (
+                float(self._total_filtered_outgoing_payload_bytes) /
+                self._total_outgoing_payload_bytes)
+        self._logger.debug(
+            'Outgoing compress ratio: %f (average: %f)' %
+            (ratio, average_ratio))
+
     def _incoming_filter(self, frame):
         """Transform incoming frames. This method is called only by
         an _IncomingFilter instance.
         """
 
+        received_payload_size = len(frame.payload)
+        self._total_incoming_payload_bytes += received_payload_size
+
         if frame.rsv1 != 1 or common.is_control_opcode(frame.opcode):
+            self._total_filtered_incoming_payload_bytes += (
+                received_payload_size)
             return
 
         frame.payload = self._inflater.filter(frame.payload)
         frame.rsv1 = 0
 
+        filtered_payload_size = len(frame.payload)
+        self._total_filtered_incoming_payload_bytes += filtered_payload_size
+
+        # Print inf when ratio is not available.
+        ratio = float('inf')
+        average_ratio = float('inf')
+        if received_payload_size != 0:
+            ratio = float(received_payload_size) / filtered_payload_size
+        if self._total_filtered_incoming_payload_bytes != 0:
+            average_ratio = (
+                float(self._total_incoming_payload_bytes) /
+                self._total_filtered_incoming_payload_bytes)
+        self._logger.debug(
+            'Incoming compress ratio: %f (average: %f)' %
+            (ratio, average_ratio))
+
 
 _available_processors[common.DEFLATE_FRAME_EXTENSION] = (
     DeflateFrameExtensionProcessor)
 
 
+# Adding vendor-prefixed deflate-frame extension.
+# TODO(bashi): Remove this after WebKit stops using vender prefix.
+_available_processors[common.X_WEBKIT_DEFLATE_FRAME_EXTENSION] = (
+    DeflateFrameExtensionProcessor)
+
+
 def get_extension_processor(extension_request):
     global _available_processors
     processor_class = _available_processors.get(extension_request.name())
     if processor_class is None:
         return None
     return processor_class(extension_request)
 
 
--- a/testing/mochitest/pywebsocket/mod_pywebsocket/handshake/__init__.py
+++ b/testing/mochitest/pywebsocket/mod_pywebsocket/handshake/__init__.py
@@ -78,25 +78,25 @@ def do_handshake(request, dispatcher, al
     # dict(mimetools.Message object) returns the map from header names to
     # header values. While MpTable_Type doesn't have such __str__ but just
     # __repr__ which formats itself as well as dictionary object.
     _LOGGER.debug(
         'Client\'s opening handshake headers: %r', dict(request.headers_in))
 
     handshakers = []
     handshakers.append(
-        ('IETF HyBi latest', hybi.Handshaker(request, dispatcher)))
+        ('RFC 6455', hybi.Handshaker(request, dispatcher)))
     handshakers.append(
-        ('IETF HyBi 00', hybi00.Handshaker(request, dispatcher)))
+        ('HyBi 00', hybi00.Handshaker(request, dispatcher)))
     if allowDraft75:
         handshakers.append(
-            ('IETF Hixie 75', draft75.Handshaker(request, dispatcher, strict)))
+            ('Hixie 75', draft75.Handshaker(request, dispatcher, strict)))
 
     for name, handshaker in handshakers:
-        _LOGGER.debug('Trying %s protocol', name)
+        _LOGGER.debug('Trying protocol version %s', name)
         try:
             handshaker.do_handshake()
             _LOGGER.info('Established (%s protocol)', name)
             return
         except HandshakeException, e:
             _LOGGER.debug(
                 'Failed to complete opening handshake as %s protocol: %r',
                 name, e)
--- a/testing/mochitest/pywebsocket/mod_pywebsocket/handshake/_base.py
+++ b/testing/mochitest/pywebsocket/mod_pywebsocket/handshake/_base.py
@@ -84,17 +84,17 @@ def get_default_port(is_secure):
         return common.DEFAULT_WEB_SOCKET_PORT
 
 
 def validate_subprotocol(subprotocol, hixie):
     """Validate a value in subprotocol fields such as WebSocket-Protocol,
     Sec-WebSocket-Protocol.
 
     See
-    - HyBi 10: Section 5.1. and 5.2.2.
+    - RFC 6455: Section 4.1., 4.2.2., and 4.3.
     - HyBi 00: Section 4.1. Opening handshake
     - Hixie 75: Section 4.1. Handshake
     """
 
     if not subprotocol:
         raise HandshakeException('Invalid subprotocol name: empty')
     if hixie:
         # Parameter should be in the range U+0020 to U+007E.
--- a/testing/mochitest/pywebsocket/mod_pywebsocket/handshake/hybi.py
+++ b/testing/mochitest/pywebsocket/mod_pywebsocket/handshake/hybi.py
@@ -23,17 +23,22 @@
 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
-"""WebSocket HyBi latest opening handshake processor."""
+"""This file provides the opening handshake processor for the WebSocket
+protocol (RFC 6455).
+
+Specification:
+http://tools.ietf.org/html/rfc6455
+"""
 
 
 # Note: request.connection.write is used in this module, even though mod_python
 # document says that it should be used only in connection handlers.
 # Unfortunately, we have no other options. For example, request.write is not
 # suitable because it doesn't allow direct raw bytes writing.
 
 
@@ -54,17 +59,20 @@ from mod_pywebsocket.handshake._base imp
 from mod_pywebsocket.handshake._base import validate_mandatory_header
 from mod_pywebsocket.handshake._base import validate_subprotocol
 from mod_pywebsocket.handshake._base import VersionException
 from mod_pywebsocket.stream import Stream
 from mod_pywebsocket.stream import StreamOptions
 from mod_pywebsocket import util
 
 
-_BASE64_REGEX = re.compile('^[+/0-9A-Za-z]*=*$')
+# Used to validate the value in the Sec-WebSocket-Key header strictly. RFC 4648
+# disallows non-zero padding, so the character right before == must be any of
+# A, Q, g and w.
+_SEC_WEBSOCKET_KEY_REGEX = re.compile('^[+/0-9A-Za-z]{21}[AQgw]==$')
 
 # Defining aliases for values used frequently.
 _VERSION_HYBI08 = common.VERSION_HYBI08
 _VERSION_HYBI08_STRING = str(_VERSION_HYBI08)
 _VERSION_LATEST = common.VERSION_HYBI_LATEST
 _VERSION_LATEST_STRING = str(_VERSION_LATEST)
 _SUPPORTED_VERSIONS = [
     _VERSION_LATEST,
@@ -80,17 +88,17 @@ def compute_accept(key):
     accept_binary = util.sha1_hash(
         key + common.WEBSOCKET_ACCEPT_UUID).digest()
     accept = base64.b64encode(accept_binary)
 
     return (accept, accept_binary)
 
 
 class Handshaker(object):
-    """This class performs WebSocket handshake."""
+    """Opening handshake processor for the WebSocket protocol (RFC 6455)."""
 
     def __init__(self, request, dispatcher):
         """Construct an instance.
 
         Args:
             request: mod_python request.
             dispatcher: Dispatcher (dispatch.Dispatcher).
 
@@ -156,17 +164,17 @@ class Handshaker(object):
             key = self._get_key()
             (accept, accept_binary) = compute_accept(key)
             self._logger.debug(
                 '%s: %r (%s)',
                 common.SEC_WEBSOCKET_ACCEPT_HEADER,
                 accept,
                 util.hexify(accept_binary))
 
-            self._logger.debug('IETF HyBi protocol')
+            self._logger.debug('Protocol version is RFC 6455')
 
             # Setup extension processors.
 
             processors = []
             if self._request.ws_requested_extensions is not None:
                 for extension_request in self._request.ws_requested_extensions:
                     processor = get_extension_processor(extension_request)
                     # Unknown extension requests are just ignored.
@@ -254,20 +262,16 @@ class Handshaker(object):
         raise VersionException(
             'Unsupported version %r for header %s' %
             (version, common.SEC_WEBSOCKET_VERSION_HEADER),
             supported_versions=', '.join(map(str, _SUPPORTED_VERSIONS)))
 
     def _set_protocol(self):
         self._request.ws_protocol = None
 
-        # MOZILLA
-        self._request.sts = None
-        # /MOZILLA
-
         protocol_header = self._request.headers_in.get(
             common.SEC_WEBSOCKET_PROTOCOL_HEADER)
 
         if not protocol_header:
             self._request.ws_requested_protocols = None
             return
 
         self._request.ws_requested_protocols = parse_token_list(
@@ -302,17 +306,17 @@ class Handshaker(object):
 
         # Validate
         key_is_valid = False
         try:
             # Validate key by quick regex match before parsing by base64
             # module. Because base64 module skips invalid characters, we have
             # to do this in advance to make this server strictly reject illegal
             # keys.
-            if _BASE64_REGEX.match(key):
+            if _SEC_WEBSOCKET_KEY_REGEX.match(key):
                 decoded_key = base64.b64decode(key)
                 if len(decoded_key) == 16:
                     key_is_valid = True
         except TypeError, e:
             pass
 
         if not key_is_valid:
             raise HandshakeException(
@@ -350,21 +354,16 @@ class Handshaker(object):
             response.append(format_header(
                 common.SEC_WEBSOCKET_PROTOCOL_HEADER,
                 self._request.ws_protocol))
         if (self._request.ws_extensions is not None and
             len(self._request.ws_extensions) != 0):
             response.append(format_header(
                 common.SEC_WEBSOCKET_EXTENSIONS_HEADER,
                 format_extensions(self._request.ws_extensions)))
-        # MOZILLA: Add HSTS header if requested to
-        if self._request.sts is not None:
-            response.append(format_header("Strict-Transport-Security",
-                                          self._request.sts))
-        # /MOZILLA
         response.append('\r\n')
 
         raw_response = ''.join(response)
         self._request.connection.write(raw_response)
         self._logger.debug('Sent server\'s opening handshake: %r',
                            raw_response)
 
 
--- a/testing/mochitest/pywebsocket/mod_pywebsocket/handshake/hybi00.py
+++ b/testing/mochitest/pywebsocket/mod_pywebsocket/handshake/hybi00.py
@@ -23,17 +23,22 @@
 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
-"""WebSocket initial handshake hander for HyBi 00 protocol."""
+"""This file provides the opening handshake processor for the WebSocket
+protocol version HyBi 00.
+
+Specification:
+http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00
+"""
 
 
 # Note: request.connection.write/read are used in this module, even though
 # mod_python document says that they should be used only in connection
 # handlers. Unfortunately, we have no other options. For example,
 # request.write/read are not suitable because they don't allow direct raw bytes
 # writing/reading.
 
@@ -56,17 +61,18 @@ from mod_pywebsocket.handshake._base imp
 _MANDATORY_HEADERS = [
     # key, expected value or None
     [common.UPGRADE_HEADER, common.WEBSOCKET_UPGRADE_TYPE_HIXIE75],
     [common.CONNECTION_HEADER, common.UPGRADE_CONNECTION_TYPE],
 ]
 
 
 class Handshaker(object):
-    """This class performs WebSocket handshake."""
+    """Opening handshake processor for the WebSocket protocol version HyBi 00.
+    """
 
     def __init__(self, request, dispatcher):
         """Construct an instance.
 
         Args:
             request: mod_python request.
             dispatcher: Dispatcher (dispatch.Dispatcher).
 
@@ -134,17 +140,17 @@ class Handshaker(object):
     def _set_protocol_version(self):
         # |Sec-WebSocket-Draft|
         draft = self._request.headers_in.get(common.SEC_WEBSOCKET_DRAFT_HEADER)
         if draft is not None and draft != '0':
             raise HandshakeException('Illegal value for %s: %s' %
                                      (common.SEC_WEBSOCKET_DRAFT_HEADER,
                                       draft))
 
-        self._logger.debug('IETF HyBi 00 protocol')
+        self._logger.debug('Protocol version is HyBi 00')
         self._request.ws_version = common.VERSION_HYBI00
         self._request.ws_stream = StreamHixie75(self._request, True)
 
     def _set_challenge_response(self):
         # 5.2 4-8.
         self._request.ws_challenge = self._get_challenge()
         # 5.2 9. let /response/ be the MD5 finterprint of /challenge/
         self._request.ws_challenge_md5 = util.md5_hash(
--- a/testing/mochitest/pywebsocket/mod_pywebsocket/msgutil.py
+++ b/testing/mochitest/pywebsocket/mod_pywebsocket/msgutil.py
@@ -77,17 +77,25 @@ def send_message(request, message, end=T
 
 def receive_message(request):
     """Receive a WebSocket frame and return its payload as a text in
     unicode or a binary in str.
 
     Args:
         request: mod_python request.
     Raises:
-        BadOperationException: when client already terminated.
+        InvalidFrameException:     when client send invalid frame.
+        UnsupportedFrameException: when client send unsupported frame e.g. some
+                                   of reserved bit is set but no extension can
+                                   recognize it.
+        InvalidUTF8Exception:      when client send a text frame containing any
+                                   invalid UTF-8 string.
+        ConnectionTerminatedException: when the connection is closed
+                                   unexpectedly.
+        BadOperationException:     when client already terminated.
     """
     return request.ws_stream.receive_message()
 
 
 def send_ping(request, body=''):
     request.ws_stream.send_ping(body)
 
 
--- a/testing/mochitest/pywebsocket/mod_pywebsocket/util.py
+++ b/testing/mochitest/pywebsocket/mod_pywebsocket/util.py
@@ -172,19 +172,26 @@ class RepeatedXorMasker(object):
     def __init__(self, mask):
         self._mask = map(ord, mask)
         self._mask_size = len(self._mask)
         self._count = 0
 
     def mask(self, s):
         result = array.array('B')
         result.fromstring(s)
+        # Use temporary local variables to eliminate the cost to access
+        # attributes
+        count = self._count
+        mask = self._mask
+        mask_size = self._mask_size
         for i in xrange(len(result)):
-            result[i] ^= self._mask[self._count]
-            self._count = (self._count + 1) % self._mask_size
+            result[i] ^= mask[count]
+            count = (count + 1) % mask_size
+        self._count = count
+
         return result.tostring()
 
 
 class DeflateRequest(object):
     """A wrapper class for request object to intercept send and recv to perform
     deflate compression and decompression transparently.
     """
 
--- a/testing/mochitest/pywebsocket/standalone.py
+++ b/testing/mochitest/pywebsocket/standalone.py
@@ -27,16 +27,18 @@
 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 """Standalone WebSocket server.
 
+BASIC USAGE
+
 Use this server to run mod_pywebsocket without Apache HTTP Server.
 
 Usage:
     python standalone.py [-p <ws_port>] [-w <websock_handlers>]
                          [-s <scan_dir>]
                          [-d <document_root>]
                          [-m <websock_handlers_map_file>]
                          ... for other options, see _main below ...
@@ -47,48 +49,81 @@ Usage:
 
 <websock_handlers> is the path to the root directory of WebSocket handlers.
 See __init__.py for details of <websock_handlers> and how to write WebSocket
 handlers. If this path is relative, <document_root> is used as the base.
 
 <scan_dir> is a path under the root directory. If specified, only the
 handlers under scan_dir are scanned. This is useful in saving scan time.
 
-Note:
+
+CONFIGURATION FILE
+
+You can also write a configuration file and use it by specifying the path to
+the configuration file by --config option. Please write a configuration file
+following the documentation of the Python ConfigParser library. Name of each
+entry must be the long version argument name. E.g. to set log level to debug,
+add the following line:
+
+log_level=debug
+
+For options which doesn't take value, please add some fake value. E.g. for
+--tls option, add the following line:
+
+tls=True
+
+Note that tls will be enabled even if you write tls=False as the value part is
+fake.
+
+When both a command line argument and a configuration file entry are set for
+the same configuration item, the command line value will override one in the
+configuration file.
+
+
+THREADING
+
 This server is derived from SocketServer.ThreadingMixIn. Hence a thread is
 used for each request.
 
-SECURITY WARNING: This uses CGIHTTPServer and CGIHTTPServer is not secure.
+
+SECURITY WARNING
+
+This uses CGIHTTPServer and CGIHTTPServer is not secure.
 It may execute arbitrary Python code or external programs. It should not be
 used outside a firewall.
 """
 
 import BaseHTTPServer
 import CGIHTTPServer
 import SimpleHTTPServer
 import SocketServer
+import ConfigParser
 import httplib
 import logging
 import logging.handlers
 import optparse
 import os
 import re
 import select
 import socket
 import sys
 import threading
 import time
 
-
+_HAS_SSL = False
 _HAS_OPEN_SSL = False
 try:
-    import OpenSSL.SSL
-    _HAS_OPEN_SSL = True
+    import ssl
+    _HAS_SSL = True
 except ImportError:
-    pass
+    try:
+        import OpenSSL.SSL
+        _HAS_OPEN_SSL = True
+    except ImportError:
+        pass
 
 from mod_pywebsocket import common
 from mod_pywebsocket import dispatch
 from mod_pywebsocket import handshake
 from mod_pywebsocket import http_header_util
 from mod_pywebsocket import memorizingfile
 from mod_pywebsocket import util
 
@@ -188,16 +223,38 @@ class _StandaloneRequest(object):
         raw_socket = self._request_handler.connection
         drained_data = util.drain_received_data(raw_socket)
 
         if drained_data:
             self._logger.debug(
                 'Drained data following close frame: %r', drained_data)
 
 
+class _StandaloneSSLConnection(object):
+    """A wrapper class for OpenSSL.SSL.Connection to provide makefile method
+    which is not supported by the class.
+    """
+
+    def __init__(self, connection):
+        self._connection = connection
+
+    def __getattribute__(self, name):
+        if name in ('_connection', 'makefile'):
+            return object.__getattribute__(self, name)
+        return self._connection.__getattribute__(name)
+
+    def __setattr__(self, name, value):
+        if name in ('_connection', 'makefile'):
+            return object.__setattr__(self, name, value)
+        return self._connection.__setattr__(name, value)
+
+    def makefile(self, mode='r', bufsize=-1):
+        return socket._fileobject(self._connection, mode, bufsize)
+
+
 class WebSocketServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer):
     """HTTPServer specialized for WebSocket."""
 
     # Overrides SocketServer.ThreadingMixIn.daemon_threads
     daemon_threads = True
     # Overrides BaseHTTPServer.HTTPServer.allow_reuse_address
     allow_reuse_address = True
 
@@ -248,22 +305,28 @@ class WebSocketServer(SocketServer.Threa
             self._logger.info('Create socket on: %r', addrinfo)
             family, socktype, proto, canonname, sockaddr = addrinfo
             try:
                 socket_ = socket.socket(family, socktype)
             except Exception, e:
                 self._logger.info('Skip by failure: %r', e)
                 continue
             if self.websocket_server_options.use_tls:
-                ctx = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD)
-                ctx.use_privatekey_file(
-                    self.websocket_server_options.private_key)
-                ctx.use_certificate_file(
-                    self.websocket_server_options.certificate)
-                socket_ = OpenSSL.SSL.Connection(ctx, socket_)
+                if _HAS_SSL:
+                    socket_ = ssl.wrap_socket(socket_,
+                        keyfile=self.websocket_server_options.private_key,
+                        certfile=self.websocket_server_options.certificate,
+                        ssl_version=ssl.PROTOCOL_SSLv23)
+                if _HAS_OPEN_SSL:
+                    ctx = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD)
+                    ctx.use_privatekey_file(
+                        self.websocket_server_options.private_key)
+                    ctx.use_certificate_file(
+                        self.websocket_server_options.certificate)
+                    socket_ = OpenSSL.SSL.Connection(ctx, socket_)
             self._sockets.append((socket_, addrinfo))
 
     def server_bind(self):
         """Override SocketServer.TCPServer.server_bind to enable multiple
         sockets bind.
         """
 
         failed_sockets = []
@@ -323,16 +386,28 @@ class WebSocketServer(SocketServer.Threa
         """Override SocketServer.handle_error."""
 
         self._logger.error(
             'Exception in processing request from: %r\n%s',
             client_address,
             util.get_stack_trace())
         # Note: client_address is a tuple.
 
+    def get_request(self):
+        """Override TCPServer.get_request to wrap OpenSSL.SSL.Connection
+        object with _StandaloneSSLConnection to provide makefile method. We
+        cannot substitute OpenSSL.SSL.Connection.makefile since it's readonly
+        attribute.
+        """
+
+        accepted_socket, client_address = self.socket.accept()
+        if self.websocket_server_options.use_tls and _HAS_OPEN_SSL:
+            accepted_socket = _StandaloneSSLConnection(accepted_socket)
+        return accepted_socket, client_address
+
     def serve_forever(self, poll_interval=0.5):
         """Override SocketServer.BaseServer.serve_forever."""
 
         self.__ws_serving = True
         self.__ws_is_shut_down.clear()
         handle_request = self.handle_request
         if hasattr(self, '_handle_request_noblock'):
             handle_request = self._handle_request_noblock
@@ -408,17 +483,19 @@ class WebSocketRequestHandler(CGIHTTPSer
         """
 
         # We hook parse_request method, but also call the original
         # CGIHTTPRequestHandler.parse_request since when we return False,
         # CGIHTTPRequestHandler.handle_one_request continues processing and
         # it needs variables set by CGIHTTPRequestHandler.parse_request.
         #
         # Variables set by this method will be also used by WebSocket request
-        # handling. See _StandaloneRequest.get_request, etc.
+        # handling (self.path, self.command, self.requestline, etc. See also
+        # how _StandaloneRequest's members are implemented using these
+        # attributes).
         if not CGIHTTPServer.CGIHTTPRequestHandler.parse_request(self):
             return False
         host, port, resource = http_header_util.parse_uri(self.path)
         if resource is None:
             self._logger.info('Invalid URI: %r', self.path)
             self._logger.info('Fallback to CGIHTTPRequestHandler')
             return True
         server_options = self.server.websocket_server_options
@@ -562,16 +639,21 @@ def _alias_handlers(dispatcher, websock_
                 logging.error(str(e))
     finally:
         fp.close()
 
 
 def _build_option_parser():
     parser = optparse.OptionParser()
 
+    parser.add_option('--config', dest='config_file', type='string',
+                      default=None,
+                      help=('Path to configuration file. See the file comment '
+                            'at the top of this file for the configuration '
+                            'file format'))
     parser.add_option('-H', '--server-host', '--server_host',
                       dest='server_host',
                       default='',
                       help='server hostname to listen to')
     parser.add_option('-V', '--validation-host', '--validation_host',
                       dest='validation_host',
                       default=None,
                       help='server hostname to validate in absolute path.')
@@ -671,24 +753,56 @@ class ThreadMonitor(threading.Thread):
                 thread_name_list.append(thread.name)
             self._logger.info(
                 "%d active threads: %s",
                 threading.active_count(),
                 ', '.join(thread_name_list))
             time.sleep(self._interval_in_sec)
 
 
-def _main(args=None):
+def _parse_args_and_config(args):
     parser = _build_option_parser()
 
-    options, args = parser.parse_args(args=args)
-    if args:
-        logging.critical('Unrecognized positional arguments: %r', args)
+    # First, parse options without configuration file.
+    temporary_options, temporary_args = parser.parse_args(args=args)
+    if temporary_args:
+        logging.critical(
+            'Unrecognized positional arguments: %r', temporary_args)
         sys.exit(1)
 
+    if temporary_options.config_file:
+        try:
+            config_fp = open(temporary_options.config_file, 'r')
+        except IOError, e:
+            logging.critical(
+                'Failed to open configuration file %r: %r',
+                temporary_options.config_file,
+                e)
+            sys.exit(1)
+
+        config_parser = ConfigParser.SafeConfigParser()
+        config_parser.readfp(config_fp)
+        config_fp.close()
+
+        args_from_config = []
+        for name, value in config_parser.items('pywebsocket'):
+            args_from_config.append('--' + name)
+            args_from_config.append(value)
+        if args is None:
+            args = args_from_config
+        else:
+            args = args_from_config + args
+        return parser.parse_args(args=args)
+    else:
+        return temporary_options, temporary_args
+
+
+def _main(args=None):
+    options, args = _parse_args_and_config(args=args)
+
     os.chdir(options.document_root)
 
     _configure_logging(options)
 
     # TODO(tyoshino): Clean up initialization of CGI related values. Move some
     # of code here to WebSocketRequestHandler class if it's better.
     options.cgi_directories = []
     options.is_executable_method = None
@@ -705,18 +819,18 @@ def _main(args=None):
             util.wrap_popen3_for_win(cygwin_path)
 
             def __check_script(scriptpath):
                 return util.get_script_interp(scriptpath, cygwin_path)
 
             options.is_executable_method = __check_script
 
     if options.use_tls:
-        if not _HAS_OPEN_SSL:
-            logging.critical('To use TLS, install pyOpenSSL.')
+        if not (_HAS_SSL or _HAS_OPEN_SSL):
+            logging.critical('TLS support requires ssl or pyOpenSSL.')
             sys.exit(1)
         if not options.private_key or not options.certificate:
             logging.critical(
                     'To use TLS, specify private_key and certificate.')
             sys.exit(1)
 
     if not options.scan_dir:
         options.scan_dir = options.websock_handlers
@@ -745,12 +859,12 @@ def _main(args=None):
         server.serve_forever()
     except Exception, e:
         logging.critical('mod_pywebsocket: %s' % e)
         logging.critical('mod_pywebsocket: %s' % util.get_stack_trace())
         sys.exit(1)
 
 
 if __name__ == '__main__':
-    _main()
+    _main(sys.argv[1:])
 
 
 # vi:sts=4 sw=4 et