Merge appache_fix
http://code.google.com/p/pywebsocket/issue/detail?id=51
diff --git a/src/mod_pywebsocket/__init__.py b/src/mod_pywebsocket/__init__.py
index 05e80e8..d947128 100755
--- a/src/mod_pywebsocket/__init__.py
+++ b/src/mod_pywebsocket/__init__.py
@@ -53,12 +53,18 @@
To limit the search for Web Socket handlers to a directory <scan_dir>
under <websock_handlers>, configure as follows:
-
+
PythonOption mod_pywebsocket.handler_scan <scan_dir>
-
+
<scan_dir> is useful in saving scan time when <websock_handlers>
contains many non-Web Socket handler files.
+ If you want to support old handshake based on
+ draft-hixie-thewebsocketprotocol-75:
+
+ PythonOption mod_pywebsocket.allow_draft75 On
+
+
Example snippet of httpd.conf:
(mod_pywebsocket is in /websock_lib, Web Socket handlers are in
/websock_handlers, port is 80 for ws, 443 for wss.)
diff --git a/src/mod_pywebsocket/handshake.py b/src/mod_pywebsocket/handshake.py
deleted file mode 100755
index b86278e..0000000
--- a/src/mod_pywebsocket/handshake.py
+++ /dev/null
@@ -1,220 +0,0 @@
-# Copyright 2009, 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.
-# * Redistributions in binary form must reproduce the above
-# copyright notice, this list of conditions and the following disclaimer
-# in the documentation and/or other materials provided with the
-# distribution.
-# * Neither the name of Google Inc. nor the names of its
-# contributors may be used to endorse or promote products derived from
-# this software without specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-# 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.
-
-
-"""Web Socket handshaking.
-
-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.
-"""
-
-
-import re
-
-import util
-
-
-_DEFAULT_WEB_SOCKET_PORT = 80
-_DEFAULT_WEB_SOCKET_SECURE_PORT = 443
-_WEB_SOCKET_SCHEME = 'ws'
-_WEB_SOCKET_SECURE_SCHEME = 'wss'
-
-_MANDATORY_HEADERS = [
- # key, expected value or None
- ['Upgrade', 'WebSocket'],
- ['Connection', 'Upgrade'],
- ['Host', None],
- ['Origin', None],
-]
-
-_FIRST_FIVE_LINES = map(re.compile, [
- r'^GET /[\S]* HTTP/1.1\r\n$',
- r'^Upgrade: WebSocket\r\n$',
- r'^Connection: Upgrade\r\n$',
- r'^Host: [\S]+\r\n$',
- r'^Origin: [\S]+\r\n$',
-])
-
-_SIXTH_AND_LATER = re.compile(
- r'^'
- r'(WebSocket-Protocol: [\x20-\x7e]+\r\n)?'
- r'(Cookie: [^\r]*\r\n)*'
- r'(Cookie2: [^\r]*\r\n)?'
- r'(Cookie: [^\r]*\r\n)*'
- r'\r\n')
-
-
-def _default_port(is_secure):
- if is_secure:
- return _DEFAULT_WEB_SOCKET_SECURE_PORT
- else:
- return _DEFAULT_WEB_SOCKET_PORT
-
-
-class HandshakeError(Exception):
- """Exception in Web Socket Handshake."""
-
- pass
-
-
-def _validate_protocol(protocol):
- """Validate WebSocket-Protocol string."""
-
- if not protocol:
- raise HandshakeError('Invalid WebSocket-Protocol: empty')
- for c in protocol:
- if not 0x20 <= ord(c) <= 0x7e:
- raise HandshakeError('Illegal character in protocol: %r' % c)
-
-
-class Handshaker(object):
- """This class performs Web Socket handshake."""
-
- def __init__(self, request, dispatcher, strict=False):
- """Construct an instance.
-
- Args:
- request: mod_python request.
- dispatcher: Dispatcher (dispatch.Dispatcher).
- strict: Strictly check handshake request. Default: False.
- If True, request.connection must provide get_memorized_lines
- method.
-
- Handshaker will add attributes such as ws_resource in performing
- handshake.
- """
-
- self._request = request
- self._dispatcher = dispatcher
- self._strict = strict
-
- def do_handshake(self):
- """Perform Web Socket Handshake."""
-
- self._check_header_lines()
- self._set_resource()
- self._set_origin()
- self._set_location()
- self._set_protocol()
- self._dispatcher.do_extra_handshake(self._request)
- self._send_handshake()
-
- def _set_resource(self):
- self._request.ws_resource = self._request.uri
-
- def _set_origin(self):
- self._request.ws_origin = self._request.headers_in['Origin']
-
- def _set_location(self):
- location_parts = []
- if self._request.is_https():
- location_parts.append(_WEB_SOCKET_SECURE_SCHEME)
- else:
- location_parts.append(_WEB_SOCKET_SCHEME)
- location_parts.append('://')
- host, port = self._parse_host_header()
- connection_port = self._request.connection.local_addr[1]
- if port != connection_port:
- raise HandshakeError('Header/connection port mismatch: %d/%d' %
- (port, connection_port))
- location_parts.append(host)
- if (port != _default_port(self._request.is_https())):
- location_parts.append(':')
- location_parts.append(str(port))
- location_parts.append(self._request.uri)
- self._request.ws_location = ''.join(location_parts)
-
- def _parse_host_header(self):
- fields = self._request.headers_in['Host'].split(':', 1)
- if len(fields) == 1:
- return fields[0], _default_port(self._request.is_https())
- try:
- return fields[0], int(fields[1])
- except ValueError, e:
- raise HandshakeError('Invalid port number format: %r' % e)
-
- def _set_protocol(self):
- protocol = self._request.headers_in.get('WebSocket-Protocol')
- if protocol is not None:
- _validate_protocol(protocol)
- self._request.ws_protocol = protocol
-
- def _send_handshake(self):
- self._request.connection.write(
- 'HTTP/1.1 101 Web Socket Protocol Handshake\r\n')
- self._request.connection.write('Upgrade: WebSocket\r\n')
- self._request.connection.write('Connection: Upgrade\r\n')
- self._request.connection.write('WebSocket-Origin: ')
- self._request.connection.write(self._request.ws_origin)
- self._request.connection.write('\r\n')
- self._request.connection.write('WebSocket-Location: ')
- self._request.connection.write(self._request.ws_location)
- self._request.connection.write('\r\n')
- if self._request.ws_protocol:
- self._request.connection.write('WebSocket-Protocol: ')
- self._request.connection.write(self._request.ws_protocol)
- self._request.connection.write('\r\n')
- self._request.connection.write('\r\n')
-
- def _check_header_lines(self):
- for key, expected_value in _MANDATORY_HEADERS:
- actual_value = self._request.headers_in.get(key)
- if not actual_value:
- raise HandshakeError('Header %s is not defined' % key)
- if expected_value:
- if actual_value != expected_value:
- raise HandshakeError('Illegal value for header %s: %s' %
- (key, actual_value))
- if self._strict:
- try:
- lines = self._request.connection.get_memorized_lines()
- except AttributeError, e:
- util.prepend_message_to_exception(
- 'Strict handshake is specified but the connection '
- 'doesn\'t provide get_memorized_lines()', e)
- raise
- self._check_first_lines(lines)
-
- def _check_first_lines(self, lines):
- if len(lines) < len(_FIRST_FIVE_LINES):
- raise HandshakeError('Too few header lines: %d' % len(lines))
- for line, regexp in zip(lines, _FIRST_FIVE_LINES):
- if not regexp.search(line):
- raise HandshakeError('Unexpected header: %r doesn\'t match %r'
- % (line, regexp.pattern))
- sixth_and_later = ''.join(lines[5:])
- if not _SIXTH_AND_LATER.search(sixth_and_later):
- raise HandshakeError('Unexpected header: %r doesn\'t match %r'
- % (sixth_and_later,
- _SIXTH_AND_LATER.pattern))
-
-
-# vi:sts=4 sw=4 et
diff --git a/src/mod_pywebsocket/handshake/__init__.py b/src/mod_pywebsocket/handshake/__init__.py
index 0c058d1..4c4e41a 100755
--- a/src/mod_pywebsocket/handshake/__init__.py
+++ b/src/mod_pywebsocket/handshake/__init__.py
@@ -68,6 +68,7 @@
handshake.
"""
+ self._logger = logging.getLogger("mod_pywebsocket.handshake")
self._request = request
self._dispatcher = dispatcher
self._strict = strict
@@ -83,9 +84,9 @@
try:
self._handshaker.do_handshake()
except HandshakeError, e:
- logging.info('Handshake error: %s' % e)
+ self._logger.error('Handshake error: %s' % e)
if self._fallbackHandshaker:
- logging.info('fallback to old protocol')
+ self._logger.warning('fallback to old protocol')
self._fallbackHandshaker.do_handshake()
return
raise e
diff --git a/src/mod_pywebsocket/handshake/handshake.py b/src/mod_pywebsocket/handshake/handshake.py
index 3985717..cec80c0 100755
--- a/src/mod_pywebsocket/handshake/handshake.py
+++ b/src/mod_pywebsocket/handshake/handshake.py
@@ -68,6 +68,7 @@
handshake.
"""
+ self._logger = logging.getLogger("mod_pywebsocket.handshake")
self._request = request
self._dispatcher = dispatcher
@@ -119,8 +120,10 @@
self._request.ws_challenge = self._get_challenge()
self._request.ws_challenge_md5 = md5(
self._request.ws_challenge).digest()
- logging.debug("challenge: %s" % _hexify(self._request.ws_challenge))
- logging.debug("response: %s" % _hexify(self._request.ws_challenge_md5))
+ self._logger.debug("challenge: %s" % _hexify(
+ self._request.ws_challenge))
+ self._logger.debug("response: %s" % _hexify(
+ self._request.ws_challenge_md5))
def _get_key_value(self, key_field):
key_field = self._request.headers_in.get(key_field)
@@ -131,7 +134,7 @@
digit = int(re.sub("\\D", "", key_field))
# number of spaces characters in the value.
num_spaces = re.subn(" ", "", key_field)[1]
- logging.debug("%s: %d / %d => %d" % (
+ self._logger.debug("%s: %d / %d => %d" % (
key_field, digit, num_spaces, digit / num_spaces))
return digit / num_spaces
except:
diff --git a/src/mod_pywebsocket/headerparserhandler.py b/src/mod_pywebsocket/headerparserhandler.py
index 124b9f1..9d40ab6 100755
--- a/src/mod_pywebsocket/headerparserhandler.py
+++ b/src/mod_pywebsocket/headerparserhandler.py
@@ -39,6 +39,7 @@
import dispatch
import handshake
+import logging
import util
@@ -50,6 +51,35 @@
# The default is the root directory.
_PYOPT_HANDLER_SCAN = 'mod_pywebsocket.handler_scan'
+# PythonOption to specify to allow draft75 handshake.
+# The default is None (Off)
+_PYOPT_ALLOW_DRAFT75 = 'mod_pywebsocket.allow_draft75'
+
+
+class ApacheLogHandler(logging.Handler):
+ """Wrapper logging.Handler to emit log message to apache's error.log"""
+ _LEVELS = {
+ logging.DEBUG: apache.APLOG_DEBUG,
+ logging.INFO: apache.APLOG_INFO,
+ logging.WARNING: apache.APLOG_WARNING,
+ logging.ERROR: apache.APLOG_ERR,
+ logging.CRITICAL: apache.APLOG_CRIT,
+ }
+ def __init__(self, request=None):
+ logging.Handler.__init__(self)
+ self.log_error = apache.log_error
+ if request is not None:
+ self.log_error = request.log_error
+
+ def emit(self, record):
+ apache_level = apache.APLOG_DEBUG
+ if record.levelno in ApacheLogHandler._LEVELS:
+ apache_level = ApacheLogHandler._LEVELS[record.levelno]
+ self.log_error(record.getMessage(), apache_level)
+
+
+logging.getLogger("mod_pywebsocket").addHandler(ApacheLogHandler())
+
def _create_dispatcher():
_HANDLER_ROOT = apache.main_server.get_options().get(
@@ -80,11 +110,20 @@
"""
try:
- handshaker = handshake.Handshaker(request, _dispatcher)
+ allowDraft75 = apache.main_server.get_options().get(
+ _PYOPT_ALLOW_DRAFT75, None)
+ handshaker = handshake.Handshaker(request, _dispatcher,
+ allowDraft75=allowDraft75)
handshaker.do_handshake()
request.log_error('mod_pywebsocket: resource: %r' % request.ws_resource,
apache.APLOG_DEBUG)
- _dispatcher.transfer_data(request)
+ try:
+ _dispatcher.transfer_data(request)
+ except Exception, e:
+ # Catch exception in transfer_data.
+ # In this case, handshake has been successful, so just log the
+ # exception and return apache.DONE
+ request.log_error('mod_pywebsocket: %s' % e, apache.APLOG_WARNING)
except handshake.HandshakeError, e:
# Handshake for ws/wss failed.
# But the request can be valid http/https request.
diff --git a/src/setup.py b/src/setup.py
index a34a83b..80e6895 100755
--- a/src/setup.py
+++ b/src/setup.py
@@ -54,7 +54,7 @@
'See mod_pywebsocket/__init__.py for more detail.'),
license='See COPYING',
name=_PACKAGE_NAME,
- packages=[_PACKAGE_NAME],
+ packages=[_PACKAGE_NAME, _PACKAGE_NAME + '.handshake'],
url='http://code.google.com/p/pywebsocket/',
version='0.4.9.2',
)