Skip to content
Permalink
Browse files
2009-11-25 Yuzo Fujishima <yuzo@google.com>
        Reviewed by Eric Seidel.

        Update pywebsocket to 0.4.2

        Update pywebsocket to 0.4.2
        https://bugs.webkit.org/show_bug.cgi?id=31861

        * pywebsocket/example/echo_client.py:
        * pywebsocket/example/echo_wsh.py:
        * pywebsocket/mod_pywebsocket/__init__.py:
        * pywebsocket/mod_pywebsocket/dispatch.py:
        * pywebsocket/mod_pywebsocket/msgutil.py:
        * pywebsocket/mod_pywebsocket/standalone.py:
        * pywebsocket/setup.py:
        * pywebsocket/test/test_dispatch.py:
        * pywebsocket/test/test_msgutil.py:

Canonical link: https://commits.webkit.org/42835@main
git-svn-id: https://svn.webkit.org/repository/webkit/trunk@51406 268f45cc-cd09-0410-ab3c-d52691b4dbfc
  • Loading branch information
eseidel committed Nov 26, 2009
1 parent 38f8b98 commit 4f97684ec16f9e0a94747b3d5d97a65a7e15cb47
Showing 10 changed files with 158 additions and 26 deletions.
@@ -1,3 +1,22 @@
2009-11-25 Yuzo Fujishima <yuzo@google.com>

Reviewed by Eric Seidel.

Update pywebsocket to 0.4.2

Update pywebsocket to 0.4.2
https://bugs.webkit.org/show_bug.cgi?id=31861

* pywebsocket/example/echo_client.py:
* pywebsocket/example/echo_wsh.py:
* pywebsocket/mod_pywebsocket/__init__.py:
* pywebsocket/mod_pywebsocket/dispatch.py:
* pywebsocket/mod_pywebsocket/msgutil.py:
* pywebsocket/mod_pywebsocket/standalone.py:
* pywebsocket/setup.py:
* pywebsocket/test/test_dispatch.py:
* pywebsocket/test/test_msgutil.py:

2009-11-25 Adam Barth <abarth@webkit.org>

Reviewed by Eric Seidel.
@@ -46,6 +46,8 @@
import sys


_TIMEOUT_SEC = 10

_DEFAULT_PORT = 80
_DEFAULT_SECURE_PORT = 443
_UNDEFINED_PORT = -1
@@ -57,6 +59,8 @@
_UPGRADE_HEADER +
_CONNECTION_HEADER)

_GOODBYE_MESSAGE = 'Goodbye'


def _method_line(resource):
return 'GET %s HTTP/1.1\r\n' % resource
@@ -96,13 +100,14 @@ def run(self):
Shake hands and then repeat sending message and receiving its echo.
"""
self._socket = socket.socket()
self._socket.settimeout(self._options.socket_timeout)
try:
self._socket.connect((self._options.server_host,
self._options.server_port))
if self._options.use_tls:
self._socket = _TLSSocket(self._socket)
self._handshake()
for line in self._options.message.split(','):
for line in self._options.message.split(',') + [_GOODBYE_MESSAGE]:
frame = '\x00' + line.encode('utf-8') + '\xff'
self._socket.send(frame)
if self._options.verbose:
@@ -111,7 +116,8 @@ def run(self):
if received != frame:
raise Exception('Incorrect echo: %r' % received)
if self._options.verbose:
print 'Recv: %s' % received[1:-1].decode('utf-8')
print 'Recv: %s' % received[1:-1].decode('utf-8',
'replace')
finally:
self._socket.close()

@@ -166,11 +172,17 @@ def main():
parser.add_option('-r', '--resource', dest='resource', type='string',
default='/echo', help='resource path')
parser.add_option('-m', '--message', dest='message', type='string',
help='comma-separated messages to send')
help=('comma-separated messages to send excluding "%s" '
'that is always sent at the end' %
_GOODBYE_MESSAGE))
parser.add_option('-q', '--quiet', dest='verbose', action='store_false',
default=True, help='suppress messages')
parser.add_option('-t', '--tls', dest='use_tls', action='store_true',
default=False, help='use TLS (wss://)')
parser.add_option('-k', '--socket_timeout', dest='socket_timeout',
type='int', default=_TIMEOUT_SEC,
help='Timeout(sec) for sockets')

(options, unused_args) = parser.parse_args()

# Default port number depends on whether TLS is used.
@@ -31,6 +31,9 @@
from mod_pywebsocket import msgutil


_GOODBYE_MESSAGE = 'Goodbye'


def web_socket_do_extra_handshake(request):
pass # Always accept.

@@ -39,6 +42,8 @@ def web_socket_transfer_data(request):
while True:
line = msgutil.receive_message(request)
msgutil.send_message(request, line)
if line == _GOODBYE_MESSAGE:
return


# vi:sts=4 sw=4 et
@@ -96,6 +96,9 @@
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
standalone.py) is configured to use threads.
"""


@@ -62,7 +62,7 @@ def _normalize_path(path):
"""

path = path.replace('\\', os.path.sep)
path = os.path.abspath(path)
path = os.path.realpath(path)
path = path.replace('\\', '/')
return path

@@ -136,7 +136,8 @@ def __init__(self, root_dir, scan_dir=None):
self._source_warnings = []
if scan_dir is None:
scan_dir = root_dir
if not os.path.realpath(scan_dir).startswith(os.path.realpath(root_dir)):
if not os.path.realpath(scan_dir).startswith(
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)
@@ -182,13 +183,14 @@ def transfer_data(self, request):

def _handler(self, request):
try:
return self._handlers[request.ws_resource]
ws_resource_path = request.ws_resource.split('?', 1)[0]
return self._handlers[ws_resource_path]
except KeyError:
raise DispatchError('No handler for: %r' % request.ws_resource)

def _source_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.
"""

@@ -73,7 +73,9 @@ def receive_message(request):
else:
# The payload is delimited with \xff.
bytes = _read_until(request, '\xff')
message = bytes.decode('utf-8')
# 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.
@@ -38,6 +38,7 @@
python standalone.py [-p <ws_port>] [-w <websock_handlers>]
[-s <scan_dir>]
[-d <document_root>]
... for other options, see _main below ...
<ws_port> is the port number to use for ws:// connection.
@@ -59,6 +60,7 @@
import SimpleHTTPServer
import SocketServer
import logging
import logging.handlers
import optparse
import os
import socket
@@ -75,6 +77,24 @@
import handshake


_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


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."""

@@ -152,22 +172,21 @@ def _create_socket(self):
socket_ = OpenSSL.SSL.Connection(ctx, socket_)
return socket_


class WebSocketRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
"""SimpleHTTPRequestHandler specialized for Web Socket."""

def setup(self):
"""Override SocketServer.StreamRequestHandler.setup."""

self.connection = self.request
self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
self.rfile = socket._fileobject(self.request, 'rb', self.rbufsize)
self.wfile = socket._fileobject(self.request, 'wb', self.wbufsize)

def __init__(self, *args, **keywords):
self._request = _StandaloneRequest(
self, WebSocketRequestHandler.options.use_tls)
self._dispatcher = dispatch.Dispatcher(
WebSocketRequestHandler.options.websock_handlers,
WebSocketRequestHandler.options.scan_dir)
self._dispatcher = WebSocketRequestHandler.options.dispatcher
self._print_warnings_if_any()
self._handshaker = handshake.Handshaker(self._request,
self._dispatcher)
@@ -200,10 +219,35 @@ def parse_request(self):
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 _main():
logging.basicConfig()

def _configure_logging(options):
logger = logging.getLogger()
logger.setLevel(_LOG_LEVELS[options.log_level])
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 _main():
parser = optparse.OptionParser()
parser.add_option('-p', '--port', dest='port', type='int',
default=handshake._DEFAULT_WEB_SOCKET_PORT,
@@ -224,27 +268,51 @@ def _main():
default='', help='TLS private key file.')
parser.add_option('-c', '--certificate', dest='certificate',
default='', help='TLS certificate file.')
parser.add_option('-l', '--log_file', dest='log_file',
default='', help='Log file.')
parser.add_option('--log_level', type='choice', dest='log_level',
default='warn',
choices=['debug', 'info', 'warn', 'error', 'critical'],
help='Log level.')
parser.add_option('--log_max', dest='log_max', type='int',
default=_DEFAULT_LOG_MAX_BYTES,
help='Log maximum bytes')
parser.add_option('--log_count', dest='log_count', type='int',
default=_DEFAULT_LOG_BACKUP_COUNT,
help='Log backup count')
options = parser.parse_args()[0]

os.chdir(options.document_root)

_configure_logging(options)

if options.use_tls:
if not _HAS_OPEN_SSL:
print >>sys.stderr, 'To use TLS, install pyOpenSSL.'
logging.critical('To use TLS, install pyOpenSSL.')
sys.exit(1)
if not options.private_key or not options.certificate:
print >>sys.stderr, ('To use TLS, specify private_key and '
'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

WebSocketRequestHandler.options = options
WebSocketServer.options = options

os.chdir(options.document_root)

server = WebSocketServer(('', options.port), WebSocketRequestHandler)
server.serve_forever()
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)
_print_warnings_if_any(options.dispatcher)

WebSocketRequestHandler.options = options
WebSocketServer.options = options

server = WebSocketServer(('', options.port), WebSocketRequestHandler)
server.serve_forever()
except Exception, e:
logging.critical(str(e))
sys.exit(1)


if __name__ == '__main__':
@@ -56,7 +56,7 @@
name=_PACKAGE_NAME,
packages=[_PACKAGE_NAME],
url='http://code.google.com/p/pywebsocket/',
version='0.4.1',
version='0.4.2',
)


@@ -156,6 +156,20 @@ def test_transfer_data(self):
self.assertEqual('sub/plain_wsh.py is called for /sub/plain, None',
request.connection.written_data())

request = mock.MockRequest(connection=mock.MockConn(''))
request.ws_resource = '/sub/plain?'
request.ws_protocol = None
dispatcher.transfer_data(request)
self.assertEqual('sub/plain_wsh.py is called for /sub/plain?, None',
request.connection.written_data())

request = mock.MockRequest(connection=mock.MockConn(''))
request.ws_resource = '/sub/plain?q=v'
request.ws_protocol = None
dispatcher.transfer_data(request)
self.assertEqual('sub/plain_wsh.py is called for /sub/plain?q=v, None',
request.connection.written_data())

def test_transfer_data_no_handler(self):
dispatcher = dispatch.Dispatcher(_TEST_HANDLERS_DIR, None)
for resource in ['/blank', '/sub/non_callable',
@@ -71,6 +71,13 @@ def test_receive_message_unicode(self):
# U+672c is encoded as e6,9c,ac in UTF-8
self.assertEqual(u'\u672c', msgutil.receive_message(request))

def test_receive_message_erroneous_unicode(self):
# \x80 and \x81 are invalid as UTF-8.
request = _create_request('\x00\x80\x81\xff')
# Invalid characters should be replaced with
# U+fffd REPLACEMENT CHARACTER
self.assertEqual(u'\ufffd\ufffd', msgutil.receive_message(request))

def test_receive_message_discard(self):
request = _create_request('\x80\x06IGNORE\x00Hello\xff'
'\x01DISREGARD\xff\x00World!\xff')

0 comments on commit 4f97684

Please sign in to comment.