146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)# Copyright 2012 The Chromium Authors. All rights reserved.
25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Use of this source code is governed by a BSD-style license that can be
35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# found in the LICENSE file.
45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
55821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)"""Start and stop Web Page Replay.
65821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
72a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)Of the public module names, the following one is key:
85821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  ReplayServer: a class to start/stop Web Page Replay.
95821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)"""
105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import logging
125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import os
13f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)import re
145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import signal
155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import subprocess
165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import sys
175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import urllib
185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
196e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)from telemetry.core import util
205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
215d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)_CHROME_SRC_DIR = os.path.abspath(os.path.join(
22c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch    os.path.dirname(__file__), os.pardir, os.pardir, os.pardir, os.pardir))
235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)REPLAY_DIR = os.path.join(
245d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    _CHROME_SRC_DIR, 'third_party', 'webpagereplay')
255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)LOG_PATH = os.path.join(
265d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    _CHROME_SRC_DIR, 'webpagereplay_logs', 'logs.txt')
275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
292a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# Chrome options to make it work with Web Page Replay.
302a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)def GetChromeFlags(replay_host, http_port, https_port):
31f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  assert replay_host and http_port and https_port, 'All arguments required'
322a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  return [
332a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      '--host-resolver-rules=MAP * %s,EXCLUDE localhost' % replay_host,
342a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      '--testing-fixed-http-port=%s' % http_port,
352a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      '--testing-fixed-https-port=%s' % https_port,
362a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      '--ignore-certificate-errors',
372a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      ]
382a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
39424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)
40eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch# Signal masks on Linux are inherited from parent processes.  If anything
41eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch# invoking us accidentally masks SIGINT (e.g. by putting a process in the
42eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch# background from a shell script), sending a SIGINT to the child will fail
43eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch# to terminate it.  Running this signal handler before execing should fix that
44eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch# problem.
45eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochdef ResetInterruptHandler():
46eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  signal.signal(signal.SIGINT, signal.SIG_DFL)
47eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
48424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)
495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class ReplayError(Exception):
505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """Catch-all exception for the module."""
515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  pass
525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
53424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)
545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class ReplayNotFoundError(ReplayError):
555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def __init__(self, label, path):
56c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch    super(ReplayNotFoundError, self).__init__()
575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.args = (label, path)
585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def __str__(self):
605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    label, path = self.args
615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return 'Path does not exist for %s: %s' % (label, path)
625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
63424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)
645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class ReplayNotStartedError(ReplayError):
655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  pass
665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class ReplayServer(object):
695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """Start and Stop Web Page Replay.
705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Web Page Replay is a proxy that can record and "replay" web pages with
725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  simulated network characteristics -- without having to edit the pages
735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  by hand. With WPR, tests can use "real" web content, and catch
745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  performance issues that may result from introducing network delays and
755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  bandwidth throttling.
765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Example:
785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     with ReplayServer(archive_path):
795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)       self.NavigateToURL(start_url)
805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)       self.WaitUntil(...)
815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Environment Variables (for development):
835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    WPR_ARCHIVE_PATH: path to alternate archive file (e.g. '/tmp/foo.wpr').
845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    WPR_RECORD: if set, puts Web Page Replay in record mode instead of replay.
855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    WPR_REPLAY_DIR: path to alternate Web Page Replay source.
865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """
87424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)
885d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  def __init__(self, archive_path, replay_host, dns_port, http_port, https_port,
892a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)               replay_options=None, replay_dir=None,
905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)               log_path=None):
915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Initialize ReplayServer.
925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      archive_path: a path to a specific WPR archive (required).
95f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      replay_host: the hostname to serve traffic.
965d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      dns_port: an integer port on which to serve DNS traffic. May be zero
97a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)          to let the OS choose an available port. If None DNS forwarding is
98a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)          disabled.
99f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      http_port: an integer port on which to serve HTTP traffic. May be zero
100f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)          to let the OS choose an available port.
101f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      https_port: an integer port on which to serve HTTPS traffic. May be zero
102f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)          to let the OS choose an available port.
1035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      replay_options: an iterable of options strings to forward to replay.py.
1045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      replay_dir: directory that has replay.py and related modules.
1055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      log_path: a path to a log file.
106424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    """
1075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.archive_path = os.environ.get('WPR_ARCHIVE_PATH', archive_path)
1085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.replay_options = list(replay_options or ())
1095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.replay_dir = os.environ.get('WPR_REPLAY_DIR', replay_dir or REPLAY_DIR)
1105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.log_path = log_path or LOG_PATH
1115d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    self.dns_port = dns_port
112f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    self.http_port = http_port
113f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    self.https_port = https_port
1142a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    self._replay_host = replay_host
1155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if 'WPR_RECORD' in os.environ and '--record' not in self.replay_options:
1175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self.replay_options.append('--record')
1185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.is_record_mode = '--record' in self.replay_options
1195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self._AddDefaultReplayOptions()
1205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.replay_py = os.path.join(self.replay_dir, 'replay.py')
1225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if self.is_record_mode:
1245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self._CheckPath('archive directory', os.path.dirname(self.archive_path))
1255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    elif not os.path.exists(self.archive_path):
1265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self._CheckPath('archive file', self.archive_path)
1275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self._CheckPath('replay script', self.replay_py)
1285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.replay_process = None
1305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def _AddDefaultReplayOptions(self):
1325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Set WPR command-line options. Can be overridden if needed."""
13358537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)    self.replay_options = [
134424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)        '--host', str(self._replay_host),
135f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        '--port', str(self.http_port),
136f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        '--ssl_port', str(self.https_port),
1375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        '--use_closest_match',
1385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        '--no-dns_forwarding',
1392a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        '--log_level', 'warning'
14058537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)        ] + self.replay_options
141a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    if self.dns_port is not None:
142a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      self.replay_options.extend(['--dns_port', str(self.dns_port)])
1435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def _CheckPath(self, label, path):
1455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if not os.path.exists(path):
1465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      raise ReplayNotFoundError(label, path)
1475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def _OpenLogFile(self):
1495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    log_dir = os.path.dirname(self.log_path)
1505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if not os.path.exists(log_dir):
1515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      os.makedirs(log_dir)
1525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return open(self.log_path, 'w')
1535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1546e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  def IsStarted(self):
1555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Checks to see if the server is up and running."""
156f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    port_re = re.compile(
1575d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        '.*?(?P<protocol>[A-Z]+) server started on (?P<host>.*):(?P<port>\d+)')
158f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
1596e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    if self.replay_process.poll() is not None:
1606e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)      return False
161f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
1626e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    # Read the ports from the WPR log.
1636e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    if not self.http_port or not self.https_port or not self.dns_port:
1646e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)      with open(self.log_path) as f:
1656e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        for line in f.readlines():
1666e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)          m = port_re.match(line.strip())
1676e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)          if m:
1686e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)            if not self.http_port and m.group('protocol') == 'HTTP':
1696e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)              self.http_port = int(m.group('port'))
1706e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)            elif not self.https_port and m.group('protocol') == 'HTTPS':
1716e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)              self.https_port = int(m.group('port'))
1726e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)            elif not self.dns_port and m.group('protocol') == 'DNS':
1736e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)              self.dns_port = int(m.group('port'))
1746e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
1756e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    # Try to connect to the WPR ports.
1766e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    if self.http_port and self.https_port:
1776e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)      try:
1786e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        up_url = '%s://%s:%s/web-page-replay-generate-200'
1796e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        http_up_url = up_url % ('http', self._replay_host, self.http_port)
1806e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        https_up_url = up_url % ('https', self._replay_host, self.https_port)
1816e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        if (200 == urllib.urlopen(http_up_url, None, {}).getcode() and
1826e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)            200 == urllib.urlopen(https_up_url, None, {}).getcode()):
1836e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)          return True
1846e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)      except IOError:
1856e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        pass
1865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return False
1875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def StartServer(self):
1895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Start Web Page Replay and verify that it started.
1905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Raises:
192424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      ReplayNotStartedError: if Replay start-up fails.
1935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
1945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    cmd_line = [sys.executable, self.replay_py]
1955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    cmd_line.extend(self.replay_options)
1965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    cmd_line.append(self.archive_path)
1975c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
1985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    logging.debug('Starting Web-Page-Replay: %s', cmd_line)
1995c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    with self._OpenLogFile() as log_fh:
2005c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      kwargs = {'stdout': log_fh, 'stderr': subprocess.STDOUT}
2015c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      if sys.platform.startswith('linux') or sys.platform == 'darwin':
2025c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        kwargs['preexec_fn'] = ResetInterruptHandler
2035c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      self.replay_process = subprocess.Popen(cmd_line, **kwargs)
2045c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
2056e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    try:
2066e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)      util.WaitFor(self.IsStarted, 30)
2076e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    except util.TimeoutException:
2085c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      with open(self.log_path) as f:
2095c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        log = f.read()
2105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      raise ReplayNotStartedError(
2115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          'Web Page Replay failed to start. Log output:\n%s' % log)
2125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def StopServer(self):
2145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Stop Web Page Replay."""
2156e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    if not self.replay_process:
2166e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)      return
2176e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
2186e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    logging.debug('Trying to stop Web-Page-Replay gracefully')
2196e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    try:
2206e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)      urllib.urlopen('http://%s:%s/web-page-replay-command-exit' % (
2216e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)          self._replay_host, self.http_port), None, {}).close()
2226e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    except IOError:
2236e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)      # IOError is possible because the server might exit without response.
2246e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)      pass
2256e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
2266e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    try:
2276e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)      util.WaitFor(lambda: self.replay_process.poll() is not None, 10)
2286e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    except util.TimeoutException:
2295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      try:
2306e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        # Use a SIGINT so that it can do graceful cleanup.
2316e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        self.replay_process.send_signal(signal.SIGINT)
2326e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)      except:  # pylint: disable=W0702
2336e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        # On Windows, we are left with no other option than terminate().
2346e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        if 'no-dns_forwarding' not in self.replay_options:
2356e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)          logging.warning('DNS configuration might not be restored!')
236424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)        try:
2376e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)          self.replay_process.terminate()
238424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)        except:  # pylint: disable=W0702
2396e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)          pass
2406e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)      self.replay_process.wait()
2415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def __enter__(self):
2435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Add support for with-statement."""
2445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.StartServer()
2455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return self
2465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def __exit__(self, unused_exc_type, unused_exc_val, unused_exc_tb):
2485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Add support for with-statement."""
2495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.StopServer()
250