mirror of
				https://github.com/mozilla/gecko-dev.git
				synced 2025-11-04 10:18:41 +02:00 
			
		
		
		
	This was vendored without any reference of the vendored version, no update script and a patch bundled into a README. Vendor it using mach vendor, updating to the latest version. Differential Revision: https://phabricator.services.mozilla.com/D190584
		
			
				
	
	
		
			491 lines
		
	
	
	
		
			18 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable file
		
	
	
	
	
			
		
		
	
	
			491 lines
		
	
	
	
		
			18 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable file
		
	
	
	
	
#!/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 as a standalone 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 __future__ import absolute_import
 | 
						|
from six.moves import configparser
 | 
						|
import base64
 | 
						|
import logging
 | 
						|
import argparse
 | 
						|
import os
 | 
						|
import six
 | 
						|
import sys
 | 
						|
import traceback
 | 
						|
 | 
						|
from mod_pywebsocket import common
 | 
						|
from mod_pywebsocket import util
 | 
						|
from mod_pywebsocket import server_util
 | 
						|
from mod_pywebsocket.websocket_server import WebSocketServer
 | 
						|
 | 
						|
_DEFAULT_LOG_MAX_BYTES = 1024 * 256
 | 
						|
_DEFAULT_LOG_BACKUP_COUNT = 5
 | 
						|
 | 
						|
_DEFAULT_REQUEST_QUEUE_SIZE = 128
 | 
						|
 | 
						|
 | 
						|
def _build_option_parser():
 | 
						|
    parser = argparse.ArgumentParser()
 | 
						|
 | 
						|
    parser.add_argument(
 | 
						|
        '--config',
 | 
						|
        dest='config_file',
 | 
						|
        type=six.text_type,
 | 
						|
        default=None,
 | 
						|
        help=('Path to configuration file. See the file comment '
 | 
						|
              'at the top of this file for the configuration '
 | 
						|
              'file format'))
 | 
						|
    parser.add_argument('-H',
 | 
						|
                        '--server-host',
 | 
						|
                        '--server_host',
 | 
						|
                        dest='server_host',
 | 
						|
                        default='',
 | 
						|
                        help='server hostname to listen to')
 | 
						|
    parser.add_argument('-V',
 | 
						|
                        '--validation-host',
 | 
						|
                        '--validation_host',
 | 
						|
                        dest='validation_host',
 | 
						|
                        default=None,
 | 
						|
                        help='server hostname to validate in absolute path.')
 | 
						|
    parser.add_argument('-p',
 | 
						|
                        '--port',
 | 
						|
                        dest='port',
 | 
						|
                        type=int,
 | 
						|
                        default=common.DEFAULT_WEB_SOCKET_PORT,
 | 
						|
                        help='port to listen to')
 | 
						|
    parser.add_argument('-P',
 | 
						|
                        '--validation-port',
 | 
						|
                        '--validation_port',
 | 
						|
                        dest='validation_port',
 | 
						|
                        type=int,
 | 
						|
                        default=None,
 | 
						|
                        help='server port to validate in absolute path.')
 | 
						|
    parser.add_argument(
 | 
						|
        '-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_argument('-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_argument('-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_argument(
 | 
						|
        '--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_argument('-d',
 | 
						|
                        '--document-root',
 | 
						|
                        '--document_root',
 | 
						|
                        dest='document_root',
 | 
						|
                        default='.',
 | 
						|
                        help='Document root directory.')
 | 
						|
    parser.add_argument('-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_argument('-t',
 | 
						|
                        '--tls',
 | 
						|
                        dest='use_tls',
 | 
						|
                        action='store_true',
 | 
						|
                        default=False,
 | 
						|
                        help='use TLS (wss://)')
 | 
						|
    parser.add_argument('-k',
 | 
						|
                        '--private-key',
 | 
						|
                        '--private_key',
 | 
						|
                        dest='private_key',
 | 
						|
                        default='',
 | 
						|
                        help='TLS private key file.')
 | 
						|
    parser.add_argument('-c',
 | 
						|
                        '--certificate',
 | 
						|
                        dest='certificate',
 | 
						|
                        default='',
 | 
						|
                        help='TLS certificate file.')
 | 
						|
    parser.add_argument('--tls-client-auth',
 | 
						|
                        dest='tls_client_auth',
 | 
						|
                        action='store_true',
 | 
						|
                        default=False,
 | 
						|
                        help='Requests TLS client auth on every connection.')
 | 
						|
    parser.add_argument('--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_argument('--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_argument('--basic-auth',
 | 
						|
                        dest='use_basic_auth',
 | 
						|
                        action='store_true',
 | 
						|
                        default=False,
 | 
						|
                        help='Requires Basic authentication.')
 | 
						|
    parser.add_argument(
 | 
						|
        '--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_argument('-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_argument('--log-level',
 | 
						|
                        '--log_level',
 | 
						|
                        type=six.text_type,
 | 
						|
                        dest='log_level',
 | 
						|
                        default='warn',
 | 
						|
                        choices=[
 | 
						|
                            'fine', 'debug', 'info', 'warning', 'warn',
 | 
						|
                            'error', 'critical'
 | 
						|
                        ],
 | 
						|
                        help='Log level.')
 | 
						|
    parser.add_argument(
 | 
						|
        '--deflate-log-level',
 | 
						|
        '--deflate_log_level',
 | 
						|
        type=six.text_type,
 | 
						|
        dest='deflate_log_level',
 | 
						|
        default='warn',
 | 
						|
        choices=['debug', 'info', 'warning', 'warn', 'error', 'critical'],
 | 
						|
        help='Log level for _Deflater and _Inflater.')
 | 
						|
    parser.add_argument('--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_argument('--log-max',
 | 
						|
                        '--log_max',
 | 
						|
                        dest='log_max',
 | 
						|
                        type=int,
 | 
						|
                        default=_DEFAULT_LOG_MAX_BYTES,
 | 
						|
                        help='Log maximum bytes')
 | 
						|
    parser.add_argument('--log-count',
 | 
						|
                        '--log_count',
 | 
						|
                        dest='log_count',
 | 
						|
                        type=int,
 | 
						|
                        default=_DEFAULT_LOG_BACKUP_COUNT,
 | 
						|
                        help='Log backup count')
 | 
						|
    parser.add_argument('-q',
 | 
						|
                        '--queue',
 | 
						|
                        dest='request_queue_size',
 | 
						|
                        type=int,
 | 
						|
                        default=_DEFAULT_REQUEST_QUEUE_SIZE,
 | 
						|
                        help='request queue size')
 | 
						|
    parser.add_argument(
 | 
						|
        '--handler-encoding',
 | 
						|
        '--handler_encoding',
 | 
						|
        dest='handler_encoding',
 | 
						|
        type=six.text_type,
 | 
						|
        default=None,
 | 
						|
        help=('Text encoding used for loading handlers. '
 | 
						|
              'By default, the encoding from the locale is used when '
 | 
						|
              'reading handler files, but this option can override it. '
 | 
						|
              'Any encoding supported by the codecs module may be used.'))
 | 
						|
 | 
						|
    return parser
 | 
						|
 | 
						|
 | 
						|
def _parse_args_and_config(args):
 | 
						|
    parser = _build_option_parser()
 | 
						|
 | 
						|
    # First, parse options without configuration file.
 | 
						|
    temporary_options, temporary_args = parser.parse_known_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_known_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, it changes the current directory.
 | 
						|
    """
 | 
						|
 | 
						|
    options, args = _parse_args_and_config(args=args)
 | 
						|
 | 
						|
    os.chdir(options.document_root)
 | 
						|
 | 
						|
    server_util.configure_logging(options)
 | 
						|
 | 
						|
    # 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']
 | 
						|
 | 
						|
            def __check_script(scriptpath):
 | 
						|
                return util.get_script_interp(scriptpath, cygwin_path)
 | 
						|
 | 
						|
            options.is_executable_method = __check_script
 | 
						|
 | 
						|
    if options.use_tls:
 | 
						|
        logging.debug('Using ssl module')
 | 
						|
 | 
						|
        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_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.encode('UTF-8')).decode()
 | 
						|
 | 
						|
    try:
 | 
						|
        if options.thread_monitor_interval_in_sec > 0:
 | 
						|
            # Run a thread monitor to show the status of server threads for
 | 
						|
            # debugging.
 | 
						|
            server_util.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' % traceback.format_exc())
 | 
						|
        sys.exit(1)
 | 
						|
 | 
						|
 | 
						|
if __name__ == '__main__':
 | 
						|
    _main(sys.argv[1:])
 | 
						|
 | 
						|
# vi:sts=4 sw=4 et
 |