httpd.py 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. #!/usr/bin/env python
  2. # Copyright (c) 2012 The Chromium Authors. All rights reserved.
  3. # Use of this source code is governed by a BSD-style license that can be
  4. # found in the LICENSE file.
  5. import BaseHTTPServer
  6. import imp
  7. import logging
  8. import multiprocessing
  9. import optparse
  10. import os
  11. import SimpleHTTPServer # pylint: disable=W0611
  12. import socket
  13. import sys
  14. import time
  15. import urlparse
  16. SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
  17. NACL_SDK_ROOT = os.path.dirname(SCRIPT_DIR)
  18. # We only run from the examples directory so that not too much is exposed
  19. # via this HTTP server. Everything in the directory is served, so there should
  20. # never be anything potentially sensitive in the serving directory, especially
  21. # if the machine might be a multi-user machine and not all users are trusted.
  22. # We only serve via the loopback interface.
  23. def SanityCheckDirectory(dirname):
  24. abs_serve_dir = os.path.abspath(dirname)
  25. # Verify we don't serve anywhere above NACL_SDK_ROOT.
  26. if abs_serve_dir[:len(NACL_SDK_ROOT)] == NACL_SDK_ROOT:
  27. return
  28. logging.error('For security, httpd.py should only be run from within the')
  29. logging.error('example directory tree.')
  30. logging.error('Attempting to serve from %s.' % abs_serve_dir)
  31. logging.error('Run with --no_dir_check to bypass this check.')
  32. sys.exit(1)
  33. class PluggableHTTPServer(BaseHTTPServer.HTTPServer):
  34. def __init__(self, *args, **kwargs):
  35. BaseHTTPServer.HTTPServer.__init__(self, *args)
  36. self.serve_dir = kwargs.get('serve_dir', '.')
  37. self.test_mode = kwargs.get('test_mode', False)
  38. self.delegate_map = {}
  39. self.running = True
  40. self.result = 0
  41. def Shutdown(self, result=0):
  42. self.running = False
  43. self.result = result
  44. class PluggableHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
  45. def _FindDelegateAtPath(self, dirname):
  46. # First check the cache...
  47. logging.debug('Looking for cached delegate in %s...' % dirname)
  48. handler_script = os.path.join(dirname, 'handler.py')
  49. if dirname in self.server.delegate_map:
  50. result = self.server.delegate_map[dirname]
  51. if result is None:
  52. logging.debug('Found None.')
  53. else:
  54. logging.debug('Found delegate.')
  55. return result
  56. # Don't have one yet, look for one.
  57. delegate = None
  58. logging.debug('Testing file %s for existence...' % handler_script)
  59. if os.path.exists(handler_script):
  60. logging.debug(
  61. 'File %s exists, looking for HTTPRequestHandlerDelegate.' %
  62. handler_script)
  63. module = imp.load_source('handler', handler_script)
  64. delegate_class = getattr(module, 'HTTPRequestHandlerDelegate', None)
  65. delegate = delegate_class()
  66. if not delegate:
  67. logging.warn(
  68. 'Unable to find symbol HTTPRequestHandlerDelegate in module %s.' %
  69. handler_script)
  70. return delegate
  71. def _FindDelegateForURLRecurse(self, cur_dir, abs_root):
  72. delegate = self._FindDelegateAtPath(cur_dir)
  73. if not delegate:
  74. # Didn't find it, try the parent directory, but stop if this is the server
  75. # root.
  76. if cur_dir != abs_root:
  77. parent_dir = os.path.dirname(cur_dir)
  78. delegate = self._FindDelegateForURLRecurse(parent_dir, abs_root)
  79. logging.debug('Adding delegate to cache for %s.' % cur_dir)
  80. self.server.delegate_map[cur_dir] = delegate
  81. return delegate
  82. def _FindDelegateForURL(self, url_path):
  83. path = self.translate_path(url_path)
  84. if os.path.isdir(path):
  85. dirname = path
  86. else:
  87. dirname = os.path.dirname(path)
  88. abs_serve_dir = os.path.abspath(self.server.serve_dir)
  89. delegate = self._FindDelegateForURLRecurse(dirname, abs_serve_dir)
  90. if not delegate:
  91. logging.info('No handler found for path %s. Using default.' % url_path)
  92. return delegate
  93. def _SendNothingAndDie(self, result=0):
  94. self.send_response(200, 'OK')
  95. self.send_header('Content-type', 'text/html')
  96. self.send_header('Content-length', '0')
  97. self.end_headers()
  98. self.server.Shutdown(result)
  99. def send_head(self):
  100. delegate = self._FindDelegateForURL(self.path)
  101. if delegate:
  102. return delegate.send_head(self)
  103. return self.base_send_head()
  104. def base_send_head(self):
  105. return SimpleHTTPServer.SimpleHTTPRequestHandler.send_head(self)
  106. def do_GET(self):
  107. # TODO(binji): pyauto tests use the ?quit=1 method to kill the server.
  108. # Remove this when we kill the pyauto tests.
  109. _, _, _, query, _ = urlparse.urlsplit(self.path)
  110. if query:
  111. params = urlparse.parse_qs(query)
  112. if '1' in params.get('quit', None):
  113. self._SendNothingAndDie()
  114. return
  115. delegate = self._FindDelegateForURL(self.path)
  116. if delegate:
  117. return delegate.do_GET(self)
  118. return self.base_do_GET()
  119. def base_do_GET(self):
  120. return SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
  121. def do_POST(self):
  122. delegate = self._FindDelegateForURL(self.path)
  123. if delegate:
  124. return delegate.do_POST(self)
  125. return self.base_do_POST()
  126. def base_do_POST(self):
  127. if self.server.test_mode:
  128. if self.path == '/ok':
  129. self._SendNothingAndDie(0)
  130. elif self.path == '/fail':
  131. self._SendNothingAndDie(1)
  132. class LocalHTTPServer(object):
  133. """Class to start a local HTTP server as a child process."""
  134. def __init__(self, dirname, port, test_mode):
  135. parent_conn, child_conn = multiprocessing.Pipe()
  136. self.process = multiprocessing.Process(
  137. target=_HTTPServerProcess,
  138. args=(child_conn, dirname, port, {
  139. 'serve_dir': dirname,
  140. 'test_mode': test_mode,
  141. }))
  142. self.process.start()
  143. if parent_conn.poll(10): # wait 10 seconds
  144. self.port = parent_conn.recv()
  145. else:
  146. raise Exception('Unable to launch HTTP server.')
  147. self.conn = parent_conn
  148. def ServeForever(self):
  149. """Serve until the child HTTP process tells us to stop.
  150. Returns:
  151. The result from the child (as an errorcode), or 0 if the server was
  152. killed not by the child (by KeyboardInterrupt for example).
  153. """
  154. child_result = 0
  155. try:
  156. # Block on this pipe, waiting for a response from the child process.
  157. child_result = self.conn.recv()
  158. except KeyboardInterrupt:
  159. pass
  160. finally:
  161. self.Shutdown()
  162. return child_result
  163. def ServeUntilSubprocessDies(self, process):
  164. """Serve until the child HTTP process tells us to stop or |subprocess| dies.
  165. Returns:
  166. The result from the child (as an errorcode), or 0 if |subprocess| died,
  167. or the server was killed some other way (by KeyboardInterrupt for
  168. example).
  169. """
  170. child_result = 0
  171. try:
  172. while True:
  173. if process.poll() is not None:
  174. child_result = 0
  175. break
  176. if self.conn.poll():
  177. child_result = self.conn.recv()
  178. break
  179. time.sleep(0)
  180. except KeyboardInterrupt:
  181. pass
  182. finally:
  183. self.Shutdown()
  184. return child_result
  185. def Shutdown(self):
  186. """Send a message to the child HTTP server process and wait for it to
  187. finish."""
  188. self.conn.send(False)
  189. self.process.join()
  190. def GetURL(self, rel_url):
  191. """Get the full url for a file on the local HTTP server.
  192. Args:
  193. rel_url: A URL fragment to convert to a full URL. For example,
  194. GetURL('foobar.baz') -> 'http://localhost:1234/foobar.baz'
  195. """
  196. return 'http://localhost:%d/%s' % (self.port, rel_url)
  197. def _HTTPServerProcess(conn, dirname, port, server_kwargs):
  198. """Run a local httpserver with the given port or an ephemeral port.
  199. This function assumes it is run as a child process using multiprocessing.
  200. Args:
  201. conn: A connection to the parent process. The child process sends
  202. the local port, and waits for a message from the parent to
  203. stop serving. It also sends a "result" back to the parent -- this can
  204. be used to allow a client-side test to notify the server of results.
  205. dirname: The directory to serve. All files are accessible through
  206. http://localhost:<port>/path/to/filename.
  207. port: The port to serve on. If 0, an ephemeral port will be chosen.
  208. server_kwargs: A dict that will be passed as kwargs to the server.
  209. """
  210. try:
  211. os.chdir(dirname)
  212. httpd = PluggableHTTPServer(('', port), PluggableHTTPRequestHandler,
  213. **server_kwargs)
  214. except socket.error as e:
  215. sys.stderr.write('Error creating HTTPServer: %s\n' % e)
  216. sys.exit(1)
  217. try:
  218. conn.send(httpd.server_address[1]) # the chosen port number
  219. httpd.timeout = 0.5 # seconds
  220. while httpd.running:
  221. # Flush output for MSVS Add-In.
  222. sys.stdout.flush()
  223. sys.stderr.flush()
  224. httpd.handle_request()
  225. if conn.poll():
  226. httpd.running = conn.recv()
  227. except KeyboardInterrupt:
  228. pass
  229. finally:
  230. conn.send(httpd.result)
  231. conn.close()
  232. def main(args):
  233. parser = optparse.OptionParser()
  234. parser.add_option('-C', '--serve-dir',
  235. help='Serve files out of this directory.',
  236. dest='serve_dir', default=os.path.abspath('.'))
  237. parser.add_option('-p', '--port',
  238. help='Run server on this port.',
  239. dest='port', default=5103)
  240. parser.add_option('--no_dir_check',
  241. help='No check to ensure serving from safe directory.',
  242. dest='do_safe_check', action='store_false', default=True)
  243. parser.add_option('--test-mode',
  244. help='Listen for posts to /ok or /fail and shut down the server with '
  245. ' errorcodes 0 and 1 respectively.',
  246. dest='test_mode', action='store_true')
  247. options, args = parser.parse_args(args)
  248. if options.do_safe_check:
  249. SanityCheckDirectory(options.serve_dir)
  250. server = LocalHTTPServer(options.serve_dir, int(options.port),
  251. options.test_mode)
  252. # Serve until the client tells us to stop. When it does, it will give us an
  253. # errorcode.
  254. print 'Serving %s on %s...' % (options.serve_dir, server.GetURL(''))
  255. return server.ServeForever()
  256. if __name__ == '__main__':
  257. sys.exit(main(sys.argv[1:]))
粤ICP备19079148号