bug 640003 websockets - update incorporated pywebsockets to support -07 r=biesi

This commit is contained in:
Patrick McManus 2011-05-21 21:27:52 -04:00
Родитель 6ce0ea820e
Коммит 4ff34a4169
21 изменённых файлов: 2573 добавлений и 523 удалений

Просмотреть файл

@ -108,18 +108,25 @@ _PYWEBSOCKET_FILES = \
_MOD_PYWEBSOCKET_FILES = \
pywebsocket/mod_pywebsocket/__init__.py \
pywebsocket/mod_pywebsocket/common.py \
pywebsocket/mod_pywebsocket/dispatch.py \
pywebsocket/mod_pywebsocket/util.py \
pywebsocket/mod_pywebsocket/msgutil.py \
pywebsocket/mod_pywebsocket/memorizingfile.py \
pywebsocket/mod_pywebsocket/headerparserhandler.py \
pywebsocket/mod_pywebsocket/memorizingfile.py \
pywebsocket/mod_pywebsocket/util.py \
pywebsocket/mod_pywebsocket/stream.py \
pywebsocket/mod_pywebsocket/_stream_hixie75.py \
pywebsocket/mod_pywebsocket/msgutil.py \
pywebsocket/mod_pywebsocket/_stream_hybi06.py \
pywebsocket/mod_pywebsocket/standalone.py \
pywebsocket/mod_pywebsocket/_stream_base.py \
$(NULL)
_HANDSHAKE_FILES = \
pywebsocket/mod_pywebsocket/handshake/__init__.py \
pywebsocket/mod_pywebsocket/handshake/hybi00.py \
pywebsocket/mod_pywebsocket/handshake/_base.py \
pywebsocket/mod_pywebsocket/handshake/draft75.py \
pywebsocket/mod_pywebsocket/handshake/handshake.py \
pywebsocket/mod_pywebsocket/handshake/hybi06.py \
$(NULL)
_DEST_DIR = $(DEPTH)/_tests/$(relativesrcdir)

Просмотреть файл

@ -1 +1,29 @@
This is mod_pywebsocket 0.5, from http://code.google.com/p/pywebsocket/
mod_pywebsocket http://pywebsocket.googlecode.com/svn
version 470
supporting ietf-07
includes the following minor patch::
diff --git a/testing/mochitest/pywebsocket/mod_pywebsocket/dispatch.py b/testing/mochitest/pywebsocket/mod_pywebsocket/dispatch.py
--- a/testing/mochitest/pywebsocket/mod_pywebsocket/dispatch.py
+++ b/testing/mochitest/pywebsocket/mod_pywebsocket/dispatch.py
@@ -60,17 +60,18 @@ def _normalize_path(path):
path: the path to normalize.
Path is converted to the absolute path.
The input path can use either '\\' or '/' as the separator.
The normalized path always uses '/' regardless of the platform.
"""
path = path.replace('\\', os.path.sep)
- path = os.path.realpath(path)
+ # do not normalize away symlinks in mochitest
+ # path = os.path.realpath(path)
path = path.replace('\\', '/')
return path
def _create_path_to_resource_converter(base_dir):
base_dir = _normalize_path(base_dir)
base_len = len(base_dir)

Просмотреть файл

@ -28,11 +28,12 @@
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""Web Socket extension for Apache HTTP Server.
"""WebSocket extension for Apache HTTP Server.
mod_pywebsocket is a Web Socket extension for Apache HTTP Server
mod_pywebsocket is a WebSocket extension for Apache HTTP Server
intended for testing or experimental purposes. mod_python is required.
Installation:
0. Prepare an Apache HTTP Server for which mod_python is enabled.
@ -46,27 +47,26 @@ Installation:
PythonPath "sys.path+['<websock_lib>']"
Always specify the following. <websock_handlers> is the directory where
user-written Web Socket handlers are placed.
user-written WebSocket handlers are placed.
PythonOption mod_pywebsocket.handler_root <websock_handlers>
PythonHeaderParserHandler mod_pywebsocket.headerparserhandler
To limit the search for Web Socket handlers to a directory <scan_dir>
To limit the search for WebSocket 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.
contains many non-WebSocket 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
(mod_pywebsocket is in /websock_lib, WebSocket handlers are in
/websock_handlers, port is 80 for ws, 443 for wss.)
<IfModule python_module>
@ -75,9 +75,17 @@ Installation:
PythonHeaderParserHandler mod_pywebsocket.headerparserhandler
</IfModule>
Writing Web Socket handlers:
2. Tune Apache parameters for serving WebSocket. We'd like to note that at
least TimeOut directive from core features and RequestReadTimeout directive
from mod_reqtimeout should be modified not to kill connections in only a few
seconds of idle time.
When a Web Socket request comes in, the resource name
3. Verify installation. You can use example/console.html to poke the server.
Writing WebSocket handlers:
When a WebSocket request comes in, the resource name
specified in the handshake is considered as if it is a file path under
<websock_handlers> and the handler defined in
<websock_handlers>/<resource_name>_wsh.py is invoked.
@ -85,7 +93,7 @@ specified in the handshake is considered as if it is a file path under
For example, if the resource name is /example/chat, the handler defined in
<websock_handlers>/example/chat_wsh.py is invoked.
A Web Socket handler is composed of the following two functions:
A WebSocket handler is composed of the following two functions:
web_socket_do_extra_handshake(request)
web_socket_transfer_data(request)
@ -94,16 +102,65 @@ where:
request: mod_python request.
web_socket_do_extra_handshake is called during the handshake after the
headers are successfully parsed and Web Socket properties (ws_location,
ws_origin, ws_protocol, and ws_resource) are added to request. A handler
headers are successfully parsed and WebSocket properties (ws_location,
ws_origin, and ws_resource) are added to request. A handler
can reject the request by raising an exception.
A request object has the following properties that you can use during the extra
handshake (web_socket_do_extra_handshake):
- ws_resource
- ws_origin
- ws_version
- ws_location (Hixie 75 and HyBi 00 only)
- ws_extensions (Hybi 06 and later)
- ws_deflate (HyBi 06 and later)
- ws_protocol
- ws_requested_protocols (HyBi 06 and later)
The last two are a bit tricky.
For HyBi 06 and later, ws_protocol is always set to None when
web_socket_do_extra_handshake is called. If ws_requested_protocols is not
None, you must choose one subprotocol from this list and set it to ws_protocol.
For Hixie 75 and HyBi 00, when web_socket_do_extra_handshake is called,
ws_protocol is set to the value given by the client in Sec-WebSocket-Protocol
(WebSocket-Protocol for Hixie 75) header or None if such header was not found
in the opening handshake request. Finish extra handshake with ws_protocol
untouched to accept the request subprotocol. Then, Sec-WebSocket-Protocol
(or WebSocket-Protocol) header will be sent to the client in response with the
same value as requested. Raise an exception in web_socket_do_extra_handshake to
reject the requested subprotocol.
web_socket_transfer_data is called after the handshake completed
successfully. A handler can receive/send messages from/to the client
using request. mod_pywebsocket.msgutil module provides utilities
for data transfer.
A Web Socket handler must be thread-safe if the server (Apache or
You can receive a message by the following statement.
message = request.ws_stream.receive_message()
This call blocks until any complete text frame arrives, and the payload data of
the incoming frame will be stored into message. When you're using IETF HyBi 00
or later protocol, receive_message() will return None on receiving
client-initiated closing handshake. When any error occurs, receive_message()
will raise some exception.
You can send a message by the following statement.
request.ws_stream.send_message(message)
Executing the following statement or just return-ing from
web_socket_transfer_data cause connection close.
request.ws_stream.close_connection()
When you're using IETF HyBi 00 or later protocol, close_connection will wait
for closing handshake acknowledgement coming from the client. When it couldn't
receive a valid acknowledgement, raises an exception.
A WebSocket handler must be thread-safe if the server (Apache or
standalone.py) is configured to use threads.
"""

Просмотреть файл

@ -0,0 +1,151 @@
# Copyright 2011, 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.
"""Base stream class.
"""
# 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.
from mod_pywebsocket import util
# Exceptions
class ConnectionTerminatedException(Exception):
"""This exception will be raised when a connection is terminated
unexpectedly.
"""
pass
class InvalidFrameException(ConnectionTerminatedException):
"""This exception will be raised when we received an invalid frame we
cannot parse.
"""
pass
class BadOperationException(RuntimeError):
"""This exception will be raised when send_message() is called on
server-terminated connection or receive_message() is called on
client-terminated connection.
"""
pass
class UnsupportedFrameException(RuntimeError):
"""This exception will be raised when we receive a frame with flag, opcode
we cannot handle. Handlers can just catch and ignore this exception and
call receive_message() again to continue processing the next frame.
"""
pass
class StreamBase(object):
"""Base stream class."""
def __init__(self, request):
"""Construct an instance.
Args:
request: mod_python request.
"""
self._logger = util.get_class_logger(self)
self._request = request
def _read(self, length):
"""Reads length bytes from connection. In case we catch any exception,
prepends remote address to the exception message and raise again.
Raises:
ConnectionTerminatedException: when read returns empty string.
"""
bytes = self._request.connection.read(length)
if not bytes:
raise ConnectionTerminatedException(
'Receiving %d byte failed. Peer (%r) closed connection' %
(length, (self._request.connection.remote_addr,)))
return bytes
def _write(self, bytes):
"""Writes given bytes to connection. In case we catch any exception,
prepends remote address to the exception message and raise again.
"""
try:
self._request.connection.write(bytes)
except Exception, e:
util.prepend_message_to_exception(
'Failed to send message to %r: ' %
(self._request.connection.remote_addr,),
e)
raise
def receive_bytes(self, length):
"""Receives multiple bytes. Retries read when we couldn't receive the
specified amount.
Raises:
ConnectionTerminatedException: when read returns empty string.
"""
bytes = []
while length > 0:
new_bytes = self._read(length)
bytes.append(new_bytes)
length -= len(new_bytes)
return ''.join(bytes)
def _read_until(self, delim_char):
"""Reads bytes until we encounter delim_char. The result will not
contain delim_char.
Raises:
ConnectionTerminatedException: when read returns empty string.
"""
bytes = []
while True:
ch = self._read(1)
if ch == delim_char:
break
bytes.append(ch)
return ''.join(bytes)
# vi:sts=4 sw=4 et

Просмотреть файл

@ -0,0 +1,218 @@
# Copyright 2011, 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.
"""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.
"""
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."""
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
this option to True.
"""
StreamBase.__init__(self, request)
self._logger = util.get_class_logger(self)
self._enable_closing_handshake = enable_closing_handshake
self._request.client_terminated = False
self._request.server_terminated = False
def send_message(self, message, end=True):
"""Send message.
Args:
message: unicode string to send.
Raises:
BadOperationException: when called on a server-terminated
connection.
"""
if not end:
raise BadOperationException(
'StreamHixie75 doesn\'t support send_message with end=False')
if self._request.server_terminated:
raise BadOperationException(
'Requested send_message after sending out a closing handshake')
self._write(''.join(['\x00', message.encode('utf-8'), '\xff']))
def _read_payload_length_hixie75(self):
"""Reads a length header in a Hixie75 version frame with length.
Raises:
ConnectionTerminatedException: when read returns empty string.
"""
length = 0
while True:
b_str = self._read(1)
b = ord(b_str)
length = length * 128 + (b & 0x7f)
if (b & 0x80) == 0:
break
return length
def receive_message(self):
"""Receive a WebSocket frame and return its payload an unicode string.
Returns:
payload unicode string in a WebSocket frame.
Raises:
ConnectionTerminatedException: when read returns empty
string.
BadOperationException: when called on a client-terminated
connection.
"""
if self._request.client_terminated:
raise BadOperationException(
'Requested receive_message after receiving a closing '
'handshake')
while True:
# Read 1 byte.
# mp_conn.read will block if no bytes are available.
# Timeout is controlled by TimeOut directive of Apache.
frame_type_str = self.receive_bytes(1)
frame_type = ord(frame_type_str)
if (frame_type & 0x80) == 0x80:
# The payload length is specified in the frame.
# Read and discard.
length = self._read_payload_length_hixie75()
if length > 0:
_ = self.receive_bytes(length)
# 5.3 3. 12. if /type/ is 0xFF and /length/ is 0, then set the
# /client terminated/ flag and abort these steps.
if not self._enable_closing_handshake:
continue
if frame_type == 0xFF and length == 0:
self._request.client_terminated = True
if self._request.server_terminated:
self._logger.debug(
'Received ack for server-initiated closing '
'handshake')
return None
self._logger.debug(
'Received client-initiated closing handshake')
self._send_closing_handshake()
self._logger.debug(
'Sent ack for client-initiated closing handshake')
return None
else:
# The payload is delimited with \xff.
bytes = self._read_until('\xff')
# The WebSocket protocol section 4.4 specifies that invalid
# characters must be replaced with U+fffd REPLACEMENT
# CHARACTER.
message = bytes.decode('utf-8', 'replace')
if frame_type == 0x00:
return message
# Discard data of other types.
def _send_closing_handshake(self):
if not self._enable_closing_handshake:
raise BadOperationException(
'Closing handshake is not supported in Hixie 75 protocol')
self._request.server_terminated = True
# 5.3 the server may decide to terminate the WebSocket connection by
# running through the following steps:
# 1. send a 0xFF byte and a 0x00 byte to the client to indicate the
# start of the closing handshake.
self._write('\xff\x00')
def close_connection(self, unused_code='', unused_reason=''):
"""Closes a WebSocket connection.
Raises:
ConnectionTerminatedException: when closing handshake was
not successfull.
"""
if self._request.server_terminated:
self._logger.debug(
'Requested close_connection but server is already terminated')
return
if not self._enable_closing_handshake:
self._request.server_terminated = True
self._logger.debug('Connection closed')
return
self._send_closing_handshake()
self._logger.debug('Sent server-initiated closing handshake')
# TODO(ukai): 2. wait until the /client terminated/ flag has been set,
# or until a server-defined timeout expires.
#
# For now, we expect receiving closing handshake right after sending
# out closing handshake, and if we couldn't receive non-handshake
# frame, we take it as ConnectionTerminatedException.
message = self.receive_message()
if message is not None:
raise ConnectionTerminatedException(
'Didn\'t receive valid ack for closing handshake')
# TODO: 3. close the WebSocket connection.
# note: mod_python Connection (mp_conn) doesn't have close method.
def send_ping(self, body):
raise BadOperationException(
'StreamHixie75 doesn\'t support send_ping')
# vi:sts=4 sw=4 et

Просмотреть файл

@ -0,0 +1,510 @@
# Copyright 2011, 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.
"""Stream class for IETF HyBi 07 WebSocket protocol.
"""
from collections import deque
import os
import struct
from mod_pywebsocket import common
from mod_pywebsocket import util
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
def is_control_opcode(opcode):
return (opcode >> 3) == 1
_NOOP_MASKER = util.NoopMasker()
# Helper functions made public to be used for writing unittests for WebSocket
# clients.
def create_length_header(length, mask):
"""Creates a length header.
Args:
length: Frame length. Must be less than 2^63.
mask: Mask bit. Must be boolean.
Raises:
ValueError: when bad data is given.
"""
if mask:
mask_bit = 1 << 7
else:
mask_bit = 0
if length < 0:
raise ValueError('length must be non negative integer')
elif length <= 125:
return chr(mask_bit | length)
elif length < (1 << 16):
return chr(mask_bit | 126) + struct.pack('!H', length)
elif length < (1 << 63):
return chr(mask_bit | 127) + struct.pack('!Q', length)
else:
raise ValueError('Payload is too big for one frame')
def create_header(opcode, payload_length, fin, rsv1, rsv2, rsv3, mask):
"""Creates a frame header.
Raises:
Exception: when bad data is given.
"""
if opcode < 0 or 0xf < opcode:
raise ValueError('Opcode out of range')
if payload_length < 0 or (1 << 63) <= payload_length:
raise ValueError('payload_length out of range')
if (fin | rsv1 | rsv2 | rsv3) & ~1:
raise ValueError('FIN bit and Reserved bit parameter must be 0 or 1')
header = ''
first_byte = ((fin << 7)
| (rsv1 << 6) | (rsv2 << 5) | (rsv3 << 4)
| opcode)
header += chr(first_byte)
header += create_length_header(payload_length, mask)
return header
def _build_frame(header, body, mask):
if not mask:
return header + body
masking_nonce = os.urandom(4)
masker = util.RepeatedXorMasker(masking_nonce)
return header + masking_nonce + masker.mask(body)
def create_text_frame(message, opcode=common.OPCODE_TEXT, fin=1, mask=False):
"""Creates a simple text frame with no extension, reserved bit."""
encoded_message = message.encode('utf-8')
header = create_header(opcode, len(encoded_message), fin, 0, 0, 0, mask)
return _build_frame(header, encoded_message, mask)
class FragmentedTextFrameBuilder(object):
"""A stateful class to send a message as fragments."""
def __init__(self, mask):
"""Constructs an instance."""
self._mask = mask
self._started = False
def build(self, message, end):
if self._started:
opcode = common.OPCODE_CONTINUATION
else:
opcode = common.OPCODE_TEXT
if end:
self._started = False
fin = 1
else:
self._started = True
fin = 0
return create_text_frame(message, opcode, fin, self._mask)
def create_ping_frame(body, mask=False):
header = create_header(common.OPCODE_PING, len(body), 1, 0, 0, 0, mask)
return _build_frame(header, body, mask)
def create_pong_frame(body, mask=False):
header = create_header(common.OPCODE_PONG, len(body), 1, 0, 0, 0, mask)
return _build_frame(header, body, mask)
def create_close_frame(body, mask=False):
header = create_header(common.OPCODE_CLOSE, len(body), 1, 0, 0, 0, mask)
return _build_frame(header, body, mask)
class StreamOptions(object):
def __init__(self):
self.deflate = False
self.mask_send = False
self.unmask_receive = True
class Stream(StreamBase):
"""Stream of WebSocket messages."""
def __init__(self, request, options):
"""Constructs an instance.
Args:
request: mod_python request.
"""
StreamBase.__init__(self, request)
self._logger = util.get_class_logger(self)
self._options = options
if self._options.deflate:
self._logger.debug('Deflated stream')
self._request = util.DeflateRequest(self._request)
self._request.client_terminated = False
self._request.server_terminated = False
# Holds body of received fragments.
self._received_fragments = []
# Holds the opcode of the first fragment.
self._original_opcode = None
self._writer = FragmentedTextFrameBuilder(self._options.mask_send)
self._ping_queue = deque()
def _receive_frame(self):
"""Receives a frame and return data in the frame as a tuple containing
each header field and payload separately.
Raises:
ConnectionTerminatedException: when read returns empty
string.
InvalidFrameException: when the frame contains invalid data.
"""
received = self.receive_bytes(2)
first_byte = ord(received[0])
fin = (first_byte >> 7) & 1
rsv1 = (first_byte >> 6) & 1
rsv2 = (first_byte >> 5) & 1
rsv3 = (first_byte >> 4) & 1
opcode = first_byte & 0xf
second_byte = ord(received[1])
mask = (second_byte >> 7) & 1
payload_length = second_byte & 0x7f
if (mask == 1) != self._options.unmask_receive:
raise InvalidFrameException(
'Mask bit on the received frame did\'nt match masking '
'configuration for received frames')
if payload_length == 127:
extended_payload_length = self.receive_bytes(8)
payload_length = struct.unpack(
'!Q', extended_payload_length)[0]
if payload_length > 0x7FFFFFFFFFFFFFFF:
raise InvalidFrameException(
'Extended payload length >= 2^63')
elif payload_length == 126:
extended_payload_length = self.receive_bytes(2)
payload_length = struct.unpack(
'!H', extended_payload_length)[0]
if mask == 1:
masking_nonce = self.receive_bytes(4)
masker = util.RepeatedXorMasker(masking_nonce)
else:
masker = _NOOP_MASKER
bytes = masker.mask(self.receive_bytes(payload_length))
return opcode, bytes, fin, rsv1, rsv2, rsv3
def send_message(self, message, end=True):
"""Send message.
Args:
message: unicode string to send.
Raises:
BadOperationException: when called on a server-terminated
connection.
"""
if self._request.server_terminated:
raise BadOperationException(
'Requested send_message after sending out a closing handshake')
self._write(self._writer.build(message, end))
def receive_message(self):
"""Receive a WebSocket frame and return its payload an unicode string.
Returns:
payload unicode string in a WebSocket frame. None iff received
closing handshake.
Raises:
BadOperationException: when called on a client-terminated
connection.
ConnectionTerminatedException: when read returns empty
string.
InvalidFrameException: when the frame contains invalid
data.
UnsupportedFrameException: when the received frame has
flags, opcode we cannot handle. You can ignore this exception
and continue receiving the next frame.
"""
if self._request.client_terminated:
raise BadOperationException(
'Requested receive_message after receiving a closing handshake')
while True:
# mp_conn.read will block if no bytes are available.
# Timeout is controlled by TimeOut directive of Apache.
opcode, bytes, fin, rsv1, rsv2, rsv3 = self._receive_frame()
if rsv1 or rsv2 or rsv3:
raise UnsupportedFrameException(
'Unsupported flag is set (rsv = %d%d%d)' %
(rsv1, rsv2, rsv3))
if opcode == common.OPCODE_CONTINUATION:
if not self._received_fragments:
if fin:
raise InvalidFrameException(
'Received a termination frame but fragmentation '
'not started')
else:
raise InvalidFrameException(
'Received an intermediate frame but '
'fragmentation not started')
if fin:
# End of fragmentation frame
self._received_fragments.append(bytes)
message = ''.join(self._received_fragments)
self._received_fragments = []
else:
# Intermediate frame
self._received_fragments.append(bytes)
continue
else:
if self._received_fragments:
if fin:
raise InvalidFrameException(
'Received an unfragmented frame without '
'terminating existing fragmentation')
else:
raise InvalidFrameException(
'New fragmentation started without terminating '
'existing fragmentation')
if fin:
# Unfragmented frame
self._original_opcode = opcode
message = bytes
if is_control_opcode(opcode) and len(message) > 125:
raise InvalidFrameException(
'Application data size of control frames must be '
'125 bytes or less')
else:
# Start of fragmentation frame
if is_control_opcode(opcode):
raise InvalidFrameException(
'Control frames must not be fragmented')
self._original_opcode = opcode
self._received_fragments.append(bytes)
continue
if self._original_opcode == common.OPCODE_TEXT:
# The WebSocket protocol section 4.4 specifies that invalid
# characters must be replaced with U+fffd REPLACEMENT
# CHARACTER.
return message.decode('utf-8', 'replace')
elif self._original_opcode == common.OPCODE_CLOSE:
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:
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')
if self._request.server_terminated:
self._logger.debug(
'Received ack for server-initiated closing '
'handshake')
return None
self._logger.debug(
'Received client-initiated closing handshake')
self._send_closing_handshake(common.STATUS_NORMAL, '')
self._logger.debug(
'Sent ack for client-initiated closing handshake')
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:
pass
self._send_pong(message)
elif self._original_opcode == common.OPCODE_PONG:
# TODO(tyoshino): Add ping timeout handling.
inflight_pings = deque()
while True:
try:
expected_body = self._ping_queue.popleft()
if expected_body == message:
# inflight_pings contains pings ignored by the
# other peer. Just forget them.
self._logger.debug(
'Ping %r is acked (%d pings were ignored)' %
(expected_body, len(inflight_pings)))
break
else:
inflight_pings.append(expected_body)
except IndexError, e:
# The received pong was unsolicited pong. Keep the
# ping queue as is.
self._ping_queue = inflight_pings
self._logger.debug('Received a unsolicited pong')
break
try:
handler = self._request.on_pong_handler
if handler:
handler(self._request, message)
continue
except AttributeError, e:
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')
frame = create_close_frame(
struct.pack('!H', code) + encoded_reason, self._options.mask_send)
self._request.server_terminated = True
self._write(frame)
def close_connection(self, code=common.STATUS_NORMAL, reason=''):
"""Closes a WebSocket connection."""
if self._request.server_terminated:
self._logger.debug(
'Requested close_connection but server is already terminated')
return
self._send_closing_handshake(code, reason)
self._logger.debug('Sent server-initiated closing handshake')
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
# TODO(ukai): 2. wait until the /client terminated/ flag has been set,
# or until a server-defined timeout expires.
#
# For now, we expect receiving closing handshake right after sending
# out closing handshake.
message = self.receive_message()
if message is not None:
raise ConnectionTerminatedException(
'Didn\'t receive valid ack for closing handshake')
# TODO: 3. close the WebSocket connection.
# note: mod_python Connection (mp_conn) doesn't have close method.
def send_ping(self, body=''):
if len(body) > 125:
raise ValueError(
'Application data size of control frames must be 125 bytes or '
'less')
frame = create_ping_frame(body, self._options.mask_send)
self._write(frame)
self._ping_queue.append(body)
def _send_pong(self, body):
if len(body) > 125:
raise ValueError(
'Application data size of control frames must be 125 bytes or '
'less')
frame = create_pong_frame(body, self._options.mask_send)
self._write(frame)
# vi:sts=4 sw=4 et

Просмотреть файл

@ -0,0 +1,74 @@
# Copyright 2011, 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.
# Constants indicating WebSocket protocol version.
VERSION_HYBI07 = 7
VERSION_HYBI00 = 0
VERSION_HIXIE75 = -1
# Port numbers
DEFAULT_WEB_SOCKET_PORT = 80
DEFAULT_WEB_SOCKET_SECURE_PORT = 443
# Schemes
WEB_SOCKET_SCHEME = 'ws'
WEB_SOCKET_SECURE_SCHEME = 'wss'
# Frame opcodes defined in the spec.
OPCODE_CONTINUATION = 0x0
OPCODE_TEXT = 0x1
OPCODE_BINARY = 0x2
OPCODE_CLOSE = 0x8
OPCODE_PING = 0x9
OPCODE_PONG = 0xa
# UUIDs used by HyBi 07 opening handshake and frame masking.
WEBSOCKET_ACCEPT_UUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
# Opening handshake header names and expected values.
UPGRADE_HEADER = 'Upgrade'
WEBSOCKET_UPGRADE_TYPE = 'websocket'
WEBSOCKET_UPGRADE_TYPE_HIXIE75 = 'WebSocket'
CONNECTION_HEADER = 'Connection'
UPGRADE_CONNECTION_TYPE = 'Upgrade'
HOST_HEADER = 'Host'
SEC_WEBSOCKET_ORIGIN_HEADER = 'Sec-WebSocket-Origin'
SEC_WEBSOCKET_KEY_HEADER = 'Sec-WebSocket-Key'
SEC_WEBSOCKET_ACCEPT_HEADER = 'Sec-WebSocket-Accept'
SEC_WEBSOCKET_VERSION_HEADER = 'Sec-WebSocket-Version'
SEC_WEBSOCKET_PROTOCOL_HEADER = 'Sec-WebSocket-Protocol'
SEC_WEBSOCKET_EXTENSIONS_HEADER = 'Sec-WebSocket-Extensions'
# Status codes
STATUS_NORMAL = 1000
STATUS_GOING_AWAY = 1001
STATUS_PROTOCOL_ERROR = 1002
STATUS_UNSUPPORTED = 1003
STATUS_TOO_LARGE = 1004

Просмотреть файл

@ -1,4 +1,4 @@
# Copyright 2009, Google Inc.
# Copyright 2011, Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
@ -28,13 +28,15 @@
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""Dispatch Web Socket request.
"""Dispatch WebSocket request.
"""
import logging
import os
import re
from mod_pywebsocket import common
from mod_pywebsocket import msgutil
from mod_pywebsocket import util
@ -46,7 +48,7 @@ _TRANSFER_DATA_HANDLER_NAME = 'web_socket_transfer_data'
class DispatchError(Exception):
"""Exception in dispatching Web Socket request."""
"""Exception in dispatching WebSocket request."""
pass
@ -63,15 +65,18 @@ def _normalize_path(path):
"""
path = path.replace('\\', os.path.sep)
#path = os.path.realpath(path)
# do not normalize away symlinks in mochitest
# path = os.path.realpath(path)
path = path.replace('\\', '/')
return path
def _path_to_resource_converter(base_dir):
def _create_path_to_resource_converter(base_dir):
base_dir = _normalize_path(base_dir)
base_len = len(base_dir)
suffix_len = len(_SOURCE_SUFFIX)
def converter(path):
if not path.endswith(_SOURCE_SUFFIX):
return None
@ -79,11 +84,14 @@ def _path_to_resource_converter(base_dir):
if not path.startswith(base_dir):
return None
return path[base_len:-suffix_len]
return converter
def _source_file_paths(directory):
"""Yield Web Socket Handler source file names in the given directory."""
def _enumerate_handler_file_paths(directory):
"""Returns a generator that enumerates WebSocket Handler source file names
in the given directory.
"""
for root, unused_dirs, files in os.walk(directory):
for base in files:
@ -92,20 +100,38 @@ def _source_file_paths(directory):
yield path
def _source(source_str):
"""Source a handler definition string."""
class _HandlerSuite(object):
"""A handler suite holder class."""
def __init__(self, do_extra_handshake, transfer_data):
self.do_extra_handshake = do_extra_handshake
self.transfer_data = transfer_data
def _source_handler_file(handler_definition):
"""Source a handler definition string.
Args:
handler_definition: a string containing Python statements that define
handler functions.
"""
global_dic = {}
try:
exec source_str in global_dic
exec handler_definition in global_dic
except Exception:
raise DispatchError('Error in sourcing handler:' +
util.get_stack_trace())
return (_extract_handler(global_dic, _DO_EXTRA_HANDSHAKE_HANDLER_NAME),
_extract_handler(global_dic, _TRANSFER_DATA_HANDLER_NAME))
return _HandlerSuite(
_extract_handler(global_dic, _DO_EXTRA_HANDSHAKE_HANDLER_NAME),
_extract_handler(global_dic, _TRANSFER_DATA_HANDLER_NAME))
def _extract_handler(dic, name):
"""Extracts a callable with the specified name from the given dictionary
dic.
"""
if name not in dic:
raise DispatchError('%s is not defined.' % name)
handler = dic[name]
@ -115,7 +141,7 @@ def _extract_handler(dic, name):
class Dispatcher(object):
"""Dispatches Web Socket requests.
"""Dispatches WebSocket requests.
This class maintains a map from resource name to handlers.
"""
@ -133,7 +159,9 @@ class Dispatcher(object):
scan time when root_dir contains many subdirectories.
"""
self._handlers = {}
self._logger = util.get_class_logger(self)
self._handler_suite_map = {}
self._source_warnings = []
if scan_dir is None:
scan_dir = root_dir
@ -141,7 +169,7 @@ class Dispatcher(object):
os.path.realpath(root_dir)):
raise DispatchError('scan_dir:%s must be a directory under '
'root_dir:%s.' % (scan_dir, root_dir))
self._source_files_in_dir(root_dir, scan_dir)
self._source_handler_files_in_dir(root_dir, scan_dir)
def add_resource_path_alias(self,
alias_resource_path, existing_resource_path):
@ -155,8 +183,8 @@ class Dispatcher(object):
existing_resource_path: existing resource path
"""
try:
handler = self._handlers[existing_resource_path]
self._handlers[alias_resource_path] = handler
handler_suite = self._handler_suite_map[existing_resource_path]
self._handler_suite_map[alias_resource_path] = handler_suite
except KeyError:
raise DispatchError('No handler for: %r' % existing_resource_path)
@ -166,7 +194,7 @@ class Dispatcher(object):
return self._source_warnings
def do_extra_handshake(self, request):
"""Do extra checking in Web Socket handshake.
"""Do extra checking in WebSocket handshake.
Select a handler based on request.uri and call its
web_socket_do_extra_handshake function.
@ -175,7 +203,8 @@ class Dispatcher(object):
request: mod_python request.
"""
do_extra_handshake_, unused_transfer_data = self._handler(request)
do_extra_handshake_ = self._get_handler_suite(
request).do_extra_handshake
try:
do_extra_handshake_(request)
except Exception, e:
@ -187,7 +216,7 @@ class Dispatcher(object):
raise
def transfer_data(self, request):
"""Let a handler transfer_data with a Web Socket client.
"""Let a handler transfer_data with a WebSocket client.
Select a handler based on request.ws_resource and call its
web_socket_transfer_data function.
@ -196,50 +225,58 @@ class Dispatcher(object):
request: mod_python request.
"""
unused_do_extra_handshake, transfer_data_ = self._handler(request)
transfer_data_ = self._get_handler_suite(request).transfer_data
# TODO(tyoshino): Terminate underlying TCP connection if possible.
try:
try:
request.client_terminated = False
request.server_terminated = False
transfer_data_(request)
except msgutil.ConnectionTerminatedException, e:
util.prepend_message_to_exception(
'client initiated closing handshake for %s: ' % (
request.ws_resource),
e)
raise
except Exception, e:
print 'exception: %s' % type(e)
util.prepend_message_to_exception(
'%s raised exception for %s: ' % (
transfer_data_(request)
if not request.server_terminated:
request.ws_stream.close_connection()
# Catch non-critical exceptions the handler didn't handle.
except msgutil.BadOperationException, e:
self._logger.debug(str(e))
request.ws_stream.close_connection(common.STATUS_GOING_AWAY)
except msgutil.InvalidFrameException, e:
# InvalidFrameException must be caught before
# ConnectionTerminatedException that catches InvalidFrameException.
self._logger.debug(str(e))
request.ws_stream.close_connection(common.STATUS_PROTOCOL_ERROR)
except msgutil.UnsupportedFrameException, e:
self._logger.debug(str(e))
request.ws_stream.close_connection(common.STATUS_UNSUPPORTED)
except msgutil.ConnectionTerminatedException, e:
self._logger.debug(str(e))
except Exception, e:
util.prepend_message_to_exception(
'%s raised exception for %s: ' % (
_TRANSFER_DATA_HANDLER_NAME, request.ws_resource),
e)
raise
finally:
msgutil.close_connection(request)
e)
raise
def _get_handler_suite(self, request):
"""Retrieves two handlers (one for extra handshake processing, and one
for data transfer) for the given request as a HandlerSuite object.
"""
def _handler(self, request):
try:
ws_resource_path = request.ws_resource.split('?', 1)[0]
return self._handlers[ws_resource_path]
return self._handler_suite_map[ws_resource_path]
except KeyError:
raise DispatchError('No handler for: %r' % request.ws_resource)
def _source_files_in_dir(self, root_dir, scan_dir):
def _source_handler_files_in_dir(self, root_dir, scan_dir):
"""Source all the handler source files in the scan_dir directory.
The resource path is determined relative to root_dir.
"""
to_resource = _path_to_resource_converter(root_dir)
for path in _source_file_paths(scan_dir):
convert = _create_path_to_resource_converter(root_dir)
for path in _enumerate_handler_file_paths(scan_dir):
try:
handlers = _source(open(path).read())
handler_suite = _source_handler_file(open(path).read())
except DispatchError, e:
self._source_warnings.append('%s: %s' % (path, e))
continue
self._handlers[to_resource(path)] = handlers
self._handler_suite_map[convert(path)] = handler_suite
# vi:sts=4 sw=4 et

Просмотреть файл

@ -1,4 +1,4 @@
# Copyright 2010, Google Inc.
# Copyright 2011, Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
@ -28,30 +28,23 @@
# 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.
"""WebSocket opening handshake processor. This class try to apply available
opening handshake processors for each protocol version until a connection is
successfully established.
"""
import logging
import re
from mod_pywebsocket import util
from mod_pywebsocket.handshake import draft75
from mod_pywebsocket.handshake import handshake
from mod_pywebsocket.handshake._base import DEFAULT_WEB_SOCKET_PORT
from mod_pywebsocket.handshake._base import DEFAULT_WEB_SOCKET_SECURE_PORT
from mod_pywebsocket.handshake._base import WEB_SOCKET_SCHEME
from mod_pywebsocket.handshake._base import WEB_SOCKET_SECURE_SCHEME
from mod_pywebsocket.handshake import hybi00
from mod_pywebsocket.handshake import hybi06
from mod_pywebsocket.handshake._base import HandshakeError
from mod_pywebsocket.handshake._base import validate_protocol
class Handshaker(object):
"""This class performs Web Socket handshake."""
"""This class performs WebSocket handshake."""
def __init__(self, request, dispatcher, allowDraft75=False, strict=False):
"""Construct an instance.
@ -68,28 +61,38 @@ class Handshaker(object):
handshake.
"""
self._logger = logging.getLogger("mod_pywebsocket.handshake")
self._logger = util.get_class_logger(self)
self._request = request
self._dispatcher = dispatcher
self._strict = strict
self._handshaker = handshake.Handshaker(request, dispatcher)
self._fallbackHandshaker = None
self._hybi07Handshaker = hybi06.Handshaker(request, dispatcher)
self._hybi00Handshaker = hybi00.Handshaker(request, dispatcher)
self._hixie75Handshaker = None
if allowDraft75:
self._fallbackHandshaker = draft75.Handshaker(
self._hixie75Handshaker = draft75.Handshaker(
request, dispatcher, strict)
def do_handshake(self):
"""Perform Web Socket Handshake."""
"""Perform WebSocket Handshake."""
try:
self._handshaker.do_handshake()
except HandshakeError, e:
self._logger.error('Handshake error: %s' % e)
if self._fallbackHandshaker:
self._logger.warning('fallback to old protocol')
self._fallbackHandshaker.do_handshake()
return
raise e
self._logger.debug(
'Opening handshake headers: %s' % self._request.headers_in)
handshakers = [
('HyBi 07', self._hybi07Handshaker),
('HyBi 00', self._hybi00Handshaker),
('Hixie 75', self._hixie75Handshaker)]
last_error = HandshakeError('No handshaker available')
for name, handshaker in handshakers:
if handshaker:
self._logger.info('Trying %s protocol' % name)
try:
handshaker.do_handshake()
return
except HandshakeError, e:
self._logger.info('%s handshake failed: %s' % (name, e))
last_error = e
raise last_error
# vi:sts=4 sw=4 et

Просмотреть файл

@ -1,4 +1,4 @@
# Copyright 2010, Google Inc.
# Copyright 2011, Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
@ -28,48 +28,51 @@
# 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.
"""Common functions and exceptions used by WebSocket opening handshake
processors.
"""
DEFAULT_WEB_SOCKET_PORT = 80
DEFAULT_WEB_SOCKET_SECURE_PORT = 443
WEB_SOCKET_SCHEME = 'ws'
WEB_SOCKET_SECURE_SCHEME = 'wss'
from mod_pywebsocket import common
class HandshakeError(Exception):
"""Exception in Web Socket Handshake."""
"""This exception will be raised when an error occurred while processing
WebSocket initial handshake.
"""
pass
def default_port(is_secure):
def get_default_port(is_secure):
if is_secure:
return DEFAULT_WEB_SOCKET_SECURE_PORT
return common.DEFAULT_WEB_SOCKET_SECURE_PORT
else:
return DEFAULT_WEB_SOCKET_PORT
return common.DEFAULT_WEB_SOCKET_PORT
def validate_protocol(protocol):
"""Validate WebSocket-Protocol string."""
# TODO(tyoshino): Have stricter validator for HyBi 07.
def validate_subprotocol(subprotocol):
"""Validate a value in subprotocol fields such as WebSocket-Protocol,
Sec-WebSocket-Protocol.
if not protocol:
raise HandshakeError('Invalid WebSocket-Protocol: empty')
for c in protocol:
See
- HyBi 06: Section 5.2.2.
- HyBi 00: Section 4.1. Opening handshake
- Hixie 75: Section 4.1. Handshake
"""
if not subprotocol:
raise HandshakeError('Invalid subprotocol name: empty')
for c in subprotocol:
if not 0x20 <= ord(c) <= 0x7e:
raise HandshakeError('Illegal character in protocol: %r' % c)
raise HandshakeError(
'Illegal character in subprotocol name: %r' % c)
def parse_host_header(request):
fields = request.headers_in['Host'].split(':', 1)
if len(fields) == 1:
return fields[0], default_port(request.is_https())
return fields[0], get_default_port(request.is_https())
try:
return fields[0], int(fields[1])
except ValueError, e:
@ -80,9 +83,9 @@ def build_location(request):
"""Build WebSocket location for request."""
location_parts = []
if request.is_https():
location_parts.append(WEB_SOCKET_SECURE_SCHEME)
location_parts.append(common.WEB_SOCKET_SECURE_SCHEME)
else:
location_parts.append(WEB_SOCKET_SCHEME)
location_parts.append(common.WEB_SOCKET_SCHEME)
location_parts.append('://')
host, port = parse_host_header(request)
connection_port = request.connection.local_addr[1]
@ -90,12 +93,34 @@ def build_location(request):
raise HandshakeError('Header/connection port mismatch: %d/%d' %
(port, connection_port))
location_parts.append(host)
if (port != default_port(request.is_https())):
if (port != get_default_port(request.is_https())):
location_parts.append(':')
location_parts.append(str(port))
location_parts.append(request.uri)
return ''.join(location_parts)
def get_mandatory_header(request, key, expected_value=None):
value = request.headers_in.get(key)
if value is None:
raise HandshakeError('Header %s is not defined' % key)
if expected_value is not None and expected_value != value:
raise HandshakeError(
'Illegal value for header %s: %s (expected: %s)' %
(key, value, expected_value))
return value
def check_header_lines(request, mandatory_headers):
# 5.1 1. The three character UTF-8 string "GET".
# 5.1 2. A UTF-8-encoded U+0020 SPACE character (0x20 byte).
if request.method != 'GET':
raise HandshakeError('Method is not GET')
# The expected field names, and the meaning of their corresponding
# values, are as follows.
# |Upgrade| and |Connection|
for key, expected_value in mandatory_headers:
get_mandatory_header(request, key, expected_value)
# vi:sts=4 sw=4 et

Просмотреть файл

@ -1,4 +1,4 @@
# Copyright 2010, Google Inc.
# Copyright 2011, Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
@ -28,20 +28,24 @@
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""Web Socket handshaking defined in draft-hixie-thewebsocketprotocol-75.
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.
"""
"""WebSocket handshaking defined in draft-hixie-thewebsocketprotocol-75."""
# 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.
import logging
import re
from mod_pywebsocket import common
from mod_pywebsocket.stream import StreamHixie75
from mod_pywebsocket import util
from mod_pywebsocket.handshake._base import HandshakeError
from mod_pywebsocket.handshake._base import build_location
from mod_pywebsocket.handshake._base import validate_protocol
from mod_pywebsocket.handshake._base import validate_subprotocol
_MANDATORY_HEADERS = [
@ -70,7 +74,7 @@ _SIXTH_AND_LATER = re.compile(
class Handshaker(object):
"""This class performs Web Socket handshake."""
"""This class performs WebSocket handshake."""
def __init__(self, request, dispatcher, strict=False):
"""Construct an instance.
@ -86,18 +90,28 @@ class Handshaker(object):
handshake.
"""
self._logger = util.get_class_logger(self)
self._request = request
self._dispatcher = dispatcher
self._strict = strict
def do_handshake(self):
"""Perform Web Socket Handshake."""
"""Perform WebSocket Handshake.
On _request, we set
ws_resource, ws_origin, ws_location, ws_protocol
ws_challenge_md5: WebSocket handshake information.
ws_stream: Frame generation/parsing class.
ws_version: Protocol version.
"""
self._check_header_lines()
self._set_resource()
self._set_origin()
self._set_location()
self._set_protocol()
self._set_subprotocol()
self._set_protocol_version()
self._dispatcher.do_extra_handshake(self._request)
self._send_handshake()
@ -110,28 +124,30 @@ class Handshaker(object):
def _set_location(self):
self._request.ws_location = build_location(self._request)
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 _set_subprotocol(self):
subprotocol = self._request.headers_in.get('WebSocket-Protocol')
if subprotocol is not None:
validate_subprotocol(subprotocol)
self._request.ws_protocol = subprotocol
def _set_protocol_version(self):
self._logger.debug('IETF Hixie 75 protocol')
self._request.ws_version = common.VERSION_HIXIE75
self._request.ws_stream = StreamHixie75(self._request)
def _sendall(self, data):
self._request.connection.write(data)
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')
self._sendall('HTTP/1.1 101 Web Socket Protocol Handshake\r\n')
self._sendall('Upgrade: WebSocket\r\n')
self._sendall('Connection: Upgrade\r\n')
self._sendall('WebSocket-Origin: %s\r\n' % self._request.ws_origin)
self._sendall('WebSocket-Location: %s\r\n' % self._request.ws_location)
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')
self._sendall(
'WebSocket-Protocol: %s\r\n' % self._request.ws_protocol)
self._sendall('\r\n')
def _check_header_lines(self):
for key, expected_value in _MANDATORY_HEADERS:
@ -140,8 +156,9 @@ class Handshaker(object):
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))
raise HandshakeError(
'Expected %r for header %s but found %r' %
(expected_value, key, actual_value))
if self._strict:
try:
lines = self._request.connection.get_memorized_lines()

Просмотреть файл

@ -1,208 +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 logging
from md5 import md5
import re
import struct
from mod_pywebsocket.handshake._base import HandshakeError
from mod_pywebsocket.handshake._base import build_location
from mod_pywebsocket.handshake._base import validate_protocol
_MANDATORY_HEADERS = [
# key, expected value or None
['Upgrade', 'WebSocket'],
['Connection', 'Upgrade'],
]
def _hexify(s):
return re.sub('.', lambda x: '%02x ' % ord(x.group(0)), s)
class Handshaker(object):
"""This class performs Web Socket handshake."""
def __init__(self, request, dispatcher):
"""Construct an instance.
Args:
request: mod_python request.
dispatcher: Dispatcher (dispatch.Dispatcher).
Handshaker will add attributes such as ws_resource in performing
handshake.
"""
self._logger = logging.getLogger("mod_pywebsocket.handshake")
self._request = request
self._dispatcher = dispatcher
def do_handshake(self):
"""Perform Web Socket Handshake."""
# 5.1 Reading the client's opening handshake.
# dispatcher sets it in self._request.
self._check_header_lines()
self._set_resource()
self._set_protocol()
self._set_location()
self._set_origin()
self._set_challenge_response()
self._dispatcher.do_extra_handshake(self._request)
self._send_handshake()
def _check_header_lines(self):
# 5.1 1. The three character UTF-8 string "GET".
# 5.1 2. A UTF-8-encoded U+0020 SPACE character (0x20 byte).
if self._request.method != 'GET':
raise HandshakeError('Method is not GET')
# The expected field names, and the meaning of their corresponding
# values, are as follows.
# |Upgrade| and |Connection|
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))
def _set_resource(self):
self._request.ws_resource = self._request.uri
def _set_protocol(self):
# |Sec-WebSocket-Protocol|
protocol = self._request.headers_in.get('Sec-WebSocket-Protocol')
if protocol is not None:
validate_protocol(protocol)
self._request.ws_protocol = protocol
def _set_location(self):
# |Host|
host = self._request.headers_in.get('Host')
if host is not None:
self._request.ws_location = build_location(self._request)
# TODO(ukai): check host is this host.
def _set_origin(self):
# |Origin|
origin = self._request.headers_in['Origin']
if origin is not None:
self._request.ws_origin = origin
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 = md5(
self._request.ws_challenge).digest()
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_value = self._request.headers_in.get(key_field)
if key_value is None:
self._logger.debug("no %s" % key_value)
return None
try:
# 5.2 4. let /key-number_n/ be the digits (characters in the range
# U+0030 DIGIT ZERO (0) to U+0039 DIGIT NINE (9)) in /key_n/,
# interpreted as a base ten integer, ignoring all other characters
# in /key_n/
key_number = int(re.sub("\\D", "", key_value))
# 5.2 5. let /spaces_n/ be the number of U+0020 SPACE characters
# in /key_n/.
spaces = re.subn(" ", "", key_value)[1]
# 5.2 6. if /key-number_n/ is not an integral multiple of /spaces_n/
# then abort the WebSocket connection.
if key_number % spaces != 0:
raise handshakeError('key_number %d is not an integral '
'multiple of spaces %d' % (key_number,
spaces))
# 5.2 7. let /part_n/ be /key_number_n/ divided by /spaces_n/.
part = key_number / spaces
self._logger.debug("%s: %s => %d / %d => %d" % (
key_field, key_value, key_number, spaces, part))
return part
except:
return None
def _get_challenge(self):
# 5.2 4-7.
key1 = self._get_key_value('Sec-Websocket-Key1')
if not key1:
raise HandshakeError('Sec-WebSocket-Key1 not found')
key2 = self._get_key_value('Sec-Websocket-Key2')
if not key2:
raise HandshakeError('Sec-WebSocket-Key2 not found')
# 5.2 8. let /challenge/ be the concatenation of /part_1/,
challenge = ""
challenge += struct.pack("!I", key1) # network byteorder int
challenge += struct.pack("!I", key2) # network byteorder int
challenge += self._request.connection.read(8)
return challenge
def _send_handshake(self):
# 5.2 10. send the following line.
self._request.connection.write(
'HTTP/1.1 101 WebSocket Protocol Handshake\r\n')
# 5.2 11. send the following fields to the client.
self._request.connection.write('Upgrade: WebSocket\r\n')
self._request.connection.write('Connection: Upgrade\r\n')
self._request.connection.write('Sec-WebSocket-Location: ')
self._request.connection.write(self._request.ws_location)
self._request.connection.write('\r\n')
self._request.connection.write('Sec-WebSocket-Origin: ')
self._request.connection.write(self._request.ws_origin)
self._request.connection.write('\r\n')
if self._request.ws_protocol:
self._request.connection.write('Sec-WebSocket-Protocol: ')
self._request.connection.write(self._request.ws_protocol)
self._request.connection.write('\r\n')
# 5.2 12. send two bytes 0x0D 0x0A.
self._request.connection.write('\r\n')
# 5.2 13. send /response/
self._request.connection.write(self._request.ws_challenge_md5)
# vi:sts=4 sw=4 et

Просмотреть файл

@ -0,0 +1,232 @@
# Copyright 2011, 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.
"""WebSocket initial handshake hander for HyBi 00 protocol."""
# 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 logging
import re
import struct
from mod_pywebsocket import common
from mod_pywebsocket.stream import StreamHixie75
from mod_pywebsocket import util
from mod_pywebsocket.handshake._base import HandshakeError
from mod_pywebsocket.handshake._base import build_location
from mod_pywebsocket.handshake._base import check_header_lines
from mod_pywebsocket.handshake._base import get_mandatory_header
from mod_pywebsocket.handshake._base import validate_subprotocol
_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."""
def __init__(self, request, dispatcher):
"""Construct an instance.
Args:
request: mod_python request.
dispatcher: Dispatcher (dispatch.Dispatcher).
Handshaker will add attributes such as ws_resource in performing
handshake.
"""
self._logger = util.get_class_logger(self)
self._request = request
self._dispatcher = dispatcher
def do_handshake(self):
"""Perform WebSocket Handshake.
On _request, we set
ws_resource, ws_protocol, ws_location, ws_origin, ws_challenge,
ws_challenge_md5: WebSocket handshake information.
ws_stream: Frame generation/parsing class.
ws_version: Protocol version.
"""
# 5.1 Reading the client's opening handshake.
# dispatcher sets it in self._request.
check_header_lines(self._request, _MANDATORY_HEADERS)
self._set_resource()
self._set_subprotocol()
self._set_location()
self._set_origin()
self._set_challenge_response()
self._set_protocol_version()
self._dispatcher.do_extra_handshake(self._request)
self._send_handshake()
def _set_resource(self):
self._request.ws_resource = self._request.uri
def _set_subprotocol(self):
# |Sec-WebSocket-Protocol|
subprotocol = self._request.headers_in.get(
common.SEC_WEBSOCKET_PROTOCOL_HEADER)
if subprotocol is not None:
validate_subprotocol(subprotocol)
self._request.ws_protocol = subprotocol
def _set_location(self):
# |Host|
host = self._request.headers_in.get(common.HOST_HEADER)
if host is not None:
self._request.ws_location = build_location(self._request)
# TODO(ukai): check host is this host.
def _set_origin(self):
# |Origin|
origin = self._request.headers_in['Origin']
if origin is not None:
self._request.ws_origin = origin
def _set_protocol_version(self):
# |Sec-WebSocket-Draft|
draft = self._request.headers_in.get('Sec-WebSocket-Draft')
if draft is not None:
try:
draft_int = int(draft)
# Draft value 2 is used by HyBi 02 and 03 which we no longer
# support. draft >= 3 and <= 1 are never defined in the spec.
# 0 might be used to mean HyBi 00 by somebody. 1 might be used
# to mean HyBi 01 by somebody but we no longer support it.
if draft_int == 1 or draft_int == 2:
raise HandshakeError('HyBi 01-03 are not supported')
elif draft_int != 0:
raise ValueError
except ValueError, e:
raise HandshakeError(
'Illegal value for Sec-WebSocket-Draft: %s' % draft)
self._logger.debug('IETF HyBi 00 protocol')
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(
self._request.ws_challenge).digest()
self._logger.debug(
'Challenge: %r (%s)' %
(self._request.ws_challenge,
util.hexify(self._request.ws_challenge)))
self._logger.debug(
'Challenge response: %r (%s)' %
(self._request.ws_challenge_md5,
util.hexify(self._request.ws_challenge_md5)))
def _get_key_value(self, key_field):
key_value = get_mandatory_header(self._request, key_field)
# 5.2 4. let /key-number_n/ be the digits (characters in the range
# U+0030 DIGIT ZERO (0) to U+0039 DIGIT NINE (9)) in /key_n/,
# interpreted as a base ten integer, ignoring all other characters
# in /key_n/.
try:
key_number = int(re.sub("\\D", "", key_value))
except:
raise HandshakeError('%s field contains no digit' % key_field)
# 5.2 5. let /spaces_n/ be the number of U+0020 SPACE characters
# in /key_n/.
spaces = re.subn(" ", "", key_value)[1]
if spaces == 0:
raise HandshakeError('%s field contains no space' % key_field)
# 5.2 6. if /key-number_n/ is not an integral multiple of /spaces_n/
# then abort the WebSocket connection.
if key_number % spaces != 0:
raise HandshakeError('Key-number %d is not an integral '
'multiple of spaces %d' % (key_number,
spaces))
# 5.2 7. let /part_n/ be /key-number_n/ divided by /spaces_n/.
part = key_number / spaces
self._logger.debug('%s: %s => %d / %d => %d' % (
key_field, key_value, key_number, spaces, part))
return part
def _get_challenge(self):
# 5.2 4-7.
key1 = self._get_key_value('Sec-WebSocket-Key1')
key2 = self._get_key_value('Sec-WebSocket-Key2')
# 5.2 8. let /challenge/ be the concatenation of /part_1/,
challenge = ''
challenge += struct.pack('!I', key1) # network byteorder int
challenge += struct.pack('!I', key2) # network byteorder int
challenge += self._request.connection.read(8)
return challenge
def _sendall(self, data):
self._request.connection.write(data)
def _send_header(self, name, value):
self._sendall('%s: %s\r\n' % (name, value))
def _send_handshake(self):
# 5.2 10. send the following line.
self._sendall('HTTP/1.1 101 WebSocket Protocol Handshake\r\n')
# 5.2 11. send the following fields to the client.
self._send_header(
common.UPGRADE_HEADER, common.WEBSOCKET_UPGRADE_TYPE_HIXIE75)
self._send_header(
common.CONNECTION_HEADER, common.UPGRADE_CONNECTION_TYPE)
self._send_header('Sec-WebSocket-Location', self._request.ws_location)
self._send_header(
common.SEC_WEBSOCKET_ORIGIN_HEADER, self._request.ws_origin)
if self._request.ws_protocol:
self._send_header(
common.SEC_WEBSOCKET_PROTOCOL_HEADER,
self._request.ws_protocol)
# 5.2 12. send two bytes 0x0D 0x0A.
self._sendall('\r\n')
# 5.2 13. send /response/
self._sendall(self._request.ws_challenge_md5)
# vi:sts=4 sw=4 et

Просмотреть файл

@ -0,0 +1,250 @@
# Copyright 2011, 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.
"""WebSocket HyBi 07 opening handshake processor."""
# 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.
import base64
import logging
import os
import re
from mod_pywebsocket import common
from mod_pywebsocket.stream import Stream
from mod_pywebsocket.stream import StreamOptions
from mod_pywebsocket import util
from mod_pywebsocket.handshake._base import HandshakeError
from mod_pywebsocket.handshake._base import check_header_lines
from mod_pywebsocket.handshake._base import get_mandatory_header
_MANDATORY_HEADERS = [
# key, expected value or None
[common.UPGRADE_HEADER, common.WEBSOCKET_UPGRADE_TYPE],
[common.CONNECTION_HEADER, common.UPGRADE_CONNECTION_TYPE],
]
_BASE64_REGEX = re.compile('^[+/0-9A-Za-z]*=*$')
def compute_accept(key):
"""Computes value for the Sec-WebSocket-Accept header from value of the
Sec-WebSocket-Key header.
"""
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."""
def __init__(self, request, dispatcher):
"""Construct an instance.
Args:
request: mod_python request.
dispatcher: Dispatcher (dispatch.Dispatcher).
Handshaker will add attributes such as ws_resource during handshake.
"""
self._logger = util.get_class_logger(self)
self._request = request
self._dispatcher = dispatcher
def do_handshake(self):
check_header_lines(self._request, _MANDATORY_HEADERS)
self._request.ws_resource = self._request.uri
unused_host = get_mandatory_header(self._request, common.HOST_HEADER)
self._get_origin()
self._check_version()
self._set_protocol()
self._set_extensions()
key = self._get_key()
(accept, accept_binary) = compute_accept(key)
self._logger.debug('Sec-WebSocket-Accept: %r (%s)' %
(accept, util.hexify(accept_binary)))
self._logger.debug('IETF HyBi 07 protocol')
self._request.ws_version = common.VERSION_HYBI07
stream_options = StreamOptions()
stream_options.deflate = self._request.ws_deflate
self._request.ws_stream = Stream(self._request, stream_options)
self._request.ws_close_code = None
self._request.ws_close_reason = None
self._dispatcher.do_extra_handshake(self._request)
if self._request.ws_requested_protocols is not None:
if self._request.ws_protocol is None:
raise HandshakeError(
'do_extra_handshake must choose one subprotocol from '
'ws_requested_protocols and set it to ws_protocol')
# TODO(tyoshino): Validate selected subprotocol value.
self._logger.debug(
'Subprotocol accepted: %r',
self._request.ws_protocol)
else:
if self._request.ws_protocol is not None:
raise HandshakeError(
'ws_protocol must be None when the client didn\'t request '
'any subprotocol')
self._send_handshake(accept)
def _get_origin(self):
origin = self._request.headers_in.get(
common.SEC_WEBSOCKET_ORIGIN_HEADER)
self._request.ws_origin = origin
def _check_version(self):
unused_value = get_mandatory_header(
self._request, common.SEC_WEBSOCKET_VERSION_HEADER, '7')
def _set_protocol(self):
self._request.ws_protocol = None
protocol_header = self._request.headers_in.get(
common.SEC_WEBSOCKET_PROTOCOL_HEADER)
if not protocol_header:
self._request.ws_requested_protocols = None
return
# TODO(tyoshino): Validate the header value.
requested_protocols = protocol_header.split(',')
self._request.ws_requested_protocols = [
s.strip() for s in requested_protocols]
self._logger.debug('Subprotocols requested: %r', requested_protocols)
def _set_extensions(self):
self._request.ws_deflate = False
extensions_header = self._request.headers_in.get(
common.SEC_WEBSOCKET_EXTENSIONS_HEADER)
if not extensions_header:
self._request.ws_extensions = None
return
self._request.ws_extensions = []
requested_extensions = extensions_header.split(',')
# TODO(tyoshino): Follow the ABNF in the spec.
requested_extensions = [s.strip() for s in requested_extensions]
for extension in requested_extensions:
# We now support only deflate-stream extension. Any other
# extension requests are just ignored for now.
if extension == 'deflate-stream':
self._request.ws_extensions.append(extension)
self._request.ws_deflate = True
self._logger.debug('Extensions requested: %r', requested_extensions)
self._logger.debug(
'Extensions accepted: %r', self._request.ws_extensions)
def _validate_key(self, key):
# 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):
decoded_key = base64.b64decode(key)
if len(decoded_key) == 16:
key_is_valid = True
except TypeError, e:
pass
if not key_is_valid:
raise HandshakeError(
'Illegal value for header %s: %r' %
(common.SEC_WEBSOCKET_KEY_HEADER, key))
return decoded_key
def _get_key(self):
key = get_mandatory_header(
self._request, common.SEC_WEBSOCKET_KEY_HEADER)
decoded_key = self._validate_key(key)
self._logger.debug('Sec-WebSocket-Key: %r (%s)' %
(key, util.hexify(decoded_key)))
return key
def _sendall(self, data):
self._request.connection.write(data)
def _send_header(self, name, value):
self._sendall('%s: %s\r\n' % (name, value))
def _send_handshake(self, accept):
self._sendall('HTTP/1.1 101 Switching Protocols\r\n')
self._send_header(common.UPGRADE_HEADER, common.WEBSOCKET_UPGRADE_TYPE)
self._send_header(
common.CONNECTION_HEADER, common.UPGRADE_CONNECTION_TYPE)
self._send_header(common.SEC_WEBSOCKET_ACCEPT_HEADER, accept)
# TODO(tyoshino): Encode value of protocol and extensions if any
# special character that we have to encode by some manner.
if self._request.ws_protocol is not None:
self._send_header(
common.SEC_WEBSOCKET_PROTOCOL_HEADER,
self._request.ws_protocol)
if self._request.ws_extensions is not None:
self._send_header(
common.SEC_WEBSOCKET_EXTENSIONS_HEADER,
', '.join(self._request.ws_extensions))
self._sendall('\r\n')
# vi:sts=4 sw=4 et

Просмотреть файл

@ -1,4 +1,4 @@
# Copyright 2009, Google Inc.
# Copyright 2011, Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
@ -31,9 +31,10 @@
"""PythonHeaderParserHandler for mod_pywebsocket.
Apache HTTP Server and mod_python must be configured such that this
function is called to handle Web Socket request.
function is called to handle WebSocket request.
"""
import logging
from mod_python import apache
@ -57,7 +58,8 @@ _PYOPT_ALLOW_DRAFT75 = 'mod_pywebsocket.allow_draft75'
class ApacheLogHandler(logging.Handler):
"""Wrapper logging.Handler to emit log message to apache's error.log"""
"""Wrapper logging.Handler to emit log message to apache's error.log."""
_LEVELS = {
logging.DEBUG: apache.APLOG_DEBUG,
logging.INFO: apache.APLOG_INFO,
@ -65,11 +67,12 @@ class ApacheLogHandler(logging.Handler):
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
self.log_error = request.log_error
def emit(self, record):
apache_level = apache.APLOG_DEBUG
@ -78,7 +81,7 @@ class ApacheLogHandler(logging.Handler):
self.log_error(record.getMessage(), apache_level)
logging.getLogger("mod_pywebsocket").addHandler(ApacheLogHandler())
logging.getLogger('mod_pywebsocket').addHandler(ApacheLogHandler())
def _create_dispatcher():
@ -111,12 +114,13 @@ def headerparserhandler(request):
try:
allowDraft75 = apache.main_server.get_options().get(
_PYOPT_ALLOW_DRAFT75, None)
_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)
request.log_error(
'mod_pywebsocket: resource: %r' % request.ws_resource,
apache.APLOG_DEBUG)
try:
_dispatcher.transfer_data(request)
except Exception, e:
@ -132,6 +136,8 @@ def headerparserhandler(request):
except dispatch.DispatchError, e:
request.log_error('mod_pywebsocket: %s' % e, apache.APLOG_WARNING)
return apache.DECLINED
# Set assbackwards to suppress response header generation by Apache.
request.assbackwards = 1
return apache.DONE # Return DONE such that no other handlers are invoked.

Просмотреть файл

@ -53,7 +53,7 @@ class MemorizingFile(object):
file_: the file object to wrap.
max_memorized_lines: the maximum number of lines to memorize.
Only the first max_memorized_lines are memorized.
Default: sys.maxint.
Default: sys.maxint.
"""
self._file = file_
self._memorized_lines = []

Просмотреть файл

@ -1,4 +1,4 @@
# Copyright 2009, Google Inc.
# Copyright 2011, Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
@ -40,153 +40,52 @@ not suitable because they don't allow direct raw bytes writing/reading.
import Queue
import threading
from mod_pywebsocket import util
from time import time,sleep
# Export Exception symbols from msgutil for backward compatibility
from mod_pywebsocket._stream_base import ConnectionTerminatedException
from mod_pywebsocket._stream_base import InvalidFrameException
from mod_pywebsocket._stream_base import BadOperationException
from mod_pywebsocket._stream_base import UnsupportedFrameException
class MsgUtilException(Exception):
pass
class ConnectionTerminatedException(MsgUtilException):
pass
def _read(request, length):
bytes = request.connection.read(length)
if not bytes:
raise MsgUtilException(
'Failed to receive message from %r' %
(request.connection.remote_addr,))
return bytes
def _write(request, bytes):
try:
request.connection.write(bytes)
except Exception, e:
util.prepend_message_to_exception(
'Failed to send message to %r: ' %
(request.connection.remote_addr,),
e)
raise
def close_connection(request, abort=False):
# An API for handler to send/receive WebSocket messages.
def close_connection(request):
"""Close connection.
Args:
request: mod_python request.
"""
if request.server_terminated:
return
# 5.3 the server may decide to terminate the WebSocket connection by
# running through the following steps:
# 1. send a 0xFF byte and a 0x00 byte to the client to indicate the start
# of the closing handshake.
got_exception = False
if not abort:
_write(request, '\xff\x00')
# timeout of 20 seconds to get the client's close frame ack
initial_time = time()
end_time = initial_time + 20
while time() < end_time:
try:
receive_message(request)
except ConnectionTerminatedException, e:
got_exception = True
sleep(1)
request.server_terminated = True
if got_exception:
util.prepend_message_to_exception(
'client initiated closing handshake for %s: ' % (
request.ws_resource),
e)
raise ConnectionTerminatedException
# TODO: 3. close the WebSocket connection.
# note: mod_python Connection (mp_conn) doesn't have close method.
request.ws_stream.close_connection()
def send_message(request, message):
def send_message(request, message, end=True):
"""Send message.
Args:
request: mod_python request.
message: unicode string to send.
end: False to send message as a fragment. All messages until the first
call with end=True (inclusive) will be delivered to the client
in separate frames but as one WebSocket message.
Raises:
ConnectionTerminatedException: when server already terminated.
BadOperationException: when server already terminated.
"""
if request.server_terminated:
raise ConnectionTerminatedException
_write(request, '\x00' + message.encode('utf-8') + '\xff')
request.ws_stream.send_message(message, end)
def receive_message(request):
"""Receive a Web Socket frame and return its payload as unicode string.
"""Receive a WebSocket frame and return its payload as unicode string.
Args:
request: mod_python request.
Raises:
ConnectionTerminatedException: when client already terminated.
BadOperationException: when client already terminated.
"""
if request.client_terminated:
raise ConnectionTerminatedException
while True:
# Read 1 byte.
# mp_conn.read will block if no bytes are available.
# Timeout is controlled by TimeOut directive of Apache.
frame_type_str = _read(request, 1)
frame_type = ord(frame_type_str[0])
if (frame_type & 0x80) == 0x80:
# The payload length is specified in the frame.
# Read and discard.
length = _payload_length(request)
_receive_bytes(request, length)
# 5.3 3. 12. if /type/ is 0xFF and /length/ is 0, then set the
# /client terminated/ flag and abort these steps.
if frame_type == 0xFF and length == 0:
request.client_terminated = True
raise ConnectionTerminatedException
else:
# The payload is delimited with \xff.
bytes = _read_until(request, '\xff')
# The Web Socket protocol section 4.4 specifies that invalid
# characters must be replaced with U+fffd REPLACEMENT CHARACTER.
message = bytes.decode('utf-8', 'replace')
if frame_type == 0x00:
return message
# Discard data of other types.
return request.ws_stream.receive_message()
def _payload_length(request):
length = 0
while True:
b_str = _read(request, 1)
b = ord(b_str[0])
length = length * 128 + (b & 0x7f)
if (b & 0x80) == 0:
break
return length
def _receive_bytes(request, length):
bytes = []
while length > 0:
new_bytes = _read(request, length)
bytes.append(new_bytes)
length -= len(new_bytes)
return ''.join(bytes)
def _read_until(request, delim_char):
bytes = []
while True:
ch = _read(request, 1)
if ch == delim_char:
break
bytes.append(ch)
return ''.join(bytes)
def send_ping(request, body=''):
request.ws_stream.send_ping(body)
class MessageReceiver(threading.Thread):
@ -199,6 +98,7 @@ class MessageReceiver(threading.Thread):
because pyOpenSSL used by the server raises a fatal error if the socket
is accessed from multiple threads.
"""
def __init__(self, request, onmessage=None):
"""Construct an instance.
@ -210,6 +110,7 @@ class MessageReceiver(threading.Thread):
and MessageReceiver.receive_nowait are useless because
they will never return any messages.
"""
threading.Thread.__init__(self)
self._request = request
self._queue = Queue.Queue()
@ -269,6 +170,7 @@ class MessageSender(threading.Thread):
because pyOpenSSL used by the server raises a fatal error if the socket
is accessed from multiple threads.
"""
def __init__(self, request):
"""Construct an instance.

Просмотреть файл

@ -0,0 +1,476 @@
#!/usr/bin/env python
#
# Copyright 2011, 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.
"""Standalone WebSocket server.
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 ...
<ws_port> is the port number to use for ws:// connection.
<document_root> is the path to the root directory of HTML files.
<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:
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.
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 logging
import logging.handlers
import optparse
import os
import re
import socket
import sys
_HAS_OPEN_SSL = False
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 memorizingfile
from mod_pywebsocket import util
_DEFAULT_LOG_MAX_BYTES = 1024 * 256
_DEFAULT_LOG_BACKUP_COUNT = 5
_DEFAULT_REQUEST_QUEUE_SIZE = 128
# 1024 is practically large enough to contain WebSocket handshake lines.
_MAX_MEMORIZED_LINES = 1024
def _print_warnings_if_any(dispatcher):
warnings = dispatcher.source_warnings()
if warnings:
for warning in warnings:
logging.warning('mod_pywebsocket: %s' % warning)
class _StandaloneConnection(object):
"""Mimic mod_python mp_conn."""
def __init__(self, request_handler):
"""Construct an instance.
Args:
request_handler: A WebSocketRequestHandler instance.
"""
self._request_handler = request_handler
def get_local_addr(self):
"""Getter to mimic mp_conn.local_addr."""
return (self._request_handler.server.server_name,
self._request_handler.server.server_port)
local_addr = property(get_local_addr)
def get_remote_addr(self):
"""Getter to mimic mp_conn.remote_addr.
Setting the property in __init__ won't work because the request
handler is not initialized yet there."""
return self._request_handler.client_address
remote_addr = property(get_remote_addr)
def write(self, data):
"""Mimic mp_conn.write()."""
return self._request_handler.wfile.write(data)
def read(self, length):
"""Mimic mp_conn.read()."""
return self._request_handler.rfile.read(length)
def get_memorized_lines(self):
"""Get memorized lines."""
return self._request_handler.rfile.get_memorized_lines()
class _StandaloneRequest(object):
"""Mimic mod_python request."""
def __init__(self, request_handler, use_tls):
"""Construct an instance.
Args:
request_handler: A WebSocketRequestHandler instance.
"""
self._request_handler = request_handler
self.connection = _StandaloneConnection(request_handler)
self._use_tls = use_tls
def get_uri(self):
"""Getter to mimic request.uri."""
return self._request_handler.path
uri = property(get_uri)
def get_method(self):
"""Getter to mimic request.method."""
return self._request_handler.command
method = property(get_method)
def get_headers_in(self):
"""Getter to mimic request.headers_in."""
return self._request_handler.headers
headers_in = property(get_headers_in)
def is_https(self):
"""Mimic request.is_https()."""
return self._use_tls
class WebSocketServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer):
"""HTTPServer specialized for WebSocket."""
SocketServer.ThreadingMixIn.daemon_threads = True
SocketServer.TCPServer.allow_reuse_address = True
def __init__(self, server_address, RequestHandlerClass):
"""Override SocketServer.BaseServer.__init__."""
SocketServer.BaseServer.__init__(
self, server_address, RequestHandlerClass)
self.socket = self._create_socket()
self.server_bind()
self.server_activate()
def _create_socket(self):
socket_ = socket.socket(self.address_family, self.socket_type)
if WebSocketServer.options.use_tls:
ctx = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD)
ctx.use_privatekey_file(WebSocketServer.options.private_key)
ctx.use_certificate_file(WebSocketServer.options.certificate)
socket_ = OpenSSL.SSL.Connection(ctx, socket_)
return socket_
def handle_error(self, rquest, client_address):
"""Override SocketServer.handle_error."""
logging.error(
('Exception in processing request from: %r' % (client_address,)) +
'\n' + util.get_stack_trace())
# Note: client_address is a tuple. To match it against %r, we need the
# trailing comma.
class WebSocketRequestHandler(CGIHTTPServer.CGIHTTPRequestHandler):
"""CGIHTTPRequestHandler specialized for WebSocket."""
def setup(self):
"""Override SocketServer.StreamRequestHandler.setup to wrap rfile with
MemorizingFile.
"""
# Call superclass's setup to prepare rfile, wfile, etc. See setup
# definition on the root class SocketServer.StreamRequestHandler to
# understand what this does.
CGIHTTPServer.CGIHTTPRequestHandler.setup(self)
self.rfile = memorizingfile.MemorizingFile(
self.rfile,
max_memorized_lines=_MAX_MEMORIZED_LINES)
def __init__(self, *args, **keywords):
self._request = _StandaloneRequest(
self, WebSocketRequestHandler.options.use_tls)
self._dispatcher = WebSocketRequestHandler.options.dispatcher
self._print_warnings_if_any()
self._handshaker = handshake.Handshaker(
self._request, self._dispatcher,
allowDraft75=WebSocketRequestHandler.options.allow_draft75,
strict=WebSocketRequestHandler.options.strict)
CGIHTTPServer.CGIHTTPRequestHandler.__init__(
self, *args, **keywords)
def _print_warnings_if_any(self):
warnings = self._dispatcher.source_warnings()
if warnings:
for warning in warnings:
logging.warning('mod_pywebsocket: %s' % warning)
def parse_request(self):
"""Override BaseHTTPServer.BaseHTTPRequestHandler.parse_request.
Return True to continue processing for HTTP(S), False otherwise.
"""
result = CGIHTTPServer.CGIHTTPRequestHandler.parse_request(self)
if result:
try:
self._handshaker.do_handshake()
try:
self._dispatcher.transfer_data(self._request)
except Exception, e:
# Catch exception in transfer_data.
# In this case, handshake has been successful, so just log
# the exception and return False.
logging.info('mod_pywebsocket: %s' % e)
logging.info('mod_pywebsocket: %s' % util.get_stack_trace())
return False
except handshake.HandshakeError, e:
# Handshake for ws(s) failed. Assume http(s).
logging.info('mod_pywebsocket: %s' % e)
return True
except dispatch.DispatchError, e:
logging.warning('mod_pywebsocket: %s' % e)
return False
except Exception, e:
logging.warning('mod_pywebsocket: %s' % e)
logging.warning('mod_pywebsocket: %s' % util.get_stack_trace())
return False
return result
def log_request(self, code='-', size='-'):
"""Override BaseHTTPServer.log_request."""
logging.info('"%s" %s %s',
self.requestline, str(code), str(size))
def log_error(self, *args):
"""Override BaseHTTPServer.log_error."""
# Despite the name, this method is for warnings than for errors.
# For example, HTTP status code is logged by this method.
logging.warn('%s - %s' % (self.address_string(), (args[0] % args[1:])))
def is_cgi(self):
"""Test whether self.path corresponds to a CGI script.
Add extra check that self.path doesn't contains ..
Also check if the file is a executable file or not.
If the file is not executable, it is handled as static file or dir
rather than a CGI script.
"""
if CGIHTTPServer.CGIHTTPRequestHandler.is_cgi(self):
if '..' in self.path:
return False
# strip query parameter from request path
resource_name = self.path.split('?', 2)[0]
# convert resource_name into real path name in filesystem.
scriptfile = self.translate_path(resource_name)
if not os.path.isfile(scriptfile):
return False
if not self.is_executable(scriptfile):
return False
return True
return False
def _configure_logging(options):
logger = logging.getLogger()
logger.setLevel(logging.getLevelName(options.log_level.upper()))
if options.log_file:
handler = logging.handlers.RotatingFileHandler(
options.log_file, 'a', options.log_max, options.log_count)
else:
handler = logging.StreamHandler()
formatter = logging.Formatter(
'[%(asctime)s] [%(levelname)s] %(name)s: %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
def _alias_handlers(dispatcher, websock_handlers_map_file):
"""Set aliases specified in websock_handler_map_file in dispatcher.
Args:
dispatcher: dispatch.Dispatcher instance
websock_handler_map_file: alias map file
"""
fp = open(websock_handlers_map_file)
try:
for line in fp:
if line[0] == '#' or line.isspace():
continue
m = re.match('(\S+)\s+(\S+)', line)
if not m:
logging.warning('Wrong format in map file:' + line)
continue
try:
dispatcher.add_resource_path_alias(
m.group(1), m.group(2))
except dispatch.DispatchError, e:
logging.error(str(e))
finally:
fp.close()
def _main():
parser = optparse.OptionParser()
parser.add_option('-H', '--server-host', '--server_host',
dest='server_host',
default='',
help='server hostname to listen to')
parser.add_option('-p', '--port', dest='port', type='int',
default=common.DEFAULT_WEB_SOCKET_PORT,
help='port to listen to')
parser.add_option('-w', '--websock-handlers', '--websock_handlers',
dest='websock_handlers',
default='.',
help='WebSocket handlers root directory.')
parser.add_option('-m', '--websock-handlers-map-file',
'--websock_handlers_map_file',
dest='websock_handlers_map_file',
default=None,
help=('WebSocket handlers map file. '
'Each line consists of alias_resource_path and '
'existing_resource_path, separated by spaces.'))
parser.add_option('-s', '--scan-dir', '--scan_dir', dest='scan_dir',
default=None,
help=('WebSocket handlers scan directory. '
'Must be a directory under websock_handlers.'))
parser.add_option('-d', '--document-root', '--document_root',
dest='document_root', default='.',
help='Document root directory.')
parser.add_option('-x', '--cgi-paths', '--cgi_paths', dest='cgi_paths',
default=None,
help=('CGI paths relative to document_root.'
'Comma-separated. (e.g -x /cgi,/htbin) '
'Files under document_root/cgi_path are handled '
'as CGI programs. Must be executable.'))
parser.add_option('-t', '--tls', dest='use_tls', action='store_true',
default=False, help='use TLS (wss://)')
parser.add_option('-k', '--private-key', '--private_key',
dest='private_key',
default='', help='TLS private key file.')
parser.add_option('-c', '--certificate', dest='certificate',
default='', help='TLS certificate file.')
parser.add_option('-l', '--log-file', '--log_file', dest='log_file',
default='', help='Log file.')
parser.add_option('--log-level', '--log_level', type='choice',
dest='log_level', default='warn',
choices=['debug', 'info', 'warning', 'warn', 'error',
'critical'],
help='Log level.')
parser.add_option('--log-max', '--log_max', dest='log_max', type='int',
default=_DEFAULT_LOG_MAX_BYTES,
help='Log maximum bytes')
parser.add_option('--log-count', '--log_count', dest='log_count',
type='int', default=_DEFAULT_LOG_BACKUP_COUNT,
help='Log backup count')
parser.add_option('--allow-draft75', dest='allow_draft75',
action='store_true', default=False,
help='Allow draft 75 handshake')
parser.add_option('--strict', dest='strict', action='store_true',
default=False, help='Strictly check handshake request')
parser.add_option('-q', '--queue', dest='request_queue_size', type='int',
default=_DEFAULT_REQUEST_QUEUE_SIZE,
help='request queue size')
options = parser.parse_args()[0]
os.chdir(options.document_root)
_configure_logging(options)
SocketServer.TCPServer.request_queue_size = options.request_queue_size
CGIHTTPServer.CGIHTTPRequestHandler.cgi_directories = []
if options.cgi_paths:
CGIHTTPServer.CGIHTTPRequestHandler.cgi_directories = \
options.cgi_paths.split(',')
if sys.platform in ('cygwin', 'win32'):
cygwin_path = None
# For Win32 Python, it is expected that CYGWIN_PATH
# is set to a directory of cygwin binaries.
# For example, websocket_server.py in Chromium sets CYGWIN_PATH to
# full path of third_party/cygwin/bin.
if 'CYGWIN_PATH' in os.environ:
cygwin_path = os.environ['CYGWIN_PATH']
util.wrap_popen3_for_win(cygwin_path)
def __check_script(scriptpath):
return util.get_script_interp(scriptpath, cygwin_path)
CGIHTTPServer.executable = __check_script
if options.use_tls:
if not _HAS_OPEN_SSL:
logging.critical('To use TLS, install 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
try:
# Share a Dispatcher among request handlers to save time for
# instantiation. Dispatcher can be shared because it is thread-safe.
options.dispatcher = dispatch.Dispatcher(options.websock_handlers,
options.scan_dir)
if options.websock_handlers_map_file:
_alias_handlers(options.dispatcher,
options.websock_handlers_map_file)
_print_warnings_if_any(options.dispatcher)
WebSocketRequestHandler.options = options
WebSocketServer.options = options
server = WebSocketServer((options.server_host, options.port),
WebSocketRequestHandler)
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()
# vi:sts=4 sw=4 et

Просмотреть файл

@ -0,0 +1,53 @@
# Copyright 2010, 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.
"""This file exports public symbols.
"""
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 UnsupportedFrameException
from mod_pywebsocket._stream_hixie75 import StreamHixie75
from mod_pywebsocket._stream_hybi06 import Stream
from mod_pywebsocket._stream_hybi06 import StreamOptions
# These methods are intended to be used by WebSocket client developers to have
# their implementations receive broken data in tests.
from mod_pywebsocket._stream_hybi06 import create_close_frame
from mod_pywebsocket._stream_hybi06 import create_header
from mod_pywebsocket._stream_hybi06 import create_length_header
from mod_pywebsocket._stream_hybi06 import create_ping_frame
from mod_pywebsocket._stream_hybi06 import create_pong_frame
from mod_pywebsocket._stream_hybi06 import create_text_frame
# vi:sts=4 sw=4 et

Просмотреть файл

@ -28,14 +28,31 @@
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""Web Sockets utilities.
"""WebSocket utilities.
"""
import array
# Import hash classes from a module available and recommended for each Python
# version and re-export those symbol. Use sha and md5 module in Python 2.4, and
# hashlib module in Python 2.6.
try:
import hashlib
md5_hash = hashlib.md5
sha1_hash = hashlib.sha1
except ImportError:
import md5
import sha
md5_hash = md5.md5
sha1_hash = sha.sha
import StringIO
import logging
import os
import re
import traceback
import zlib
def get_stack_trace():
@ -72,7 +89,7 @@ def __translate_interp(interp, cygwin_path):
"""
if not cygwin_path:
return interp
m = re.match("^[^ ]*/([^ ]+)( .*)?", interp)
m = re.match('^[^ ]*/([^ ]+)( .*)?', interp)
if m:
cmd = os.path.join(cygwin_path, m.group(1))
return cmd + m.group(2)
@ -96,7 +113,7 @@ def get_script_interp(script_path, cygwin_path=None):
fp = open(script_path)
line = fp.readline()
fp.close()
m = re.match("^#!(.*)", line)
m = re.match('^#!(.*)', line)
if m:
return __translate_interp(m.group(1), cygwin_path)
return None
@ -113,9 +130,200 @@ def wrap_popen3_for_win(cygwin_path):
cmdline = cmd.split(' ')
interp = get_script_interp(cmdline[0], cygwin_path)
if interp:
cmd = interp + " " + cmd
cmd = interp + ' ' + cmd
return __orig_popen3(cmd, mode, bufsize)
os.popen3 = __wrap_popen3
def hexify(s):
return ' '.join(map(lambda x: '%02x' % ord(x), s))
def get_class_logger(o):
return logging.getLogger(
'%s.%s' % (o.__class__.__module__, o.__class__.__name__))
class NoopMasker(object):
def __init__(self):
pass
def mask(self, s):
return s
class RepeatedXorMasker(object):
"""A masking object that applies XOR on the string given to mask method
with the masking bytes given to the constructor repeatedly. This object
remembers the position in the masking bytes the last mask method call ended
and resumes from that point on the next mask method call.
"""
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)
for i in xrange(len(result)):
result[i] ^= self._mask[self._count]
self._count = (self._count + 1) % self._mask_size
return result.tostring()
class DeflateRequest(object):
"""A wrapper class for request object to intercept send and recv to perform
deflate compression and decompression transparently.
"""
def __init__(self, request):
self._request = request
self.connection = DeflateConnection(request.connection)
def __getattribute__(self, name):
if name in ('_request', 'connection'):
return object.__getattribute__(self, name)
return self._request.__getattribute__(name)
def __setattr__(self, name, value):
if name in ('_request', 'connection'):
return object.__setattr__(self, name, value)
return self._request.__setattr__(name, value)
# By making wbits option negative, we can suppress CMF/FLG (2 octet) and
# ADLER32 (4 octet) fields of zlib so that we can use zlib module just as
# deflate library. DICTID won't be added as far as we don't set dictionary.
# LZ77 window of 32K will be used for both compression and decompression.
# For decompression, we can just use 32K to cover any windows size. For
# compression, we use 32K so receivers must use 32K.
#
# Compression level is Z_DEFAULT_COMPRESSION. We don't have to match level
# to decode.
#
# See zconf.h, deflate.cc, inflate.cc of zlib library, and zlibmodule.c of
# Python. See also RFC1950 (ZLIB 3.3).
class DeflateSocket(object):
"""A wrapper class for socket object to intercept send and recv to perform
deflate compression and decompression transparently.
"""
# Size of the buffer passed to recv to receive compressed data.
_RECV_SIZE = 4096
def __init__(self, socket):
self._socket = socket
self._logger = logging.getLogger(
'mod_pywebsocket.util.DeflateSocket')
self._compress = zlib.compressobj(
zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS)
self._decompress = zlib.decompressobj(-zlib.MAX_WBITS)
self._unconsumed = ''
def recv(self, size):
# TODO(tyoshino): Allow call with size=0. It should block until any
# decompressed data is available.
if size <= 0:
raise Exception('Non-positive size passed')
data = ''
while True:
data += self._decompress.decompress(
self._unconsumed, size - len(data))
self._unconsumed = self._decompress.unconsumed_tail
if self._decompress.unused_data:
raise Exception('Non-decompressible data found: %r' %
self._decompress.unused_data)
if len(data) != 0:
break
read_data = self._socket.recv(DeflateSocket._RECV_SIZE)
self._logger.debug('Received compressed: %r' % read_data)
if not read_data:
break
self._unconsumed += read_data
self._logger.debug('Received: %r' % data)
return data
def sendall(self, bytes):
self.send(bytes)
def send(self, bytes):
compressed_bytes = self._compress.compress(bytes)
compressed_bytes += self._compress.flush(zlib.Z_SYNC_FLUSH)
self._socket.sendall(compressed_bytes)
self._logger.debug('Wrote: %r' % bytes)
self._logger.debug('Wrote compressed: %r' % compressed_bytes)
return len(bytes)
class DeflateConnection(object):
"""A wrapper class for request object to intercept write and read to
perform deflate compression and decompression transparently.
"""
# Size of the buffer passed to recv to receive compressed data.
_RECV_SIZE = 4096
def __init__(self, connection):
self._connection = connection
self._logger = logging.getLogger(
'mod_pywebsocket.util.DeflateConnection')
self._compress = zlib.compressobj(
zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS)
self._decompress = zlib.decompressobj(-zlib.MAX_WBITS)
self._unconsumed = ''
def put_bytes(self, bytes):
self.write(bytes)
def read(self, size=-1):
# TODO(tyoshino): Allow call with size=0.
if size == 0 or size < -1:
raise Exception('size must be -1 or positive')
data = ''
while True:
if size < 0:
data += self._decompress.decompress(self._unconsumed)
else:
data += self._decompress.decompress(
self._unconsumed, size - len(data))
self._unconsumed = self._decompress.unconsumed_tail
if self._decompress.unused_data:
raise Exception('Non-decompressible data found: %r' %
self._decompress.unused_data)
if size >= 0 and len(data) != 0:
break
# TODO(tyoshino): Make this read efficient by some workaround.
#
# In 3.0.3 and prior of mod_python, read blocks until length bytes
# was read. We don't know the exact size to read while using
# deflate, so read byte-by-byte.
#
# _StandaloneRequest.read that ultimately performs
# socket._fileobject.read also blocks until length bytes was read
read_data = self._connection.read(1)
self._logger.debug('Read compressed: %r' % read_data)
if not read_data:
break
self._unconsumed += read_data
self._logger.debug('Read: %r' % data)
return data
def write(self, bytes):
compressed_bytes = self._compress.compress(bytes)
compressed_bytes += self._compress.flush(zlib.Z_SYNC_FLUSH)
self._logger.debug('Wrote compressed: %r' % compressed_bytes)
self._logger.debug('Wrote: %r' % bytes)
self._connection.write(compressed_bytes)
# vi:sts=4 sw=4 et

Просмотреть файл

@ -1,6 +1,6 @@
#!/usr/bin/env python
#
# Copyright 2009, Google Inc.
# Copyright 2011, Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
@ -30,7 +30,7 @@
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""Standalone Web Socket server.
"""Standalone WebSocket server.
Use this server to run mod_pywebsocket without Apache HTTP Server.
@ -45,8 +45,8 @@ Usage:
<document_root> is the path to the root directory of HTML files.
<websock_handlers> is the path to the root directory of Web Socket handlers.
See __init__.py for details of <websock_handlers> and how to write Web Socket
<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
@ -80,19 +80,13 @@ try:
except ImportError:
pass
from mod_pywebsocket import common
from mod_pywebsocket import dispatch
from mod_pywebsocket import handshake
from mod_pywebsocket import memorizingfile
from mod_pywebsocket import util
_LOG_LEVELS = {
'debug': logging.DEBUG,
'info': logging.INFO,
'warn': logging.WARN,
'error': logging.ERROR,
'critical': logging.CRITICAL};
_DEFAULT_LOG_MAX_BYTES = 1024 * 256
_DEFAULT_LOG_BACKUP_COUNT = 5
@ -101,6 +95,7 @@ _DEFAULT_REQUEST_QUEUE_SIZE = 128
# 1024 is practically large enough to contain WebSocket handshake lines.
_MAX_MEMORIZED_LINES = 1024
def _print_warnings_if_any(dispatcher):
warnings = dispatcher.source_warnings()
if warnings:
@ -180,9 +175,10 @@ class _StandaloneRequest(object):
class WebSocketServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer):
"""HTTPServer specialized for Web Socket."""
"""HTTPServer specialized for WebSocket."""
SocketServer.ThreadingMixIn.daemon_threads = True
SocketServer.TCPServer.allow_reuse_address = True
def __init__(self, server_address, RequestHandlerClass):
"""Override SocketServer.BaseServer.__init__."""
@ -213,16 +209,21 @@ class WebSocketServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer):
class WebSocketRequestHandler(CGIHTTPServer.CGIHTTPRequestHandler):
"""CGIHTTPRequestHandler specialized for Web Socket."""
"""CGIHTTPRequestHandler specialized for WebSocket."""
def setup(self):
"""Override SocketServer.StreamRequestHandler.setup."""
"""Override SocketServer.StreamRequestHandler.setup to wrap rfile with
MemorizingFile.
"""
# Call superclass's setup to prepare rfile, wfile, etc. See setup
# definition on the root class SocketServer.StreamRequestHandler to
# understand what this does.
CGIHTTPServer.CGIHTTPRequestHandler.setup(self)
self.connection = self.request
self.rfile = memorizingfile.MemorizingFile(
socket._fileobject(self.request, 'rb', self.rbufsize),
max_memorized_lines=_MAX_MEMORIZED_LINES)
self.wfile = socket._fileobject(self.request, 'wb', self.wbufsize)
self.rfile,
max_memorized_lines=_MAX_MEMORIZED_LINES)
def __init__(self, *args, **keywords):
self._request = _StandaloneRequest(
@ -258,6 +259,7 @@ class WebSocketRequestHandler(CGIHTTPServer.CGIHTTPRequestHandler):
# In this case, handshake has been successful, so just log
# the exception and return False.
logging.info('mod_pywebsocket: %s' % e)
logging.info('mod_pywebsocket: %s' % util.get_stack_trace())
return False
except handshake.HandshakeError, e:
# Handshake for ws(s) failed. Assume http(s).
@ -268,7 +270,7 @@ class WebSocketRequestHandler(CGIHTTPServer.CGIHTTPRequestHandler):
return False
except Exception, e:
logging.warning('mod_pywebsocket: %s' % e)
logging.info('mod_pywebsocket: %s' % util.get_stack_trace())
logging.warning('mod_pywebsocket: %s' % util.get_stack_trace())
return False
return result
@ -310,17 +312,18 @@ class WebSocketRequestHandler(CGIHTTPServer.CGIHTTPRequestHandler):
def _configure_logging(options):
logger = logging.getLogger()
logger.setLevel(_LOG_LEVELS[options.log_level])
logger.setLevel(logging.getLevelName(options.log_level.upper()))
if options.log_file:
handler = logging.handlers.RotatingFileHandler(
options.log_file, 'a', options.log_max, options.log_count)
else:
handler = logging.StreamHandler()
formatter = logging.Formatter(
"[%(asctime)s] [%(levelname)s] %(name)s: %(message)s")
'[%(asctime)s] [%(levelname)s] %(name)s: %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
def _alias_handlers(dispatcher, websock_handlers_map_file):
"""Set aliases specified in websock_handler_map_file in dispatcher.
@ -346,7 +349,6 @@ def _alias_handlers(dispatcher, websock_handlers_map_file):
fp.close()
def _main():
parser = optparse.OptionParser()
parser.add_option('-H', '--server-host', '--server_host',
@ -354,22 +356,22 @@ def _main():
default='',
help='server hostname to listen to')
parser.add_option('-p', '--port', dest='port', type='int',
default=handshake.DEFAULT_WEB_SOCKET_PORT,
default=common.DEFAULT_WEB_SOCKET_PORT,
help='port to listen to')
parser.add_option('-w', '--websock-handlers', '--websock_handlers',
dest='websock_handlers',
default='.',
help='Web Socket handlers root directory.')
help='WebSocket handlers root directory.')
parser.add_option('-m', '--websock-handlers-map-file',
'--websock_handlers_map_file',
dest='websock_handlers_map_file',
default=None,
help=('Web Socket handlers map file. '
help=('WebSocket handlers map file. '
'Each line consists of alias_resource_path and '
'existing_resource_path, separated by spaces.'))
parser.add_option('-s', '--scan-dir', '--scan_dir', dest='scan_dir',
default=None,
help=('Web Socket handlers scan directory. '
help=('WebSocket handlers scan directory. '
'Must be a directory under websock_handlers.'))
parser.add_option('-d', '--document-root', '--document_root',
dest='document_root', default='.',
@ -391,7 +393,8 @@ def _main():
default='', help='Log file.')
parser.add_option('--log-level', '--log_level', type='choice',
dest='log_level', default='warn',
choices=['debug', 'info', 'warn', 'error', 'critical'],
choices=['debug', 'info', 'warning', 'warn', 'error',
'critical'],
help='Log level.')
parser.add_option('--log-max', '--log_max', dest='log_max', type='int',
default=_DEFAULT_LOG_MAX_BYTES,
@ -461,7 +464,8 @@ def _main():
WebSocketRequestHandler)
server.serve_forever()
except Exception, e:
logging.critical(str(e))
logging.critical('mod_pywebsocket: %s' % e)
logging.critical('mod_pywebsocket: %s' % util.get_stack_trace())
sys.exit(1)