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.
12 import SimpleHTTPServer
# pylint: disable=W0611
18 if sys
.version_info
< (2, 6, 0):
19 sys
.stderr
.write("python 2.6 or later is required run this script\n")
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
:
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.')
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
= {}
54 def Shutdown(self
, result
=0):
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
]
68 logging
.debug('Found None.')
70 logging
.debug('Found delegate.')
73 # Don't have one yet, look for one.
75 logging
.debug('Testing file %s for existence...' % handler_script
)
76 if os
.path
.exists(handler_script
):
78 'File %s exists, looking for HTTPRequestHandlerDelegate.' %
81 module
= imp
.load_source('handler', handler_script
)
82 delegate_class
= getattr(module
, 'HTTPRequestHandlerDelegate', None)
83 delegate
= delegate_class()
86 'Unable to find symbol HTTPRequestHandlerDelegate in module %s.' %
91 def _FindDelegateForURLRecurse(self
, cur_dir
, abs_root
):
92 delegate
= self
._FindDelegateAtPath
(cur_dir
)
94 # Didn't find it, try the parent directory, but stop if this is the server
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
104 def _FindDelegateForURL(self
, url_path
):
105 path
= self
.translate_path(url_path
)
106 if os
.path
.isdir(path
):
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
)
114 logging
.info('No handler found for path %s. Using default.' % url_path
)
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')
122 self
.server
.Shutdown(result
)
125 delegate
= self
._FindDelegateForURL
(self
.path
)
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
)
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
)
138 params
= urlparse
.parse_qs(query
)
139 if '1' in params
.get('quit', None):
140 self
._SendNothingAndDie
()
143 delegate
= self
._FindDelegateForURL
(self
.path
)
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
)
152 delegate
= self
._FindDelegateForURL
(self
.path
)
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
,
177 if parent_conn
.poll(10): # wait 10 seconds
178 self
.port
= parent_conn
.recv()
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.
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).
193 # Block on this pipe, waiting for a response from the child process.
194 child_result
= self
.conn
.recv()
195 except KeyboardInterrupt:
201 def ServeUntilSubprocessDies(self
, process
):
202 """Serve until the child HTTP process tells us to stop or |subprocess| dies.
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
212 if process
.poll() is not None:
216 child_result
= self
.conn
.recv()
219 except KeyboardInterrupt:
226 """Send a message to the child HTTP server process and wait for it to
228 self
.conn
.send(False)
231 def GetURL(self
, rel_url
):
232 """Get the full url for a file on the local HTTP server.
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.
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.
258 httpd
= PluggableHTTPServer(('', port
), PluggableHTTPRequestHandler
,
260 except socket
.error
as e
:
261 sys
.stderr
.write('Error creating HTTPServer: %s\n' % e
)
265 conn
.send(httpd
.server_address
[1]) # the chosen port number
266 httpd
.timeout
= 0.5 # seconds
268 # Flush output for MSVS Add-In.
271 httpd
.handle_request()
273 httpd
.running
= conn
.recv()
274 except KeyboardInterrupt:
277 conn
.send(httpd
.result
)
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
),
303 # Serve until the client tells us to stop. When it does, it will give us an
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:]))