Index: media/tools/constrained_network_server/cns.py |
diff --git a/media/tools/constrained_network_server/cns.py b/media/tools/constrained_network_server/cns.py |
index 9b0e96b10feb28b4f1d263a70f427f45652f8fc2..58e2ba000d8cce73cb3a48bff7247e77dcfd17b7 100755 |
--- a/media/tools/constrained_network_server/cns.py |
+++ b/media/tools/constrained_network_server/cns.py |
@@ -21,6 +21,9 @@ import signal |
import sys |
import threading |
import time |
+import urllib |
+import urllib2 |
+ |
import traffic_control |
try: |
@@ -216,19 +219,86 @@ class ConstrainedNetworkServer(object): |
if not f: |
raise cherrypy.HTTPError(400, 'Invalid request. File must be specified.') |
+ # Check existence early to prevent wasted constraint setup. |
+ self._CheckRequestedFileExist(f) |
+ |
+ # If there are no constraints, just serve the file. |
+ if bandwidth is None and latency is None and loss is None: |
+ return self._ServeFile(f) |
+ |
+ constrained_port = self._GetConstrainedPort( |
+ f, bandwidth=bandwidth, latency=latency, loss=loss, new_port=new_port, |
+ **kwargs) |
+ |
+ # Build constrained URL using the constrained port and original URL |
+ # parameters except the network constraints (bandwidth, latency, and loss). |
+ constrained_url = self._GetServerURL(f, constrained_port, |
+ no_cache=no_cache, **kwargs) |
+ |
+ # Redirect request to the constrained port. |
+ cherrypy.log('Redirect to %s' % constrained_url) |
+ cherrypy.lib.cptools.redirect(constrained_url, internal=False) |
+ |
+ def _CheckRequestedFileExist(self, f): |
+ """Checks if the requested file exists, raises HTTPError otherwise.""" |
+ if self._options.local_server_port: |
+ self._CheckFileExistOnLocalServer(f) |
+ else: |
+ self._CheckFileExistOnServer(f) |
+ |
+ def _CheckFileExistOnServer(self, f): |
+ """Checks if requested file f exists to be served by this server.""" |
# Sanitize and check the path to prevent www-root escapes. |
sanitized_path = os.path.abspath(os.path.join(self._options.www_root, f)) |
if not sanitized_path.startswith(self._options.www_root): |
raise cherrypy.HTTPError(403, 'Invalid file requested.') |
- |
- # Check existence early to prevent wasted constraint setup. |
if not os.path.exists(sanitized_path): |
raise cherrypy.HTTPError(404, 'File not found.') |
- # If there are no constraints, just serve the file. |
- if bandwidth is None and latency is None and loss is None: |
+ def _CheckFileExistOnLocalServer(self, f): |
+ """Checks if requested file exists on local server hosting files.""" |
+ test_url = self._GetServerURL(f, self._options.local_server_port) |
+ try: |
+ cherrypy.log('Check file exist using URL: %s' % test_url) |
+ return urllib2.urlopen(test_url) is not None |
+ except Exception: |
+ raise cherrypy.HTTPError(404, 'File not found on local server.') |
+ |
+ def _ServeFile(self, f): |
+ """Serves the file as an http response.""" |
+ if self._options.local_server_port: |
+ redirect_url = self._GetServerURL(f, self._options.local_server_port) |
+ cherrypy.log('Redirect to %s' % redirect_url) |
+ cherrypy.lib.cptools.redirect(redirect_url, internal=False) |
+ else: |
+ sanitized_path = os.path.abspath(os.path.join(self._options.www_root, f)) |
return cherrypy.lib.static.serve_file(sanitized_path) |
+ def _GetServerURL(self, f, port, **kwargs): |
+ """Returns a URL for local server to serve the file on given port. |
+ |
+ Args: |
+ f: file name to serve on local server. Relative to www_root. |
+ port: Local server port (it can be a configured constrained port). |
+ kwargs: extra parameteres passed in the URL. |
+ """ |
+ url = '%s?f=%s&' % (cherrypy.url(), f) |
+ if self._options.local_server_port: |
+ url = '%s/%s?' % ( |
+ cherrypy.url().replace('ServeConstrained', self._options.www_root), f) |
+ |
+ url = url.replace(':%d' % self._options.port, ':%d' % port) |
+ extra_args = urllib.urlencode(kwargs) |
+ if extra_args: |
+ url += extra_args |
+ return url |
+ |
+ def _GetConstrainedPort(self, f=None, bandwidth=None, latency=None, loss=None, |
+ new_port=False, **kwargs): |
+ """Creates or gets a port with specified network constraints. |
+ |
+ See ServeConstrained() for more details. |
+ """ |
# Validate inputs. isdigit() guarantees a natural number. |
bandwidth = self._ParseIntParameter( |
bandwidth, 'Invalid bandwidth constraint.', lambda x: x > 0) |
@@ -237,35 +307,24 @@ class ConstrainedNetworkServer(object): |
loss = self._ParseIntParameter( |
loss, 'Invalid loss constraint.', lambda x: x <= 100 and x >= 0) |
- # Allocate a port using the given constraints. If a port with the requested |
- # key is already allocated, it will be reused. |
- # |
- # TODO(dalecurtis): The key cherrypy.request.remote.ip might not be unique |
- # if build slaves are sharing the same VM. |
+ redirect_port = self._options.port |
+ if self._options.local_server_port: |
+ redirect_port = self._options.local_server_port |
+ |
start_time = time.time() |
+ # Allocate a port using the given constraints. If a port with the requested |
+ # key and kwargs already exist then reuse that port. |
constrained_port = self._port_allocator.Get( |
- cherrypy.request.remote.ip, server_port=self._options.port, |
+ cherrypy.request.remote.ip, server_port=redirect_port, |
interface=self._options.interface, bandwidth=bandwidth, latency=latency, |
loss=loss, new_port=new_port, file=f, **kwargs) |
- end_time = time.time() |
+ |
+ cherrypy.log('Time to set up port %d = %.3fsec.' % |
+ (constrained_port, time.time() - start_time)) |
if not constrained_port: |
raise cherrypy.HTTPError(503, 'Service unavailable. Out of ports.') |
- |
- cherrypy.log('Time to set up port %d = %ssec.' % |
- (constrained_port, end_time - start_time)) |
- |
- # Build constrained URL using the constrained port and original URL |
- # parameters except the network constraints (bandwidth, latency, and loss). |
- constrained_url = '%s?f=%s&no_cache=%s&%s' % ( |
- cherrypy.url().replace( |
- ':%d' % self._options.port, ':%d' % constrained_port), |
- f, |
- no_cache, |
- '&'.join(['%s=%s' % (key, kwargs[key]) for key in kwargs])) |
- |
- # Redirect request to the constrained port. |
- cherrypy.lib.cptools.redirect(constrained_url, internal=False) |
+ return constrained_port |
def _ParseIntParameter(self, param, msg, check): |
"""Returns integer value of param and verifies it satisfies the check. |
@@ -316,9 +375,14 @@ def ParseArgs(): |
default=cherrypy._cpserver.Server.thread_pool, |
help=('Number of threads in the thread pool. Default: ' |
'%default')) |
- parser.add_option('--www-root', default=os.getcwd(), |
- help=('Directory root to serve files from. Defaults to the ' |
- 'current directory: %default')) |
+ parser.add_option('--www-root', default='', |
+ help=('Directory root to serve files from. If --local-' |
+ 'server-port is used, the path is appended to the ' |
+ 'redirected URL of local server. Defaults to the ' |
+ 'current directory (if --local-server-port is not ' |
+ 'used): %s' % os.getcwd())) |
+ parser.add_option('--local-server-port', type='int', |
+ help=('Optional local server port to host files.')) |
parser.add_option('-v', '--verbose', action='store_true', default=False, |
help='Turn on verbose output.') |
@@ -335,7 +399,10 @@ def ParseArgs(): |
parser.error('Invalid expiry time specified.') |
# Convert the path to an absolute to remove any . or .. |
- options.www_root = os.path.abspath(options.www_root) |
+ if not options.local_server_port: |
+ if not options.www_root: |
+ options.www_root = os.getcwd() |
+ options.www_root = os.path.abspath(options.www_root) |
_SetLogger(options.verbose) |