Bug 752776 - Upgrade pywebsocket to v631. r=mcmanus, a=akeybl
authorJason Duell <jduell.mcbugs@gmail.com>
Mon, 21 May 2012 19:48:06 -0700
changeset 95819 031abdd1b0a03f3df6aba5d8b6d0b04abe6c2bdc
parent 95818 7cee3454847170b9c7417de9405f08b3fcb41350
child 95820 b6a7abca23b5d39f256740ab5e8b203260522504
push id886
push userlsblakk@mozilla.com
push dateMon, 04 Jun 2012 19:57:52 +0000
treeherdermozilla-beta@bbd8d5efd6d1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmcmanus, akeybl
bugs752776
milestone14.0a2
Bug 752776 - Upgrade pywebsocket to v631. r=mcmanus, a=akeybl
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