| 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 """ |
| 38 A TCPServer that won't display any error, unless debugging is enabled. This is |
| 39 useful because the client might stop while it is fetching an URL, which causes |
| 40 spurious error messages. |
| 41 """ |
| 42 def handle_error(self, request, client_address): |
| 43 """ |
| 44 Override the base class method to have conditional logging. |
| 45 """ |
| 46 if logging.getLogger().isEnabledFor(logging.DEBUG): |
| 47 SocketServer.TCPServer.handle_error(self, request, client_address) |
| 48 |
| 49 |
| 50 def _GetHandlerClassForPath(base_path): |
| 51 class RequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): |
| 52 """ |
| 53 Handler for SocketServer.TCPServer that will serve the files from |
| 54 |base_path| directory over http. |
| 55 """ |
| 56 |
| 57 def __init__(self, *args, **kwargs): |
| 58 self.etag = None |
| 59 SimpleHTTPServer.SimpleHTTPRequestHandler.__init__(self, *args, **kwargs) |
| 60 |
| 61 def get_etag(self): |
| 62 if self.etag: |
| 63 return self.etag |
| 64 |
| 65 path = self.translate_path(self.path) |
| 66 if not os.path.isfile(path): |
| 67 return None |
| 68 |
| 69 sha256 = hashlib.sha256() |
| 70 BLOCKSIZE = 65536 |
| 71 with open(path, 'rb') as hashed: |
| 72 buf = hashed.read(BLOCKSIZE) |
| 73 while len(buf) > 0: |
| 74 sha256.update(buf) |
| 75 buf = hashed.read(BLOCKSIZE) |
| 76 self.etag = '"%s"' % sha256.hexdigest() |
| 77 return self.etag |
| 78 |
| 79 def send_head(self): |
| 80 # Always close the connection after each request, as the keep alive |
| 81 # support from SimpleHTTPServer doesn't like when the client requests to |
| 82 # close the connection before downloading the full response content. |
| 83 # pylint: disable=W0201 |
| 84 self.close_connection = 1 |
| 85 |
| 86 path = self.translate_path(self.path) |
| 87 if os.path.isfile(path): |
| 88 # Handle If-None-Match |
| 89 etag = self.get_etag() |
| 90 if ('If-None-Match' in self.headers and |
| 91 etag == self.headers['If-None-Match']): |
| 92 self.send_response(304) |
| 93 return None |
| 94 |
| 95 # Handle If-Modified-Since |
| 96 if ('If-None-Match' not in self.headers and |
| 97 'If-Modified-Since' in self.headers): |
| 98 last_modified = datetime.datetime.fromtimestamp( |
| 99 math.floor(os.stat(path).st_mtime), tz=UTC) |
| 100 ims = datetime.datetime( |
| 101 *email.utils.parsedate(self.headers['If-Modified-Since'])[:6], |
| 102 tzinfo=UTC) |
| 103 if last_modified <= ims: |
| 104 self.send_response(304) |
| 105 return None |
| 106 |
| 107 return SimpleHTTPServer.SimpleHTTPRequestHandler.send_head(self) |
| 108 |
| 109 def end_headers(self): |
| 110 path = self.translate_path(self.path) |
| 111 |
| 112 if os.path.isfile(path): |
| 113 etag = self.get_etag() |
| 114 if etag: |
| 115 self.send_header('ETag', etag) |
| 116 self.send_header('Cache-Control', 'must-revalidate') |
| 117 |
| 118 return SimpleHTTPServer.SimpleHTTPRequestHandler.end_headers(self) |
| 119 |
| 120 def translate_path(self, path): |
| 121 path_from_current = ( |
| 122 SimpleHTTPServer.SimpleHTTPRequestHandler.translate_path(self, path)) |
| 123 return os.path.join(base_path, os.path.relpath(path_from_current)) |
| 124 |
| 125 def log_message(self, *_): |
| 126 """ |
| 127 Override the base class method to disable logging. |
| 128 """ |
| 129 pass |
| 130 |
| 131 RequestHandler.protocol_version = 'HTTP/1.1' |
| 132 return RequestHandler |
| 133 |
| 134 |
| 135 def StartHttpServer(path): |
| 136 """Starts an http server serving files from |path| on random |
| 137 (system-allocated) port. Returns the server address.""" |
| 138 assert path |
| 139 httpd = _SilentTCPServer(('127.0.0.1', 0), _GetHandlerClassForPath(path)) |
| 140 atexit.register(httpd.shutdown) |
| 141 |
| 142 http_thread = threading.Thread(target=httpd.serve_forever) |
| 143 http_thread.daemon = True |
| 144 http_thread.start() |
| 145 return httpd.server_address |
| OLD | NEW |