testing/mochitest/pywebsocket/standalone.py

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rwxr-xr-x

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 #!/usr/bin/env python
     2 #
     3 # Copyright 2011, Google Inc.
     4 # All rights reserved.
     5 #
     6 # Redistribution and use in source and binary forms, with or without
     7 # modification, are permitted provided that the following conditions are
     8 # met:
     9 #
    10 #     * Redistributions of source code must retain the above copyright
    11 # notice, this list of conditions and the following disclaimer.
    12 #     * Redistributions in binary form must reproduce the above
    13 # copyright notice, this list of conditions and the following disclaimer
    14 # in the documentation and/or other materials provided with the
    15 # distribution.
    16 #     * Neither the name of Google Inc. nor the names of its
    17 # contributors may be used to endorse or promote products derived from
    18 # this software without specific prior written permission.
    19 #
    20 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
    21 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
    22 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
    23 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
    24 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
    25 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
    26 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
    27 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
    28 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    29 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
    30 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    33 """Standalone WebSocket server.
    35 BASIC USAGE
    37 Use this server to run mod_pywebsocket without Apache HTTP Server.
    39 Usage:
    40     python standalone.py [-p <ws_port>] [-w <websock_handlers>]
    41                          [-s <scan_dir>]
    42                          [-d <document_root>]
    43                          [-m <websock_handlers_map_file>]
    44                          ... for other options, see _main below ...
    46 <ws_port> is the port number to use for ws:// connection.
    48 <document_root> is the path to the root directory of HTML files.
    50 <websock_handlers> is the path to the root directory of WebSocket handlers.
    51 See __init__.py for details of <websock_handlers> and how to write WebSocket
    52 handlers. If this path is relative, <document_root> is used as the base.
    54 <scan_dir> is a path under the root directory. If specified, only the
    55 handlers under scan_dir are scanned. This is useful in saving scan time.
    58 CONFIGURATION FILE
    60 You can also write a configuration file and use it by specifying the path to
    61 the configuration file by --config option. Please write a configuration file
    62 following the documentation of the Python ConfigParser library. Name of each
    63 entry must be the long version argument name. E.g. to set log level to debug,
    64 add the following line:
    66 log_level=debug
    68 For options which doesn't take value, please add some fake value. E.g. for
    69 --tls option, add the following line:
    71 tls=True
    73 Note that tls will be enabled even if you write tls=False as the value part is
    74 fake.
    76 When both a command line argument and a configuration file entry are set for
    77 the same configuration item, the command line value will override one in the
    78 configuration file.
    81 THREADING
    83 This server is derived from SocketServer.ThreadingMixIn. Hence a thread is
    84 used for each request.
    87 SECURITY WARNING
    89 This uses CGIHTTPServer and CGIHTTPServer is not secure.
    90 It may execute arbitrary Python code or external programs. It should not be
    91 used outside a firewall.
    92 """
    94 import BaseHTTPServer
    95 import CGIHTTPServer
    96 import SimpleHTTPServer
    97 import SocketServer
    98 import ConfigParser
    99 import httplib
   100 import logging
   101 import logging.handlers
   102 import optparse
   103 import os
   104 import re
   105 import select
   106 import socket
   107 import sys
   108 import threading
   109 import time
   111 _HAS_SSL = False
   112 _HAS_OPEN_SSL = False
   113 try:
   114     import ssl
   115     _HAS_SSL = True
   116 except ImportError:
   117     try:
   118         import OpenSSL.SSL
   119         _HAS_OPEN_SSL = True
   120     except ImportError:
   121         pass
   123 from mod_pywebsocket import common
   124 from mod_pywebsocket import dispatch
   125 from mod_pywebsocket import handshake
   126 from mod_pywebsocket import http_header_util
   127 from mod_pywebsocket import memorizingfile
   128 from mod_pywebsocket import util
   131 _DEFAULT_LOG_MAX_BYTES = 1024 * 256
   132 _DEFAULT_LOG_BACKUP_COUNT = 5
   134 _DEFAULT_REQUEST_QUEUE_SIZE = 128
   136 # 1024 is practically large enough to contain WebSocket handshake lines.
   137 _MAX_MEMORIZED_LINES = 1024
   140 class _StandaloneConnection(object):
   141     """Mimic mod_python mp_conn."""
   143     def __init__(self, request_handler):
   144         """Construct an instance.
   146         Args:
   147             request_handler: A WebSocketRequestHandler instance.
   148         """
   150         self._request_handler = request_handler
   152     def get_local_addr(self):
   153         """Getter to mimic mp_conn.local_addr."""
   155         return (self._request_handler.server.server_name,
   156                 self._request_handler.server.server_port)
   157     local_addr = property(get_local_addr)
   159     def get_remote_addr(self):
   160         """Getter to mimic mp_conn.remote_addr.
   162         Setting the property in __init__ won't work because the request
   163         handler is not initialized yet there."""
   165         return self._request_handler.client_address
   166     remote_addr = property(get_remote_addr)
   168     def write(self, data):
   169         """Mimic mp_conn.write()."""
   171         return self._request_handler.wfile.write(data)
   173     def read(self, length):
   174         """Mimic mp_conn.read()."""
   176         return self._request_handler.rfile.read(length)
   178     def get_memorized_lines(self):
   179         """Get memorized lines."""
   181         return self._request_handler.rfile.get_memorized_lines()
   184 class _StandaloneRequest(object):
   185     """Mimic mod_python request."""
   187     def __init__(self, request_handler, use_tls):
   188         """Construct an instance.
   190         Args:
   191             request_handler: A WebSocketRequestHandler instance.
   192         """
   194         self._logger = util.get_class_logger(self)
   196         self._request_handler = request_handler
   197         self.connection = _StandaloneConnection(request_handler)
   198         self._use_tls = use_tls
   199         self.headers_in = request_handler.headers
   201     def get_uri(self):
   202         """Getter to mimic request.uri."""
   204         return self._request_handler.path
   205     uri = property(get_uri)
   207     def get_method(self):
   208         """Getter to mimic request.method."""
   210         return self._request_handler.command
   211     method = property(get_method)
   213     def is_https(self):
   214         """Mimic request.is_https()."""
   216         return self._use_tls
   218     def _drain_received_data(self):
   219         """Don't use this method from WebSocket handler. Drains unread data
   220         in the receive buffer.
   221         """
   223         raw_socket = self._request_handler.connection
   224         drained_data = util.drain_received_data(raw_socket)
   226         if drained_data:
   227             self._logger.debug(
   228                 'Drained data following close frame: %r', drained_data)
   231 class _StandaloneSSLConnection(object):
   232     """A wrapper class for OpenSSL.SSL.Connection to provide makefile method
   233     which is not supported by the class.
   234     """
   236     def __init__(self, connection):
   237         self._connection = connection
   239     def __getattribute__(self, name):
   240         if name in ('_connection', 'makefile'):
   241             return object.__getattribute__(self, name)
   242         return self._connection.__getattribute__(name)
   244     def __setattr__(self, name, value):
   245         if name in ('_connection', 'makefile'):
   246             return object.__setattr__(self, name, value)
   247         return self._connection.__setattr__(name, value)
   249     def makefile(self, mode='r', bufsize=-1):
   250         return socket._fileobject(self._connection, mode, bufsize)
   253 class WebSocketServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer):
   254     """HTTPServer specialized for WebSocket."""
   256     # Overrides SocketServer.ThreadingMixIn.daemon_threads
   257     daemon_threads = True
   258     # Overrides BaseHTTPServer.HTTPServer.allow_reuse_address
   259     allow_reuse_address = True
   261     def __init__(self, options):
   262         """Override SocketServer.TCPServer.__init__ to set SSL enabled
   263         socket object to self.socket before server_bind and server_activate,
   264         if necessary.
   265         """
   267         self._logger = util.get_class_logger(self)
   269         self.request_queue_size = options.request_queue_size
   270         self.__ws_is_shut_down = threading.Event()
   271         self.__ws_serving = False
   273         SocketServer.BaseServer.__init__(
   274             self, (options.server_host, options.port), WebSocketRequestHandler)
   276         # Expose the options object to allow handler objects access it. We name
   277         # it with websocket_ prefix to avoid conflict.
   278         self.websocket_server_options = options
   280         self._create_sockets()
   281         self.server_bind()
   282         self.server_activate()
   284     def _create_sockets(self):
   285         self.server_name, self.server_port = self.server_address
   286         self._sockets = []
   287         if not self.server_name:
   288             # On platforms that doesn't support IPv6, the first bind fails.
   289             # On platforms that supports IPv6
   290             # - If it binds both IPv4 and IPv6 on call with AF_INET6, the
   291             #   first bind succeeds and the second fails (we'll see 'Address
   292             #   already in use' error).
   293             # - If it binds only IPv6 on call with AF_INET6, both call are
   294             #   expected to succeed to listen both protocol.
   295             addrinfo_array = [
   296                 (socket.AF_INET6, socket.SOCK_STREAM, '', '', ''),
   297                 (socket.AF_INET, socket.SOCK_STREAM, '', '', '')]
   298         else:
   299             addrinfo_array = socket.getaddrinfo(self.server_name,
   300                                                 self.server_port,
   301                                                 socket.AF_UNSPEC,
   302                                                 socket.SOCK_STREAM,
   303                                                 socket.IPPROTO_TCP)
   304         for addrinfo in addrinfo_array:
   305             self._logger.info('Create socket on: %r', addrinfo)
   306             family, socktype, proto, canonname, sockaddr = addrinfo
   307             try:
   308                 socket_ = socket.socket(family, socktype)
   309             except Exception, e:
   310                 self._logger.info('Skip by failure: %r', e)
   311                 continue
   312             if self.websocket_server_options.use_tls:
   313                 if _HAS_SSL:
   314                     socket_ = ssl.wrap_socket(socket_,
   315                         keyfile=self.websocket_server_options.private_key,
   316                         certfile=self.websocket_server_options.certificate,
   317                         ssl_version=ssl.PROTOCOL_SSLv23)
   318                 if _HAS_OPEN_SSL:
   319                     ctx = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD)
   320                     ctx.use_privatekey_file(
   321                         self.websocket_server_options.private_key)
   322                     ctx.use_certificate_file(
   323                         self.websocket_server_options.certificate)
   324                     socket_ = OpenSSL.SSL.Connection(ctx, socket_)
   325             self._sockets.append((socket_, addrinfo))
   327     def server_bind(self):
   328         """Override SocketServer.TCPServer.server_bind to enable multiple
   329         sockets bind.
   330         """
   332         failed_sockets = []
   334         for socketinfo in self._sockets:
   335             socket_, addrinfo = socketinfo
   336             self._logger.info('Bind on: %r', addrinfo)
   337             if self.allow_reuse_address:
   338                 socket_.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
   339             try:
   340                 socket_.bind(self.server_address)
   341             except Exception, e:
   342                 self._logger.info('Skip by failure: %r', e)
   343                 socket_.close()
   344                 failed_sockets.append(socketinfo)
   346         for socketinfo in failed_sockets:
   347             self._sockets.remove(socketinfo)
   349     def server_activate(self):
   350         """Override SocketServer.TCPServer.server_activate to enable multiple
   351         sockets listen.
   352         """
   354         failed_sockets = []
   356         for socketinfo in self._sockets:
   357             socket_, addrinfo = socketinfo
   358             self._logger.info('Listen on: %r', addrinfo)
   359             try:
   360                 socket_.listen(self.request_queue_size)
   361             except Exception, e:
   362                 self._logger.info('Skip by failure: %r', e)
   363                 socket_.close()
   364                 failed_sockets.append(socketinfo)
   366         for socketinfo in failed_sockets:
   367             self._sockets.remove(socketinfo)
   369     def server_close(self):
   370         """Override SocketServer.TCPServer.server_close to enable multiple
   371         sockets close.
   372         """
   374         for socketinfo in self._sockets:
   375             socket_, addrinfo = socketinfo
   376             self._logger.info('Close on: %r', addrinfo)
   377             socket_.close()
   379     def fileno(self):
   380         """Override SocketServer.TCPServer.fileno."""
   382         self._logger.critical('Not supported: fileno')
   383         return self._sockets[0][0].fileno()
   385     def handle_error(self, rquest, client_address):
   386         """Override SocketServer.handle_error."""
   388         self._logger.error(
   389             'Exception in processing request from: %r\n%s',
   390             client_address,
   391             util.get_stack_trace())
   392         # Note: client_address is a tuple.
   394     def get_request(self):
   395         """Override TCPServer.get_request to wrap OpenSSL.SSL.Connection
   396         object with _StandaloneSSLConnection to provide makefile method. We
   397         cannot substitute OpenSSL.SSL.Connection.makefile since it's readonly
   398         attribute.
   399         """
   401         accepted_socket, client_address = self.socket.accept()
   402         if self.websocket_server_options.use_tls and _HAS_OPEN_SSL:
   403             accepted_socket = _StandaloneSSLConnection(accepted_socket)
   404         return accepted_socket, client_address
   406     def serve_forever(self, poll_interval=0.5):
   407         """Override SocketServer.BaseServer.serve_forever."""
   409         self.__ws_serving = True
   410         self.__ws_is_shut_down.clear()
   411         handle_request = self.handle_request
   412         if hasattr(self, '_handle_request_noblock'):
   413             handle_request = self._handle_request_noblock
   414         else:
   415             self._logger.warning('Fallback to blocking request handler')
   416         try:
   417             while self.__ws_serving:
   418                 r, w, e = select.select(
   419                     [socket_[0] for socket_ in self._sockets],
   420                     [], [], poll_interval)
   421                 for socket_ in r:
   422                     self.socket = socket_
   423                     handle_request()
   424                 self.socket = None
   425         finally:
   426             self.__ws_is_shut_down.set()
   428     def shutdown(self):
   429         """Override SocketServer.BaseServer.shutdown."""
   431         self.__ws_serving = False
   432         self.__ws_is_shut_down.wait()
   435 class WebSocketRequestHandler(CGIHTTPServer.CGIHTTPRequestHandler):
   436     """CGIHTTPRequestHandler specialized for WebSocket."""
   438     # Use httplib.HTTPMessage instead of mimetools.Message.
   439     MessageClass = httplib.HTTPMessage
   441     def setup(self):
   442         """Override SocketServer.StreamRequestHandler.setup to wrap rfile
   443         with MemorizingFile.
   445         This method will be called by BaseRequestHandler's constructor
   446         before calling BaseHTTPRequestHandler.handle.
   447         BaseHTTPRequestHandler.handle will call
   448         BaseHTTPRequestHandler.handle_one_request and it will call
   449         WebSocketRequestHandler.parse_request.
   450         """
   452         # Call superclass's setup to prepare rfile, wfile, etc. See setup
   453         # definition on the root class SocketServer.StreamRequestHandler to
   454         # understand what this does.
   455         CGIHTTPServer.CGIHTTPRequestHandler.setup(self)
   457         self.rfile = memorizingfile.MemorizingFile(
   458             self.rfile,
   459             max_memorized_lines=_MAX_MEMORIZED_LINES)
   461     def __init__(self, request, client_address, server):
   462         self._logger = util.get_class_logger(self)
   464         self._options = server.websocket_server_options
   466         # Overrides CGIHTTPServerRequestHandler.cgi_directories.
   467         self.cgi_directories = self._options.cgi_directories
   468         # Replace CGIHTTPRequestHandler.is_executable method.
   469         if self._options.is_executable_method is not None:
   470             self.is_executable = self._options.is_executable_method
   472         # This actually calls BaseRequestHandler.__init__.
   473         CGIHTTPServer.CGIHTTPRequestHandler.__init__(
   474             self, request, client_address, server)
   476     def parse_request(self):
   477         """Override BaseHTTPServer.BaseHTTPRequestHandler.parse_request.
   479         Return True to continue processing for HTTP(S), False otherwise.
   481         See BaseHTTPRequestHandler.handle_one_request method which calls
   482         this method to understand how the return value will be handled.
   483         """
   485         # We hook parse_request method, but also call the original
   486         # CGIHTTPRequestHandler.parse_request since when we return False,
   487         # CGIHTTPRequestHandler.handle_one_request continues processing and
   488         # it needs variables set by CGIHTTPRequestHandler.parse_request.
   489         #
   490         # Variables set by this method will be also used by WebSocket request
   491         # handling (self.path, self.command, self.requestline, etc. See also
   492         # how _StandaloneRequest's members are implemented using these
   493         # attributes).
   494         if not CGIHTTPServer.CGIHTTPRequestHandler.parse_request(self):
   495             return False
   496         host, port, resource = http_header_util.parse_uri(self.path)
   497         if resource is None:
   498             self._logger.info('Invalid URI: %r', self.path)
   499             self._logger.info('Fallback to CGIHTTPRequestHandler')
   500             return True
   501         server_options = self.server.websocket_server_options
   502         if host is not None:
   503             validation_host = server_options.validation_host
   504             if validation_host is not None and host != validation_host:
   505                 self._logger.info('Invalid host: %r (expected: %r)',
   506                                   host,
   507                                   validation_host)
   508                 self._logger.info('Fallback to CGIHTTPRequestHandler')
   509                 return True
   510         if port is not None:
   511             validation_port = server_options.validation_port
   512             if validation_port is not None and port != validation_port:
   513                 self._logger.info('Invalid port: %r (expected: %r)',
   514                                   port,
   515                                   validation_port)
   516                 self._logger.info('Fallback to CGIHTTPRequestHandler')
   517                 return True
   518         self.path = resource
   520         request = _StandaloneRequest(self, self._options.use_tls)
   522         try:
   523             # Fallback to default http handler for request paths for which
   524             # we don't have request handlers.
   525             if not self._options.dispatcher.get_handler_suite(self.path):
   526                 self._logger.info('No handler for resource: %r',
   527                                   self.path)
   528                 self._logger.info('Fallback to CGIHTTPRequestHandler')
   529                 return True
   530         except dispatch.DispatchException, e:
   531             self._logger.info('%s', e)
   532             self.send_error(e.status)
   533             return False
   535         # If any Exceptions without except clause setup (including
   536         # DispatchException) is raised below this point, it will be caught
   537         # and logged by WebSocketServer.
   539         try:
   540             try:
   541                 handshake.do_handshake(
   542                     request,
   543                     self._options.dispatcher,
   544                     allowDraft75=self._options.allow_draft75,
   545                     strict=self._options.strict)
   546             except handshake.VersionException, e:
   547                 self._logger.info('%s', e)
   548                 self.send_response(common.HTTP_STATUS_BAD_REQUEST)
   549                 self.send_header(common.SEC_WEBSOCKET_VERSION_HEADER,
   550                                  e.supported_versions)
   551                 self.end_headers()
   552                 return False
   553             except handshake.HandshakeException, e:
   554                 # Handshake for ws(s) failed.
   555                 self._logger.info('%s', e)
   556                 self.send_error(e.status)
   557                 return False
   559             request._dispatcher = self._options.dispatcher
   560             self._options.dispatcher.transfer_data(request)
   561         except handshake.AbortedByUserException, e:
   562             self._logger.info('%s', e)
   563         return False
   565     def log_request(self, code='-', size='-'):
   566         """Override BaseHTTPServer.log_request."""
   568         self._logger.info('"%s" %s %s',
   569                           self.requestline, str(code), str(size))
   571     def log_error(self, *args):
   572         """Override BaseHTTPServer.log_error."""
   574         # Despite the name, this method is for warnings than for errors.
   575         # For example, HTTP status code is logged by this method.
   576         self._logger.warning('%s - %s',
   577                              self.address_string(),
   578                              args[0] % args[1:])
   580     def is_cgi(self):
   581         """Test whether self.path corresponds to a CGI script.
   583         Add extra check that self.path doesn't contains ..
   584         Also check if the file is a executable file or not.
   585         If the file is not executable, it is handled as static file or dir
   586         rather than a CGI script.
   587         """
   589         if CGIHTTPServer.CGIHTTPRequestHandler.is_cgi(self):
   590             if '..' in self.path:
   591                 return False
   592             # strip query parameter from request path
   593             resource_name = self.path.split('?', 2)[0]
   594             # convert resource_name into real path name in filesystem.
   595             scriptfile = self.translate_path(resource_name)
   596             if not os.path.isfile(scriptfile):
   597                 return False
   598             if not self.is_executable(scriptfile):
   599                 return False
   600             return True
   601         return False
   604 def _configure_logging(options):
   605     logger = logging.getLogger()
   606     logger.setLevel(logging.getLevelName(options.log_level.upper()))
   607     if options.log_file:
   608         handler = logging.handlers.RotatingFileHandler(
   609                 options.log_file, 'a', options.log_max, options.log_count)
   610     else:
   611         handler = logging.StreamHandler()
   612     formatter = logging.Formatter(
   613             '[%(asctime)s] [%(levelname)s] %(name)s: %(message)s')
   614     handler.setFormatter(formatter)
   615     logger.addHandler(handler)
   618 def _alias_handlers(dispatcher, websock_handlers_map_file):
   619     """Set aliases specified in websock_handler_map_file in dispatcher.
   621     Args:
   622         dispatcher: dispatch.Dispatcher instance
   623         websock_handler_map_file: alias map file
   624     """
   626     fp = open(websock_handlers_map_file)
   627     try:
   628         for line in fp:
   629             if line[0] == '#' or line.isspace():
   630                 continue
   631             m = re.match('(\S+)\s+(\S+)', line)
   632             if not m:
   633                 logging.warning('Wrong format in map file:' + line)
   634                 continue
   635             try:
   636                 dispatcher.add_resource_path_alias(
   637                     m.group(1), m.group(2))
   638             except dispatch.DispatchException, e:
   639                 logging.error(str(e))
   640     finally:
   641         fp.close()
   644 def _build_option_parser():
   645     parser = optparse.OptionParser()
   647     parser.add_option('--config', dest='config_file', type='string',
   648                       default=None,
   649                       help=('Path to configuration file. See the file comment '
   650                             'at the top of this file for the configuration '
   651                             'file format'))
   652     parser.add_option('-H', '--server-host', '--server_host',
   653                       dest='server_host',
   654                       default='',
   655                       help='server hostname to listen to')
   656     parser.add_option('-V', '--validation-host', '--validation_host',
   657                       dest='validation_host',
   658                       default=None,
   659                       help='server hostname to validate in absolute path.')
   660     parser.add_option('-p', '--port', dest='port', type='int',
   661                       default=common.DEFAULT_WEB_SOCKET_PORT,
   662                       help='port to listen to')
   663     parser.add_option('-P', '--validation-port', '--validation_port',
   664                       dest='validation_port', type='int',
   665                       default=None,
   666                       help='server port to validate in absolute path.')
   667     parser.add_option('-w', '--websock-handlers', '--websock_handlers',
   668                       dest='websock_handlers',
   669                       default='.',
   670                       help='WebSocket handlers root directory.')
   671     parser.add_option('-m', '--websock-handlers-map-file',
   672                       '--websock_handlers_map_file',
   673                       dest='websock_handlers_map_file',
   674                       default=None,
   675                       help=('WebSocket handlers map file. '
   676                             'Each line consists of alias_resource_path and '
   677                             'existing_resource_path, separated by spaces.'))
   678     parser.add_option('-s', '--scan-dir', '--scan_dir', dest='scan_dir',
   679                       default=None,
   680                       help=('WebSocket handlers scan directory. '
   681                             'Must be a directory under websock_handlers.'))
   682     parser.add_option('--allow-handlers-outside-root-dir',
   683                       '--allow_handlers_outside_root_dir',
   684                       dest='allow_handlers_outside_root_dir',
   685                       action='store_true',
   686                       default=False,
   687                       help=('Scans WebSocket handlers even if their canonical '
   688                             'path is not under websock_handlers.'))
   689     parser.add_option('-d', '--document-root', '--document_root',
   690                       dest='document_root', default='.',
   691                       help='Document root directory.')
   692     parser.add_option('-x', '--cgi-paths', '--cgi_paths', dest='cgi_paths',
   693                       default=None,
   694                       help=('CGI paths relative to document_root.'
   695                             'Comma-separated. (e.g -x /cgi,/htbin) '
   696                             'Files under document_root/cgi_path are handled '
   697                             'as CGI programs. Must be executable.'))
   698     parser.add_option('-t', '--tls', dest='use_tls', action='store_true',
   699                       default=False, help='use TLS (wss://)')
   700     parser.add_option('-k', '--private-key', '--private_key',
   701                       dest='private_key',
   702                       default='', help='TLS private key file.')
   703     parser.add_option('-c', '--certificate', dest='certificate',
   704                       default='', help='TLS certificate file.')
   705     parser.add_option('-l', '--log-file', '--log_file', dest='log_file',
   706                       default='', help='Log file.')
   707     parser.add_option('--log-level', '--log_level', type='choice',
   708                       dest='log_level', default='warn',
   709                       choices=['debug', 'info', 'warning', 'warn', 'error',
   710                                'critical'],
   711                       help='Log level.')
   712     parser.add_option('--thread-monitor-interval-in-sec',
   713                       '--thread_monitor_interval_in_sec',
   714                       dest='thread_monitor_interval_in_sec',
   715                       type='int', default=-1,
   716                       help=('If positive integer is specified, run a thread '
   717                             'monitor to show the status of server threads '
   718                             'periodically in the specified inteval in '
   719                             'second. If non-positive integer is specified, '
   720                             'disable the thread monitor.'))
   721     parser.add_option('--log-max', '--log_max', dest='log_max', type='int',
   722                       default=_DEFAULT_LOG_MAX_BYTES,
   723                       help='Log maximum bytes')
   724     parser.add_option('--log-count', '--log_count', dest='log_count',
   725                       type='int', default=_DEFAULT_LOG_BACKUP_COUNT,
   726                       help='Log backup count')
   727     parser.add_option('--allow-draft75', dest='allow_draft75',
   728                       action='store_true', default=False,
   729                       help='Allow draft 75 handshake')
   730     parser.add_option('--strict', dest='strict', action='store_true',
   731                       default=False, help='Strictly check handshake request')
   732     parser.add_option('-q', '--queue', dest='request_queue_size', type='int',
   733                       default=_DEFAULT_REQUEST_QUEUE_SIZE,
   734                       help='request queue size')
   736     return parser
   739 class ThreadMonitor(threading.Thread):
   740     daemon = True
   742     def __init__(self, interval_in_sec):
   743         threading.Thread.__init__(self, name='ThreadMonitor')
   745         self._logger = util.get_class_logger(self)
   747         self._interval_in_sec = interval_in_sec
   749     def run(self):
   750         while True:
   751             thread_name_list = []
   752             for thread in threading.enumerate():
   753                 thread_name_list.append(thread.name)
   754             self._logger.info(
   755                 "%d active threads: %s",
   756                 threading.active_count(),
   757                 ', '.join(thread_name_list))
   758             time.sleep(self._interval_in_sec)
   761 def _parse_args_and_config(args):
   762     parser = _build_option_parser()
   764     # First, parse options without configuration file.
   765     temporary_options, temporary_args = parser.parse_args(args=args)
   766     if temporary_args:
   767         logging.critical(
   768             'Unrecognized positional arguments: %r', temporary_args)
   769         sys.exit(1)
   771     if temporary_options.config_file:
   772         try:
   773             config_fp = open(temporary_options.config_file, 'r')
   774         except IOError, e:
   775             logging.critical(
   776                 'Failed to open configuration file %r: %r',
   777                 temporary_options.config_file,
   778                 e)
   779             sys.exit(1)
   781         config_parser = ConfigParser.SafeConfigParser()
   782         config_parser.readfp(config_fp)
   783         config_fp.close()
   785         args_from_config = []
   786         for name, value in config_parser.items('pywebsocket'):
   787             args_from_config.append('--' + name)
   788             args_from_config.append(value)
   789         if args is None:
   790             args = args_from_config
   791         else:
   792             args = args_from_config + args
   793         return parser.parse_args(args=args)
   794     else:
   795         return temporary_options, temporary_args
   798 def _main(args=None):
   799     options, args = _parse_args_and_config(args=args)
   801     os.chdir(options.document_root)
   803     _configure_logging(options)
   805     # TODO(tyoshino): Clean up initialization of CGI related values. Move some
   806     # of code here to WebSocketRequestHandler class if it's better.
   807     options.cgi_directories = []
   808     options.is_executable_method = None
   809     if options.cgi_paths:
   810         options.cgi_directories = options.cgi_paths.split(',')
   811         if sys.platform in ('cygwin', 'win32'):
   812             cygwin_path = None
   813             # For Win32 Python, it is expected that CYGWIN_PATH
   814             # is set to a directory of cygwin binaries.
   815             # For example, websocket_server.py in Chromium sets CYGWIN_PATH to
   816             # full path of third_party/cygwin/bin.
   817             if 'CYGWIN_PATH' in os.environ:
   818                 cygwin_path = os.environ['CYGWIN_PATH']
   819             util.wrap_popen3_for_win(cygwin_path)
   821             def __check_script(scriptpath):
   822                 return util.get_script_interp(scriptpath, cygwin_path)
   824             options.is_executable_method = __check_script
   826     if options.use_tls:
   827         if not (_HAS_SSL or _HAS_OPEN_SSL):
   828             logging.critical('TLS support requires ssl or pyOpenSSL.')
   829             sys.exit(1)
   830         if not options.private_key or not options.certificate:
   831             logging.critical(
   832                     'To use TLS, specify private_key and certificate.')
   833             sys.exit(1)
   835     if not options.scan_dir:
   836         options.scan_dir = options.websock_handlers
   838     try:
   839         if options.thread_monitor_interval_in_sec > 0:
   840             # Run a thread monitor to show the status of server threads for
   841             # debugging.
   842             ThreadMonitor(options.thread_monitor_interval_in_sec).start()
   844         # Share a Dispatcher among request handlers to save time for
   845         # instantiation.  Dispatcher can be shared because it is thread-safe.
   846         options.dispatcher = dispatch.Dispatcher(
   847             options.websock_handlers,
   848             options.scan_dir,
   849             options.allow_handlers_outside_root_dir)
   850         if options.websock_handlers_map_file:
   851             _alias_handlers(options.dispatcher,
   852                             options.websock_handlers_map_file)
   853         warnings = options.dispatcher.source_warnings()
   854         if warnings:
   855             for warning in warnings:
   856                 logging.warning('mod_pywebsocket: %s' % warning)
   858         server = WebSocketServer(options)
   859         server.serve_forever()
   860     except Exception, e:
   861         logging.critical('mod_pywebsocket: %s' % e)
   862         logging.critical('mod_pywebsocket: %s' % util.get_stack_trace())
   863         sys.exit(1)
   866 if __name__ == '__main__':
   867     _main(sys.argv[1:])
   870 # vi:sts=4 sw=4 et

mercurial