Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / native_client_sdk / src / tools / httpd.py
blob059f28b822792d43468d28b57f347bcb247576a4
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 argparse
7 import BaseHTTPServer
8 import logging
9 import multiprocessing
10 import os
11 import SimpleHTTPServer # pylint: disable=W0611
12 import socket
13 import sys
14 import time
15 import urlparse
17 if sys.version_info < (2, 7, 0):
18 sys.stderr.write("python 2.7 or later is required run this script\n")
19 sys.exit(1)
22 SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
23 NACL_SDK_ROOT = os.path.dirname(SCRIPT_DIR)
26 # We only run from the examples directory so that not too much is exposed
27 # via this HTTP server. Everything in the directory is served, so there should
28 # never be anything potentially sensitive in the serving directory, especially
29 # if the machine might be a multi-user machine and not all users are trusted.
30 # We only serve via the loopback interface.
31 def SanityCheckDirectory(dirname):
32 abs_serve_dir = os.path.abspath(dirname)
34 # Verify we don't serve anywhere above NACL_SDK_ROOT.
35 if abs_serve_dir[:len(NACL_SDK_ROOT)] == NACL_SDK_ROOT:
36 return
37 logging.error('For security, httpd.py should only be run from within the')
38 logging.error('example directory tree.')
39 logging.error('Attempting to serve from %s.' % abs_serve_dir)
40 logging.error('Run with --no-dir-check to bypass this check.')
41 sys.exit(1)
44 class HTTPServer(BaseHTTPServer.HTTPServer):
45 def __init__(self, *args, **kwargs):
46 BaseHTTPServer.HTTPServer.__init__(self, *args)
47 self.running = True
48 self.result = 0
50 def Shutdown(self, result=0):
51 self.running = False
52 self.result = result
55 class HTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
56 def _SendNothingAndDie(self, result=0):
57 self.send_response(200, 'OK')
58 self.send_header('Content-type', 'text/html')
59 self.send_header('Content-length', '0')
60 self.end_headers()
61 self.server.Shutdown(result)
63 def do_GET(self):
64 # Browsing to ?quit=1 will kill the server cleanly.
65 _, _, _, query, _ = urlparse.urlsplit(self.path)
66 if query:
67 params = urlparse.parse_qs(query)
68 if '1' in params.get('quit', []):
69 self._SendNothingAndDie()
70 return
72 return SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
75 class LocalHTTPServer(object):
76 """Class to start a local HTTP server as a child process."""
78 def __init__(self, dirname, port):
79 parent_conn, child_conn = multiprocessing.Pipe()
80 self.process = multiprocessing.Process(
81 target=_HTTPServerProcess,
82 args=(child_conn, dirname, port, {}))
83 self.process.start()
84 if parent_conn.poll(10): # wait 10 seconds
85 self.port = parent_conn.recv()
86 else:
87 raise Exception('Unable to launch HTTP server.')
89 self.conn = parent_conn
91 def ServeForever(self):
92 """Serve until the child HTTP process tells us to stop.
94 Returns:
95 The result from the child (as an errorcode), or 0 if the server was
96 killed not by the child (by KeyboardInterrupt for example).
97 """
98 child_result = 0
99 try:
100 # Block on this pipe, waiting for a response from the child process.
101 child_result = self.conn.recv()
102 except KeyboardInterrupt:
103 pass
104 finally:
105 self.Shutdown()
106 return child_result
108 def ServeUntilSubprocessDies(self, process):
109 """Serve until the child HTTP process tells us to stop or |subprocess| dies.
111 Returns:
112 The result from the child (as an errorcode), or 0 if |subprocess| died,
113 or the server was killed some other way (by KeyboardInterrupt for
114 example).
116 child_result = 0
117 try:
118 while True:
119 if process.poll() is not None:
120 child_result = 0
121 break
122 if self.conn.poll():
123 child_result = self.conn.recv()
124 break
125 time.sleep(0)
126 except KeyboardInterrupt:
127 pass
128 finally:
129 self.Shutdown()
130 return child_result
132 def Shutdown(self):
133 """Send a message to the child HTTP server process and wait for it to
134 finish."""
135 self.conn.send(False)
136 self.process.join()
138 def GetURL(self, rel_url):
139 """Get the full url for a file on the local HTTP server.
141 Args:
142 rel_url: A URL fragment to convert to a full URL. For example,
143 GetURL('foobar.baz') -> 'http://localhost:1234/foobar.baz'
145 return 'http://localhost:%d/%s' % (self.port, rel_url)
148 def _HTTPServerProcess(conn, dirname, port, server_kwargs):
149 """Run a local httpserver with the given port or an ephemeral port.
151 This function assumes it is run as a child process using multiprocessing.
153 Args:
154 conn: A connection to the parent process. The child process sends
155 the local port, and waits for a message from the parent to
156 stop serving. It also sends a "result" back to the parent -- this can
157 be used to allow a client-side test to notify the server of results.
158 dirname: The directory to serve. All files are accessible through
159 http://localhost:<port>/path/to/filename.
160 port: The port to serve on. If 0, an ephemeral port will be chosen.
161 server_kwargs: A dict that will be passed as kwargs to the server.
163 try:
164 os.chdir(dirname)
165 httpd = HTTPServer(('', port), HTTPRequestHandler, **server_kwargs)
166 except socket.error as e:
167 sys.stderr.write('Error creating HTTPServer: %s\n' % e)
168 sys.exit(1)
170 try:
171 conn.send(httpd.server_address[1]) # the chosen port number
172 httpd.timeout = 0.5 # seconds
173 while httpd.running:
174 # Flush output for MSVS Add-In.
175 sys.stdout.flush()
176 sys.stderr.flush()
177 httpd.handle_request()
178 if conn.poll():
179 httpd.running = conn.recv()
180 except KeyboardInterrupt:
181 pass
182 finally:
183 conn.send(httpd.result)
184 conn.close()
187 def main(args):
188 parser = argparse.ArgumentParser()
189 parser.add_argument('-C', '--serve-dir',
190 help='Serve files out of this directory.',
191 default=os.path.abspath('.'))
192 parser.add_argument('-p', '--port',
193 help='Run server on this port.', default=5103)
194 parser.add_argument('--no-dir-check', '--no_dir_check',
195 help='No check to ensure serving from safe directory.',
196 dest='do_safe_check', action='store_false', default=True)
198 # To enable bash completion for this command first install optcomplete
199 # and then add this line to your .bashrc:
200 # complete -F _optcomplete httpd.py
201 try:
202 import optcomplete
203 optcomplete.autocomplete(parser)
204 except ImportError:
205 pass
207 options = parser.parse_args(args)
208 if options.do_safe_check:
209 SanityCheckDirectory(options.serve_dir)
211 server = LocalHTTPServer(options.serve_dir, int(options.port))
213 # Serve until the client tells us to stop. When it does, it will give us an
214 # errorcode.
215 print 'Serving %s on %s...' % (options.serve_dir, server.GetURL(''))
216 return server.ServeForever()
218 if __name__ == '__main__':
219 sys.exit(main(sys.argv[1:]))