testing/mochitest/pywebsocket/standalone.py

branch
TOR_BUG_9701
changeset 15
b8a032363ba2
equal deleted inserted replaced
-1:000000000000 0:167c51b8a21e
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.
31
32
33 """Standalone WebSocket server.
34
35 BASIC USAGE
36
37 Use this server to run mod_pywebsocket without Apache HTTP Server.
38
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 ...
45
46 <ws_port> is the port number to use for ws:// connection.
47
48 <document_root> is the path to the root directory of HTML files.
49
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.
53
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.
56
57
58 CONFIGURATION FILE
59
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:
65
66 log_level=debug
67
68 For options which doesn't take value, please add some fake value. E.g. for
69 --tls option, add the following line:
70
71 tls=True
72
73 Note that tls will be enabled even if you write tls=False as the value part is
74 fake.
75
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.
79
80
81 THREADING
82
83 This server is derived from SocketServer.ThreadingMixIn. Hence a thread is
84 used for each request.
85
86
87 SECURITY WARNING
88
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 """
93
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
110
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
122
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
129
130
131 _DEFAULT_LOG_MAX_BYTES = 1024 * 256
132 _DEFAULT_LOG_BACKUP_COUNT = 5
133
134 _DEFAULT_REQUEST_QUEUE_SIZE = 128
135
136 # 1024 is practically large enough to contain WebSocket handshake lines.
137 _MAX_MEMORIZED_LINES = 1024
138
139
140 class _StandaloneConnection(object):
141 """Mimic mod_python mp_conn."""
142
143 def __init__(self, request_handler):
144 """Construct an instance.
145
146 Args:
147 request_handler: A WebSocketRequestHandler instance.
148 """
149
150 self._request_handler = request_handler
151
152 def get_local_addr(self):
153 """Getter to mimic mp_conn.local_addr."""
154
155 return (self._request_handler.server.server_name,
156 self._request_handler.server.server_port)
157 local_addr = property(get_local_addr)
158
159 def get_remote_addr(self):
160 """Getter to mimic mp_conn.remote_addr.
161
162 Setting the property in __init__ won't work because the request
163 handler is not initialized yet there."""
164
165 return self._request_handler.client_address
166 remote_addr = property(get_remote_addr)
167
168 def write(self, data):
169 """Mimic mp_conn.write()."""
170
171 return self._request_handler.wfile.write(data)
172
173 def read(self, length):
174 """Mimic mp_conn.read()."""
175
176 return self._request_handler.rfile.read(length)
177
178 def get_memorized_lines(self):
179 """Get memorized lines."""
180
181 return self._request_handler.rfile.get_memorized_lines()
182
183
184 class _StandaloneRequest(object):
185 """Mimic mod_python request."""
186
187 def __init__(self, request_handler, use_tls):
188 """Construct an instance.
189
190 Args:
191 request_handler: A WebSocketRequestHandler instance.
192 """
193
194 self._logger = util.get_class_logger(self)
195
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
200
201 def get_uri(self):
202 """Getter to mimic request.uri."""
203
204 return self._request_handler.path
205 uri = property(get_uri)
206
207 def get_method(self):
208 """Getter to mimic request.method."""
209
210 return self._request_handler.command
211 method = property(get_method)
212
213 def is_https(self):
214 """Mimic request.is_https()."""
215
216 return self._use_tls
217
218 def _drain_received_data(self):
219 """Don't use this method from WebSocket handler. Drains unread data
220 in the receive buffer.
221 """
222
223 raw_socket = self._request_handler.connection
224 drained_data = util.drain_received_data(raw_socket)
225
226 if drained_data:
227 self._logger.debug(
228 'Drained data following close frame: %r', drained_data)
229
230
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 """
235
236 def __init__(self, connection):
237 self._connection = connection
238
239 def __getattribute__(self, name):
240 if name in ('_connection', 'makefile'):
241 return object.__getattribute__(self, name)
242 return self._connection.__getattribute__(name)
243
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)
248
249 def makefile(self, mode='r', bufsize=-1):
250 return socket._fileobject(self._connection, mode, bufsize)
251
252
253 class WebSocketServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer):
254 """HTTPServer specialized for WebSocket."""
255
256 # Overrides SocketServer.ThreadingMixIn.daemon_threads
257 daemon_threads = True
258 # Overrides BaseHTTPServer.HTTPServer.allow_reuse_address
259 allow_reuse_address = True
260
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 """
266
267 self._logger = util.get_class_logger(self)
268
269 self.request_queue_size = options.request_queue_size
270 self.__ws_is_shut_down = threading.Event()
271 self.__ws_serving = False
272
273 SocketServer.BaseServer.__init__(
274 self, (options.server_host, options.port), WebSocketRequestHandler)
275
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
279
280 self._create_sockets()
281 self.server_bind()
282 self.server_activate()
283
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))
326
327 def server_bind(self):
328 """Override SocketServer.TCPServer.server_bind to enable multiple
329 sockets bind.
330 """
331
332 failed_sockets = []
333
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)
345
346 for socketinfo in failed_sockets:
347 self._sockets.remove(socketinfo)
348
349 def server_activate(self):
350 """Override SocketServer.TCPServer.server_activate to enable multiple
351 sockets listen.
352 """
353
354 failed_sockets = []
355
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)
365
366 for socketinfo in failed_sockets:
367 self._sockets.remove(socketinfo)
368
369 def server_close(self):
370 """Override SocketServer.TCPServer.server_close to enable multiple
371 sockets close.
372 """
373
374 for socketinfo in self._sockets:
375 socket_, addrinfo = socketinfo
376 self._logger.info('Close on: %r', addrinfo)
377 socket_.close()
378
379 def fileno(self):
380 """Override SocketServer.TCPServer.fileno."""
381
382 self._logger.critical('Not supported: fileno')
383 return self._sockets[0][0].fileno()
384
385 def handle_error(self, rquest, client_address):
386 """Override SocketServer.handle_error."""
387
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.
393
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 """
400
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
405
406 def serve_forever(self, poll_interval=0.5):
407 """Override SocketServer.BaseServer.serve_forever."""
408
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()
427
428 def shutdown(self):
429 """Override SocketServer.BaseServer.shutdown."""
430
431 self.__ws_serving = False
432 self.__ws_is_shut_down.wait()
433
434
435 class WebSocketRequestHandler(CGIHTTPServer.CGIHTTPRequestHandler):
436 """CGIHTTPRequestHandler specialized for WebSocket."""
437
438 # Use httplib.HTTPMessage instead of mimetools.Message.
439 MessageClass = httplib.HTTPMessage
440
441 def setup(self):
442 """Override SocketServer.StreamRequestHandler.setup to wrap rfile
443 with MemorizingFile.
444
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 """
451
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)
456
457 self.rfile = memorizingfile.MemorizingFile(
458 self.rfile,
459 max_memorized_lines=_MAX_MEMORIZED_LINES)
460
461 def __init__(self, request, client_address, server):
462 self._logger = util.get_class_logger(self)
463
464 self._options = server.websocket_server_options
465
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
471
472 # This actually calls BaseRequestHandler.__init__.
473 CGIHTTPServer.CGIHTTPRequestHandler.__init__(
474 self, request, client_address, server)
475
476 def parse_request(self):
477 """Override BaseHTTPServer.BaseHTTPRequestHandler.parse_request.
478
479 Return True to continue processing for HTTP(S), False otherwise.
480
481 See BaseHTTPRequestHandler.handle_one_request method which calls
482 this method to understand how the return value will be handled.
483 """
484
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
519
520 request = _StandaloneRequest(self, self._options.use_tls)
521
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
534
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.
538
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
558
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
564
565 def log_request(self, code='-', size='-'):
566 """Override BaseHTTPServer.log_request."""
567
568 self._logger.info('"%s" %s %s',
569 self.requestline, str(code), str(size))
570
571 def log_error(self, *args):
572 """Override BaseHTTPServer.log_error."""
573
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:])
579
580 def is_cgi(self):
581 """Test whether self.path corresponds to a CGI script.
582
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 """
588
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
602
603
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)
616
617
618 def _alias_handlers(dispatcher, websock_handlers_map_file):
619 """Set aliases specified in websock_handler_map_file in dispatcher.
620
621 Args:
622 dispatcher: dispatch.Dispatcher instance
623 websock_handler_map_file: alias map file
624 """
625
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()
642
643
644 def _build_option_parser():
645 parser = optparse.OptionParser()
646
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')
735
736 return parser
737
738
739 class ThreadMonitor(threading.Thread):
740 daemon = True
741
742 def __init__(self, interval_in_sec):
743 threading.Thread.__init__(self, name='ThreadMonitor')
744
745 self._logger = util.get_class_logger(self)
746
747 self._interval_in_sec = interval_in_sec
748
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)
759
760
761 def _parse_args_and_config(args):
762 parser = _build_option_parser()
763
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)
770
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)
780
781 config_parser = ConfigParser.SafeConfigParser()
782 config_parser.readfp(config_fp)
783 config_fp.close()
784
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
796
797
798 def _main(args=None):
799 options, args = _parse_args_and_config(args=args)
800
801 os.chdir(options.document_root)
802
803 _configure_logging(options)
804
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)
820
821 def __check_script(scriptpath):
822 return util.get_script_interp(scriptpath, cygwin_path)
823
824 options.is_executable_method = __check_script
825
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)
834
835 if not options.scan_dir:
836 options.scan_dir = options.websock_handlers
837
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()
843
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)
857
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)
864
865
866 if __name__ == '__main__':
867 _main(sys.argv[1:])
868
869
870 # vi:sts=4 sw=4 et

mercurial