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.
11 import SimpleHTTPServer
# pylint: disable=W0611
17 if sys
.version_info
< (2, 7, 0):
18 sys
.stderr
.write("python 2.7 or later is required run this script\n")
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
:
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.')
44 class HTTPServer(BaseHTTPServer
.HTTPServer
):
45 def __init__(self
, *args
, **kwargs
):
46 BaseHTTPServer
.HTTPServer
.__init
__(self
, *args
)
50 def Shutdown(self
, result
=0):
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')
61 self
.server
.Shutdown(result
)
64 # Browsing to ?quit=1 will kill the server cleanly.
65 _
, _
, _
, query
, _
= urlparse
.urlsplit(self
.path
)
67 params
= urlparse
.parse_qs(query
)
68 if '1' in params
.get('quit', []):
69 self
._SendNothingAndDie
()
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
, {}))
84 if parent_conn
.poll(10): # wait 10 seconds
85 self
.port
= parent_conn
.recv()
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.
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).
100 # Block on this pipe, waiting for a response from the child process.
101 child_result
= self
.conn
.recv()
102 except KeyboardInterrupt:
108 def ServeUntilSubprocessDies(self
, process
):
109 """Serve until the child HTTP process tells us to stop or |subprocess| dies.
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
119 if process
.poll() is not None:
123 child_result
= self
.conn
.recv()
126 except KeyboardInterrupt:
133 """Send a message to the child HTTP server process and wait for it to
135 self
.conn
.send(False)
138 def GetURL(self
, rel_url
):
139 """Get the full url for a file on the local HTTP server.
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.
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.
165 httpd
= HTTPServer(('', port
), HTTPRequestHandler
, **server_kwargs
)
166 except socket
.error
as e
:
167 sys
.stderr
.write('Error creating HTTPServer: %s\n' % e
)
171 conn
.send(httpd
.server_address
[1]) # the chosen port number
172 httpd
.timeout
= 0.5 # seconds
174 # Flush output for MSVS Add-In.
177 httpd
.handle_request()
179 httpd
.running
= conn
.recv()
180 except KeyboardInterrupt:
183 conn
.send(httpd
.result
)
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
203 optcomplete
.autocomplete(parser
)
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
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:]))