Init
This commit is contained in:
305
Samples/nacl_sdk/RakNet_NativeClient/httpd.py
Normal file
305
Samples/nacl_sdk/RakNet_NativeClient/httpd.py
Normal file
@ -0,0 +1,305 @@
|
||||
#!/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.
|
||||
|
||||
import BaseHTTPServer
|
||||
import imp
|
||||
import logging
|
||||
import multiprocessing
|
||||
import optparse
|
||||
import os
|
||||
import SimpleHTTPServer # pylint: disable=W0611
|
||||
import socket
|
||||
import sys
|
||||
import time
|
||||
import urlparse
|
||||
|
||||
|
||||
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
NACL_SDK_ROOT = os.path.dirname(SCRIPT_DIR)
|
||||
|
||||
|
||||
# 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 PluggableHTTPServer(BaseHTTPServer.HTTPServer):
|
||||
def __init__(self, *args, **kwargs):
|
||||
BaseHTTPServer.HTTPServer.__init__(self, *args)
|
||||
self.serve_dir = kwargs.get('serve_dir', '.')
|
||||
self.test_mode = kwargs.get('test_mode', False)
|
||||
self.delegate_map = {}
|
||||
self.running = True
|
||||
self.result = 0
|
||||
|
||||
def Shutdown(self, result=0):
|
||||
self.running = False
|
||||
self.result = result
|
||||
|
||||
|
||||
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 self.server.delegate_map:
|
||||
result = self.server.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)
|
||||
self.server.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(self.server.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 _SendNothingAndDie(self, result=0):
|
||||
self.send_response(200, 'OK')
|
||||
self.send_header('Content-type', 'text/html')
|
||||
self.send_header('Content-length', '0')
|
||||
self.end_headers()
|
||||
self.server.Shutdown(result)
|
||||
|
||||
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):
|
||||
# TODO(binji): pyauto tests use the ?quit=1 method to kill the server.
|
||||
# Remove this when we kill the pyauto tests.
|
||||
_, _, _, query, _ = urlparse.urlsplit(self.path)
|
||||
if query:
|
||||
params = urlparse.parse_qs(query)
|
||||
if '1' in params.get('quit', None):
|
||||
self._SendNothingAndDie()
|
||||
return
|
||||
|
||||
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):
|
||||
if self.server.test_mode:
|
||||
if self.path == '/ok':
|
||||
self._SendNothingAndDie(0)
|
||||
elif self.path == '/fail':
|
||||
self._SendNothingAndDie(1)
|
||||
|
||||
|
||||
class LocalHTTPServer(object):
|
||||
"""Class to start a local HTTP server as a child process."""
|
||||
|
||||
def __init__(self, dirname, port, test_mode):
|
||||
parent_conn, child_conn = multiprocessing.Pipe()
|
||||
self.process = multiprocessing.Process(
|
||||
target=_HTTPServerProcess,
|
||||
args=(child_conn, dirname, port, {
|
||||
'serve_dir': dirname,
|
||||
'test_mode': test_mode,
|
||||
}))
|
||||
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 ServeForever(self):
|
||||
"""Serve until the child HTTP process tells us to stop.
|
||||
|
||||
Returns:
|
||||
The result from the child (as an errorcode), or 0 if the server was
|
||||
killed not by the child (by KeyboardInterrupt for example).
|
||||
"""
|
||||
child_result = 0
|
||||
try:
|
||||
# Block on this pipe, waiting for a response from the child process.
|
||||
child_result = self.conn.recv()
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
finally:
|
||||
self.Shutdown()
|
||||
return child_result
|
||||
|
||||
def ServeUntilSubprocessDies(self, process):
|
||||
"""Serve until the child HTTP process tells us to stop or |subprocess| dies.
|
||||
|
||||
Returns:
|
||||
The result from the child (as an errorcode), or 0 if |subprocess| died,
|
||||
or the server was killed some other way (by KeyboardInterrupt for
|
||||
example).
|
||||
"""
|
||||
child_result = 0
|
||||
try:
|
||||
while True:
|
||||
if process.poll() is not None:
|
||||
child_result = 0
|
||||
break
|
||||
if self.conn.poll():
|
||||
child_result = self.conn.recv()
|
||||
break
|
||||
time.sleep(0)
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
finally:
|
||||
self.Shutdown()
|
||||
return child_result
|
||||
|
||||
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, server_kwargs):
|
||||
"""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. It also sends a "result" back to the parent -- this can
|
||||
be used to allow a client-side test to notify the server of results.
|
||||
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.
|
||||
server_kwargs: A dict that will be passed as kwargs to the server.
|
||||
"""
|
||||
try:
|
||||
os.chdir(dirname)
|
||||
httpd = PluggableHTTPServer(('', port), PluggableHTTPRequestHandler,
|
||||
**server_kwargs)
|
||||
except socket.error as e:
|
||||
sys.stderr.write('Error creating HTTPServer: %s\n' % e)
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
conn.send(httpd.server_address[1]) # the chosen port number
|
||||
httpd.timeout = 0.5 # seconds
|
||||
while httpd.running:
|
||||
# Flush output for MSVS Add-In.
|
||||
sys.stdout.flush()
|
||||
sys.stderr.flush()
|
||||
httpd.handle_request()
|
||||
if conn.poll():
|
||||
httpd.running = conn.recv()
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
finally:
|
||||
conn.send(httpd.result)
|
||||
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)
|
||||
parser.add_option('--test-mode',
|
||||
help='Listen for posts to /ok or /fail and shut down the server with '
|
||||
' errorcodes 0 and 1 respectively.',
|
||||
dest='test_mode', action='store_true')
|
||||
options, args = parser.parse_args(args)
|
||||
if options.do_safe_check:
|
||||
SanityCheckDirectory(options.serve_dir)
|
||||
|
||||
server = LocalHTTPServer(options.serve_dir, int(options.port),
|
||||
options.test_mode)
|
||||
|
||||
# Serve until the client tells us to stop. When it does, it will give us an
|
||||
# errorcode.
|
||||
print 'Serving %s on %s...' % (options.serve_dir, server.GetURL(''))
|
||||
return server.ServeForever()
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main(sys.argv[1:]))
|
||||
Reference in New Issue
Block a user