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