• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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