1
0
Fork 0
forked from s3lph/matemat

Merge branch '11-static-cache' into 'master'

Resolve "Caching of Static Resources"

Closes #11

See merge request s3lph/matemat!13
This commit is contained in:
s3lph 2018-07-14 21:08:26 +00:00
commit 8da1746d0c
3 changed files with 49 additions and 2 deletions

View file

@ -323,7 +323,7 @@ class HttpHandler(BaseHTTPRequestHandler):
# Send the HTTP status code # Send the HTTP status code
self.send_response(hsc) self.send_response(hsc)
# Format the session cookie timeout string and send the session cookie header # Format the session cookie timeout string and send the session cookie header
expires = timeout.strftime("%a, %d %b %Y %H:%M:%S GMT") expires = timeout.strftime('%a, %d %b %Y %H:%M:%S GMT')
self.send_header('Set-Cookie', self.send_header('Set-Cookie',
f'matemat_session_id={session_id}; expires={expires}') f'matemat_session_id={session_id}; expires={expires}')
# Compute the body length and add the appropriate header # Compute the body length and add the appropriate header
@ -345,6 +345,21 @@ class HttpHandler(BaseHTTPRequestHandler):
filepath: str = os.path.abspath(os.path.join(self.server.webroot, path[1:])) filepath: str = os.path.abspath(os.path.join(self.server.webroot, path[1:]))
# Make sure the file is actually inside the webroot directory and that it exists # Make sure the file is actually inside the webroot directory and that it exists
if os.path.commonpath([filepath, self.server.webroot]) == self.server.webroot and os.path.exists(filepath): if os.path.commonpath([filepath, self.server.webroot]) == self.server.webroot and os.path.exists(filepath):
# Parse the If-Modified-Since header to check whether the browser can reuse cached content
datestr: str = self.headers.get('If-Modified-Since', 'Thu, 01 Jan 1970 00:00:00 GMT')
maxage: datetime = datetime.strptime(datestr, '%a, %d %b %Y %H:%M:%S %Z')
# Get file modification time
filestat: int = int(os.path.getmtime(filepath))
# Create UTC datetime object from mtime
fileage: datetime = datetime.utcfromtimestamp(filestat)
# If the file has not been replaced by a newer version than requested by the client, send a 304 response
if fileage <= maxage:
self.send_response(304, 'Not Modified')
self.send_header('Content-Length', '0')
self.end_headers()
return
# Open and read the file # Open and read the file
with open(filepath, 'rb') as f: with open(filepath, 'rb') as f:
data = f.read() data = f.read()
@ -358,6 +373,8 @@ class HttpHandler(BaseHTTPRequestHandler):
# Send content type and length header # Send content type and length header
self.send_header('Content-Type', mimetype) self.send_header('Content-Type', mimetype)
self.send_header('Content-Length', str(len(data))) self.send_header('Content-Length', str(len(data)))
self.send_header('Last-Modified', fileage.strftime('%a, %d %b %Y %H:%M:%S GMT'))
self.send_header('Cache-Control', 'max-age=1')
self.end_headers() self.end_headers()
# Send the requested resource as response body # Send the requested resource as response body
self.wfile.write(data) self.wfile.write(data)

View file

@ -118,7 +118,7 @@ class MockServer:
) )
# Set up logger # Set up logger
self.logger: logging.Logger = logging.getLogger('matemat unit test') self.logger: logging.Logger = logging.getLogger('matemat unit test')
self.logger.setLevel(0) self.logger.setLevel(logging.DEBUG)
# Initalize a log handler to stderr and set the log format # Initalize a log handler to stderr and set the log format
sh: logging.StreamHandler = logging.StreamHandler() sh: logging.StreamHandler = logging.StreamHandler()
sh.setFormatter(logging.Formatter('%(asctime)s %(name)s [%(levelname)s]: %(message)s')) sh.setFormatter(logging.Formatter('%(asctime)s %(name)s [%(levelname)s]: %(message)s'))

View file

@ -3,6 +3,8 @@ from typing import Any, Dict, Union
import os import os
import os.path import os.path
from datetime import datetime, timedelta
from matemat.exceptions import HttpException from matemat.exceptions import HttpException
from matemat.webserver import HttpHandler, RequestArguments, PageletResponse, RedirectResponse, TemplateResponse from matemat.webserver import HttpHandler, RequestArguments, PageletResponse, RedirectResponse, TemplateResponse
from matemat.webserver.test.abstract_httpd_test import AbstractHttpdTest, test_pagelet from matemat.webserver.test.abstract_httpd_test import AbstractHttpdTest, test_pagelet
@ -163,6 +165,34 @@ class TestServe(AbstractHttpdTest):
self.assertEqual(403, packet.statuscode) self.assertEqual(403, packet.statuscode)
self.assertNotEqual(b'This should not be readable', packet.body) self.assertNotEqual(b'This should not be readable', packet.body)
def test_serve_static_cache(self):
# Request a static resource
timeout: datetime = datetime.utcnow() + timedelta(hours=1)
timeoutstr = timeout.strftime('%a, %d %b %Y %H:%M:%S GMT')
self.client_sock.set_request(
f'GET /static_resource.txt HTTP/1.1\r\nIf-Modified-Since: {timeoutstr}\r\n\r\n'.encode('utf-8'))
HttpHandler(self.client_sock, ('::1', 45678), self.server)
packet = self.client_sock.get_response()
# Make sure that no pagelet was called
self.assertIsNone(packet.pagelet)
# Make sure a 304 Not Modified is sent and the body is empty
self.assertEqual(304, packet.statuscode)
self.assertEqual(0, len(packet.body))
def test_serve_static_cache_renew(self):
# Request a static resource
self.client_sock.set_request(
b'GET /static_resource.txt HTTP/1.1\r\nIf-Modified-Since: Mon, 01 Jan 2018 13:37:42 GMT\r\n\r\n')
HttpHandler(self.client_sock, ('::1', 45678), self.server)
packet = self.client_sock.get_response()
# Make sure that no pagelet was called
self.assertIsNone(packet.pagelet)
# Make sure the expected content is served
self.assertEqual(200, packet.statuscode)
self.assertEqual(b'static resource test', packet.body)
def test_serve_not_found(self): def test_serve_not_found(self):
# Request a nonexistent resource # Request a nonexistent resource
self.client_sock.set_request(b'GET /nonexistent HTTP/1.1\r\n\r\n') self.client_sock.set_request(b'GET /nonexistent HTTP/1.1\r\n\r\n')