http_server.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. # Copyright (C) 2011 Google Inc. All rights reserved.
  2. #
  3. # Redistribution and use in source and binary forms, with or without
  4. # modification, are permitted provided that the following conditions are
  5. # met:
  6. #
  7. # * Redistributions of source code must retain the above copyright
  8. # notice, this list of conditions and the following disclaimer.
  9. # * Redistributions in binary form must reproduce the above
  10. # copyright notice, this list of conditions and the following disclaimer
  11. # in the documentation and/or other materials provided with the
  12. # distribution.
  13. # * Neither the name of Google Inc. nor the names of its
  14. # contributors may be used to endorse or promote products derived from
  15. # this software without specific prior written permission.
  16. #
  17. # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  18. # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  19. # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  20. # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  21. # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  22. # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  23. # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  24. # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  25. # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  26. # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  27. # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  28. """A class to help start/stop the lighttpd server used by layout tests."""
  29. import logging
  30. import os
  31. import time
  32. from webkitpy.layout_tests.servers import http_server_base
  33. _log = logging.getLogger(__name__)
  34. class Lighttpd(http_server_base.HttpServerBase):
  35. def __init__(self, port_obj, output_dir, background=False, port=None,
  36. root=None, run_background=None, additional_dirs=None,
  37. layout_tests_dir=None, number_of_servers=None):
  38. """Args:
  39. output_dir: the absolute path to the layout test result directory
  40. """
  41. # Webkit tests
  42. http_server_base.HttpServerBase.__init__(self, port_obj, number_of_servers)
  43. self._name = 'lighttpd'
  44. self._output_dir = output_dir
  45. self._port = port
  46. self._root = root
  47. self._run_background = run_background
  48. self._additional_dirs = additional_dirs
  49. self._layout_tests_dir = layout_tests_dir
  50. self._pid_file = self._filesystem.join(self._runtime_path, '%s.pid' % self._name)
  51. if self._port:
  52. self._port = int(self._port)
  53. if not self._layout_tests_dir:
  54. self._layout_tests_dir = self._port_obj.layout_tests_dir()
  55. self._webkit_tests = os.path.join(self._layout_tests_dir, 'http', 'tests')
  56. self._js_test_resource = os.path.join(self._layout_tests_dir, 'fast', 'js', 'resources')
  57. self._media_resource = os.path.join(self._layout_tests_dir, 'media')
  58. # Self generated certificate for SSL server (for client cert get
  59. # <base-path>\chrome\test\data\ssl\certs\root_ca_cert.crt)
  60. self._pem_file = os.path.join(
  61. os.path.dirname(os.path.abspath(__file__)), 'httpd2.pem')
  62. # One mapping where we can get to everything
  63. self.VIRTUALCONFIG = []
  64. if self._webkit_tests:
  65. self.VIRTUALCONFIG.extend(
  66. # Three mappings (one with SSL) for LayoutTests http tests
  67. [{'port': 8000, 'docroot': self._webkit_tests},
  68. {'port': 8081, 'docroot': self._webkit_tests},
  69. {'port': 8443, 'docroot': self._webkit_tests,
  70. 'sslcert': self._pem_file}])
  71. def _prepare_config(self):
  72. base_conf_file = self._port_obj.path_from_webkit_base('Tools',
  73. 'Scripts', 'webkitpy', 'layout_tests', 'servers', 'lighttpd.conf')
  74. out_conf_file = os.path.join(self._output_dir, 'lighttpd.conf')
  75. time_str = time.strftime("%d%b%Y-%H%M%S")
  76. access_file_name = "access.log-" + time_str + ".txt"
  77. access_log = os.path.join(self._output_dir, access_file_name)
  78. log_file_name = "error.log-" + time_str + ".txt"
  79. error_log = os.path.join(self._output_dir, log_file_name)
  80. # Write out the config
  81. base_conf = self._filesystem.read_text_file(base_conf_file)
  82. # FIXME: This should be re-worked so that this block can
  83. # use with open() instead of a manual file.close() call.
  84. f = self._filesystem.open_text_file_for_writing(out_conf_file)
  85. f.write(base_conf)
  86. # Write out our cgi handlers. Run perl through env so that it
  87. # processes the #! line and runs perl with the proper command
  88. # line arguments. Emulate apache's mod_asis with a cat cgi handler.
  89. f.write(('cgi.assign = ( ".cgi" => "/usr/bin/env",\n'
  90. ' ".pl" => "/usr/bin/env",\n'
  91. ' ".asis" => "/bin/cat",\n'
  92. ' ".php" => "%s" )\n\n') %
  93. self._port_obj._path_to_lighttpd_php())
  94. # Setup log files
  95. f.write(('server.errorlog = "%s"\n'
  96. 'accesslog.filename = "%s"\n\n') % (error_log, access_log))
  97. # Setup upload folders. Upload folder is to hold temporary upload files
  98. # and also POST data. This is used to support XHR layout tests that
  99. # does POST.
  100. f.write(('server.upload-dirs = ( "%s" )\n\n') % (self._output_dir))
  101. # Setup a link to where the js test templates are stored
  102. f.write(('alias.url = ( "/js-test-resources" => "%s" )\n\n') %
  103. (self._js_test_resource))
  104. if self._additional_dirs:
  105. for alias, path in self._additional_dirs.iteritems():
  106. f.write(('alias.url += ( "%s" => "%s" )\n\n') % (alias, path))
  107. # Setup a link to where the media resources are stored.
  108. f.write(('alias.url += ( "/media-resources" => "%s" )\n\n') %
  109. (self._media_resource))
  110. # dump out of virtual host config at the bottom.
  111. if self._root:
  112. if self._port:
  113. # Have both port and root dir.
  114. mappings = [{'port': self._port, 'docroot': self._root}]
  115. else:
  116. # Have only a root dir - set the ports as for LayoutTests.
  117. # This is used in ui_tests to run http tests against a browser.
  118. # default set of ports as for LayoutTests but with a
  119. # specified root.
  120. mappings = [{'port': 8000, 'docroot': self._root},
  121. {'port': 8081, 'docroot': self._root},
  122. {'port': 8443, 'docroot': self._root,
  123. 'sslcert': self._pem_file}]
  124. else:
  125. mappings = self.VIRTUALCONFIG
  126. for mapping in mappings:
  127. ssl_setup = ''
  128. if 'sslcert' in mapping:
  129. ssl_setup = (' ssl.engine = "enable"\n'
  130. ' ssl.pemfile = "%s"\n' % mapping['sslcert'])
  131. f.write(('$SERVER["socket"] == "127.0.0.1:%d" {\n'
  132. ' server.document-root = "%s"\n' +
  133. ssl_setup +
  134. '}\n\n') % (mapping['port'], mapping['docroot']))
  135. f.close()
  136. executable = self._port_obj._path_to_lighttpd()
  137. module_path = self._port_obj._path_to_lighttpd_modules()
  138. start_cmd = [executable,
  139. # Newly written config file
  140. '-f', os.path.join(self._output_dir, 'lighttpd.conf'),
  141. # Where it can find its module dynamic libraries
  142. '-m', module_path]
  143. if not self._run_background:
  144. start_cmd.append(# Don't background
  145. '-D')
  146. # Copy liblightcomp.dylib to /tmp/lighttpd/lib to work around the
  147. # bug that mod_alias.so loads it from the hard coded path.
  148. if self._port_obj.host.platform.is_mac():
  149. tmp_module_path = '/tmp/lighttpd/lib'
  150. if not self._filesystem.exists(tmp_module_path):
  151. self._filesystem.maybe_make_directory(tmp_module_path)
  152. lib_file = 'liblightcomp.dylib'
  153. self._filesystem.copyfile(self._filesystem.join(module_path, lib_file),
  154. self._filesystem.join(tmp_module_path, lib_file))
  155. self._start_cmd = start_cmd
  156. self._env = self._port_obj.setup_environ_for_server('lighttpd')
  157. self._mappings = mappings
  158. def _remove_stale_logs(self):
  159. # Sometimes logs are open in other processes but they should clear eventually.
  160. for log_prefix in ('access.log-', 'error.log-'):
  161. try:
  162. self._remove_log_files(self._output_dir, log_prefix)
  163. except OSError, e:
  164. _log.warning('Failed to remove old %s %s files' % (self._name, log_prefix))
  165. def _spawn_process(self):
  166. _log.debug('Starting %s server, cmd="%s"' % (self._name, self._start_cmd))
  167. process = self._executive.popen(self._start_cmd, env=self._env, shell=False, stderr=self._executive.PIPE)
  168. pid = process.pid
  169. self._filesystem.write_text_file(self._pid_file, str(pid))
  170. return pid
  171. def _stop_running_server(self):
  172. # FIXME: It would be nice if we had a cleaner way of killing this process.
  173. # Currently we throw away the process object created in _spawn_process,
  174. # since there doesn't appear to be any way to kill the server any more
  175. # cleanly using it than just killing the pid, and we need to support
  176. # killing a pid directly anyway for run-webkit-httpd and run-webkit-websocketserver.
  177. self._wait_for_action(self._check_and_kill)
  178. if self._filesystem.exists(self._pid_file):
  179. self._filesystem.remove(self._pid_file)
  180. def _check_and_kill(self):
  181. if self._executive.check_running_pid(self._pid):
  182. host = self._port_obj.host
  183. if host.platform.is_win() and not host.platform.is_cygwin():
  184. # FIXME: https://bugs.webkit.org/show_bug.cgi?id=106838
  185. # We need to kill all of the child processes as well as the
  186. # parent, so we can't use executive.kill_process().
  187. #
  188. # If this is actually working, we should figure out a clean API.
  189. self._executive.run_command(["taskkill.exe", "/f", "/t", "/pid", self._pid], error_handler=self._executive.ignore_error)
  190. else:
  191. self._executive.kill_process(self._pid)
  192. return False
  193. return True