1
0
Fork 0
forked from s3lph/matemat

Implemented configurable static response headers.

This commit is contained in:
s3lph 2018-08-03 18:42:30 +02:00
parent 5b771312d1
commit 30be649ae2
5 changed files with 67 additions and 12 deletions

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 target: An IO stream (stderr, stdout, ...) or a filename
'log_handler': logging.StreamHandler(), 'log_handler': logging.StreamHandler(),
# Variables passed to pagelets # Variables passed to pagelets
'pagelet_variables': dict() 'pagelet_variables': dict(),
# Statically configured headers
'headers': dict()
} }
# Initialize the config parser # 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(): for k, v in parser['Pagelets'].items():
config['pagelet_variables'][k] = v 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 return config

View file

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

@ -112,6 +112,10 @@ class MockServer:
self.webroot: str = webroot self.webroot: str = webroot
# Variables to pass to pagelets # Variables to pass to pagelets
self.pagelet_variables: Dict[str, str] = dict() 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 # Jinja environment with a single, static template
self.jinja_env = jinja2.Environment( self.jinja_env = jinja2.Environment(
loader=jinja2.DictLoader({'test.txt': 'Hello, {{ what }}!'}) loader=jinja2.DictLoader({'test.txt': 'Hello, {{ what }}!'})

View file

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

View file

@ -272,3 +272,19 @@ class TestServe(AbstractHttpdTest):
packet = self.client_sock.get_response() packet = self.client_sock.get_response()
# No charset should be in the header. Yes, this is a stupid example # No charset should be in the header. Yes, this is a stupid example
self.assertEqual('text/plain', packet.headers['Content-Type']) 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'])