3 # Copyright 2011, Google Inc.
6 # Redistribution and use in source and binary forms, with or without
7 # modification, are permitted provided that the following conditions are
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
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.
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.
37 Use this server to run mod_pywebsocket without Apache HTTP Server.
40 python standalone.py [-p <ws_port>] [-w <websock_handlers>]
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.
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:
68 For options which doesn't take value, please add some fake value. E.g. for
69 --tls option, add the following line:
73 Note that tls will be enabled even if you write tls=False as the value part is
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
83 This server is derived from SocketServer.ThreadingMixIn. Hence a thread is
84 used for each request.
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.
96 import SimpleHTTPServer
101 import logging
.handlers
112 _HAS_OPEN_SSL
= False
119 from mod_pywebsocket
import common
120 from mod_pywebsocket
import dispatch
121 from mod_pywebsocket
import handshake
122 from mod_pywebsocket
import http_header_util
123 from mod_pywebsocket
import memorizingfile
124 from mod_pywebsocket
import util
127 _DEFAULT_LOG_MAX_BYTES
= 1024 * 256
128 _DEFAULT_LOG_BACKUP_COUNT
= 5
130 _DEFAULT_REQUEST_QUEUE_SIZE
= 128
132 # 1024 is practically large enough to contain WebSocket handshake lines.
133 _MAX_MEMORIZED_LINES
= 1024
136 class _StandaloneConnection(object):
137 """Mimic mod_python mp_conn."""
139 def __init__(self
, request_handler
):
140 """Construct an instance.
143 request_handler: A WebSocketRequestHandler instance.
146 self
._request
_handler
= request_handler
148 def get_local_addr(self
):
149 """Getter to mimic mp_conn.local_addr."""
151 return (self
._request
_handler
.server
.server_name
,
152 self
._request
_handler
.server
.server_port
)
153 local_addr
= property(get_local_addr
)
155 def get_remote_addr(self
):
156 """Getter to mimic mp_conn.remote_addr.
158 Setting the property in __init__ won't work because the request
159 handler is not initialized yet there."""
161 return self
._request
_handler
.client_address
162 remote_addr
= property(get_remote_addr
)
164 def write(self
, data
):
165 """Mimic mp_conn.write()."""
167 return self
._request
_handler
.wfile
.write(data
)
169 def read(self
, length
):
170 """Mimic mp_conn.read()."""
172 return self
._request
_handler
.rfile
.read(length
)
174 def get_memorized_lines(self
):
175 """Get memorized lines."""
177 return self
._request
_handler
.rfile
.get_memorized_lines()
180 class _StandaloneRequest(object):
181 """Mimic mod_python request."""
183 def __init__(self
, request_handler
, use_tls
):
184 """Construct an instance.
187 request_handler: A WebSocketRequestHandler instance.
190 self
._logger
= util
.get_class_logger(self
)
192 self
._request
_handler
= request_handler
193 self
.connection
= _StandaloneConnection(request_handler
)
194 self
._use
_tls
= use_tls
195 self
.headers_in
= request_handler
.headers
198 """Getter to mimic request.uri."""
200 return self
._request
_handler
.path
201 uri
= property(get_uri
)
203 def get_method(self
):
204 """Getter to mimic request.method."""
206 return self
._request
_handler
.command
207 method
= property(get_method
)
210 """Mimic request.is_https()."""
214 def _drain_received_data(self
):
215 """Don't use this method from WebSocket handler. Drains unread data
216 in the receive buffer.
219 raw_socket
= self
._request
_handler
.connection
220 drained_data
= util
.drain_received_data(raw_socket
)
224 'Drained data following close frame: %r', drained_data
)
227 class _StandaloneSSLConnection(object):
228 """A wrapper class for OpenSSL.SSL.Connection to provide makefile method
229 which is not supported by the class.
232 def __init__(self
, connection
):
233 self
._connection
= connection
235 def __getattribute__(self
, name
):
236 if name
in ('_connection', 'makefile'):
237 return object.__getattribute
__(self
, name
)
238 return self
._connection
.__getattribute
__(name
)
240 def __setattr__(self
, name
, value
):
241 if name
in ('_connection', 'makefile'):
242 return object.__setattr
__(self
, name
, value
)
243 return self
._connection
.__setattr
__(name
, value
)
245 def makefile(self
, mode
='r', bufsize
=-1):
246 return socket
._fileobject
(self
._connection
, mode
, bufsize
)
249 class WebSocketServer(SocketServer
.ThreadingMixIn
, BaseHTTPServer
.HTTPServer
):
250 """HTTPServer specialized for WebSocket."""
252 # Overrides SocketServer.ThreadingMixIn.daemon_threads
253 daemon_threads
= True
254 # Overrides BaseHTTPServer.HTTPServer.allow_reuse_address
255 allow_reuse_address
= True
257 def __init__(self
, options
):
258 """Override SocketServer.TCPServer.__init__ to set SSL enabled
259 socket object to self.socket before server_bind and server_activate,
263 self
._logger
= util
.get_class_logger(self
)
265 self
.request_queue_size
= options
.request_queue_size
266 self
.__ws
_is
_shut
_down
= threading
.Event()
267 self
.__ws
_serving
= False
269 SocketServer
.BaseServer
.__init
__(
270 self
, (options
.server_host
, options
.port
), WebSocketRequestHandler
)
272 # Expose the options object to allow handler objects access it. We name
273 # it with websocket_ prefix to avoid conflict.
274 self
.websocket_server_options
= options
276 self
._create
_sockets
()
278 self
.server_activate()
280 def _create_sockets(self
):
281 self
.server_name
, self
.server_port
= self
.server_address
283 if not self
.server_name
:
284 # On platforms that doesn't support IPv6, the first bind fails.
285 # On platforms that supports IPv6
286 # - If it binds both IPv4 and IPv6 on call with AF_INET6, the
287 # first bind succeeds and the second fails (we'll see 'Address
288 # already in use' error).
289 # - If it binds only IPv6 on call with AF_INET6, both call are
290 # expected to succeed to listen both protocol.
292 (socket
.AF_INET6
, socket
.SOCK_STREAM
, '', '', ''),
293 (socket
.AF_INET
, socket
.SOCK_STREAM
, '', '', '')]
295 addrinfo_array
= socket
.getaddrinfo(self
.server_name
,
300 for addrinfo
in addrinfo_array
:
301 self
._logger
.info('Create socket on: %r', addrinfo
)
302 family
, socktype
, proto
, canonname
, sockaddr
= addrinfo
304 socket_
= socket
.socket(family
, socktype
)
306 self
._logger
.info('Skip by failure: %r', e
)
308 if self
.websocket_server_options
.use_tls
:
309 ctx
= OpenSSL
.SSL
.Context(OpenSSL
.SSL
.SSLv23_METHOD
)
310 ctx
.use_privatekey_file(
311 self
.websocket_server_options
.private_key
)
312 ctx
.use_certificate_file(
313 self
.websocket_server_options
.certificate
)
314 socket_
= OpenSSL
.SSL
.Connection(ctx
, socket_
)
315 self
._sockets
.append((socket_
, addrinfo
))
317 def server_bind(self
):
318 """Override SocketServer.TCPServer.server_bind to enable multiple
324 for socketinfo
in self
._sockets
:
325 socket_
, addrinfo
= socketinfo
326 self
._logger
.info('Bind on: %r', addrinfo
)
327 if self
.allow_reuse_address
:
328 socket_
.setsockopt(socket
.SOL_SOCKET
, socket
.SO_REUSEADDR
, 1)
330 socket_
.bind(self
.server_address
)
332 self
._logger
.info('Skip by failure: %r', e
)
334 failed_sockets
.append(socketinfo
)
336 for socketinfo
in failed_sockets
:
337 self
._sockets
.remove(socketinfo
)
339 def server_activate(self
):
340 """Override SocketServer.TCPServer.server_activate to enable multiple
346 for socketinfo
in self
._sockets
:
347 socket_
, addrinfo
= socketinfo
348 self
._logger
.info('Listen on: %r', addrinfo
)
350 socket_
.listen(self
.request_queue_size
)
352 self
._logger
.info('Skip by failure: %r', e
)
354 failed_sockets
.append(socketinfo
)
356 for socketinfo
in failed_sockets
:
357 self
._sockets
.remove(socketinfo
)
359 def server_close(self
):
360 """Override SocketServer.TCPServer.server_close to enable multiple
364 for socketinfo
in self
._sockets
:
365 socket_
, addrinfo
= socketinfo
366 self
._logger
.info('Close on: %r', addrinfo
)
370 """Override SocketServer.TCPServer.fileno."""
372 self
._logger
.critical('Not supported: fileno')
373 return self
._sockets
[0][0].fileno()
375 def handle_error(self
, rquest
, client_address
):
376 """Override SocketServer.handle_error."""
379 'Exception in processing request from: %r\n%s',
381 util
.get_stack_trace())
382 # Note: client_address is a tuple.
384 def get_request(self
):
385 """Override TCPServer.get_request to wrap OpenSSL.SSL.Connection
386 object with _StandaloneSSLConnection to provide makefile method. We
387 cannot substitute OpenSSL.SSL.Connection.makefile since it's readonly
391 accepted_socket
, client_address
= self
.socket
.accept()
392 return _StandaloneSSLConnection(accepted_socket
), client_address
394 def serve_forever(self
, poll_interval
=0.5):
395 """Override SocketServer.BaseServer.serve_forever."""
397 self
.__ws
_serving
= True
398 self
.__ws
_is
_shut
_down
.clear()
399 handle_request
= self
.handle_request
400 if hasattr(self
, '_handle_request_noblock'):
401 handle_request
= self
._handle
_request
_noblock
403 self
._logger
.warning('Fallback to blocking request handler')
405 while self
.__ws
_serving
:
406 r
, w
, e
= select
.select(
407 [socket_
[0] for socket_
in self
._sockets
],
408 [], [], poll_interval
)
410 self
.socket
= socket_
414 self
.__ws
_is
_shut
_down
.set()
417 """Override SocketServer.BaseServer.shutdown."""
419 self
.__ws
_serving
= False
420 self
.__ws
_is
_shut
_down
.wait()
423 class WebSocketRequestHandler(CGIHTTPServer
.CGIHTTPRequestHandler
):
424 """CGIHTTPRequestHandler specialized for WebSocket."""
426 # Use httplib.HTTPMessage instead of mimetools.Message.
427 MessageClass
= httplib
.HTTPMessage
430 """Override SocketServer.StreamRequestHandler.setup to wrap rfile
433 This method will be called by BaseRequestHandler's constructor
434 before calling BaseHTTPRequestHandler.handle.
435 BaseHTTPRequestHandler.handle will call
436 BaseHTTPRequestHandler.handle_one_request and it will call
437 WebSocketRequestHandler.parse_request.
440 # Call superclass's setup to prepare rfile, wfile, etc. See setup
441 # definition on the root class SocketServer.StreamRequestHandler to
442 # understand what this does.
443 CGIHTTPServer
.CGIHTTPRequestHandler
.setup(self
)
445 self
.rfile
= memorizingfile
.MemorizingFile(
447 max_memorized_lines
=_MAX_MEMORIZED_LINES
)
449 def __init__(self
, request
, client_address
, server
):
450 self
._logger
= util
.get_class_logger(self
)
452 self
._options
= server
.websocket_server_options
454 # Overrides CGIHTTPServerRequestHandler.cgi_directories.
455 self
.cgi_directories
= self
._options
.cgi_directories
456 # Replace CGIHTTPRequestHandler.is_executable method.
457 if self
._options
.is_executable_method
is not None:
458 self
.is_executable
= self
._options
.is_executable_method
460 # This actually calls BaseRequestHandler.__init__.
461 CGIHTTPServer
.CGIHTTPRequestHandler
.__init
__(
462 self
, request
, client_address
, server
)
464 def parse_request(self
):
465 """Override BaseHTTPServer.BaseHTTPRequestHandler.parse_request.
467 Return True to continue processing for HTTP(S), False otherwise.
469 See BaseHTTPRequestHandler.handle_one_request method which calls
470 this method to understand how the return value will be handled.
473 # We hook parse_request method, but also call the original
474 # CGIHTTPRequestHandler.parse_request since when we return False,
475 # CGIHTTPRequestHandler.handle_one_request continues processing and
476 # it needs variables set by CGIHTTPRequestHandler.parse_request.
478 # Variables set by this method will be also used by WebSocket request
479 # handling (self.path, self.command, self.requestline, etc. See also
480 # how _StandaloneRequest's members are implemented using these
482 if not CGIHTTPServer
.CGIHTTPRequestHandler
.parse_request(self
):
484 host
, port
, resource
= http_header_util
.parse_uri(self
.path
)
486 self
._logger
.info('Invalid URI: %r', self
.path
)
487 self
._logger
.info('Fallback to CGIHTTPRequestHandler')
489 server_options
= self
.server
.websocket_server_options
491 validation_host
= server_options
.validation_host
492 if validation_host
is not None and host
!= validation_host
:
493 self
._logger
.info('Invalid host: %r (expected: %r)',
496 self
._logger
.info('Fallback to CGIHTTPRequestHandler')
499 validation_port
= server_options
.validation_port
500 if validation_port
is not None and port
!= validation_port
:
501 self
._logger
.info('Invalid port: %r (expected: %r)',
504 self
._logger
.info('Fallback to CGIHTTPRequestHandler')
508 request
= _StandaloneRequest(self
, self
._options
.use_tls
)
511 # Fallback to default http handler for request paths for which
512 # we don't have request handlers.
513 if not self
._options
.dispatcher
.get_handler_suite(self
.path
):
514 self
._logger
.info('No handler for resource: %r',
516 self
._logger
.info('Fallback to CGIHTTPRequestHandler')
518 except dispatch
.DispatchException
, e
:
519 self
._logger
.info('%s', e
)
520 self
.send_error(e
.status
)
523 # If any Exceptions without except clause setup (including
524 # DispatchException) is raised below this point, it will be caught
525 # and logged by WebSocketServer.
529 handshake
.do_handshake(
531 self
._options
.dispatcher
,
532 allowDraft75
=self
._options
.allow_draft75
,
533 strict
=self
._options
.strict
)
534 except handshake
.VersionException
, e
:
535 self
._logger
.info('%s', e
)
536 self
.send_response(common
.HTTP_STATUS_BAD_REQUEST
)
537 self
.send_header(common
.SEC_WEBSOCKET_VERSION_HEADER
,
538 e
.supported_versions
)
541 except handshake
.HandshakeException
, e
:
542 # Handshake for ws(s) failed.
543 self
._logger
.info('%s', e
)
544 self
.send_error(e
.status
)
547 request
._dispatcher
= self
._options
.dispatcher
548 self
._options
.dispatcher
.transfer_data(request
)
549 except handshake
.AbortedByUserException
, e
:
550 self
._logger
.info('%s', e
)
553 def log_request(self
, code
='-', size
='-'):
554 """Override BaseHTTPServer.log_request."""
556 self
._logger
.info('"%s" %s %s',
557 self
.requestline
, str(code
), str(size
))
559 def log_error(self
, *args
):
560 """Override BaseHTTPServer.log_error."""
562 # Despite the name, this method is for warnings than for errors.
563 # For example, HTTP status code is logged by this method.
564 self
._logger
.warning('%s - %s',
565 self
.address_string(),
569 """Test whether self.path corresponds to a CGI script.
571 Add extra check that self.path doesn't contains ..
572 Also check if the file is a executable file or not.
573 If the file is not executable, it is handled as static file or dir
574 rather than a CGI script.
577 if CGIHTTPServer
.CGIHTTPRequestHandler
.is_cgi(self
):
578 if '..' in self
.path
:
580 # strip query parameter from request path
581 resource_name
= self
.path
.split('?', 2)[0]
582 # convert resource_name into real path name in filesystem.
583 scriptfile
= self
.translate_path(resource_name
)
584 if not os
.path
.isfile(scriptfile
):
586 if not self
.is_executable(scriptfile
):
592 def _configure_logging(options
):
593 logger
= logging
.getLogger()
594 logger
.setLevel(logging
.getLevelName(options
.log_level
.upper()))
596 handler
= logging
.handlers
.RotatingFileHandler(
597 options
.log_file
, 'a', options
.log_max
, options
.log_count
)
599 handler
= logging
.StreamHandler()
600 formatter
= logging
.Formatter(
601 '[%(asctime)s] [%(levelname)s] %(name)s: %(message)s')
602 handler
.setFormatter(formatter
)
603 logger
.addHandler(handler
)
606 def _alias_handlers(dispatcher
, websock_handlers_map_file
):
607 """Set aliases specified in websock_handler_map_file in dispatcher.
610 dispatcher: dispatch.Dispatcher instance
611 websock_handler_map_file: alias map file
614 fp
= open(websock_handlers_map_file
)
617 if line
[0] == '#' or line
.isspace():
619 m
= re
.match('(\S+)\s+(\S+)', line
)
621 logging
.warning('Wrong format in map file:' + line
)
624 dispatcher
.add_resource_path_alias(
625 m
.group(1), m
.group(2))
626 except dispatch
.DispatchException
, e
:
627 logging
.error(str(e
))
632 def _build_option_parser():
633 parser
= optparse
.OptionParser()
635 parser
.add_option('--config', dest
='config_file', type='string',
637 help=('Path to configuration file. See the file comment '
638 'at the top of this file for the configuration '
640 parser
.add_option('-H', '--server-host', '--server_host',
643 help='server hostname to listen to')
644 parser
.add_option('-V', '--validation-host', '--validation_host',
645 dest
='validation_host',
647 help='server hostname to validate in absolute path.')
648 parser
.add_option('-p', '--port', dest
='port', type='int',
649 default
=common
.DEFAULT_WEB_SOCKET_PORT
,
650 help='port to listen to')
651 parser
.add_option('-P', '--validation-port', '--validation_port',
652 dest
='validation_port', type='int',
654 help='server port to validate in absolute path.')
655 parser
.add_option('-w', '--websock-handlers', '--websock_handlers',
656 dest
='websock_handlers',
658 help='WebSocket handlers root directory.')
659 parser
.add_option('-m', '--websock-handlers-map-file',
660 '--websock_handlers_map_file',
661 dest
='websock_handlers_map_file',
663 help=('WebSocket handlers map file. '
664 'Each line consists of alias_resource_path and '
665 'existing_resource_path, separated by spaces.'))
666 parser
.add_option('-s', '--scan-dir', '--scan_dir', dest
='scan_dir',
668 help=('WebSocket handlers scan directory. '
669 'Must be a directory under websock_handlers.'))
670 parser
.add_option('--allow-handlers-outside-root-dir',
671 '--allow_handlers_outside_root_dir',
672 dest
='allow_handlers_outside_root_dir',
675 help=('Scans WebSocket handlers even if their canonical '
676 'path is not under websock_handlers.'))
677 parser
.add_option('-d', '--document-root', '--document_root',
678 dest
='document_root', default
='.',
679 help='Document root directory.')
680 parser
.add_option('-x', '--cgi-paths', '--cgi_paths', dest
='cgi_paths',
682 help=('CGI paths relative to document_root.'
683 'Comma-separated. (e.g -x /cgi,/htbin) '
684 'Files under document_root/cgi_path are handled '
685 'as CGI programs. Must be executable.'))
686 parser
.add_option('-t', '--tls', dest
='use_tls', action
='store_true',
687 default
=False, help='use TLS (wss://)')
688 parser
.add_option('-k', '--private-key', '--private_key',
690 default
='', help='TLS private key file.')
691 parser
.add_option('-c', '--certificate', dest
='certificate',
692 default
='', help='TLS certificate file.')
693 parser
.add_option('-l', '--log-file', '--log_file', dest
='log_file',
694 default
='', help='Log file.')
695 parser
.add_option('--log-level', '--log_level', type='choice',
696 dest
='log_level', default
='warn',
697 choices
=['debug', 'info', 'warning', 'warn', 'error',
700 parser
.add_option('--thread-monitor-interval-in-sec',
701 '--thread_monitor_interval_in_sec',
702 dest
='thread_monitor_interval_in_sec',
703 type='int', default
=-1,
704 help=('If positive integer is specified, run a thread '
705 'monitor to show the status of server threads '
706 'periodically in the specified inteval in '
707 'second. If non-positive integer is specified, '
708 'disable the thread monitor.'))
709 parser
.add_option('--log-max', '--log_max', dest
='log_max', type='int',
710 default
=_DEFAULT_LOG_MAX_BYTES
,
711 help='Log maximum bytes')
712 parser
.add_option('--log-count', '--log_count', dest
='log_count',
713 type='int', default
=_DEFAULT_LOG_BACKUP_COUNT
,
714 help='Log backup count')
715 parser
.add_option('--allow-draft75', dest
='allow_draft75',
716 action
='store_true', default
=False,
717 help='Allow draft 75 handshake')
718 parser
.add_option('--strict', dest
='strict', action
='store_true',
719 default
=False, help='Strictly check handshake request')
720 parser
.add_option('-q', '--queue', dest
='request_queue_size', type='int',
721 default
=_DEFAULT_REQUEST_QUEUE_SIZE
,
722 help='request queue size')
727 class ThreadMonitor(threading
.Thread
):
730 def __init__(self
, interval_in_sec
):
731 threading
.Thread
.__init
__(self
, name
='ThreadMonitor')
733 self
._logger
= util
.get_class_logger(self
)
735 self
._interval
_in
_sec
= interval_in_sec
739 thread_name_list
= []
740 for thread
in threading
.enumerate():
741 thread_name_list
.append(thread
.name
)
743 "%d active threads: %s",
744 threading
.active_count(),
745 ', '.join(thread_name_list
))
746 time
.sleep(self
._interval
_in
_sec
)
749 def _parse_args_and_config(args
):
750 parser
= _build_option_parser()
752 # First, parse options without configuration file.
753 temporary_options
, temporary_args
= parser
.parse_args(args
=args
)
756 'Unrecognized positional arguments: %r', temporary_args
)
759 if temporary_options
.config_file
:
761 config_fp
= open(temporary_options
.config_file
, 'r')
764 'Failed to open configuration file %r: %r',
765 temporary_options
.config_file
,
769 config_parser
= ConfigParser
.SafeConfigParser()
770 config_parser
.readfp(config_fp
)
773 args_from_config
= []
774 for name
, value
in config_parser
.items('pywebsocket'):
775 args_from_config
.append('--' + name
)
776 args_from_config
.append(value
)
778 args
= args_from_config
780 args
= args_from_config
+ args
781 return parser
.parse_args(args
=args
)
783 return temporary_options
, temporary_args
786 def _main(args
=None):
787 options
, args
= _parse_args_and_config(args
=args
)
789 os
.chdir(options
.document_root
)
791 _configure_logging(options
)
793 # TODO(tyoshino): Clean up initialization of CGI related values. Move some
794 # of code here to WebSocketRequestHandler class if it's better.
795 options
.cgi_directories
= []
796 options
.is_executable_method
= None
797 if options
.cgi_paths
:
798 options
.cgi_directories
= options
.cgi_paths
.split(',')
799 if sys
.platform
in ('cygwin', 'win32'):
801 # For Win32 Python, it is expected that CYGWIN_PATH
802 # is set to a directory of cygwin binaries.
803 # For example, websocket_server.py in Chromium sets CYGWIN_PATH to
804 # full path of third_party/cygwin/bin.
805 if 'CYGWIN_PATH' in os
.environ
:
806 cygwin_path
= os
.environ
['CYGWIN_PATH']
807 util
.wrap_popen3_for_win(cygwin_path
)
809 def __check_script(scriptpath
):
810 return util
.get_script_interp(scriptpath
, cygwin_path
)
812 options
.is_executable_method
= __check_script
815 if not _HAS_OPEN_SSL
:
816 logging
.critical('To use TLS, install pyOpenSSL.')
818 if not options
.private_key
or not options
.certificate
:
820 'To use TLS, specify private_key and certificate.')
823 if not options
.scan_dir
:
824 options
.scan_dir
= options
.websock_handlers
827 if options
.thread_monitor_interval_in_sec
> 0:
828 # Run a thread monitor to show the status of server threads for
830 ThreadMonitor(options
.thread_monitor_interval_in_sec
).start()
832 # Share a Dispatcher among request handlers to save time for
833 # instantiation. Dispatcher can be shared because it is thread-safe.
834 options
.dispatcher
= dispatch
.Dispatcher(
835 options
.websock_handlers
,
837 options
.allow_handlers_outside_root_dir
)
838 if options
.websock_handlers_map_file
:
839 _alias_handlers(options
.dispatcher
,
840 options
.websock_handlers_map_file
)
841 warnings
= options
.dispatcher
.source_warnings()
843 for warning
in warnings
:
844 logging
.warning('mod_pywebsocket: %s' % warning
)
846 server
= WebSocketServer(options
)
847 server
.serve_forever()
849 logging
.critical('mod_pywebsocket: %s' % e
)
850 logging
.critical('mod_pywebsocket: %s' % util
.get_stack_trace())
854 if __name__
== '__main__':