Merge branch '22-configurable-static-headers' into 'staging'

Resolve "Configurable static headers"

See merge request s3lph/matemat!29
This commit is contained in:
s3lph 2018-08-03 17:02:05 +00:00
commit 36176610c0
6 changed files with 68 additions and 13 deletions

2
doc

@ -1 +1 @@
Subproject commit e52eec0831ef72edab816d549d7ee2a85575e292
Subproject commit 1a181d3c72cf20fe21e93b063dc3c395e1fa8f93

View file

@ -67,7 +67,9 @@ def parse_config_file(paths: Union[str, Iterable[str]]) -> Dict[str, Any]:
# Log target: An IO stream (stderr, stdout, ...) or a filename
'log_handler': logging.StreamHandler(),
# Variables passed to pagelets
'pagelet_variables': dict()
'pagelet_variables': dict(),
# Statically configured headers
'headers': dict()
}
# Initialize the config parser
@ -101,4 +103,9 @@ def parse_config_file(paths: Union[str, Iterable[str]]) -> Dict[str, Any]:
for k, v in parser['Pagelets'].items():
config['pagelet_variables'][k] = v
# Read all values from the [HttpHeaders] section, if present. These values are set as HTTP response headers
if 'HttpHeaders' in parser.sections():
for k, v in parser['HttpHeaders'].items():
config['headers'][k] = v
return config

View file

@ -105,6 +105,7 @@ class MatematHTTPServer(HTTPServer):
staticroot: str,
templateroot: str,
pagelet_variables: Dict[str, str],
static_headers: Dict[str, str],
log_level: int,
log_handler: logging.Handler,
bind_and_activate: bool = True) -> None:
@ -115,6 +116,8 @@ class MatematHTTPServer(HTTPServer):
self.session_vars: Dict[str, Tuple[datetime, Dict[str, Any]]] = dict()
# Set up pagelet arguments dict
self.pagelet_variables = pagelet_variables
# Set up static HTTP Response headers
self.static_headers = static_headers
# Set up the Jinja2 environment
self.jinja_env: jinja2.Environment = jinja2.Environment(
loader=jinja2.FileSystemLoader(os.path.abspath(templateroot)),
@ -150,6 +153,7 @@ class MatematWebserver(object):
staticroot: str,
templateroot: str,
pagelet_variables: Dict[str, str],
headers: Dict[str, str],
log_level: int,
log_handler: logging.Handler) -> None:
"""
@ -160,6 +164,7 @@ class MatematWebserver(object):
:param staticroot: Path to the static webroot directory.
:param templateroot: Path to the Jinja2 templates root directory.
:param pagelet_variables: Dictionary of variables to pass to pagelet functions.
:param headers: Dictionary of statically configured headers.
:param log_level: The log level, as defined in the builtin logging module.
:param log_handler: The logging handler.
"""
@ -173,6 +178,7 @@ class MatematWebserver(object):
staticroot,
templateroot,
pagelet_variables,
headers,
log_level,
log_handler)
@ -308,12 +314,13 @@ class HttpHandler(BaseHTTPRequestHandler):
return
self.session_id: str = session_id
# Set all static headers. These can still be overwritten by a pagelet
headers: Dict[str, str] = dict()
for k, v in self.server.static_headers.items():
headers[k] = v
# Call a pagelet function, if one is registered for the requested path
if path in _PAGELET_PATHS:
# Prepare some headers. Those can still be overwritten by the pagelet
headers: Dict[str, str] = {
'Cache-Control': 'no-cache'
}
# Call the pagelet function
pagelet_res = _PAGELET_PATHS[path](method,
path,
@ -327,8 +334,9 @@ class HttpHandler(BaseHTTPRequestHandler):
self.send_response(hsc)
# Format the session cookie timeout string and send the session cookie header
expires = timeout.strftime('%a, %d %b %Y %H:%M:%S GMT')
self.send_header('Set-Cookie',
f'matemat_session_id={session_id}; expires={expires}')
headers['Set-Cookie'] = f'matemat_session_id={session_id}; expires={expires}'
# Disable caching
headers['Cache-Control'] = 'no-cache'
# Compute the body length and add the appropriate header
headers['Content-Length'] = str(len(data))
# If the pagelet did not set its own Content-Type header, use libmagic to guess an appropriate one
@ -394,12 +402,15 @@ class HttpHandler(BaseHTTPRequestHandler):
charset = 'binary'
# Send content type and length header. Only set the charset if it's not "binary"
if charset == 'binary':
self.send_header('Content-Type', mimetype)
headers['Content-Type'] = mimetype
else:
self.send_header('Content-Type', f'{mimetype}; charset={charset}')
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')
headers['Content-Type'] = f'{mimetype}; charset={charset}'
headers['Content-Length'] = str(len(data))
headers['Last-Modified'] = fileage.strftime('%a, %d %b %Y %H:%M:%S GMT')
headers['Cache-Control'] = 'max-age=1'
# Send all headers
for name, value in headers.items():
self.send_header(name, value)
self.end_headers()
# Send the requested resource as response body
self.wfile.write(data)

View file

@ -112,6 +112,10 @@ class MockServer:
self.webroot: str = webroot
# Variables to pass to pagelets
self.pagelet_variables: Dict[str, str] = dict()
# Static response headers
self.static_headers: Dict[str, str] = {
'X-Static-Testing-Header': 'helloworld!'
}
# Jinja environment with a single, static template
self.jinja_env = jinja2.Environment(
loader=jinja2.DictLoader({'test.txt': 'Hello, {{ what }}!'})

View file

@ -29,6 +29,10 @@ Name=Matemat
(Unit Test)
UploadDir= /var/test/static/upload
DatabaseFile=/var/test/db/test.db
[HttpHeaders]
Content-Security-Policy = default-src: 'self';
X-I-Am-A-Header = andthisismyvalue
'''
_PARTIAL_CONFIG = '''
@ -37,6 +41,9 @@ Port=443
[Pagelets]
Name=Matemat (Unit Test 2)
[HttpHeaders]
X-I-Am-A-Header = andthisismyothervalue
'''
_LOG_NONE_CONFIG = '''
@ -113,6 +120,8 @@ class TestConfig(TestCase):
self.assertEqual(sys.stderr, config['log_handler'].stream)
self.assertIsInstance(config['pagelet_variables'], dict)
self.assertEqual(0, len(config['pagelet_variables']))
self.assertIsInstance(config['headers'], dict)
self.assertEqual(0, len(config['headers']))
def test_parse_config_full(self):
"""
@ -144,6 +153,10 @@ class TestConfig(TestCase):
self.assertEqual('Matemat\n(Unit Test)', config['pagelet_variables']['Name'])
self.assertEqual('/var/test/static/upload', config['pagelet_variables']['UploadDir'])
self.assertEqual('/var/test/db/test.db', config['pagelet_variables']['DatabaseFile'])
self.assertIsInstance(config['headers'], dict)
self.assertEqual(2, len(config['headers']))
self.assertEqual('default-src: \'self\';', config['headers']['Content-Security-Policy'])
self.assertEqual('andthisismyvalue', config['headers']['X-I-Am-A-Header'])
def test_parse_config_precedence(self):
"""
@ -172,6 +185,10 @@ class TestConfig(TestCase):
self.assertEqual('Matemat (Unit Test 2)', config['pagelet_variables']['Name'])
self.assertEqual('/var/test/static/upload', config['pagelet_variables']['UploadDir'])
self.assertEqual('/var/test/db/test.db', config['pagelet_variables']['DatabaseFile'])
self.assertIsInstance(config['headers'], dict)
self.assertEqual(2, len(config['headers']))
self.assertEqual('default-src: \'self\';', config['headers']['Content-Security-Policy'])
self.assertEqual('andthisismyothervalue', config['headers']['X-I-Am-A-Header'])
def test_parse_config_logging_none(self):
"""

View file

@ -272,3 +272,19 @@ class TestServe(AbstractHttpdTest):
packet = self.client_sock.get_response()
# No charset should be in the header. Yes, this is a stupid example
self.assertEqual('text/plain', packet.headers['Content-Type'])
def test_serve_static_pagelet_header(self):
# Send a request
self.client_sock.set_request(b'GET /just/testing/serve_pagelet_str HTTP/1.1\r\n\r\n')
HttpHandler(self.client_sock, ('::1', 45678), self.server)
packet = self.client_sock.get_response()
# The statically set header must be present
self.assertEqual('helloworld!', packet.headers['X-Static-Testing-Header'])
def test_serve_static_static_header(self):
# Send a request
self.client_sock.set_request(b'GET /testbin.txt HTTP/1.1\r\n\r\n')
HttpHandler(self.client_sock, ('::1', 45678), self.server)
packet = self.client_sock.get_response()
# The statically set header must be present
self.assertEqual('helloworld!', packet.headers['X-Static-Testing-Header'])