aboutsummaryrefslogtreecommitdiffstats
path: root/tests/wpt/web-platform-tests/tools/pywebsocket/mod_pywebsocket/standalone.py
diff options
context:
space:
mode:
authorWPT Sync Bot <josh+wptsync@joshmatthews.net>2020-02-25 08:19:47 +0000
committerWPT Sync Bot <josh+wptsync@joshmatthews.net>2020-02-25 11:29:59 +0000
commit5a55ae1b13497d1ae78ff01596f4342dce16d9be (patch)
treef0ca23f9676c398c4c548fa0a652f556b530ff94 /tests/wpt/web-platform-tests/tools/pywebsocket/mod_pywebsocket/standalone.py
parentc9c5f8b9e545ae292d4ccb256758cb681a744612 (diff)
downloadservo-5a55ae1b13497d1ae78ff01596f4342dce16d9be.tar.gz
servo-5a55ae1b13497d1ae78ff01596f4342dce16d9be.zip
Update web-platform-tests to revision 2be2d7e3abcde5baded3448b85d0bb88e58d3cf7
Diffstat (limited to 'tests/wpt/web-platform-tests/tools/pywebsocket/mod_pywebsocket/standalone.py')
-rwxr-xr-xtests/wpt/web-platform-tests/tools/pywebsocket/mod_pywebsocket/standalone.py1208
1 files changed, 0 insertions, 1208 deletions
diff --git a/tests/wpt/web-platform-tests/tools/pywebsocket/mod_pywebsocket/standalone.py b/tests/wpt/web-platform-tests/tools/pywebsocket/mod_pywebsocket/standalone.py
deleted file mode 100755
index 2c878606488..00000000000
--- a/tests/wpt/web-platform-tests/tools/pywebsocket/mod_pywebsocket/standalone.py
+++ /dev/null
@@ -1,1208 +0,0 @@
-#!/usr/bin/env python
-#
-# Copyright 2012, Google Inc.
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are
-# met:
-#
-# * Redistributions of source code must retain the above copyright
-# notice, this list of conditions and the following disclaimer.
-# * 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 file to launch pywebsocket without Apache HTTP Server.
-
-
-BASIC USAGE
-===========
-
-Go to the src directory and run
-
- $ python mod_pywebsocket/standalone.py [-p <ws_port>]
- [-w <websock_handlers>]
- [-d <document_root>]
-
-<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.
-If not specified, <document_root> will be used. See __init__.py (or
-run $ pydoc mod_pywebsocket) for how to write WebSocket handlers.
-
-For more detail and other options, run
-
- $ python mod_pywebsocket/standalone.py --help
-
-or see _build_option_parser method below.
-
-For trouble shooting, adding "--log_level debug" might help you.
-
-
-TRY DEMO
-========
-
-Go to the src directory and run standalone.py with -d option to set the
-document root to the directory containing example HTMLs and handlers like this:
-
- $ cd src
- $ PYTHONPATH=. python mod_pywebsocket/standalone.py -d example
-
-to launch pywebsocket with the sample handler and html on port 80. Open
-http://localhost/console.html, click the connect button, type something into
-the text box next to the send button and click the send button. If everything
-is working, you'll see the message you typed echoed by the server.
-
-
-USING TLS
-=========
-
-To run the standalone server with TLS support, run it with -t, -k, and -c
-options. When TLS is enabled, the standalone server accepts only TLS connection.
-
-Note that when ssl module is used and the key/cert location is incorrect,
-TLS connection silently fails while pyOpenSSL fails on startup.
-
-Example:
-
- $ PYTHONPATH=. python mod_pywebsocket/standalone.py \
- -d example \
- -p 10443 \
- -t \
- -c ../test/cert/cert.pem \
- -k ../test/cert/key.pem \
-
-Note that when passing a relative path to -c and -k option, it will be resolved
-using the document root directory as the base.
-
-
-USING CLIENT AUTHENTICATION
-===========================
-
-To run the standalone server with TLS client authentication support, run it with
---tls-client-auth and --tls-client-ca options in addition to ones required for
-TLS support.
-
-Example:
-
- $ PYTHONPATH=. python mod_pywebsocket/standalone.py -d example -p 10443 -t \
- -c ../test/cert/cert.pem -k ../test/cert/key.pem \
- --tls-client-auth \
- --tls-client-ca=../test/cert/cacert.pem
-
-Note that when passing a relative path to --tls-client-ca option, it will be
-resolved using the document root directory as the base.
-
-
-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.
-It may execute arbitrary Python code or external programs. It should not be
-used outside a firewall.
-"""
-
-from six.moves import BaseHTTPServer
-from six.moves import CGIHTTPServer
-from six.moves import SimpleHTTPServer
-from six.moves import socketserver
-from six.moves import configparser
-import base64
-from six.moves import http_client
-import logging
-import logging.handlers
-import optparse
-import os
-import re
-import select
-import socket
-import sys
-import threading
-import time
-from six.moves import urllib
-
-from mod_pywebsocket import common
-from mod_pywebsocket import dispatch
-from mod_pywebsocket import handshake
-from mod_pywebsocket import http_header_util
-from mod_pywebsocket import memorizingfile
-from mod_pywebsocket import util
-from mod_pywebsocket.xhr_benchmark_handler import XHRBenchmarkHandler
-
-
-_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
-
-# Constants for the --tls_module flag.
-_TLS_BY_STANDARD_MODULE = 'ssl'
-_TLS_BY_PYOPENSSL = 'pyopenssl'
-
-
-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._logger = util.get_class_logger(self)
-
- self._request_handler = request_handler
- self.connection = _StandaloneConnection(request_handler)
- self._use_tls = use_tls
- self.headers_in = request_handler.headers
-
- def get_uri(self):
- """Getter to mimic request.uri.
-
- This method returns the raw data at the Request-URI part of the
- Request-Line, while the uri method on the request object of mod_python
- returns the path portion after parsing the raw data. This behavior is
- kept for compatibility.
- """
-
- return self._request_handler.path
- uri = property(get_uri)
-
- def get_unparsed_uri(self):
- """Getter to mimic request.unparsed_uri."""
-
- return self._request_handler.path
- unparsed_uri = property(get_unparsed_uri)
-
- def get_method(self):
- """Getter to mimic request.method."""
-
- return self._request_handler.command
- method = property(get_method)
-
- def get_protocol(self):
- """Getter to mimic request.protocol."""
-
- return self._request_handler.request_version
- protocol = property(get_protocol)
-
- def is_https(self):
- """Mimic request.is_https()."""
-
- return self._use_tls
-
-
-def _import_ssl():
- global ssl
- try:
- import ssl
- return True
- except ImportError:
- return False
-
-
-def _import_pyopenssl():
- global OpenSSL
- try:
- import OpenSSL.SSL
- return True
- except ImportError:
- return False
-
-
-class _StandaloneSSLConnection(object):
- """A wrapper class for OpenSSL.SSL.Connection to
- - provide makefile method which is not supported by the class
- - tweak shutdown method since OpenSSL.SSL.Connection.shutdown doesn't
- accept the "how" argument.
- - convert SysCallError exceptions that its recv method may raise into a
- return value of '', meaning EOF. We cannot overwrite the recv method on
- self._connection since it's immutable.
- """
-
- _OVERRIDDEN_ATTRIBUTES = ['_connection', 'makefile', 'shutdown', 'recv']
-
- def __init__(self, connection):
- self._connection = connection
-
- def __getattribute__(self, name):
- if name in _StandaloneSSLConnection._OVERRIDDEN_ATTRIBUTES:
- return object.__getattribute__(self, name)
- return self._connection.__getattribute__(name)
-
- def __setattr__(self, name, value):
- if name in _StandaloneSSLConnection._OVERRIDDEN_ATTRIBUTES:
- return object.__setattr__(self, name, value)
- return self._connection.__setattr__(name, value)
-
- def makefile(self, mode='r', bufsize=-1):
- return socket._fileobject(self, mode, bufsize)
-
- def shutdown(self, unused_how):
- self._connection.shutdown()
-
- def recv(self, bufsize, flags=0):
- if flags != 0:
- raise ValueError('Non-zero flags not allowed')
-
- try:
- return self._connection.recv(bufsize)
- except OpenSSL.SSL.SysCallError as e:
- if e.args[0] == -1:
- # Suppress "unexpected EOF" exception. See the OpenSSL document
- # for SSL_get_error.
- return ''
- raise
-
-
-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(r'(\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.DispatchException as e:
- logging.error(str(e))
- finally:
- fp.close()
-
-
-class WebSocketServer(socketserver.ThreadingMixIn, BaseHTTPServer.HTTPServer):
- """HTTPServer specialized for WebSocket."""
-
- # Overrides socketserver.ThreadingMixIn.daemon_threads
- daemon_threads = True
- # Overrides BaseHTTPServer.HTTPServer.allow_reuse_address
- allow_reuse_address = True
-
- def __init__(self, options):
- """Override socketserver.TCPServer.__init__ to set SSL enabled
- socket object to self.socket before server_bind and server_activate,
- if necessary.
- """
-
- # 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,
- options.allow_handlers_outside_root_dir)
- if options.websock_handlers_map_file:
- _alias_handlers(options.dispatcher,
- options.websock_handlers_map_file)
- warnings = options.dispatcher.source_warnings()
- if warnings:
- for warning in warnings:
- logging.warning('Warning in source loading: %s' % warning)
-
- self._logger = util.get_class_logger(self)
-
- self.request_queue_size = options.request_queue_size
- self.__ws_is_shut_down = threading.Event()
- self.__ws_serving = False
-
- socketserver.BaseServer.__init__(
- self, (options.server_host, options.port), WebSocketRequestHandler)
-
- # Expose the options object to allow handler objects access it. We name
- # it with websocket_ prefix to avoid conflict.
- self.websocket_server_options = options
-
- self._create_sockets()
- self.server_bind()
- self.server_activate()
-
- def _create_sockets(self):
- self.server_name, self.server_port = self.server_address
- self._sockets = []
- if not self.server_name:
- # On platforms that doesn't support IPv6, the first bind fails.
- # On platforms that supports IPv6
- # - If it binds both IPv4 and IPv6 on call with AF_INET6, the
- # first bind succeeds and the second fails (we'll see 'Address
- # already in use' error).
- # - If it binds only IPv6 on call with AF_INET6, both call are
- # expected to succeed to listen both protocol.
- addrinfo_array = [
- (socket.AF_INET6, socket.SOCK_STREAM, '', '', ''),
- (socket.AF_INET, socket.SOCK_STREAM, '', '', '')]
- else:
- addrinfo_array = socket.getaddrinfo(self.server_name,
- self.server_port,
- socket.AF_UNSPEC,
- socket.SOCK_STREAM,
- socket.IPPROTO_TCP)
- for addrinfo in addrinfo_array:
- self._logger.info('Create socket on: %r', addrinfo)
- family, socktype, proto, canonname, sockaddr = addrinfo
- try:
- socket_ = socket.socket(family, socktype)
- except Exception as e:
- self._logger.info('Skip by failure: %r', e)
- continue
- server_options = self.websocket_server_options
- if server_options.use_tls:
- # For the case of _HAS_OPEN_SSL, we do wrapper setup after
- # accept.
- if server_options.tls_module == _TLS_BY_STANDARD_MODULE:
- if server_options.tls_client_auth:
- if server_options.tls_client_cert_optional:
- client_cert_ = ssl.CERT_OPTIONAL
- else:
- client_cert_ = ssl.CERT_REQUIRED
- else:
- client_cert_ = ssl.CERT_NONE
- socket_ = ssl.wrap_socket(socket_,
- keyfile=server_options.private_key,
- certfile=server_options.certificate,
- ssl_version=ssl.PROTOCOL_SSLv23,
- ca_certs=server_options.tls_client_ca,
- cert_reqs=client_cert_,
- do_handshake_on_connect=False)
- self._sockets.append((socket_, addrinfo))
-
- def server_bind(self):
- """Override socketserver.TCPServer.server_bind to enable multiple
- sockets bind.
- """
-
- failed_sockets = []
-
- for socketinfo in self._sockets:
- socket_, addrinfo = socketinfo
- self._logger.info('Bind on: %r', addrinfo)
- if self.allow_reuse_address:
- socket_.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
- try:
- socket_.bind(self.server_address)
- except Exception as e:
- self._logger.info('Skip by failure: %r', e)
- socket_.close()
- failed_sockets.append(socketinfo)
- if self.server_address[1] == 0:
- # The operating system assigns the actual port number for port
- # number 0. This case, the second and later sockets should use
- # the same port number. Also self.server_port is rewritten
- # because it is exported, and will be used by external code.
- self.server_address = (
- self.server_name, socket_.getsockname()[1])
- self.server_port = self.server_address[1]
- self._logger.info('Port %r is assigned', self.server_port)
-
- for socketinfo in failed_sockets:
- self._sockets.remove(socketinfo)
-
- def server_activate(self):
- """Override socketserver.TCPServer.server_activate to enable multiple
- sockets listen.
- """
-
- failed_sockets = []
-
- for socketinfo in self._sockets:
- socket_, addrinfo = socketinfo
- self._logger.info('Listen on: %r', addrinfo)
- try:
- socket_.listen(self.request_queue_size)
- except Exception as e:
- self._logger.info('Skip by failure: %r', e)
- socket_.close()
- failed_sockets.append(socketinfo)
-
- for socketinfo in failed_sockets:
- self._sockets.remove(socketinfo)
-
- if len(self._sockets) == 0:
- self._logger.critical(
- 'No sockets activated. Use info log level to see the reason.')
-
- def server_close(self):
- """Override socketserver.TCPServer.server_close to enable multiple
- sockets close.
- """
-
- for socketinfo in self._sockets:
- socket_, addrinfo = socketinfo
- self._logger.info('Close on: %r', addrinfo)
- socket_.close()
-
- def fileno(self):
- """Override socketserver.TCPServer.fileno."""
-
- self._logger.critical('Not supported: fileno')
- return self._sockets[0][0].fileno()
-
- def handle_error(self, request, client_address):
- """Override socketserver.handle_error."""
-
- self._logger.error(
- 'Exception in processing request from: %r\n%s',
- client_address,
- util.get_stack_trace())
- # Note: client_address is a tuple.
-
- def get_request(self):
- """Override TCPServer.get_request to wrap OpenSSL.SSL.Connection
- object with _StandaloneSSLConnection to provide makefile method. We
- cannot substitute OpenSSL.SSL.Connection.makefile since it's readonly
- attribute.
- """
-
- accepted_socket, client_address = self.socket.accept()
-
- server_options = self.websocket_server_options
- if server_options.use_tls:
- if server_options.tls_module == _TLS_BY_STANDARD_MODULE:
- try:
- accepted_socket.do_handshake()
- except ssl.SSLError as e:
- self._logger.debug('%r', e)
- raise
-
- # Print cipher in use. Handshake is done on accept.
- self._logger.debug('Cipher: %s', accepted_socket.cipher())
- self._logger.debug('Client cert: %r',
- accepted_socket.getpeercert())
- elif server_options.tls_module == _TLS_BY_PYOPENSSL:
- # We cannot print the cipher in use. pyOpenSSL doesn't provide
- # any method to fetch that.
-
- ctx = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD)
- ctx.use_privatekey_file(server_options.private_key)
- ctx.use_certificate_file(server_options.certificate)
-
- def default_callback(conn, cert, errnum, errdepth, ok):
- return ok == 1
-
- # See the OpenSSL document for SSL_CTX_set_verify.
- if server_options.tls_client_auth:
- verify_mode = OpenSSL.SSL.VERIFY_PEER
- if not server_options.tls_client_cert_optional:
- verify_mode |= OpenSSL.SSL.VERIFY_FAIL_IF_NO_PEER_CERT
- ctx.set_verify(verify_mode, default_callback)
- ctx.load_verify_locations(server_options.tls_client_ca,
- None)
- else:
- ctx.set_verify(OpenSSL.SSL.VERIFY_NONE, default_callback)
-
- accepted_socket = OpenSSL.SSL.Connection(ctx, accepted_socket)
- accepted_socket.set_accept_state()
-
- # Convert SSL related error into socket.error so that
- # SocketServer ignores them and keeps running.
- #
- # TODO(tyoshino): Convert all kinds of errors.
- try:
- accepted_socket.do_handshake()
- except OpenSSL.SSL.Error as e:
- # Set errno part to 1 (SSL_ERROR_SSL) like the ssl module
- # does.
- self._logger.debug('%r', e)
- raise socket.error(1, '%r' % e)
- cert = accepted_socket.get_peer_certificate()
- if cert is not None:
- self._logger.debug('Client cert subject: %r',
- cert.get_subject().get_components())
- accepted_socket = _StandaloneSSLConnection(accepted_socket)
- else:
- raise ValueError('No TLS support module is available')
-
- return accepted_socket, client_address
-
- def serve_forever(self, poll_interval=0.5):
- """Override socketserver.BaseServer.serve_forever."""
-
- self.__ws_serving = True
- self.__ws_is_shut_down.clear()
- handle_request = self.handle_request
- if hasattr(self, '_handle_request_noblock'):
- handle_request = self._handle_request_noblock
- else:
- self._logger.warning('Fallback to blocking request handler')
- try:
- while self.__ws_serving:
- r, w, e = select.select(
- [socket_[0] for socket_ in self._sockets],
- [], [], poll_interval)
- for socket_ in r:
- self.socket = socket_
- handle_request()
- self.socket = None
- finally:
- self.__ws_is_shut_down.set()
-
- def shutdown(self):
- """Override socketserver.BaseServer.shutdown."""
-
- self.__ws_serving = False
- self.__ws_is_shut_down.wait()
-
-
-class WebSocketRequestHandler(CGIHTTPServer.CGIHTTPRequestHandler):
- """CGIHTTPRequestHandler specialized for WebSocket."""
-
- # Use http_client.HTTPMessage instead of mimetools.Message.
- MessageClass = http_client.HTTPMessage
-
- protocol_version = "HTTP/1.1"
-
- def setup(self):
- """Override socketserver.StreamRequestHandler.setup to wrap rfile
- with MemorizingFile.
-
- This method will be called by BaseRequestHandler's constructor
- before calling BaseHTTPRequestHandler.handle.
- BaseHTTPRequestHandler.handle will call
- BaseHTTPRequestHandler.handle_one_request and it will call
- WebSocketRequestHandler.parse_request.
- """
-
- # 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, request, client_address, server):
- self._logger = util.get_class_logger(self)
-
- self._options = server.websocket_server_options
-
- # Overrides CGIHTTPServerRequestHandler.cgi_directories.
- self.cgi_directories = self._options.cgi_directories
- # Replace CGIHTTPRequestHandler.is_executable method.
- if self._options.is_executable_method is not None:
- self.is_executable = self._options.is_executable_method
-
- # This actually calls BaseRequestHandler.__init__.
- CGIHTTPServer.CGIHTTPRequestHandler.__init__(
- self, request, client_address, server)
-
- def parse_request(self):
- """Override BaseHTTPServer.BaseHTTPRequestHandler.parse_request.
-
- Return True to continue processing for HTTP(S), False otherwise.
-
- See BaseHTTPRequestHandler.handle_one_request method which calls
- this method to understand how the return value will be handled.
- """
-
- # We hook parse_request method, but also call the original
- # CGIHTTPRequestHandler.parse_request since when we return False,
- # CGIHTTPRequestHandler.handle_one_request continues processing and
- # it needs variables set by CGIHTTPRequestHandler.parse_request.
- #
- # Variables set by this method will be also used by WebSocket request
- # handling (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
-
- if self.command == "CONNECT":
- self.send_response(200, "Connected")
- self.send_header("Connection", "keep-alive")
- self.end_headers()
- return False
-
- if self._options.use_basic_auth:
- auth = self.headers.getheader('Authorization')
- if auth != self._options.basic_auth_credential:
- self.send_response(401)
- self.send_header('WWW-Authenticate',
- 'Basic realm="Pywebsocket"')
- self.end_headers()
- self._logger.info('Request basic authentication')
- return False
-
- # Special paths for XMLHttpRequest benchmark
- xhr_benchmark_helper_prefix = '/073be001e10950692ccbf3a2ad21c245'
- parsed_path = urllib.parse.urlsplit(self.path)
- if parsed_path.path == (xhr_benchmark_helper_prefix + '_send'):
- xhr_benchmark_handler = XHRBenchmarkHandler(
- self.headers, self.rfile, self.wfile)
- xhr_benchmark_handler.do_send()
- return False
- if parsed_path.path == (xhr_benchmark_helper_prefix + '_receive'):
- xhr_benchmark_handler = XHRBenchmarkHandler(
- self.headers, self.rfile, self.wfile)
- xhr_benchmark_handler.do_receive_and_parse()
- return False
- if parsed_path.path == (xhr_benchmark_helper_prefix +
- '_receive_getnocache'):
- xhr_benchmark_handler = XHRBenchmarkHandler(
- self.headers, self.rfile, self.wfile)
- xhr_benchmark_handler.do_receive(
- int(parsed_path.query), False, False)
- return False
- if parsed_path.path == (xhr_benchmark_helper_prefix +
- '_receive_getcache'):
- xhr_benchmark_handler = XHRBenchmarkHandler(
- self.headers, self.rfile, self.wfile)
- xhr_benchmark_handler.do_receive(
- int(parsed_path.query), False, True)
- return False
-
- host, port, resource = http_header_util.parse_uri(self.path)
- if resource is None:
- self._logger.info('Invalid URI: %r', self.path)
- self._logger.info('Fallback to CGIHTTPRequestHandler')
- return True
- server_options = self.server.websocket_server_options
- if host is not None:
- validation_host = server_options.validation_host
- if validation_host is not None and host != validation_host:
- self._logger.info('Invalid host: %r (expected: %r)',
- host,
- validation_host)
- self._logger.info('Fallback to CGIHTTPRequestHandler')
- return True
- if port is not None:
- validation_port = server_options.validation_port
- if validation_port is not None and port != validation_port:
- self._logger.info('Invalid port: %r (expected: %r)',
- port,
- validation_port)
- self._logger.info('Fallback to CGIHTTPRequestHandler')
- return True
- self.path = resource
-
- request = _StandaloneRequest(self, self._options.use_tls)
-
- try:
- # Fallback to default http handler for request paths for which
- # we don't have request handlers.
- if not self._options.dispatcher.get_handler_suite(self.path):
- self._logger.info('No handler for resource: %r',
- self.path)
- self._logger.info('Fallback to CGIHTTPRequestHandler')
- return True
- except dispatch.DispatchException as e:
- self._logger.info('Dispatch failed for error: %s', e)
- self.send_error(e.status)
- return False
-
- # If any Exceptions without except clause setup (including
- # DispatchException) is raised below this point, it will be caught
- # and logged by WebSocketServer.
-
- try:
- try:
- handshake.do_handshake(
- request,
- self._options.dispatcher,
- allowDraft75=self._options.allow_draft75,
- strict=self._options.strict)
- except handshake.VersionException as e:
- self._logger.info('Handshake failed for version error: %s', e)
- self.send_response(common.HTTP_STATUS_BAD_REQUEST)
- self.send_header(common.SEC_WEBSOCKET_VERSION_HEADER,
- e.supported_versions)
- self.end_headers()
- return False
- except handshake.HandshakeException as e:
- # Handshake for ws(s) failed.
- self._logger.info('Handshake failed for error: %s', e)
- self.send_error(e.status)
- return False
-
- request._dispatcher = self._options.dispatcher
- self._options.dispatcher.transfer_data(request)
- except handshake.AbortedByUserException as e:
- self._logger.info('Aborted: %s', e)
- return False
-
- def log_request(self, code='-', size='-'):
- """Override BaseHTTPServer.log_request."""
-
- self._logger.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.
- self._logger.warning('%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 _get_logger_from_class(c):
- return logging.getLogger('%s.%s' % (c.__module__, c.__name__))
-
-
-def _configure_logging(options):
- logging.addLevelName(common.LOGLEVEL_FINE, 'FINE')
-
- 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)
-
- deflate_log_level_name = logging.getLevelName(
- options.deflate_log_level.upper())
- _get_logger_from_class(util._Deflater).setLevel(
- deflate_log_level_name)
- _get_logger_from_class(util._Inflater).setLevel(
- deflate_log_level_name)
-
-
-def _build_option_parser():
- parser = optparse.OptionParser()
-
- parser.add_option('--config', dest='config_file', type='string',
- default=None,
- help=('Path to configuration file. See the file comment '
- 'at the top of this file for the configuration '
- 'file format'))
- parser.add_option('-H', '--server-host', '--server_host',
- dest='server_host',
- default='',
- help='server hostname to listen to')
- parser.add_option('-V', '--validation-host', '--validation_host',
- dest='validation_host',
- default=None,
- help='server hostname to validate in absolute path.')
- parser.add_option('-p', '--port', dest='port', type='int',
- default=common.DEFAULT_WEB_SOCKET_PORT,
- help='port to listen to')
- parser.add_option('-P', '--validation-port', '--validation_port',
- dest='validation_port', type='int',
- default=None,
- help='server port to validate in absolute path.')
- parser.add_option('-w', '--websock-handlers', '--websock_handlers',
- dest='websock_handlers',
- default='.',
- help=('The root directory of WebSocket handler files. '
- 'If the path is relative, --document-root is used '
- 'as the base.'))
- 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=('Must be a directory under --websock-handlers. '
- 'Only handlers under this directory are scanned '
- 'and registered to the server. '
- 'Useful for saving scan time when the handler '
- 'root directory contains lots of files that are '
- 'not handler file or are handler files but you '
- 'don\'t want them to be registered. '))
- parser.add_option('--allow-handlers-outside-root-dir',
- '--allow_handlers_outside_root_dir',
- dest='allow_handlers_outside_root_dir',
- action='store_true',
- default=False,
- help=('Scans WebSocket handlers even if their canonical '
- 'path is not 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('--tls-module', '--tls_module', dest='tls_module',
- type='choice',
- choices = [_TLS_BY_STANDARD_MODULE, _TLS_BY_PYOPENSSL],
- help='Use ssl module if "%s" is specified. '
- 'Use pyOpenSSL module if "%s" is specified' %
- (_TLS_BY_STANDARD_MODULE, _TLS_BY_PYOPENSSL))
- 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('--tls-client-auth', dest='tls_client_auth',
- action='store_true', default=False,
- help='Requests TLS client auth on every connection.')
- parser.add_option('--tls-client-cert-optional',
- dest='tls_client_cert_optional',
- action='store_true', default=False,
- help=('Makes client certificate optional even though '
- 'TLS client auth is enabled.'))
- parser.add_option('--tls-client-ca', dest='tls_client_ca', default='',
- help=('Specifies a pem file which contains a set of '
- 'concatenated CA certificates which are used to '
- 'validate certificates passed from clients'))
- parser.add_option('--basic-auth', dest='use_basic_auth',
- action='store_true', default=False,
- help='Requires Basic authentication.')
- parser.add_option('--basic-auth-credential',
- dest='basic_auth_credential', default='test:test',
- help='Specifies the credential of basic authentication '
- 'by username:password pair (e.g. test:test).')
- parser.add_option('-l', '--log-file', '--log_file', dest='log_file',
- default='', help='Log file.')
- # Custom log level:
- # - FINE: Prints status of each frame processing step
- parser.add_option('--log-level', '--log_level', type='choice',
- dest='log_level', default='warn',
- choices=['fine',
- 'debug', 'info', 'warning', 'warn', 'error',
- 'critical'],
- help='Log level.')
- parser.add_option('--deflate-log-level', '--deflate_log_level',
- type='choice',
- dest='deflate_log_level', default='warn',
- choices=['debug', 'info', 'warning', 'warn', 'error',
- 'critical'],
- help='Log level for _Deflater and _Inflater.')
- parser.add_option('--thread-monitor-interval-in-sec',
- '--thread_monitor_interval_in_sec',
- dest='thread_monitor_interval_in_sec',
- type='int', default=-1,
- help=('If positive integer is specified, run a thread '
- 'monitor to show the status of server threads '
- 'periodically in the specified inteval in '
- 'second. If non-positive integer is specified, '
- 'disable the thread monitor.'))
- 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='Obsolete option. Ignored.')
- parser.add_option('--strict', dest='strict', action='store_true',
- default=False, help='Obsolete option. Ignored.')
- parser.add_option('-q', '--queue', dest='request_queue_size', type='int',
- default=_DEFAULT_REQUEST_QUEUE_SIZE,
- help='request queue size')
-
- return parser
-
-
-class ThreadMonitor(threading.Thread):
- daemon = True
-
- def __init__(self, interval_in_sec):
- threading.Thread.__init__(self, name='ThreadMonitor')
-
- self._logger = util.get_class_logger(self)
-
- self._interval_in_sec = interval_in_sec
-
- def run(self):
- while True:
- thread_name_list = []
- for thread in threading.enumerate():
- thread_name_list.append(thread.name)
- self._logger.info(
- "%d active threads: %s",
- threading.active_count(),
- ', '.join(thread_name_list))
- time.sleep(self._interval_in_sec)
-
-
-def _parse_args_and_config(args):
- parser = _build_option_parser()
-
- # 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 as 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):
- """You can call this function from your own program, but please note that
- this function has some side-effects that might affect your program. For
- example, util.wrap_popen3_for_win use in this method replaces implementation
- of os.popen3.
- """
-
- options, args = _parse_args_and_config(args=args)
-
- os.chdir(options.document_root)
-
- _configure_logging(options)
-
- if options.allow_draft75:
- logging.warning('--allow_draft75 option is obsolete.')
-
- if options.strict:
- logging.warning('--strict option is obsolete.')
-
- # TODO(tyoshino): Clean up initialization of CGI related values. Move some
- # of code here to WebSocketRequestHandler class if it's better.
- options.cgi_directories = []
- options.is_executable_method = None
- if options.cgi_paths:
- options.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)
-
- options.is_executable_method = __check_script
-
- if options.use_tls:
- if options.tls_module is None:
- if _import_ssl():
- options.tls_module = _TLS_BY_STANDARD_MODULE
- logging.debug('Using ssl module')
- elif _import_pyopenssl():
- options.tls_module = _TLS_BY_PYOPENSSL
- logging.debug('Using pyOpenSSL module')
- else:
- logging.critical(
- 'TLS support requires ssl or pyOpenSSL module.')
- sys.exit(1)
- elif options.tls_module == _TLS_BY_STANDARD_MODULE:
- if not _import_ssl():
- logging.critical('ssl module is not available')
- sys.exit(1)
- elif options.tls_module == _TLS_BY_PYOPENSSL:
- if not _import_pyopenssl():
- logging.critical('pyOpenSSL module is not available')
- sys.exit(1)
- else:
- logging.critical('Invalid --tls-module option: %r',
- options.tls_module)
- 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 (options.tls_client_cert_optional and
- not options.tls_client_auth):
- logging.critical('Client authentication must be enabled to '
- 'specify tls_client_cert_optional')
- sys.exit(1)
- else:
- if options.tls_module is not None:
- logging.critical('Use --tls-module option only together with '
- '--use-tls option.')
- sys.exit(1)
-
- if options.tls_client_auth:
- logging.critical('TLS must be enabled for client authentication.')
- sys.exit(1)
-
- if options.tls_client_cert_optional:
- logging.critical('TLS must be enabled for client authentication.')
- sys.exit(1)
-
- if not options.scan_dir:
- options.scan_dir = options.websock_handlers
-
- if options.use_basic_auth:
- options.basic_auth_credential = 'Basic ' + base64.b64encode(
- options.basic_auth_credential)
-
- try:
- if options.thread_monitor_interval_in_sec > 0:
- # Run a thread monitor to show the status of server threads for
- # debugging.
- ThreadMonitor(options.thread_monitor_interval_in_sec).start()
-
- server = WebSocketServer(options)
- server.serve_forever()
- except Exception as e:
- logging.critical('mod_pywebsocket: %s' % e)
- logging.critical('mod_pywebsocket: %s' % util.get_stack_trace())
- sys.exit(1)
-
-
-if __name__ == '__main__':
- _main(sys.argv[1:])
-
-
-# vi:sts=4 sw=4 et