1#!/usr/bin/env python 2# Copyright (C) 2010 Google Inc. All rights reserved. 3# 4# Redistribution and use in source and binary forms, with or without 5# modification, are permitted provided that the following conditions are 6# met: 7# 8# * Redistributions of source code must retain the above copyright 9# notice, this list of conditions and the following disclaimer. 10# * Redistributions in binary form must reproduce the above 11# copyright notice, this list of conditions and the following disclaimer 12# in the documentation and/or other materials provided with the 13# distribution. 14# * Neither the name of Google Inc. nor the names of its 15# contributors may be used to endorse or promote products derived from 16# this software without specific prior written permission. 17# 18# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 30"""A class to help start/stop the PyWebSocket server used by layout tests.""" 31 32 33import logging 34import optparse 35import os 36import subprocess 37import sys 38import tempfile 39import time 40import urllib 41 42import http_server 43 44_WS_LOG_PREFIX = 'pywebsocket.ws.log-' 45_WSS_LOG_PREFIX = 'pywebsocket.wss.log-' 46 47_DEFAULT_WS_PORT = 8880 48_DEFAULT_WSS_PORT = 9323 49 50 51def url_is_alive(url): 52 """Checks to see if we get an http response from |url|. 53 We poll the url 5 times with a 1 second delay. If we don't 54 get a reply in that time, we give up and assume the httpd 55 didn't start properly. 56 57 Args: 58 url: The URL to check. 59 Return: 60 True if the url is alive. 61 """ 62 wait_time = 5 63 while wait_time > 0: 64 try: 65 response = urllib.urlopen(url) 66 # Server is up and responding. 67 return True 68 except IOError: 69 pass 70 wait_time -= 1 71 # Wait a second and try again. 72 time.sleep(1) 73 74 return False 75 76 77class PyWebSocketNotStarted(Exception): 78 pass 79 80 81class PyWebSocketNotFound(Exception): 82 pass 83 84 85class PyWebSocket(http_server.Lighttpd): 86 87 def __init__(self, port_obj, output_dir, port=_DEFAULT_WS_PORT, 88 root=None, use_tls=False, 89 register_cygwin=None, 90 pidfile=None): 91 """Args: 92 output_dir: the absolute path to the layout test result directory 93 """ 94 http_server.Lighttpd.__init__(self, port_obj, output_dir, 95 port=_DEFAULT_WS_PORT, 96 root=root, 97 register_cygwin=register_cygwin) 98 self._output_dir = output_dir 99 self._process = None 100 self._port = port 101 self._root = root 102 self._use_tls = use_tls 103 self._private_key = self._pem_file 104 self._certificate = self._pem_file 105 if self._port: 106 self._port = int(self._port) 107 if self._use_tls: 108 self._server_name = 'PyWebSocket(Secure)' 109 else: 110 self._server_name = 'PyWebSocket' 111 self._pidfile = pidfile 112 self._wsout = None 113 114 # Webkit tests 115 if self._root: 116 self._layout_tests = os.path.abspath(self._root) 117 self._web_socket_tests = os.path.abspath( 118 os.path.join(self._root, 'websocket', 'tests')) 119 else: 120 try: 121 self._layout_tests = self._port_obj.layout_tests_dir() 122 self._web_socket_tests = os.path.join(self._layout_tests, 123 'websocket', 'tests') 124 except: 125 self._web_socket_tests = None 126 127 def start(self): 128 if not self._web_socket_tests: 129 logging.info('No need to start %s server.' % self._server_name) 130 return 131 if self.is_running(): 132 raise PyWebSocketNotStarted('%s is already running.' % 133 self._server_name) 134 135 time_str = time.strftime('%d%b%Y-%H%M%S') 136 if self._use_tls: 137 log_prefix = _WSS_LOG_PREFIX 138 else: 139 log_prefix = _WS_LOG_PREFIX 140 log_file_name = log_prefix + time_str 141 142 # Remove old log files. We only need to keep the last ones. 143 self.remove_log_files(self._output_dir, log_prefix) 144 145 error_log = os.path.join(self._output_dir, log_file_name + "-err.txt") 146 147 output_log = os.path.join(self._output_dir, log_file_name + "-out.txt") 148 self._wsout = open(output_log, "w") 149 150 python_interp = sys.executable 151 pywebsocket_base = os.path.join( 152 os.path.dirname(os.path.dirname(os.path.dirname( 153 os.path.dirname(os.path.dirname( 154 os.path.abspath(__file__)))))), 'pywebsocket') 155 pywebsocket_script = os.path.join(pywebsocket_base, 'mod_pywebsocket', 156 'standalone.py') 157 start_cmd = [ 158 python_interp, pywebsocket_script, 159 '-p', str(self._port), 160 '-d', self._layout_tests, 161 '-s', self._web_socket_tests, 162 '-l', error_log, 163 ] 164 165 handler_map_file = os.path.join(self._web_socket_tests, 166 'handler_map.txt') 167 if os.path.exists(handler_map_file): 168 logging.debug('Using handler_map_file: %s' % handler_map_file) 169 start_cmd.append('-m') 170 start_cmd.append(handler_map_file) 171 else: 172 logging.warning('No handler_map_file found') 173 174 if self._use_tls: 175 start_cmd.extend(['-t', '-k', self._private_key, 176 '-c', self._certificate]) 177 178 # Put the cygwin directory first in the path to find cygwin1.dll 179 env = os.environ 180 if sys.platform in ('cygwin', 'win32'): 181 env['PATH'] = '%s;%s' % ( 182 self._port_obj.path_from_chromium_base('third_party', 183 'cygwin', 'bin'), 184 env['PATH']) 185 186 if sys.platform == 'win32' and self._register_cygwin: 187 setup_mount = self._port_obj.path_from_chromium_base( 188 'third_party', 'cygwin', 'setup_mount.bat') 189 subprocess.Popen(setup_mount).wait() 190 191 env['PYTHONPATH'] = (pywebsocket_base + os.path.pathsep + 192 env.get('PYTHONPATH', '')) 193 194 logging.debug('Starting %s server on %d.' % ( 195 self._server_name, self._port)) 196 logging.debug('cmdline: %s' % ' '.join(start_cmd)) 197 self._process = subprocess.Popen(start_cmd, stdout=self._wsout, 198 stderr=subprocess.STDOUT, 199 env=env) 200 201 # Wait a bit before checking the liveness of the server. 202 time.sleep(0.5) 203 204 if self._use_tls: 205 url = 'https' 206 else: 207 url = 'http' 208 url = url + '://127.0.0.1:%d/' % self._port 209 if not url_is_alive(url): 210 fp = open(output_log) 211 try: 212 for line in fp: 213 logging.error(line) 214 finally: 215 fp.close() 216 raise PyWebSocketNotStarted( 217 'Failed to start %s server on port %s.' % 218 (self._server_name, self._port)) 219 220 # Our process terminated already 221 if self._process.returncode != None: 222 raise PyWebSocketNotStarted( 223 'Failed to start %s server.' % self._server_name) 224 if self._pidfile: 225 f = open(self._pidfile, 'w') 226 f.write("%d" % self._process.pid) 227 f.close() 228 229 def stop(self, force=False): 230 if not force and not self.is_running(): 231 return 232 233 if self._process: 234 pid = self._process.pid 235 elif self._pidfile: 236 f = open(self._pidfile) 237 pid = int(f.read().strip()) 238 f.close() 239 240 if not pid: 241 raise PyWebSocketNotFound( 242 'Failed to find %s server pid.' % self._server_name) 243 244 logging.debug('Shutting down %s server %d.' % (self._server_name, pid)) 245 self._port_obj._kill_process(pid) 246 247 if self._process: 248 self._process.wait() 249 self._process = None 250 251 if self._wsout: 252 self._wsout.close() 253 self._wsout = None 254 255 256if '__main__' == __name__: 257 # Provide some command line params for starting the PyWebSocket server 258 # manually. 259 option_parser = optparse.OptionParser() 260 option_parser.add_option('--server', type='choice', 261 choices=['start', 'stop'], default='start', 262 help='Server action (start|stop)') 263 option_parser.add_option('-p', '--port', dest='port', 264 default=None, help='Port to listen on') 265 option_parser.add_option('-r', '--root', 266 help='Absolute path to DocumentRoot ' 267 '(overrides layout test roots)') 268 option_parser.add_option('-t', '--tls', dest='use_tls', 269 action='store_true', 270 default=False, help='use TLS (wss://)') 271 option_parser.add_option('-k', '--private_key', dest='private_key', 272 default='', help='TLS private key file.') 273 option_parser.add_option('-c', '--certificate', dest='certificate', 274 default='', help='TLS certificate file.') 275 option_parser.add_option('--register_cygwin', action="store_true", 276 dest="register_cygwin", 277 help='Register Cygwin paths (on Win try bots)') 278 option_parser.add_option('--pidfile', help='path to pid file.') 279 options, args = option_parser.parse_args() 280 281 if not options.port: 282 if options.use_tls: 283 options.port = _DEFAULT_WSS_PORT 284 else: 285 options.port = _DEFAULT_WS_PORT 286 287 kwds = {'port': options.port, 'use_tls': options.use_tls} 288 if options.root: 289 kwds['root'] = options.root 290 if options.private_key: 291 kwds['private_key'] = options.private_key 292 if options.certificate: 293 kwds['certificate'] = options.certificate 294 kwds['register_cygwin'] = options.register_cygwin 295 if options.pidfile: 296 kwds['pidfile'] = options.pidfile 297 298 pywebsocket = PyWebSocket(tempfile.gettempdir(), **kwds) 299 300 if 'start' == options.server: 301 pywebsocket.start() 302 else: 303 pywebsocket.stop(force=True) 304