Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1202)

Unified Diff: mojo/devtools/common/pylib/http_server.py

Issue 1109493005: Extract http_server.py from android.py. (Closed) Base URL: git@github.com:domokit/mojo.git@master
Patch Set: Fix a typo. Created 5 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « mojo/devtools/common/pylib/android.py ('k') | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: mojo/devtools/common/pylib/http_server.py
diff --git a/mojo/devtools/common/pylib/http_server.py b/mojo/devtools/common/pylib/http_server.py
new file mode 100644
index 0000000000000000000000000000000000000000..a7296d3ed1a03e225c914819d0046c06135f40cb
--- /dev/null
+++ b/mojo/devtools/common/pylib/http_server.py
@@ -0,0 +1,145 @@
+# Copyright 2015 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 atexit
+import datetime
+import email.utils
+import hashlib
+import logging
+import math
+import os.path
+import threading
+
+import SimpleHTTPServer
+import SocketServer
+
+
+ZERO = datetime.timedelta(0)
+
+
+class UTC_TZINFO(datetime.tzinfo):
+ """UTC time zone representation."""
+
+ def utcoffset(self, _):
+ return ZERO
+
+ def tzname(self, _):
+ return "UTC"
+
+ def dst(self, _):
+ return ZERO
+
+UTC = UTC_TZINFO()
+
+
+class _SilentTCPServer(SocketServer.TCPServer):
+ """
+ A TCPServer that won't display any error, unless debugging is enabled. This is
+ useful because the client might stop while it is fetching an URL, which causes
+ spurious error messages.
+ """
+ def handle_error(self, request, client_address):
+ """
+ Override the base class method to have conditional logging.
+ """
+ if logging.getLogger().isEnabledFor(logging.DEBUG):
+ SocketServer.TCPServer.handle_error(self, request, client_address)
+
+
+def _GetHandlerClassForPath(base_path):
+ class RequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
+ """
+ Handler for SocketServer.TCPServer that will serve the files from
+ |base_path| directory over http.
+ """
+
+ def __init__(self, *args, **kwargs):
+ self.etag = None
+ SimpleHTTPServer.SimpleHTTPRequestHandler.__init__(self, *args, **kwargs)
+
+ def get_etag(self):
+ if self.etag:
+ return self.etag
+
+ path = self.translate_path(self.path)
+ if not os.path.isfile(path):
+ return None
+
+ sha256 = hashlib.sha256()
+ BLOCKSIZE = 65536
+ with open(path, 'rb') as hashed:
+ buf = hashed.read(BLOCKSIZE)
+ while len(buf) > 0:
+ sha256.update(buf)
+ buf = hashed.read(BLOCKSIZE)
+ self.etag = '"%s"' % sha256.hexdigest()
+ return self.etag
+
+ def send_head(self):
+ # Always close the connection after each request, as the keep alive
+ # support from SimpleHTTPServer doesn't like when the client requests to
+ # close the connection before downloading the full response content.
+ # pylint: disable=W0201
+ self.close_connection = 1
+
+ path = self.translate_path(self.path)
+ if os.path.isfile(path):
+ # Handle If-None-Match
+ etag = self.get_etag()
+ if ('If-None-Match' in self.headers and
+ etag == self.headers['If-None-Match']):
+ self.send_response(304)
+ return None
+
+ # Handle If-Modified-Since
+ if ('If-None-Match' not in self.headers and
+ 'If-Modified-Since' in self.headers):
+ last_modified = datetime.datetime.fromtimestamp(
+ math.floor(os.stat(path).st_mtime), tz=UTC)
+ ims = datetime.datetime(
+ *email.utils.parsedate(self.headers['If-Modified-Since'])[:6],
+ tzinfo=UTC)
+ if last_modified <= ims:
+ self.send_response(304)
+ return None
+
+ return SimpleHTTPServer.SimpleHTTPRequestHandler.send_head(self)
+
+ def end_headers(self):
+ path = self.translate_path(self.path)
+
+ if os.path.isfile(path):
+ etag = self.get_etag()
+ if etag:
+ self.send_header('ETag', etag)
+ self.send_header('Cache-Control', 'must-revalidate')
+
+ return SimpleHTTPServer.SimpleHTTPRequestHandler.end_headers(self)
+
+ def translate_path(self, path):
+ path_from_current = (
+ SimpleHTTPServer.SimpleHTTPRequestHandler.translate_path(self, path))
+ return os.path.join(base_path, os.path.relpath(path_from_current))
+
+ def log_message(self, *_):
+ """
+ Override the base class method to disable logging.
+ """
+ pass
+
+ RequestHandler.protocol_version = 'HTTP/1.1'
+ return RequestHandler
+
+
+def StartHttpServer(path):
+ """Starts an http server serving files from |path| on random
+ (system-allocated) port. Returns the server address."""
+ assert path
+ httpd = _SilentTCPServer(('127.0.0.1', 0), _GetHandlerClassForPath(path))
+ atexit.register(httpd.shutdown)
+
+ http_thread = threading.Thread(target=httpd.serve_forever)
+ http_thread.daemon = True
+ http_thread.start()
+ return httpd.server_address
« no previous file with comments | « mojo/devtools/common/pylib/android.py ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698