summaryrefslogtreecommitdiffstats
path: root/native_client_sdk
diff options
context:
space:
mode:
authorbinji@chromium.org <binji@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-11-07 19:37:04 +0000
committerbinji@chromium.org <binji@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-11-07 19:37:04 +0000
commit22fa27f0ad144be69ed89e53494a4b4816851ddf (patch)
treecd2a6a8b90ed560759a8cf53899c9f6392c49c95 /native_client_sdk
parent81cffbd2dbbde6c8c119d04f2a6648b2c49231b6 (diff)
downloadchromium_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-xnative_client_sdk/src/build_tools/build_sdk.py2
-rw-r--r--native_client_sdk/src/build_tools/template.mk26
-rw-r--r--native_client_sdk/src/examples/Makefile6
-rw-r--r--native_client_sdk/src/examples/debugging/Makefile.inc16
-rw-r--r--native_client_sdk/src/examples/debugging/example.dsc14
-rw-r--r--native_client_sdk/src/examples/debugging/handler.py124
-rw-r--r--native_client_sdk/src/examples/debugging/index.html11
-rwxr-xr-xnative_client_sdk/src/examples/httpd.py216
-rw-r--r--native_client_sdk/src/tools/httpd.py223
-rwxr-xr-xnative_client_sdk/src/tools/run.py55
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:]))