Bug 752776 - Upgrade pywebsocket to v631. r=mcmanus

This commit is contained in:
Jason Duell 2012-05-16 17:04:15 -07:00
parent 000ce5b578
commit 7898e1b3f2
13 changed files with 347 additions and 89 deletions

View File

@ -20,7 +20,7 @@ STEPS TO UPDATE MOZILLA TO NEWER PYWEBSOCKET VERSION
- rsync new version into our tree, deleting files that aren't needed any more
(NOTE: this will blow away this file! hg revert it or keep a copy.)
rsync -r --delete dist/ $MOZ_SRC/testing/mochitest/pywebsocket
rsync -rv --delete dist/ $MOZ_SRC/testing/mochitest/pywebsocket
- Get rid of examples/test directory and some cruft:

View File

@ -28,8 +28,11 @@
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""Stream of WebSocket protocol with the framing used by IETF HyBi 00 and
Hixie 75. For Hixie 75 this stream doesn't perform closing handshake.
"""This file provides a class for parsing/building frames of the WebSocket
protocol version HyBi 00 and Hixie 75.
Specification:
http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00
"""
@ -43,7 +46,9 @@ from mod_pywebsocket import util
class StreamHixie75(StreamBase):
"""Stream of WebSocket messages."""
"""A class for parsing/building frames of the WebSocket protocol version
HyBi 00 and Hixie 75.
"""
def __init__(self, request, enable_closing_handshake=False):
"""Construct an instance.

View File

@ -1,4 +1,4 @@
# Copyright 2011, Google Inc.
# Copyright 2012, Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
@ -28,7 +28,11 @@
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""Stream class for IETF HyBi latest WebSocket protocol.
"""This file provides classes and helper functions for parsing/building frames
of the WebSocket protocol (RFC 6455).
Specification:
http://tools.ietf.org/html/rfc6455
"""
@ -238,7 +242,9 @@ class StreamOptions(object):
class Stream(StreamBase):
"""Stream of WebSocket messages."""
"""A class for parsing/building frames of the WebSocket protocol
(RFC 6455).
"""
def __init__(self, request, options):
"""Constructs an instance.
@ -353,8 +359,8 @@ class Stream(StreamBase):
Raises:
BadOperationException: when called on a server-terminated
connection or called with inconsistent message type or binary
parameter.
connection or called with inconsistent message type or
binary parameter.
"""
if self._request.server_terminated:
@ -482,7 +488,11 @@ class Stream(StreamBase):
# - no application data: no code no reason
# - 2 octet of application data: has code but no reason
# - 3 or more octet of application data: both code and reason
if len(message) == 1:
if len(message) == 0:
self._logger.debug('Received close frame (empty body)')
self._request.ws_close_code = (
common.STATUS_NO_STATUS_RECEIVED)
elif len(message) == 1:
raise InvalidFrameException(
'If a close frame has status code, the length of '
'status code must be 2 octet')
@ -501,22 +511,28 @@ class Stream(StreamBase):
if self._request.server_terminated:
self._logger.debug(
'Received ack for server-initiated closing '
'handshake')
'Received ack for server-initiated closing handshake')
return None
self._logger.debug(
'Received client-initiated closing handshake')
code = common.STATUS_NORMAL
code = common.STATUS_NORMAL_CLOSURE
reason = ''
if hasattr(self._request, '_dispatcher'):
dispatcher = self._request._dispatcher
code, reason = dispatcher.passive_closing_handshake(
self._request)
if code is None and reason is not None and len(reason) > 0:
self._logger.warning(
'Handler specified reason despite code being None')
reason = ''
if reason is None:
reason = ''
self._send_closing_handshake(code, reason)
self._logger.debug(
'Sent ack for client-initiated closing handshake')
'Sent ack for client-initiated closing handshake '
'(code=%r, reason=%r)', code, reason)
return None
elif self._original_opcode == common.OPCODE_PING:
try:
@ -565,17 +581,19 @@ class Stream(StreamBase):
'Opcode %d is not supported' % self._original_opcode)
def _send_closing_handshake(self, code, reason):
if code >= (1 << 16) or code < 0:
raise BadOperationException('Status code is out of range')
encoded_reason = reason.encode('utf-8')
if len(encoded_reason) + 2 > 125:
raise BadOperationException(
'Application data size of close frames must be 125 bytes or '
'less')
body = ''
if code is not None:
if code >= (1 << 16) or code < 0:
raise BadOperationException('Status code is out of range')
encoded_reason = reason.encode('utf-8')
if len(encoded_reason) + 2 > 125:
raise BadOperationException(
'Application data size of close frames must be 125 bytes '
'or less')
body = struct.pack('!H', code) + encoded_reason
frame = create_close_frame(
struct.pack('!H', code) + encoded_reason,
body,
self._options.mask_send,
self._options.outgoing_frame_filters)
@ -583,16 +601,37 @@ class Stream(StreamBase):
self._write(frame)
def close_connection(self, code=common.STATUS_NORMAL, reason=''):
"""Closes a WebSocket connection."""
def close_connection(self, code=common.STATUS_NORMAL_CLOSURE, reason=''):
"""Closes a WebSocket connection.
Args:
code: Status code for close frame. If code is None, a close
frame with empty body will be sent.
reason: string representing close reason.
Raises:
BadOperationException: when reason is specified with code None
or reason is not an instance of both str and unicode.
"""
if self._request.server_terminated:
self._logger.debug(
'Requested close_connection but server is already terminated')
return
if code is None:
if reason is not None and len(reason) > 0:
raise BadOperationException(
'close reason must not be specified if code is None')
reason = ''
else:
if not isinstance(reason, str) and not isinstance(reason, unicode):
raise BadOperationException(
'close reason must be an instance of str or unicode')
self._send_closing_handshake(code, reason)
self._logger.debug('Sent server-initiated closing handshake')
self._logger.debug(
'Sent server-initiated closing handshake (code=%r, reason=%r)',
code, reason)
if (code == common.STATUS_GOING_AWAY or
code == common.STATUS_PROTOCOL_ERROR):

View File

@ -1,4 +1,4 @@
# Copyright 2011, Google Inc.
# Copyright 2012, Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
@ -93,21 +93,41 @@ SEC_WEBSOCKET_LOCATION_HEADER = 'Sec-WebSocket-Location'
# Extensions
DEFLATE_STREAM_EXTENSION = 'deflate-stream'
DEFLATE_FRAME_EXTENSION = 'deflate-frame'
X_WEBKIT_DEFLATE_FRAME_EXTENSION = 'x-webkit-deflate-frame'
# Status codes
# Code STATUS_CODE_NOT_AVAILABLE should not be used in actual frames. This code
# is exposed to JavaScript API as pseudo status code which represent actual
# frame does not have status code.
STATUS_NORMAL = 1000
# Code STATUS_NO_STATUS_RECEIVED, STATUS_ABNORMAL_CLOSURE, and
# STATUS_TLS_HANDSHAKE are pseudo codes to indicate specific error cases.
# Could not be used for codes in actual closing frames.
# Application level errors must use codes in the range
# STATUS_USER_REGISTERED_BASE to STATUS_USER_PRIVATE_MAX. The codes in the
# range STATUS_USER_REGISTERED_BASE to STATUS_USER_REGISTERED_MAX are managed
# by IANA. Usually application must define user protocol level errors in the
# range STATUS_USER_PRIVATE_BASE to STATUS_USER_PRIVATE_MAX.
STATUS_NORMAL_CLOSURE = 1000
STATUS_GOING_AWAY = 1001
STATUS_PROTOCOL_ERROR = 1002
STATUS_UNSUPPORTED = 1003
STATUS_CODE_NOT_AVAILABLE = 1005
STATUS_ABNORMAL_CLOSE = 1006
STATUS_INVALID_FRAME_PAYLOAD = 1007
STATUS_UNSUPPORTED_DATA = 1003
STATUS_NO_STATUS_RECEIVED = 1005
STATUS_ABNORMAL_CLOSURE = 1006
STATUS_INVALID_FRAME_PAYLOAD_DATA = 1007
STATUS_POLICY_VIOLATION = 1008
STATUS_MESSAGE_TOO_BIG = 1009
STATUS_MANDATORY_EXT = 1010
STATUS_MANDATORY_EXTENSION = 1010
STATUS_INTERNAL_SERVER_ERROR = 1011
STATUS_TLS_HANDSHAKE = 1015
STATUS_USER_REGISTERED_BASE = 3000
STATUS_USER_REGISTERED_MAX = 3999
STATUS_USER_PRIVATE_BASE = 4000
STATUS_USER_PRIVATE_MAX = 4999
# Following definitions are aliases to keep compatibility. Applications must
# not use these obsoleted definitions anymore.
STATUS_NORMAL = STATUS_NORMAL_CLOSURE
STATUS_UNSUPPORTED = STATUS_UNSUPPORTED_DATA
STATUS_CODE_NOT_AVAILABLE = STATUS_NO_STATUS_RECEIVED
STATUS_ABNORMAL_CLOSE = STATUS_ABNORMAL_CLOSURE
STATUS_INVALID_FRAME_PAYLOAD = STATUS_INVALID_FRAME_PAYLOAD_DATA
STATUS_MANDATORY_EXT = STATUS_MANDATORY_EXTENSION
# HTTP status codes
HTTP_STATUS_BAD_REQUEST = 400

View File

@ -1,4 +1,4 @@
# Copyright 2011, Google Inc.
# Copyright 2012, Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
@ -62,7 +62,7 @@ class DispatchException(Exception):
def _default_passive_closing_handshake_handler(request):
"""Default web_socket_passive_closing_handshake handler."""
return common.STATUS_NORMAL, ''
return common.STATUS_NORMAL_CLOSURE, ''
def _normalize_path(path):
@ -292,7 +292,7 @@ class Dispatcher(object):
raise
except msgutil.BadOperationException, e:
self._logger.debug('%s', e)
request.ws_stream.close_connection(common.STATUS_ABNORMAL_CLOSE)
request.ws_stream.close_connection(common.STATUS_ABNORMAL_CLOSURE)
except msgutil.InvalidFrameException, e:
# InvalidFrameException must be caught before
# ConnectionTerminatedException that catches InvalidFrameException.
@ -300,11 +300,11 @@ class Dispatcher(object):
request.ws_stream.close_connection(common.STATUS_PROTOCOL_ERROR)
except msgutil.UnsupportedFrameException, e:
self._logger.debug('%s', e)
request.ws_stream.close_connection(common.STATUS_UNSUPPORTED)
request.ws_stream.close_connection(common.STATUS_UNSUPPORTED_DATA)
except stream.InvalidUTF8Exception, e:
self._logger.debug('%s', e)
request.ws_stream.close_connection(
common.STATUS_INVALID_FRAME_PAYLOAD)
common.STATUS_INVALID_FRAME_PAYLOAD_DATA)
except msgutil.ConnectionTerminatedException, e:
self._logger.debug('%s', e)
except Exception, e:

View File

@ -72,7 +72,7 @@ _available_processors[common.DEFLATE_STREAM_EXTENSION] = (
class DeflateFrameExtensionProcessor(ExtensionProcessorInterface):
"""WebSocket Per-frame DEFLATE extension processor."""
_WINDOW_BITS_PARAM = 'window_bits'
_WINDOW_BITS_PARAM = 'max_window_bits'
_NO_CONTEXT_TAKEOVER_PARAM = 'no_context_takeover'
def __init__(self, request):
@ -83,6 +83,18 @@ class DeflateFrameExtensionProcessor(ExtensionProcessorInterface):
self._response_window_bits = None
self._response_no_context_takeover = False
# Counters for statistics.
# Total number of outgoing bytes supplied to this filter.
self._total_outgoing_payload_bytes = 0
# Total number of bytes sent to the network after applying this filter.
self._total_filtered_outgoing_payload_bytes = 0
# Total number of bytes received from the network.
self._total_incoming_payload_bytes = 0
# Total number of incoming bytes obtained after applying this filter.
self._total_filtered_incoming_payload_bytes = 0
def get_extension_response(self):
# Any unknown parameter will be just ignored.
@ -110,7 +122,7 @@ class DeflateFrameExtensionProcessor(ExtensionProcessorInterface):
self._compress_outgoing = True
response = common.ExtensionParameter(common.DEFLATE_FRAME_EXTENSION)
response = common.ExtensionParameter(self._request.name())
if self._response_window_bits is not None:
response.add_parameter(
@ -123,7 +135,7 @@ class DeflateFrameExtensionProcessor(ExtensionProcessorInterface):
'Enable %s extension ('
'request: window_bits=%s; no_context_takeover=%r, '
'response: window_wbits=%s; no_context_takeover=%r)' %
(common.DEFLATE_STREAM_EXTENSION,
(self._request.name(),
window_bits,
no_context_takeover,
self._response_window_bits,
@ -171,29 +183,77 @@ class DeflateFrameExtensionProcessor(ExtensionProcessorInterface):
an _OutgoingFilter instance.
"""
original_payload_size = len(frame.payload)
self._total_outgoing_payload_bytes += original_payload_size
if (not self._compress_outgoing or
common.is_control_opcode(frame.opcode)):
self._total_filtered_outgoing_payload_bytes += (
original_payload_size)
return
frame.payload = self._deflater.filter(frame.payload)
frame.rsv1 = 1
filtered_payload_size = len(frame.payload)
self._total_filtered_outgoing_payload_bytes += filtered_payload_size
# Print inf when ratio is not available.
ratio = float('inf')
average_ratio = float('inf')
if original_payload_size != 0:
ratio = float(filtered_payload_size) / original_payload_size
if self._total_outgoing_payload_bytes != 0:
average_ratio = (
float(self._total_filtered_outgoing_payload_bytes) /
self._total_outgoing_payload_bytes)
self._logger.debug(
'Outgoing compress ratio: %f (average: %f)' %
(ratio, average_ratio))
def _incoming_filter(self, frame):
"""Transform incoming frames. This method is called only by
an _IncomingFilter instance.
"""
received_payload_size = len(frame.payload)
self._total_incoming_payload_bytes += received_payload_size
if frame.rsv1 != 1 or common.is_control_opcode(frame.opcode):
self._total_filtered_incoming_payload_bytes += (
received_payload_size)
return
frame.payload = self._inflater.filter(frame.payload)
frame.rsv1 = 0
filtered_payload_size = len(frame.payload)
self._total_filtered_incoming_payload_bytes += filtered_payload_size
# Print inf when ratio is not available.
ratio = float('inf')
average_ratio = float('inf')
if received_payload_size != 0:
ratio = float(received_payload_size) / filtered_payload_size
if self._total_filtered_incoming_payload_bytes != 0:
average_ratio = (
float(self._total_incoming_payload_bytes) /
self._total_filtered_incoming_payload_bytes)
self._logger.debug(
'Incoming compress ratio: %f (average: %f)' %
(ratio, average_ratio))
_available_processors[common.DEFLATE_FRAME_EXTENSION] = (
DeflateFrameExtensionProcessor)
# Adding vendor-prefixed deflate-frame extension.
# TODO(bashi): Remove this after WebKit stops using vender prefix.
_available_processors[common.X_WEBKIT_DEFLATE_FRAME_EXTENSION] = (
DeflateFrameExtensionProcessor)
def get_extension_processor(extension_request):
global _available_processors
processor_class = _available_processors.get(extension_request.name())

View File

@ -83,15 +83,15 @@ def do_handshake(request, dispatcher, allowDraft75=False, strict=False):
handshakers = []
handshakers.append(
('IETF HyBi latest', hybi.Handshaker(request, dispatcher)))
('RFC 6455', hybi.Handshaker(request, dispatcher)))
handshakers.append(
('IETF HyBi 00', hybi00.Handshaker(request, dispatcher)))
('HyBi 00', hybi00.Handshaker(request, dispatcher)))
if allowDraft75:
handshakers.append(
('IETF Hixie 75', draft75.Handshaker(request, dispatcher, strict)))
('Hixie 75', draft75.Handshaker(request, dispatcher, strict)))
for name, handshaker in handshakers:
_LOGGER.debug('Trying %s protocol', name)
_LOGGER.debug('Trying protocol version %s', name)
try:
handshaker.do_handshake()
_LOGGER.info('Established (%s protocol)', name)

View File

@ -89,7 +89,7 @@ def validate_subprotocol(subprotocol, hixie):
Sec-WebSocket-Protocol.
See
- HyBi 10: Section 5.1. and 5.2.2.
- RFC 6455: Section 4.1., 4.2.2., and 4.3.
- HyBi 00: Section 4.1. Opening handshake
- Hixie 75: Section 4.1. Handshake
"""

View File

@ -28,7 +28,12 @@
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""WebSocket HyBi latest opening handshake processor."""
"""This file provides the opening handshake processor for the WebSocket
protocol (RFC 6455).
Specification:
http://tools.ietf.org/html/rfc6455
"""
# Note: request.connection.write is used in this module, even though mod_python
@ -59,7 +64,10 @@ from mod_pywebsocket.stream import StreamOptions
from mod_pywebsocket import util
_BASE64_REGEX = re.compile('^[+/0-9A-Za-z]*=*$')
# Used to validate the value in the Sec-WebSocket-Key header strictly. RFC 4648
# disallows non-zero padding, so the character right before == must be any of
# A, Q, g and w.
_SEC_WEBSOCKET_KEY_REGEX = re.compile('^[+/0-9A-Za-z]{21}[AQgw]==$')
# Defining aliases for values used frequently.
_VERSION_HYBI08 = common.VERSION_HYBI08
@ -85,7 +93,7 @@ def compute_accept(key):
class Handshaker(object):
"""This class performs WebSocket handshake."""
"""Opening handshake processor for the WebSocket protocol (RFC 6455)."""
def __init__(self, request, dispatcher):
"""Construct an instance.
@ -161,7 +169,7 @@ class Handshaker(object):
accept,
util.hexify(accept_binary))
self._logger.debug('IETF HyBi protocol')
self._logger.debug('Protocol version is RFC 6455')
# Setup extension processors.
@ -259,10 +267,6 @@ class Handshaker(object):
def _set_protocol(self):
self._request.ws_protocol = None
# MOZILLA
self._request.sts = None
# /MOZILLA
protocol_header = self._request.headers_in.get(
common.SEC_WEBSOCKET_PROTOCOL_HEADER)
@ -307,7 +311,7 @@ class Handshaker(object):
# module. Because base64 module skips invalid characters, we have
# to do this in advance to make this server strictly reject illegal
# keys.
if _BASE64_REGEX.match(key):
if _SEC_WEBSOCKET_KEY_REGEX.match(key):
decoded_key = base64.b64decode(key)
if len(decoded_key) == 16:
key_is_valid = True
@ -355,11 +359,6 @@ class Handshaker(object):
response.append(format_header(
common.SEC_WEBSOCKET_EXTENSIONS_HEADER,
format_extensions(self._request.ws_extensions)))
# MOZILLA: Add HSTS header if requested to
if self._request.sts is not None:
response.append(format_header("Strict-Transport-Security",
self._request.sts))
# /MOZILLA
response.append('\r\n')
raw_response = ''.join(response)

View File

@ -28,7 +28,12 @@
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""WebSocket initial handshake hander for HyBi 00 protocol."""
"""This file provides the opening handshake processor for the WebSocket
protocol version HyBi 00.
Specification:
http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00
"""
# Note: request.connection.write/read are used in this module, even though
@ -61,7 +66,8 @@ _MANDATORY_HEADERS = [
class Handshaker(object):
"""This class performs WebSocket handshake."""
"""Opening handshake processor for the WebSocket protocol version HyBi 00.
"""
def __init__(self, request, dispatcher):
"""Construct an instance.
@ -139,7 +145,7 @@ class Handshaker(object):
(common.SEC_WEBSOCKET_DRAFT_HEADER,
draft))
self._logger.debug('IETF HyBi 00 protocol')
self._logger.debug('Protocol version is HyBi 00')
self._request.ws_version = common.VERSION_HYBI00
self._request.ws_stream = StreamHixie75(self._request, True)

View File

@ -82,7 +82,15 @@ def receive_message(request):
Args:
request: mod_python request.
Raises:
BadOperationException: when client already terminated.
InvalidFrameException: when client send invalid frame.
UnsupportedFrameException: when client send unsupported frame e.g. some
of reserved bit is set but no extension can
recognize it.
InvalidUTF8Exception: when client send a text frame containing any
invalid UTF-8 string.
ConnectionTerminatedException: when the connection is closed
unexpectedly.
BadOperationException: when client already terminated.
"""
return request.ws_stream.receive_message()

View File

@ -177,9 +177,16 @@ class RepeatedXorMasker(object):
def mask(self, s):
result = array.array('B')
result.fromstring(s)
# Use temporary local variables to eliminate the cost to access
# attributes
count = self._count
mask = self._mask
mask_size = self._mask_size
for i in xrange(len(result)):
result[i] ^= self._mask[self._count]
self._count = (self._count + 1) % self._mask_size
result[i] ^= mask[count]
count = (count + 1) % mask_size
self._count = count
return result.tostring()

View File

@ -32,6 +32,8 @@
"""Standalone WebSocket server.
BASIC USAGE
Use this server to run mod_pywebsocket without Apache HTTP Server.
Usage:
@ -52,11 +54,39 @@ handlers. If this path is relative, <document_root> is used as the base.
<scan_dir> is a path under the root directory. If specified, only the
handlers under scan_dir are scanned. This is useful in saving scan time.
Note:
CONFIGURATION FILE
You can also write a configuration file and use it by specifying the path to
the configuration file by --config option. Please write a configuration file
following the documentation of the Python ConfigParser library. Name of each
entry must be the long version argument name. E.g. to set log level to debug,
add the following line:
log_level=debug
For options which doesn't take value, please add some fake value. E.g. for
--tls option, add the following line:
tls=True
Note that tls will be enabled even if you write tls=False as the value part is
fake.
When both a command line argument and a configuration file entry are set for
the same configuration item, the command line value will override one in the
configuration file.
THREADING
This server is derived from SocketServer.ThreadingMixIn. Hence a thread is
used for each request.
SECURITY WARNING: This uses CGIHTTPServer and CGIHTTPServer is not secure.
SECURITY WARNING
This uses CGIHTTPServer and CGIHTTPServer is not secure.
It may execute arbitrary Python code or external programs. It should not be
used outside a firewall.
"""
@ -65,6 +95,7 @@ import BaseHTTPServer
import CGIHTTPServer
import SimpleHTTPServer
import SocketServer
import ConfigParser
import httplib
import logging
import logging.handlers
@ -77,13 +108,17 @@ import sys
import threading
import time
_HAS_SSL = False
_HAS_OPEN_SSL = False
try:
import OpenSSL.SSL
_HAS_OPEN_SSL = True
import ssl
_HAS_SSL = True
except ImportError:
pass
try:
import OpenSSL.SSL
_HAS_OPEN_SSL = True
except ImportError:
pass
from mod_pywebsocket import common
from mod_pywebsocket import dispatch
@ -193,6 +228,28 @@ class _StandaloneRequest(object):
'Drained data following close frame: %r', drained_data)
class _StandaloneSSLConnection(object):
"""A wrapper class for OpenSSL.SSL.Connection to provide makefile method
which is not supported by the class.
"""
def __init__(self, connection):
self._connection = connection
def __getattribute__(self, name):
if name in ('_connection', 'makefile'):
return object.__getattribute__(self, name)
return self._connection.__getattribute__(name)
def __setattr__(self, name, value):
if name in ('_connection', 'makefile'):
return object.__setattr__(self, name, value)
return self._connection.__setattr__(name, value)
def makefile(self, mode='r', bufsize=-1):
return socket._fileobject(self._connection, mode, bufsize)
class WebSocketServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer):
"""HTTPServer specialized for WebSocket."""
@ -253,12 +310,18 @@ class WebSocketServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer):
self._logger.info('Skip by failure: %r', e)
continue
if self.websocket_server_options.use_tls:
ctx = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD)
ctx.use_privatekey_file(
self.websocket_server_options.private_key)
ctx.use_certificate_file(
self.websocket_server_options.certificate)
socket_ = OpenSSL.SSL.Connection(ctx, socket_)
if _HAS_SSL:
socket_ = ssl.wrap_socket(socket_,
keyfile=self.websocket_server_options.private_key,
certfile=self.websocket_server_options.certificate,
ssl_version=ssl.PROTOCOL_SSLv23)
if _HAS_OPEN_SSL:
ctx = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD)
ctx.use_privatekey_file(
self.websocket_server_options.private_key)
ctx.use_certificate_file(
self.websocket_server_options.certificate)
socket_ = OpenSSL.SSL.Connection(ctx, socket_)
self._sockets.append((socket_, addrinfo))
def server_bind(self):
@ -328,6 +391,18 @@ class WebSocketServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer):
util.get_stack_trace())
# Note: client_address is a tuple.
def get_request(self):
"""Override TCPServer.get_request to wrap OpenSSL.SSL.Connection
object with _StandaloneSSLConnection to provide makefile method. We
cannot substitute OpenSSL.SSL.Connection.makefile since it's readonly
attribute.
"""
accepted_socket, client_address = self.socket.accept()
if self.websocket_server_options.use_tls and _HAS_OPEN_SSL:
accepted_socket = _StandaloneSSLConnection(accepted_socket)
return accepted_socket, client_address
def serve_forever(self, poll_interval=0.5):
"""Override SocketServer.BaseServer.serve_forever."""
@ -413,7 +488,9 @@ class WebSocketRequestHandler(CGIHTTPServer.CGIHTTPRequestHandler):
# it needs variables set by CGIHTTPRequestHandler.parse_request.
#
# Variables set by this method will be also used by WebSocket request
# handling. See _StandaloneRequest.get_request, etc.
# handling (self.path, self.command, self.requestline, etc. See also
# how _StandaloneRequest's members are implemented using these
# attributes).
if not CGIHTTPServer.CGIHTTPRequestHandler.parse_request(self):
return False
host, port, resource = http_header_util.parse_uri(self.path)
@ -567,6 +644,11 @@ def _alias_handlers(dispatcher, websock_handlers_map_file):
def _build_option_parser():
parser = optparse.OptionParser()
parser.add_option('--config', dest='config_file', type='string',
default=None,
help=('Path to configuration file. See the file comment '
'at the top of this file for the configuration '
'file format'))
parser.add_option('-H', '--server-host', '--server_host',
dest='server_host',
default='',
@ -676,14 +758,46 @@ class ThreadMonitor(threading.Thread):
time.sleep(self._interval_in_sec)
def _main(args=None):
def _parse_args_and_config(args):
parser = _build_option_parser()
options, args = parser.parse_args(args=args)
if args:
logging.critical('Unrecognized positional arguments: %r', args)
# First, parse options without configuration file.
temporary_options, temporary_args = parser.parse_args(args=args)
if temporary_args:
logging.critical(
'Unrecognized positional arguments: %r', temporary_args)
sys.exit(1)
if temporary_options.config_file:
try:
config_fp = open(temporary_options.config_file, 'r')
except IOError, e:
logging.critical(
'Failed to open configuration file %r: %r',
temporary_options.config_file,
e)
sys.exit(1)
config_parser = ConfigParser.SafeConfigParser()
config_parser.readfp(config_fp)
config_fp.close()
args_from_config = []
for name, value in config_parser.items('pywebsocket'):
args_from_config.append('--' + name)
args_from_config.append(value)
if args is None:
args = args_from_config
else:
args = args_from_config + args
return parser.parse_args(args=args)
else:
return temporary_options, temporary_args
def _main(args=None):
options, args = _parse_args_and_config(args=args)
os.chdir(options.document_root)
_configure_logging(options)
@ -710,8 +824,8 @@ def _main(args=None):
options.is_executable_method = __check_script
if options.use_tls:
if not _HAS_OPEN_SSL:
logging.critical('To use TLS, install pyOpenSSL.')
if not (_HAS_SSL or _HAS_OPEN_SSL):
logging.critical('TLS support requires ssl or pyOpenSSL.')
sys.exit(1)
if not options.private_key or not options.certificate:
logging.critical(
@ -750,7 +864,7 @@ def _main(args=None):
if __name__ == '__main__':
_main()
_main(sys.argv[1:])
# vi:sts=4 sw=4 et