diff options
author | binji@chromium.org <binji@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-11-07 19:37:04 +0000 |
---|---|---|
committer | binji@chromium.org <binji@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-11-07 19:37:04 +0000 |
commit | 22fa27f0ad144be69ed89e53494a4b4816851ddf (patch) | |
tree | cd2a6a8b90ed560759a8cf53899c9f6392c49c95 /native_client_sdk | |
parent | 81cffbd2dbbde6c8c119d04f2a6648b2c49231b6 (diff) | |
download | chromium_src-22fa27f0ad144be69ed89e53494a4b4816851ddf.zip chromium_src-22fa27f0ad144be69ed89e53494a4b4816851ddf.tar.gz chromium_src-22fa27f0ad144be69ed89e53494a4b4816851ddf.tar.bz2 |
[NaCl SDK] make RUN automatically starts server and launches Chrome.
BUG=none
R=noelallen@chromium.org
NOTRY=true
Review URL: https://chromiumcodereview.appspot.com/11362039
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@166491 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'native_client_sdk')
-rwxr-xr-x | native_client_sdk/src/build_tools/build_sdk.py | 2 | ||||
-rw-r--r-- | native_client_sdk/src/build_tools/template.mk | 26 | ||||
-rw-r--r-- | native_client_sdk/src/examples/Makefile | 6 | ||||
-rw-r--r-- | native_client_sdk/src/examples/debugging/Makefile.inc | 16 | ||||
-rw-r--r-- | native_client_sdk/src/examples/debugging/example.dsc | 14 | ||||
-rw-r--r-- | native_client_sdk/src/examples/debugging/handler.py | 124 | ||||
-rw-r--r-- | native_client_sdk/src/examples/debugging/index.html | 11 | ||||
-rwxr-xr-x | native_client_sdk/src/examples/httpd.py | 216 | ||||
-rw-r--r-- | native_client_sdk/src/tools/httpd.py | 223 | ||||
-rwxr-xr-x | native_client_sdk/src/tools/run.py | 55 |
10 files changed, 440 insertions, 253 deletions
diff --git a/native_client_sdk/src/build_tools/build_sdk.py b/native_client_sdk/src/build_tools/build_sdk.py index 9fd82f8..52a5c16 100755 --- a/native_client_sdk/src/build_tools/build_sdk.py +++ b/native_client_sdk/src/build_tools/build_sdk.py @@ -630,7 +630,7 @@ def BuildStepCopyExamples(pepperdir, toolchains, build_experimental, clobber): MakeDirectoryOrClobber(pepperdir, 'src', clobber) # Copy individual files - files = ['favicon.ico', 'httpd.cmd', 'httpd.py', 'index.html'] + files = ['favicon.ico', 'httpd.cmd', 'index.html'] for filename in files: oshelpers.Copy(['-v', os.path.join(SDK_EXAMPLE_DIR, filename), exampledir]) diff --git a/native_client_sdk/src/build_tools/template.mk b/native_client_sdk/src/build_tools/template.mk index bbc445b..9b68861 100644 --- a/native_client_sdk/src/build_tools/template.mk +++ b/native_client_sdk/src/build_tools/template.mk @@ -1,4 +1,4 @@ -# Copyright (c) 2012 The Native Client Authors. All rights reserved. +# Copyright (c) 2012 The Chromium Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -14,6 +14,7 @@ # from the default example directory location. # THIS_MAKEFILE:=$(abspath $(lastword $(MAKEFILE_LIST))) +THIS_DIR:=$(abspath $(dir $(THIS_MAKEFILE))) NACL_SDK_ROOT?=$(abspath $(dir $(THIS_MAKEFILE))../..) CHROME_PATH?=Undefined @@ -96,7 +97,7 @@ NMF:=python $(NACL_SDK_ROOT)/tools/create_nmf.py # # Verify we can find the Chrome executable if we need to launch it. # -.PHONY: CHECK_FOR_CHROME +.PHONY: CHECK_FOR_CHROME RUN LAUNCH CHECK_FOR_CHROME: ifeq (,$(wildcard $(CHROME_PATH))) $(warning No valid Chrome found at CHROME_PATH=$(CHROME_PATH)) @@ -109,17 +110,32 @@ __PROJECT_RULES__ __PROJECT_PRERUN__ -RUN: all - python ../httpd.py +# +# Variables for running examples with Chrome. +# +RUN_PY:=python $(NACL_SDK_ROOT)/tools/run.py + +# Add this to launch Chrome with additional environment variables defined. +# Each element should be specified as KEY=VALUE, with whitespace separating +# key-value pairs. e.g. +# CHROME_ENV=FOO=1 BAR=2 BAZ=3 +CHROME_ENV?= + +# Additional arguments to pass to Chrome. +CHROME_ARGS+=--enable-nacl --incognito + CONFIG?=Debug PAGE?=index_$(TOOLCHAIN)_$(CONFIG).html +RUN: LAUNCH LAUNCH: CHECK_FOR_CHROME all ifeq (,$(wildcard $(PAGE))) $(warning No valid HTML page found at $(PAGE)) $(error Make sure TOOLCHAIN and CONFIG are properly set) endif - $(CHROME_PATH) $(NEXE_ARGS) --register-pepper-plugins="$(PPAPI_DEBUG),$(PPAPI_RELEASE)" localhost:5103/$(PAGE) + $(RUN_PY) -C $(THIS_DIR) -P $(PAGE) $(addprefix -E ,$(CHROME_ENV)) -- \ + $(CHROME_PATH) $(CHROME_ARGS) \ + --register-pepper-plugins="$(PPAPI_DEBUG),$(PPAPI_RELEASE)" __PROJECT_POSTLAUNCH__ diff --git a/native_client_sdk/src/examples/Makefile b/native_client_sdk/src/examples/Makefile index fc9b787..2bbcaf6 100644 --- a/native_client_sdk/src/examples/Makefile +++ b/native_client_sdk/src/examples/Makefile @@ -42,7 +42,7 @@ all: $(TARGET_LIST) clean: $(CLEAN_LIST) echo "Done cleaning targets." +.PHONY: RUN RUN: all - echo "Staring up python webserver." - python httpd.py - + echo "Starting up python webserver." + python ../tools/httpd.py diff --git a/native_client_sdk/src/examples/debugging/Makefile.inc b/native_client_sdk/src/examples/debugging/Makefile.inc deleted file mode 100644 index 7d371d7..0000000 --- a/native_client_sdk/src/examples/debugging/Makefile.inc +++ /dev/null @@ -1,16 +0,0 @@ -# -# Setup environment to enable various debugging features, including -# access to the file system, untrusted exception handling, etc... -# - -CHROME_ARGS:=--incognito --no-sandbox --enable-nacl - -ifneq (,$(wildcard $(CHROME_PATH))) -export NACL_DANGEROUS_ENABLE_FILE_ACCESS=1 -export NACL_SECURITY_DISABLE=1 -export NACL_UNTRUSTED_EXCEPTION_HANDLING=1 -endif - - -TRACE: CHECK_FOR_CHROME all - $(CHROME_PATH) $(CHROME_ARGS) "localhost:5103/index.html" diff --git a/native_client_sdk/src/examples/debugging/example.dsc b/native_client_sdk/src/examples/debugging/example.dsc index d4b4a75..607edf6 100644 --- a/native_client_sdk/src/examples/debugging/example.dsc +++ b/native_client_sdk/src/examples/debugging/example.dsc @@ -15,8 +15,17 @@ 'LIBS' : ['ppapi', 'pthread'] } ], - 'POST': 'include Makefile.inc\n', - 'DATA': ['Makefile.inc', 'example.js'], + + # The debugging example needs to use a different HTTP server to handle POST + # messages from the NaCl module. + 'PRE': """ +CHROME_ARGS+=--no-sandbox +CHROME_ENV:=NACL_DANGEROUS_ENABLE_FILE_ACCESS=1 +CHROME_ENV+=NACL_SECURITY_DISABLE=1 +CHROME_ENV+=NACL_UNTRUSTED_EXCEPTION_HANDLING=1 +""", + + 'DATA': ['handler.py', 'example.js'], 'DEST': 'examples', 'NAME': 'debugging', 'TITLE': 'Debugging', @@ -24,6 +33,5 @@ Debugging example shows how to use developer only features to enable catching an exception, and then using that to create a stacktrace.""", 'INFO': 'Debugging, Stacktraces.' - } diff --git a/native_client_sdk/src/examples/debugging/handler.py b/native_client_sdk/src/examples/debugging/handler.py new file mode 100644 index 0000000..4593223 --- /dev/null +++ b/native_client_sdk/src/examples/debugging/handler.py @@ -0,0 +1,124 @@ +# Copyright (c) 2012 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Wrap the standard run.py to handle additional POST messages needed for +debugging. + +See <NACL_SDK_ROOT>/tools/run.py for more information. +""" + +import os +import SimpleHTTPServer # pylint: disable=W0611 +import sys +import urlparse + +SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) +NACL_SDK_ROOT = os.path.dirname(os.path.dirname(SCRIPT_DIR)) +TOOLS_DIR = os.path.join(NACL_SDK_ROOT, 'tools') + +sys.path.append(TOOLS_DIR) +import decode_dump +import getos + + +last_nexe = None +last_nmf = None + + +# "Safely" split a string at |sep| into a [key, value] pair. If |sep| does not +# exist in |pair|, then the entire |pair| is the key and the value is set to an +# empty string. +def KeyValuePair(pair, sep='='): + if sep in pair: + return pair.split(sep) + else: + return [pair, ''] + + +# $(NACL_SDK_ROOT)/tools/run.py looks for a file named handler.py in any +# directory serving a file, then uses the class name HTTPRequestHandlerDelegate +# to handle GET and POST requests for that directory. +class HTTPRequestHandlerDelegate(object): + def send_head(self, handler): + """Common code for GET and HEAD commands. + + This sends the response code and MIME headers. + + Return value is either a file object (which has to be copied + to the outputfile by the caller unless the command was HEAD, + and must be closed by the caller under all circumstances), or + None, in which case the caller has nothing further to do. + + """ + path = handler.translate_path(handler.path) + f = None + if os.path.isdir(path): + if not handler.path.endswith('/'): + # redirect browser - doing basically what apache does + handler.send_response(301) + handler.send_header("Location", handler.path + "/") + handler.end_headers() + return None + for index in "index.html", "index.htm": + index = os.path.join(path, index) + if os.path.exists(index): + path = index + break + else: + return handler.list_directory(path) + ctype = handler.guess_type(path) + try: + # Always read in binary mode. Opening files in text mode may cause + # newline translations, making the actual size of the content + # transmitted *less* than the content-length! + f = open(path, 'rb') + except IOError: + handler.send_error(404, "File not found") + return None + handler.send_response(200) + handler.send_header("Content-type", ctype) + fs = os.fstat(f.fileno()) + handler.send_header("Content-Length", str(fs[6])) + handler.send_header("Last-Modified", handler.date_time_string(fs.st_mtime)) + handler.send_header('Cache-Control','no-cache, must-revalidate') + handler.send_header('Expires','-1') + handler.end_headers() + return f + + def do_GET(self, handler): + global last_nexe, last_nmf + (_, _, path, query, _) = urlparse.urlsplit(handler.path) + url_params = dict([KeyValuePair(key_value) + for key_value in query.split('&')]) + if 'quit' in url_params and '1' in url_params['quit']: + handler.send_response(200, 'OK') + handler.send_header('Content-type', 'text/html') + handler.send_header('Content-length', '0') + handler.end_headers() + handler.server.shutdown() + return + + if path.endswith('.nexe'): + last_nexe = path + if path.endswith('.nmf'): + last_nmf = path + + handler.base_do_GET() + + def do_POST(self, handler): + if 'Content-Length' in handler.headers: + if not NACL_SDK_ROOT: + handler.wfile('Could not find NACL_SDK_ROOT to decode trace.') + return + data = handler.rfile.read(int(handler.headers['Content-Length'])) + nexe = '.' + last_nexe + nmf = '.' + last_nmf + addr = os.path.join(NACL_SDK_ROOT, 'toolchain', + getos.GetPlatform() + '_x86_newlib', + 'bin', 'x86_64-nacl-addr2line') + decoder = decode_dump.CoreDecoder(nexe, nmf, addr, None, None) + info = decoder.Decode(data) + trace = decoder.StackTrace(info) + decoder.PrintTrace(trace, sys.stdout) + decoder.PrintTrace(trace, handler.wfile) diff --git a/native_client_sdk/src/examples/debugging/index.html b/native_client_sdk/src/examples/debugging/index.html index b6e6986..59c2692 100644 --- a/native_client_sdk/src/examples/debugging/index.html +++ b/native_client_sdk/src/examples/debugging/index.html @@ -39,19 +39,12 @@ </p> <h2>Running the example</h2> - In one terminal window, to start the server: - <ul> - <li>Set the CHROME_PATH environment variable to the fully-qualified path of - your Chrome executable.</li> - <li>From the example directory type: <b>make RUN</b></li> - </ul> - - In another terminal window, to automatically start Chrome with the correct + In a terminal window, to automatically start Chrome with the correct environment variables and command-line switches: <ul> <li>Set the CHROME_PATH environment variable to the fully qualified path of your Chrome executable.</li> - <li>From the example directory type: <b>make TRACE</b></li> + <li>From the debugging directory type: <b>make RUN</b></li> </ul> <div id="listener"></div> diff --git a/native_client_sdk/src/examples/httpd.py b/native_client_sdk/src/examples/httpd.py deleted file mode 100755 index a130ed1..0000000 --- a/native_client_sdk/src/examples/httpd.py +++ /dev/null @@ -1,216 +0,0 @@ -#!/usr/bin/env python -# Copyright (c) 2012 The Chromium Authors. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -"""A tiny web server. - -This is intended to be used for testing, and only run from within the examples -directory. -""" - -import BaseHTTPServer -import logging -import optparse -import os -import SimpleHTTPServer -import SocketServer -import sys -import urlparse - - -EXAMPLE_PATH = os.path.dirname(os.path.abspath(__file__)) -NACL_SDK_ROOT = os.getenv('NACL_SDK_ROOT', os.path.dirname(EXAMPLE_PATH)) - - -if os.path.exists(NACL_SDK_ROOT): - sys.path.append(os.path.join(NACL_SDK_ROOT, 'tools')) - # pylint: disable=F0401 - import decode_dump - import getos -else: - NACL_SDK_ROOT = None - -last_nexe = None -last_nmf = None - -logging.getLogger().setLevel(logging.INFO) - -# Using 'localhost' means that we only accept connections -# via the loop back interface. -SERVER_PORT = 5103 -SERVER_HOST = '' - -# We only run from the examples directory so that not too much is exposed -# via this HTTP server. Everything in the directory is served, so there should -# never be anything potentially sensitive in the serving directory, especially -# if the machine might be a multi-user machine and not all users are trusted. -# We only serve via the loopback interface. -def SanityCheckDirectory(): - httpd_path = os.path.abspath(os.path.dirname(__file__)) - serve_path = os.path.abspath(os.getcwd()) - - # Verify we are serving from the directory this script came from, or bellow - if serve_path[:len(httpd_path)] == httpd_path: - return - logging.error('For security, httpd.py should only be run from within the') - logging.error('example directory tree.') - logging.error('We are currently in %s.' % serve_path) - sys.exit(1) - - -# An HTTP server that will quit when |is_running| is set to False. We also use -# SocketServer.ThreadingMixIn in order to handle requests asynchronously for -# faster responses. -class QuittableHTTPServer(SocketServer.ThreadingMixIn, - BaseHTTPServer.HTTPServer): - is_running = False - - def serve_forever(self, timeout=0.5): - self.is_running = True - self.timeout = timeout - while self.is_running: - sys.stderr.flush() - sys.stdout.flush() - self.handle_request() - - def shutdown(self): - self.is_running = False - return 1 - - -# "Safely" split a string at |sep| into a [key, value] pair. If |sep| does not -# exist in |pair|, then the entire |pair| is the key and the value is set to an -# empty string. -def KeyValuePair(pair, sep='='): - if sep in pair: - return pair.split(sep) - else: - return [pair, ''] - - -# A small handler that looks for '?quit=1' query in the path and shuts itself -# down if it finds that parameter. -class QuittableHTTPHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): - def send_head(self): - """Common code for GET and HEAD commands. - - This sends the response code and MIME headers. - - Return value is either a file object (which has to be copied - to the outputfile by the caller unless the command was HEAD, - and must be closed by the caller under all circumstances), or - None, in which case the caller has nothing further to do. - - """ - path = self.translate_path(self.path) - f = None - if os.path.isdir(path): - if not self.path.endswith('/'): - # redirect browser - doing basically what apache does - self.send_response(301) - self.send_header("Location", self.path + "/") - self.end_headers() - return None - for index in "index.html", "index.htm": - index = os.path.join(path, index) - if os.path.exists(index): - path = index - break - else: - return self.list_directory(path) - ctype = self.guess_type(path) - try: - # Always read in binary mode. Opening files in text mode may cause - # newline translations, making the actual size of the content - # transmitted *less* than the content-length! - f = open(path, 'rb') - except IOError: - self.send_error(404, "File not found") - return None - self.send_response(200) - self.send_header("Content-type", ctype) - fs = os.fstat(f.fileno()) - self.send_header("Content-Length", str(fs[6])) - self.send_header("Last-Modified", self.date_time_string(fs.st_mtime)) - self.send_header('Cache-Control','no-cache, must-revalidate') - self.send_header('Expires','-1') - self.end_headers() - return f - - def do_GET(self): - global last_nexe, last_nmf - (_, _, path, query, _) = urlparse.urlsplit(self.path) - url_params = dict([KeyValuePair(key_value) - for key_value in query.split('&')]) - if 'quit' in url_params and '1' in url_params['quit']: - self.send_response(200, 'OK') - self.send_header('Content-type', 'text/html') - self.send_header('Content-length', '0') - self.end_headers() - self.server.shutdown() - return - - if path.endswith('.nexe'): - last_nexe = path - if path.endswith('.nmf'): - last_nmf = path - - SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self) - - def do_POST(self): - if 'Content-Length' in self.headers: - if not NACL_SDK_ROOT: - self.wfile('Could not find NACL_SDK_ROOT to decode trace.') - return - data = self.rfile.read(int(self.headers['Content-Length'])) - nexe = '.' + last_nexe - nmf = '.' + last_nmf - addr = os.path.join(NACL_SDK_ROOT, 'toolchain', - getos.GetPlatform() + '_x86_newlib', - 'bin', 'x86_64-nacl-addr2line') - decoder = decode_dump.CoreDecoder(nexe, nmf, addr, None, None) - info = decoder.Decode(data) - trace = decoder.StackTrace(info) - decoder.PrintTrace(trace, sys.stdout) - decoder.PrintTrace(trace, self.wfile) - - -def Run(server_address, - server_class=QuittableHTTPServer, - handler_class=QuittableHTTPHandler): - httpd = server_class(server_address, handler_class) - logging.info("Starting local server on port %d", server_address[1]) - logging.info("To shut down send http://localhost:%d?quit=1", - server_address[1]) - try: - httpd.serve_forever() - except KeyboardInterrupt: - logging.info("Received keyboard interrupt.") - httpd.server_close() - - logging.info("Shutting down local server on port %d", server_address[1]) - - -def main(): - usage_str = "usage: %prog [options] [optional_portnum]" - parser = optparse.OptionParser(usage=usage_str) - parser.add_option( - '--no_dir_check', dest='do_safe_check', - action='store_false', default=True, - help='Do not ensure that httpd.py is being run from a safe directory.') - (options, args) = parser.parse_args(sys.argv) - if options.do_safe_check: - SanityCheckDirectory() - if len(args) > 2: - print 'Too many arguments specified.' - parser.print_help() - elif len(args) == 2: - Run((SERVER_HOST, int(args[1]))) - else: - Run((SERVER_HOST, SERVER_PORT)) - return 0 - - -if __name__ == '__main__': - sys.exit(main()) diff --git a/native_client_sdk/src/tools/httpd.py b/native_client_sdk/src/tools/httpd.py new file mode 100644 index 0000000..1fbdbe5 --- /dev/null +++ b/native_client_sdk/src/tools/httpd.py @@ -0,0 +1,223 @@ +# Copyright (c) 2012 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import imp +import logging +import multiprocessing +import optparse +import os +import SimpleHTTPServer # pylint: disable=W0611 +import sys + + +SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) +NACL_SDK_ROOT = os.path.dirname(SCRIPT_DIR) + + +serve_dir = None +delegate_map = {} + + +# We only run from the examples directory so that not too much is exposed +# via this HTTP server. Everything in the directory is served, so there should +# never be anything potentially sensitive in the serving directory, especially +# if the machine might be a multi-user machine and not all users are trusted. +# We only serve via the loopback interface. +def SanityCheckDirectory(dirname): + abs_serve_dir = os.path.abspath(dirname) + + # Verify we don't serve anywhere above NACL_SDK_ROOT. + if abs_serve_dir[:len(NACL_SDK_ROOT)] == NACL_SDK_ROOT: + return + logging.error('For security, httpd.py should only be run from within the') + logging.error('example directory tree.') + logging.error('Attempting to serve from %s.' % abs_serve_dir) + logging.error('Run with --no_dir_check to bypass this check.') + sys.exit(1) + + +class PluggableHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): + def _FindDelegateAtPath(self, dirname): + # First check the cache... + logging.debug('Looking for cached delegate in %s...' % dirname) + handler_script = os.path.join(dirname, 'handler.py') + + if dirname in delegate_map: + result = delegate_map[dirname] + if result is None: + logging.debug('Found None.') + else: + logging.debug('Found delegate.') + return result + + # Don't have one yet, look for one. + delegate = None + logging.debug('Testing file %s for existence...' % handler_script) + if os.path.exists(handler_script): + logging.debug( + 'File %s exists, looking for HTTPRequestHandlerDelegate.' % + handler_script) + + module = imp.load_source('handler', handler_script) + delegate_class = getattr(module, 'HTTPRequestHandlerDelegate', None) + delegate = delegate_class() + if not delegate: + logging.warn( + 'Unable to find symbol HTTPRequestHandlerDelegate in module %s.' % + handler_script) + + return delegate + + def _FindDelegateForURLRecurse(self, cur_dir, abs_root): + delegate = self._FindDelegateAtPath(cur_dir) + if not delegate: + # Didn't find it, try the parent directory, but stop if this is the server + # root. + if cur_dir != abs_root: + parent_dir = os.path.dirname(cur_dir) + delegate = self._FindDelegateForURLRecurse(parent_dir, abs_root) + + logging.debug('Adding delegate to cache for %s.' % cur_dir) + delegate_map[cur_dir] = delegate + return delegate + + def _FindDelegateForURL(self, url_path): + path = self.translate_path(url_path) + if os.path.isdir(path): + dirname = path + else: + dirname = os.path.dirname(path) + + abs_serve_dir = os.path.abspath(serve_dir) + delegate = self._FindDelegateForURLRecurse(dirname, abs_serve_dir) + if not delegate: + logging.info('No handler found for path %s. Using default.' % url_path) + return delegate + + def send_head(self): + delegate = self._FindDelegateForURL(self.path) + if delegate: + return delegate.send_head(self) + return self.base_send_head() + + def base_send_head(self): + return SimpleHTTPServer.SimpleHTTPRequestHandler.send_head(self) + + def do_GET(self): + delegate = self._FindDelegateForURL(self.path) + if delegate: + return delegate.do_GET(self) + return self.base_do_GET() + + def base_do_GET(self): + return SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self) + + def do_POST(self): + delegate = self._FindDelegateForURL(self.path) + if delegate: + return delegate.do_POST(self) + return self.base_do_POST() + + def base_do_POST(self): + pass + + +class LocalHTTPServer(object): + """Class to start a local HTTP server as a child process.""" + + def __init__(self, dirname, port): + global serve_dir + serve_dir = dirname + parent_conn, child_conn = multiprocessing.Pipe() + self.process = multiprocessing.Process( + target=_HTTPServerProcess, + args=(child_conn, serve_dir, port)) + self.process.start() + if parent_conn.poll(10): # wait 10 seconds + self.port = parent_conn.recv() + else: + raise Exception('Unable to launch HTTP server.') + + self.conn = parent_conn + + def Shutdown(self): + """Send a message to the child HTTP server process and wait for it to + finish.""" + self.conn.send(False) + self.process.join() + + def GetURL(self, rel_url): + """Get the full url for a file on the local HTTP server. + + Args: + rel_url: A URL fragment to convert to a full URL. For example, + GetURL('foobar.baz') -> 'http://localhost:1234/foobar.baz' + """ + return 'http://localhost:%d/%s' % (self.port, rel_url) + + +def _HTTPServerProcess(conn, dirname, port): + """Run a local httpserver with the given port or an ephemeral port. + + This function assumes it is run as a child process using multiprocessing. + + Args: + conn: A connection to the parent process. The child process sends + the local port, and waits for a message from the parent to + stop serving. + dirname: The directory to serve. All files are accessible through + http://localhost:<port>/path/to/filename. + port: The port to serve on. If 0, an ephemeral port will be chosen. + """ + import BaseHTTPServer + + try: + os.chdir(dirname) + httpd = BaseHTTPServer.HTTPServer(('', port), PluggableHTTPRequestHandler) + conn.send(httpd.server_address[1]) # the chosen port number + httpd.timeout = 0.5 # seconds + running = True + while running: + # Flush output for MSVS Add-In. + sys.stdout.flush() + sys.stderr.flush() + httpd.handle_request() + if conn.poll(): + running = conn.recv() + except KeyboardInterrupt: + pass + finally: + conn.close() + + +def main(args): + parser = optparse.OptionParser() + parser.add_option('-C', '--serve-dir', + help='Serve files out of this directory.', + dest='serve_dir', default=os.path.abspath('.')) + parser.add_option('-p', '--port', + help='Run server on this port.', + dest='port', default=5103) + parser.add_option('--no_dir_check', + help='No check to ensure serving from safe directory.', + dest='do_safe_check', action='store_false', default=True) + options, args = parser.parse_args(args) + if options.do_safe_check: + SanityCheckDirectory(options.serve_dir) + + server = LocalHTTPServer(options.serve_dir, options.port) + + # Serve forever. + print 'Serving %s on %s...' % (options.serve_dir, server.GetURL('')) + try: + while True: + pass + except KeyboardInterrupt: + pass + finally: + print 'Stopping server.' + server.Shutdown() + +if __name__ == '__main__': + sys.exit(main(sys.argv[1:])) diff --git a/native_client_sdk/src/tools/run.py b/native_client_sdk/src/tools/run.py new file mode 100755 index 0000000..7c4ddf2 --- /dev/null +++ b/native_client_sdk/src/tools/run.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python +# Copyright (c) 2012 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Launch a local server on an ephemeral port, then launch a executable that +points to that server. +""" + +import copy +import optparse +import os +import subprocess +import sys +import httpd + + +def main(args): + usage = """usage: %prog [options] -- executable args... + + This command creates a local server on an ephemeral port, then runs: + <executable> <args..> http://localhost:<port>/<page>. + + Where <page> can be set by -P, or uses index.html by default.""" + parser = optparse.OptionParser(usage) + parser.add_option('-C', '--serve-dir', + help='Serve files out of this directory.', + dest='serve_dir', default=os.path.abspath('.')) + parser.add_option('-P', '--path', help='Path to load from local server.', + dest='path', default='index.html') + parser.add_option('-E', + help='Add environment variables when launching the executable.', + dest='environ', action='append', default=[]) + options, args = parser.parse_args(args) + if not args: + parser.error('No executable given.') + + # 0 means use an ephemeral port. + server = httpd.LocalHTTPServer(options.serve_dir, 0) + + try: + env = copy.copy(os.environ) + for e in options.environ: + key, value = map(str.strip, e.split('=')) + env[key] = value + + cmd = args + [server.GetURL(options.path)] + print 'Running: %s...' % (' '.join(cmd),) + subprocess.call(cmd, env=env) + finally: + server.Shutdown() + + +if __name__ == '__main__': + sys.exit(main(sys.argv[1:])) |