forked from mirrors/gecko-dev
		
	 217bbab384
			
		
	
	
		217bbab384
		
	
	
	
	
		
			
			Update testing/mochitest/pywebsocket with the latest version available: pywebsocket3 is python 3 compatible. This keeps the basic structure of the old pywebsocket, but changes the directory name to pywebsocket3 to reflect the project renaming. Differential Revision: https://phabricator.services.mozilla.com/D84455
		
			
				
	
	
		
			483 lines
		
	
	
	
		
			18 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable file
		
	
	
	
	
			
		
		
	
	
			483 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')
 | |
| 
 | |
|     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, 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)
 | |
| 
 | |
|     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']
 | |
|             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:
 | |
|         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
 |