Home | History | Annotate | Download | only in port
      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 
     33 from __future__ import with_statement
     34 
     35 import codecs
     36 import logging
     37 import optparse
     38 import os
     39 import subprocess
     40 import sys
     41 import tempfile
     42 import time
     43 import urllib
     44 
     45 import factory
     46 import http_server
     47 
     48 from webkitpy.common.system.executive import Executive
     49 from webkitpy.thirdparty.autoinstalled.pywebsocket import mod_pywebsocket
     50 
     51 
     52 _log = logging.getLogger("webkitpy.layout_tests.port.websocket_server")
     53 
     54 _WS_LOG_PREFIX = 'pywebsocket.ws.log-'
     55 _WSS_LOG_PREFIX = 'pywebsocket.wss.log-'
     56 
     57 _DEFAULT_WS_PORT = 8880
     58 _DEFAULT_WSS_PORT = 9323
     59 
     60 
     61 def url_is_alive(url):
     62     """Checks to see if we get an http response from |url|.
     63     We poll the url 20 times with a 0.5 second delay.  If we don't
     64     get a reply in that time, we give up and assume the httpd
     65     didn't start properly.
     66 
     67     Args:
     68       url: The URL to check.
     69     Return:
     70       True if the url is alive.
     71     """
     72     sleep_time = 0.5
     73     wait_time = 10
     74     while wait_time > 0:
     75         try:
     76             response = urllib.urlopen(url, proxies={})
     77             # Server is up and responding.
     78             return True
     79         except IOError:
     80             pass
     81         # Wait for sleep_time before trying again.
     82         wait_time -= sleep_time
     83         time.sleep(sleep_time)
     84 
     85     return False
     86 
     87 
     88 class PyWebSocketNotStarted(Exception):
     89     pass
     90 
     91 
     92 class PyWebSocketNotFound(Exception):
     93     pass
     94 
     95 
     96 class PyWebSocket(http_server.Lighttpd):
     97 
     98     def __init__(self, port_obj, output_dir, port=_DEFAULT_WS_PORT,
     99                  root=None, use_tls=False,
    100                  pidfile=None):
    101         """Args:
    102           output_dir: the absolute path to the layout test result directory
    103         """
    104         http_server.Lighttpd.__init__(self, port_obj, output_dir,
    105                                       port=_DEFAULT_WS_PORT,
    106                                       root=root)
    107         self._output_dir = output_dir
    108         self._process = None
    109         self._port = port
    110         self._root = root
    111         self._use_tls = use_tls
    112         self._private_key = self._pem_file
    113         self._certificate = self._pem_file
    114         if self._port:
    115             self._port = int(self._port)
    116         if self._use_tls:
    117             self._server_name = 'PyWebSocket(Secure)'
    118         else:
    119             self._server_name = 'PyWebSocket'
    120         self._pidfile = pidfile
    121         self._wsout = None
    122 
    123         # Webkit tests
    124         if self._root:
    125             self._layout_tests = os.path.abspath(self._root)
    126             self._web_socket_tests = os.path.abspath(
    127                 os.path.join(self._root, 'http', 'tests',
    128                              'websocket', 'tests'))
    129         else:
    130             try:
    131                 self._layout_tests = self._port_obj.layout_tests_dir()
    132                 self._web_socket_tests = os.path.join(self._layout_tests,
    133                      'http', 'tests', 'websocket', 'tests')
    134             except:
    135                 self._web_socket_tests = None
    136 
    137     def start(self):
    138         if not self._web_socket_tests:
    139             _log.info('No need to start %s server.' % self._server_name)
    140             return
    141         if self.is_running():
    142             raise PyWebSocketNotStarted('%s is already running.' %
    143                                         self._server_name)
    144 
    145         time_str = time.strftime('%d%b%Y-%H%M%S')
    146         if self._use_tls:
    147             log_prefix = _WSS_LOG_PREFIX
    148         else:
    149             log_prefix = _WS_LOG_PREFIX
    150         log_file_name = log_prefix + time_str
    151 
    152         # Remove old log files. We only need to keep the last ones.
    153         self.remove_log_files(self._output_dir, log_prefix)
    154 
    155         error_log = os.path.join(self._output_dir, log_file_name + "-err.txt")
    156 
    157         output_log = os.path.join(self._output_dir, log_file_name + "-out.txt")
    158         self._wsout = codecs.open(output_log, "w", "utf-8")
    159 
    160         python_interp = sys.executable
    161         pywebsocket_base = os.path.join(
    162             os.path.dirname(os.path.dirname(os.path.dirname(
    163             os.path.abspath(__file__)))), 'thirdparty',
    164             'autoinstalled', 'pywebsocket')
    165         pywebsocket_script = os.path.join(pywebsocket_base, 'mod_pywebsocket',
    166             'standalone.py')
    167         start_cmd = [
    168             python_interp, '-u', pywebsocket_script,
    169             '--server-host', '127.0.0.1',
    170             '--port', str(self._port),
    171             '--document-root', os.path.join(self._layout_tests, 'http', 'tests'),
    172             '--scan-dir', self._web_socket_tests,
    173             '--cgi-paths', '/websocket/tests',
    174             '--log-file', error_log,
    175         ]
    176 
    177         handler_map_file = os.path.join(self._web_socket_tests,
    178                                         'handler_map.txt')
    179         if os.path.exists(handler_map_file):
    180             _log.debug('Using handler_map_file: %s' % handler_map_file)
    181             start_cmd.append('--websock-handlers-map-file')
    182             start_cmd.append(handler_map_file)
    183         else:
    184             _log.warning('No handler_map_file found')
    185 
    186         if self._use_tls:
    187             start_cmd.extend(['-t', '-k', self._private_key,
    188                               '-c', self._certificate])
    189 
    190         env = self._port_obj.setup_environ_for_server()
    191         env['PYTHONPATH'] = (pywebsocket_base + os.path.pathsep +
    192                              env.get('PYTHONPATH', ''))
    193 
    194         _log.debug('Starting %s server on %d.' % (
    195                    self._server_name, self._port))
    196         _log.debug('cmdline: %s' % ' '.join(start_cmd))
    197         # FIXME: We should direct this call through Executive for testing.
    198         # Note: Not thread safe: http://bugs.python.org/issue2320
    199         self._process = subprocess.Popen(start_cmd,
    200                                          stdin=open(os.devnull, 'r'),
    201                                          stdout=self._wsout,
    202                                          stderr=subprocess.STDOUT,
    203                                          env=env)
    204 
    205         if self._use_tls:
    206             url = 'https'
    207         else:
    208             url = 'http'
    209         url = url + '://127.0.0.1:%d/' % self._port
    210         if not url_is_alive(url):
    211             if self._process.returncode == None:
    212                 # FIXME: We should use a non-static Executive for easier
    213                 # testing.
    214                 Executive().kill_process(self._process.pid)
    215             with codecs.open(output_log, "r", "utf-8") as fp:
    216                 for line in fp:
    217                     _log.error(line)
    218             raise PyWebSocketNotStarted(
    219                 'Failed to start %s server on port %s.' %
    220                     (self._server_name, self._port))
    221 
    222         # Our process terminated already
    223         if self._process.returncode != None:
    224             raise PyWebSocketNotStarted(
    225                 'Failed to start %s server.' % self._server_name)
    226         if self._pidfile:
    227             with codecs.open(self._pidfile, "w", "ascii") as file:
    228                 file.write("%d" % self._process.pid)
    229 
    230     def stop(self, force=False):
    231         if not force and not self.is_running():
    232             return
    233 
    234         pid = None
    235         if self._process:
    236             pid = self._process.pid
    237         elif self._pidfile:
    238             with codecs.open(self._pidfile, "r", "ascii") as file:
    239                 pid = int(file.read().strip())
    240 
    241         if not pid:
    242             raise PyWebSocketNotFound(
    243                 'Failed to find %s server pid.' % self._server_name)
    244 
    245         _log.debug('Shutting down %s server %d.' % (self._server_name, pid))
    246         # FIXME: We should use a non-static Executive for easier testing.
    247         Executive().kill_process(pid)
    248 
    249         if self._process:
    250             # wait() is not threadsafe and can throw OSError due to:
    251             # http://bugs.python.org/issue1731717
    252             self._process.wait()
    253             self._process = None
    254 
    255         if self._wsout:
    256             self._wsout.close()
    257             self._wsout = None
    258