| OLD | NEW |
| (Empty) |
| 1 # Copyright 2015 The Chromium Authors. All rights reserved. | |
| 2 # Use of this source code is governed by a BSD-style license that can be | |
| 3 # found in the LICENSE file. | |
| 4 | |
| 5 import atexit | |
| 6 import datetime | |
| 7 import email.utils | |
| 8 import hashlib | |
| 9 import logging | |
| 10 import math | |
| 11 import os.path | |
| 12 import threading | |
| 13 | |
| 14 import SimpleHTTPServer | |
| 15 import SocketServer | |
| 16 | |
| 17 | |
| 18 ZERO = datetime.timedelta(0) | |
| 19 | |
| 20 | |
| 21 class UTC_TZINFO(datetime.tzinfo): | |
| 22 """UTC time zone representation.""" | |
| 23 | |
| 24 def utcoffset(self, _): | |
| 25 return ZERO | |
| 26 | |
| 27 def tzname(self, _): | |
| 28 return "UTC" | |
| 29 | |
| 30 def dst(self, _): | |
| 31 return ZERO | |
| 32 | |
| 33 UTC = UTC_TZINFO() | |
| 34 | |
| 35 | |
| 36 class _SilentTCPServer(SocketServer.TCPServer): | |
| 37 """A TCPServer that won't display any error, unless debugging is enabled. This | |
| 38 is useful because the client might stop while it is fetching an URL, which | |
| 39 causes spurious error messages. | |
| 40 """ | |
| 41 def handle_error(self, request, client_address): | |
| 42 """Override the base class method to have conditional logging.""" | |
| 43 if logging.getLogger().isEnabledFor(logging.DEBUG): | |
| 44 SocketServer.TCPServer.handle_error(self, request, client_address) | |
| 45 | |
| 46 | |
| 47 def _GetHandlerClassForPath(base_path): | |
| 48 class RequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): | |
| 49 """Handler for SocketServer.TCPServer that will serve the files from | |
| 50 |base_path| directory over http. | |
| 51 """ | |
| 52 | |
| 53 def __init__(self, *args, **kwargs): | |
| 54 self.etag = None | |
| 55 SimpleHTTPServer.SimpleHTTPRequestHandler.__init__(self, *args, **kwargs) | |
| 56 | |
| 57 def get_etag(self): | |
| 58 if self.etag: | |
| 59 return self.etag | |
| 60 | |
| 61 path = self.translate_path(self.path) | |
| 62 if not os.path.isfile(path): | |
| 63 return None | |
| 64 | |
| 65 sha256 = hashlib.sha256() | |
| 66 BLOCKSIZE = 65536 | |
| 67 with open(path, 'rb') as hashed: | |
| 68 buf = hashed.read(BLOCKSIZE) | |
| 69 while len(buf) > 0: | |
| 70 sha256.update(buf) | |
| 71 buf = hashed.read(BLOCKSIZE) | |
| 72 self.etag = '"%s"' % sha256.hexdigest() | |
| 73 return self.etag | |
| 74 | |
| 75 def send_head(self): | |
| 76 # Always close the connection after each request, as the keep alive | |
| 77 # support from SimpleHTTPServer doesn't like when the client requests to | |
| 78 # close the connection before downloading the full response content. | |
| 79 # pylint: disable=W0201 | |
| 80 self.close_connection = 1 | |
| 81 | |
| 82 path = self.translate_path(self.path) | |
| 83 if os.path.isfile(path): | |
| 84 # Handle If-None-Match | |
| 85 etag = self.get_etag() | |
| 86 if ('If-None-Match' in self.headers and | |
| 87 etag == self.headers['If-None-Match']): | |
| 88 self.send_response(304) | |
| 89 return None | |
| 90 | |
| 91 # Handle If-Modified-Since | |
| 92 if ('If-None-Match' not in self.headers and | |
| 93 'If-Modified-Since' in self.headers): | |
| 94 last_modified = datetime.datetime.fromtimestamp( | |
| 95 math.floor(os.stat(path).st_mtime), tz=UTC) | |
| 96 ims = datetime.datetime( | |
| 97 *email.utils.parsedate(self.headers['If-Modified-Since'])[:6], | |
| 98 tzinfo=UTC) | |
| 99 if last_modified <= ims: | |
| 100 self.send_response(304) | |
| 101 return None | |
| 102 | |
| 103 return SimpleHTTPServer.SimpleHTTPRequestHandler.send_head(self) | |
| 104 | |
| 105 def end_headers(self): | |
| 106 path = self.translate_path(self.path) | |
| 107 | |
| 108 if os.path.isfile(path): | |
| 109 etag = self.get_etag() | |
| 110 if etag: | |
| 111 self.send_header('ETag', etag) | |
| 112 self.send_header('Cache-Control', 'must-revalidate') | |
| 113 | |
| 114 return SimpleHTTPServer.SimpleHTTPRequestHandler.end_headers(self) | |
| 115 | |
| 116 def translate_path(self, path): | |
| 117 path_from_current = ( | |
| 118 SimpleHTTPServer.SimpleHTTPRequestHandler.translate_path(self, path)) | |
| 119 return os.path.join(base_path, os.path.relpath(path_from_current)) | |
| 120 | |
| 121 def log_message(self, *_): | |
| 122 """Override the base class method to disable logging.""" | |
| 123 pass | |
| 124 | |
| 125 RequestHandler.protocol_version = 'HTTP/1.1' | |
| 126 return RequestHandler | |
| 127 | |
| 128 | |
| 129 def StartHttpServer(path): | |
| 130 """Starts an http server serving files from |path| on random | |
| 131 (system-allocated) port. Returns the server address. | |
| 132 """ | |
| 133 assert path | |
| 134 httpd = _SilentTCPServer(('127.0.0.1', 0), _GetHandlerClassForPath(path)) | |
| 135 atexit.register(httpd.shutdown) | |
| 136 | |
| 137 http_thread = threading.Thread(target=httpd.serve_forever) | |
| 138 http_thread.daemon = True | |
| 139 http_thread.start() | |
| 140 return httpd.server_address | |
| OLD | NEW |