| OLD | NEW |
| 1 # Copyright 2014 The Chromium Authors. All rights reserved. | 1 # Copyright 2014 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 import atexit | 5 import atexit |
| 6 import datetime | |
| 7 import email.utils | |
| 8 import hashlib | |
| 9 import itertools | 6 import itertools |
| 10 import json | 7 import json |
| 11 import logging | 8 import logging |
| 12 import math | |
| 13 import os | 9 import os |
| 14 import os.path | 10 import os.path |
| 15 import random | 11 import random |
| 16 import subprocess | 12 import subprocess |
| 17 import sys | 13 import sys |
| 18 import threading | 14 import threading |
| 19 import time | 15 import time |
| 20 import urlparse | 16 import urlparse |
| 21 | 17 |
| 22 import SimpleHTTPServer | 18 from pylib.http_server import StartHttpServer |
| 23 import SocketServer | |
| 24 | 19 |
| 25 | 20 |
| 26 # Tags used by the mojo shell application logs. | 21 # Tags used by the mojo shell application logs. |
| 27 LOGCAT_TAGS = [ | 22 LOGCAT_TAGS = [ |
| 28 'AndroidHandler', | 23 'AndroidHandler', |
| 29 'MojoFileHelper', | 24 'MojoFileHelper', |
| 30 'MojoMain', | 25 'MojoMain', |
| 31 'MojoShellActivity', | 26 'MojoShellActivity', |
| 32 'MojoShellApplication', | 27 'MojoShellApplication', |
| 33 'chromium', | 28 'chromium', |
| 34 ] | 29 ] |
| 35 | 30 |
| 36 MOJO_SHELL_PACKAGE_NAME = 'org.chromium.mojo.shell' | 31 MOJO_SHELL_PACKAGE_NAME = 'org.chromium.mojo.shell' |
| 37 | 32 |
| 38 MAPPING_PREFIX = '--map-origin=' | 33 MAPPING_PREFIX = '--map-origin=' |
| 39 | 34 |
| 40 DEFAULT_BASE_PORT = 31337 | 35 DEFAULT_BASE_PORT = 31337 |
| 41 | 36 |
| 42 ZERO = datetime.timedelta(0) | |
| 43 | |
| 44 class UTC_TZINFO(datetime.tzinfo): | |
| 45 """UTC time zone representation.""" | |
| 46 | |
| 47 def utcoffset(self, _): | |
| 48 return ZERO | |
| 49 | |
| 50 def tzname(self, _): | |
| 51 return "UTC" | |
| 52 | |
| 53 def dst(self, _): | |
| 54 return ZERO | |
| 55 | |
| 56 UTC = UTC_TZINFO() | |
| 57 | |
| 58 _logger = logging.getLogger() | 37 _logger = logging.getLogger() |
| 59 | 38 |
| 60 | 39 |
| 61 class _SilentTCPServer(SocketServer.TCPServer): | |
| 62 """ | |
| 63 A TCPServer that won't display any error, unless debugging is enabled. This is | |
| 64 useful because the client might stop while it is fetching an URL, which causes | |
| 65 spurious error messages. | |
| 66 """ | |
| 67 def handle_error(self, request, client_address): | |
| 68 """ | |
| 69 Override the base class method to have conditional logging. | |
| 70 """ | |
| 71 if logging.getLogger().isEnabledFor(logging.DEBUG): | |
| 72 SocketServer.TCPServer.handle_error(self, request, client_address) | |
| 73 | |
| 74 | |
| 75 def _GetHandlerClassForPath(base_path): | |
| 76 class RequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): | |
| 77 """ | |
| 78 Handler for SocketServer.TCPServer that will serve the files from | |
| 79 |base_path| directory over http. | |
| 80 """ | |
| 81 | |
| 82 def __init__(self, *args, **kwargs): | |
| 83 self.etag = None | |
| 84 SimpleHTTPServer.SimpleHTTPRequestHandler.__init__(self, *args, **kwargs) | |
| 85 | |
| 86 def get_etag(self): | |
| 87 if self.etag: | |
| 88 return self.etag | |
| 89 | |
| 90 path = self.translate_path(self.path) | |
| 91 if not os.path.isfile(path): | |
| 92 return None | |
| 93 | |
| 94 sha256 = hashlib.sha256() | |
| 95 BLOCKSIZE = 65536 | |
| 96 with open(path, 'rb') as hashed: | |
| 97 buf = hashed.read(BLOCKSIZE) | |
| 98 while len(buf) > 0: | |
| 99 sha256.update(buf) | |
| 100 buf = hashed.read(BLOCKSIZE) | |
| 101 self.etag = '"%s"' % sha256.hexdigest() | |
| 102 return self.etag | |
| 103 | |
| 104 def send_head(self): | |
| 105 # Always close the connection after each request, as the keep alive | |
| 106 # support from SimpleHTTPServer doesn't like when the client requests to | |
| 107 # close the connection before downloading the full response content. | |
| 108 # pylint: disable=W0201 | |
| 109 self.close_connection = 1 | |
| 110 | |
| 111 path = self.translate_path(self.path) | |
| 112 if os.path.isfile(path): | |
| 113 # Handle If-None-Match | |
| 114 etag = self.get_etag() | |
| 115 if ('If-None-Match' in self.headers and | |
| 116 etag == self.headers['If-None-Match']): | |
| 117 self.send_response(304) | |
| 118 return None | |
| 119 | |
| 120 # Handle If-Modified-Since | |
| 121 if ('If-None-Match' not in self.headers and | |
| 122 'If-Modified-Since' in self.headers): | |
| 123 last_modified = datetime.datetime.fromtimestamp( | |
| 124 math.floor(os.stat(path).st_mtime), tz=UTC) | |
| 125 ims = datetime.datetime( | |
| 126 *email.utils.parsedate(self.headers['If-Modified-Since'])[:6], | |
| 127 tzinfo=UTC) | |
| 128 if last_modified <= ims: | |
| 129 self.send_response(304) | |
| 130 return None | |
| 131 | |
| 132 return SimpleHTTPServer.SimpleHTTPRequestHandler.send_head(self) | |
| 133 | |
| 134 def end_headers(self): | |
| 135 path = self.translate_path(self.path) | |
| 136 | |
| 137 if os.path.isfile(path): | |
| 138 etag = self.get_etag() | |
| 139 if etag: | |
| 140 self.send_header('ETag', etag) | |
| 141 self.send_header('Cache-Control', 'must-revalidate') | |
| 142 | |
| 143 return SimpleHTTPServer.SimpleHTTPRequestHandler.end_headers(self) | |
| 144 | |
| 145 def translate_path(self, path): | |
| 146 path_from_current = ( | |
| 147 SimpleHTTPServer.SimpleHTTPRequestHandler.translate_path(self, path)) | |
| 148 return os.path.join(base_path, os.path.relpath(path_from_current)) | |
| 149 | |
| 150 def log_message(self, *_): | |
| 151 """ | |
| 152 Override the base class method to disable logging. | |
| 153 """ | |
| 154 pass | |
| 155 | |
| 156 RequestHandler.protocol_version = 'HTTP/1.1' | |
| 157 return RequestHandler | |
| 158 | |
| 159 | |
| 160 def _IsMapOrigin(arg): | 40 def _IsMapOrigin(arg): |
| 161 """Returns whether arg is a --map-origin argument.""" | 41 """Returns whether arg is a --map-origin argument.""" |
| 162 return arg.startswith(MAPPING_PREFIX) | 42 return arg.startswith(MAPPING_PREFIX) |
| 163 | 43 |
| 164 | 44 |
| 165 def _Split(l, pred): | 45 def _Split(l, pred): |
| 166 positive = [] | 46 positive = [] |
| 167 negative = [] | 47 negative = [] |
| 168 for v in l: | 48 for v in l: |
| 169 if pred(v): | 49 if pred(v): |
| (...skipping 94 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 264 def _UnmapPort(): | 144 def _UnmapPort(): |
| 265 subprocess.Popen(unmap_command) | 145 subprocess.Popen(unmap_command) |
| 266 atexit.register(_UnmapPort) | 146 atexit.register(_UnmapPort) |
| 267 return device_port | 147 return device_port |
| 268 | 148 |
| 269 def _StartHttpServerForDirectory(self, path, port=0): | 149 def _StartHttpServerForDirectory(self, path, port=0): |
| 270 """Starts an http server serving files from |path|. Returns the local | 150 """Starts an http server serving files from |path|. Returns the local |
| 271 url.""" | 151 url.""" |
| 272 assert path | 152 assert path |
| 273 print 'starting http for', path | 153 print 'starting http for', path |
| 274 httpd = _SilentTCPServer(('127.0.0.1', 0), _GetHandlerClassForPath(path)) | 154 server_address = StartHttpServer(path) |
| 275 atexit.register(httpd.shutdown) | |
| 276 | 155 |
| 277 http_thread = threading.Thread(target=httpd.serve_forever) | 156 print 'local port=%d' % server_address[1] |
| 278 http_thread.daemon = True | 157 return 'http://127.0.0.1:%d/' % self._MapPort(port, server_address[1]) |
| 279 http_thread.start() | |
| 280 | |
| 281 print 'local port=%d' % httpd.server_address[1] | |
| 282 return 'http://127.0.0.1:%d/' % self._MapPort(port, httpd.server_address[1]) | |
| 283 | 158 |
| 284 def _StartHttpServerForOriginMapping(self, mapping, port): | 159 def _StartHttpServerForOriginMapping(self, mapping, port): |
| 285 """If |mapping| points at a local file starts an http server to serve files | 160 """If |mapping| points at a local file starts an http server to serve files |
| 286 from the directory and returns the new mapping. | 161 from the directory and returns the new mapping. |
| 287 | 162 |
| 288 This is intended to be called for every --map-origin value.""" | 163 This is intended to be called for every --map-origin value.""" |
| 289 parts = mapping.split('=') | 164 parts = mapping.split('=') |
| 290 if len(parts) != 2: | 165 if len(parts) != 2: |
| 291 return mapping | 166 return mapping |
| 292 dest = parts[1] | 167 dest = parts[1] |
| (...skipping 106 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 399 | 274 |
| 400 Returns the process responsible for reading the logs. | 275 Returns the process responsible for reading the logs. |
| 401 """ | 276 """ |
| 402 logcat = subprocess.Popen(self._CreateADBCommand([ | 277 logcat = subprocess.Popen(self._CreateADBCommand([ |
| 403 'logcat', | 278 'logcat', |
| 404 '-s', | 279 '-s', |
| 405 ' '.join(LOGCAT_TAGS)]), | 280 ' '.join(LOGCAT_TAGS)]), |
| 406 stdout=sys.stdout) | 281 stdout=sys.stdout) |
| 407 atexit.register(_ExitIfNeeded, logcat) | 282 atexit.register(_ExitIfNeeded, logcat) |
| 408 return logcat | 283 return logcat |
| OLD | NEW |