servo/tests/httpserver.py
Josh Matthews f674cba612 Beginnings of a http cache
Doom cache entries based on the initial response, and prevent matching against doomed cache enties.

Evict cache entries that have passed their expiry date instead of matching them.

Document the cache. Refactor incomplete entries to lessen Option-itis.

Revalidate expired cache entries instead of unconditionally evicting them.

Forbid missing docs in cache code.

Revalidate must-revalidate entries.

Fetch content tests from a local HTTP server.

Track requests made to the test HTTP server.

Add a simple test that a cached resource with no expiry is not revalidated. Correct inverted expiry check in revalidation code.

Fix incorrect revalidation logic that dropped the consumer channels on the floor.

Ensure that requests are cached based on their request headers.

Run a separate http server instance for each test to avoid intermittent failures due to concurrent cache tests.

Add a test for uncacheable responses.

Address review comments.
2017-11-14 17:20:39 +08:00

115 lines
3.5 KiB
Python
Vendored

from SimpleHTTPServer import SimpleHTTPRequestHandler
import SocketServer
import os
import sys
from collections import defaultdict
PORT = int(sys.argv[1]) if len(sys.argv) > 1 else 0
requests = defaultdict(int)
class CountingRequestHandler(SimpleHTTPRequestHandler):
def __init__(self, req, client_addr, server):
SimpleHTTPRequestHandler.__init__(self, req, client_addr, server)
def do_POST(self):
global requests
parts = self.path.split('/')
if parts[1] == 'reset':
requests = defaultdict(int)
self.send_response(200)
self.send_header('Content-Type', 'text/plain')
self.send_header('Content-Length', 0)
self.end_headers()
self.wfile.write('')
return
def do_GET(self):
global requests
parts = self.path.split('?')
if parts[0] == '/stats':
self.send_response(200)
self.send_header('Content-Type', 'text/plain')
if len(parts) > 1:
body = str(requests['/' + parts[1]])
else:
body = ''
for key, value in requests.iteritems():
body += key + ': ' + str(value) + '\n'
self.send_header('Content-Length', len(body))
self.end_headers()
self.wfile.write(body)
return
header_list = []
status = None
path = self.translate_path(self.path)
headers = path + '^headers'
if os.path.isfile(headers):
try:
h = open(headers, 'rb')
except IOError:
self.send_error(404, "Header file not found")
return
header_lines = h.readlines()
status = int(header_lines[0])
for header in header_lines[1:]:
parts = map(lambda x: x.strip(), header.split(':'))
header_list += [parts]
if self.headers.get('If-Modified-Since'):
self.send_response(304)
self.end_headers()
return
if not status or status == 200:
requests[self.path] += 1
if status or header_list:
ctype = self.guess_type(path)
try:
# Always read in binary mode. Opening files in text mode may cause
# newline translations, making the actual size of the content
# transmitted *less* than the content-length!
f = open(path, 'rb')
except IOError:
self.send_error(404, "File not found")
return
try:
self.send_response(status or 200)
self.send_header("Content-type", ctype)
fs = os.fstat(f.fileno())
self.send_header("Content-Length", str(fs[6]))
self.send_header("Last-Modified", self.date_time_string(fs.st_mtime))
for header in header_list:
self.send_header(header[0], header[1])
self.end_headers()
try:
self.copyfile(f, self.wfile)
finally:
f.close()
except:
f.close()
raise
else:
SimpleHTTPRequestHandler.do_GET(self)
class MyTCPServer(SocketServer.TCPServer):
request_queue_size = 2000
allow_reuse_address = True
httpd = MyTCPServer(("", PORT), CountingRequestHandler)
if not PORT:
ip, PORT = httpd.server_address
print "serving at port", PORT
sys.stdout.flush()
httpd.serve_forever()