- add sources.
[platform/framework/web/crosswalk.git] / src / native_client_sdk / src / tools / httpd.py
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
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
17
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)
21
22
23 SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
24 NACL_SDK_ROOT = os.path.dirname(SCRIPT_DIR)
25
26
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)
34
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)
43
44
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
53
54   def Shutdown(self, result=0):
55     self.running = False
56     self.result = result
57
58
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')
64
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
72
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)
80
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)
88
89     return delegate
90
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)
99
100     logging.debug('Adding delegate to cache for %s.' % cur_dir)
101     self.server.delegate_map[cur_dir] = delegate
102     return delegate
103
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)
110
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
116
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)
123
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()
129
130   def base_send_head(self):
131     return SimpleHTTPServer.SimpleHTTPRequestHandler.send_head(self)
132
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', []):
140         self._SendNothingAndDie()
141         return
142
143     delegate = self._FindDelegateForURL(self.path)
144     if delegate:
145       return delegate.do_GET(self)
146     return self.base_do_GET()
147
148   def base_do_GET(self):
149     return SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
150
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()
156
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)
163
164
165 class LocalHTTPServer(object):
166   """Class to start a local HTTP server as a child process."""
167
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,
175         }))
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.')
181
182     self.conn = parent_conn
183
184   def ServeForever(self):
185     """Serve until the child HTTP process tells us to stop.
186
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).
190     """
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
200
201   def ServeUntilSubprocessDies(self, process):
202     """Serve until the child HTTP process tells us to stop or |subprocess| dies.
203
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).
208     """
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
224
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()
230
231   def GetURL(self, rel_url):
232     """Get the full url for a file on the local HTTP server.
233
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'
237     """
238     return 'http://localhost:%d/%s' % (self.port, rel_url)
239
240
241 def _HTTPServerProcess(conn, dirname, port, server_kwargs):
242   """Run a local httpserver with the given port or an ephemeral port.
243
244   This function assumes it is run as a child process using multiprocessing.
245
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.
255   """
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)
263
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()
279
280
281 def main(args):
282   parser = optparse.OptionParser()
283   parser.add_option('-C', '--serve-dir',
284       help='Serve files out of this directory.',
285       default=os.path.abspath('.'))
286   parser.add_option('-p', '--port',
287       help='Run server on this port.', default=5103)
288   parser.add_option('--no-dir-check', '--no_dir_check',
289       help='No check to ensure serving from safe directory.',
290       dest='do_safe_check', action='store_false', default=True)
291   parser.add_option('--test-mode',
292       help='Listen for posts to /ok or /fail and shut down the server with '
293           ' errorcodes 0 and 1 respectively.',
294       action='store_true')
295
296   # To enable bash completion for this command first install optcomplete
297   # and then add this line to your .bashrc:
298   #  complete -F _optcomplete httpd.py
299   try:
300     import optcomplete
301     optcomplete.autocomplete(parser)
302   except ImportError:
303     pass
304
305   options, args = parser.parse_args(args)
306   if options.do_safe_check:
307     SanityCheckDirectory(options.serve_dir)
308
309   server = LocalHTTPServer(options.serve_dir, int(options.port),
310                            options.test_mode)
311
312   # Serve until the client tells us to stop. When it does, it will give us an
313   # errorcode.
314   print 'Serving %s on %s...' % (options.serve_dir, server.GetURL(''))
315   return server.ServeForever()
316
317 if __name__ == '__main__':
318   sys.exit(main(sys.argv[1:]))