diff --git a/README.rst b/README.rst index 66d8f2f..903590a 100644 --- a/README.rst +++ b/README.rst @@ -58,6 +58,20 @@ Serving compressed files If a gzip compressed file with the ´gz´ postfix is present, it is served, along with the corresponding headers. So if the file 'index.html' and the file 'index.html.gz' are present, the file 'index.html.gz' is served, if the the client indicated that it supports gzipped content. +Additionally, you can configure arbitrary headers. You can match files by mime +type, file extension, or prefix. For example, the following would add +Cache-Control headers to paths with a css mime type for 10s, no-cache for all +paths ending in .js for 100s, and add CORS header to all paths under the /imgs/ +dir:: + + headers = [ + {'type': 'text/css', 'Cache-Control': 'max-age=10'}, + {'ext': '.js', 'Cache-Control': 'no-cache'}, + {'prefix': '/imgs/', 'Access-Control-Allow-Origin': '*'}, + ] + Cling("/my/directory", headers=headers) + + Shock ^^^^^ diff --git a/static.py b/static.py index a593b8d..a053382 100755 --- a/static.py +++ b/static.py @@ -182,6 +182,7 @@ def __call__(self, environ, start_response): if prezipped: headers.extend([('Content-Encoding', 'gzip'), ('Vary', 'Accept-Encoding')]) + self._add_headers(headers, path_info, content_type) start_response("200 OK", headers) if environ['REQUEST_METHOD'] == 'GET': return self._body(full_path, environ, file_like) @@ -213,6 +214,18 @@ def _conditions(self, full_path, environ): mtime = stat(full_path).st_mtime return str(mtime), rfc822.formatdate(mtime) + def _add_headers(self, headers, path, content_type): + DEFAULT = '__static_no_match__' + CONFIG_ITEMS = ['prefix', 'type', 'ext'] + for config in getattr(self, 'headers', []): + if path.startswith(config.get('prefix', DEFAULT)) or \ + content_type == config.get('type', DEFAULT) or \ + path.endswith(config.get('ext', DEFAULT)): + + for key, value in config.items(): + if key not in CONFIG_ITEMS: + headers.append((key, value)) + def _file_like(self, full_path): """Return the appropriate file object.""" return open(full_path, 'rb') diff --git a/tests/test_app.py b/tests/test_app.py index 9242d75..f4aa52d 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -105,6 +105,35 @@ def test_gzip_cling(cling): assert response.headers['Vary'] == 'Accept-Encoding' +def test_headers_cling(): + from static import Cling + + def get(headers, url='/index.html'): + app = webtest.TestApp(Cling(root="testdata/pub", headers=headers)) + return app.get(url) + + response = get([{'type': 'text/html', 'Cache-Control': 'max-age=10'}]) + assert response.headers['Cache-Control'] == 'max-age=10' + + response = get( + [{'type': 'text/html', 'Cache-Control': 'max-age=10'}], '/test.xml') + assert 'Cache-Control' not in response.headers.keys() + + response = get([{'ext': 'html', 'Cache-Control': 'max-age=10'}]) + assert response.headers['Cache-Control'] == 'max-age=10' + + response = get( + [{'ext': 'html', 'Cache-Control': 'max-age=10'}], '/test.xml') + assert 'Cache-Control' not in response.headers.keys() + + response = get([{'prefix': '/index', 'Cache-Control': 'max-age=10'}]) + assert response.headers['Cache-Control'] == 'max-age=10' + + response = get( + [{'prefix': '/index', 'Cache-Control': 'max-age=10'}], '/test.xml') + assert 'Cache-Control' not in response.headers.keys() + + def test_static_shock(shock): response = shock.get("/index.html") assert "Mixed Content" in response