Add content_type flag to Sanic.static (#1267)

* Add content_type flag to Sanic.static

Fixes #1266

* Fix flake8 error in travis

Add line to document `content_type` arg

* Fix content_type for file streams

Update tests

herp derp

* Remove content_type as an arg to HTTPResponse

`response.HTTPResponse` will default to `headers['Content-Type']` instead of `content_type`
https://github.com/channelcat/sanic/pull/1267#discussion_r204190913
This commit is contained in:
Cosmo Borsky 2018-07-21 01:31:15 -04:00 committed by Raphael Deem
parent 377c9890a3
commit b238be54a4
5 changed files with 89 additions and 15 deletions

View File

@ -386,13 +386,14 @@ class Sanic:
def static(self, uri, file_or_directory, pattern=r'/?.+', def static(self, uri, file_or_directory, pattern=r'/?.+',
use_modified_since=True, use_content_range=False, use_modified_since=True, use_content_range=False,
stream_large_files=False, name='static', host=None, stream_large_files=False, name='static', host=None,
strict_slashes=None): strict_slashes=None, content_type=None):
"""Register a root to serve files from. The input can either be a """Register a root to serve files from. The input can either be a
file or a directory. See file or a directory. See
""" """
static_register(self, uri, file_or_directory, pattern, static_register(self, uri, file_or_directory, pattern,
use_modified_since, use_content_range, use_modified_since, use_content_range,
stream_large_files, name, host, strict_slashes) stream_large_files, name, host, strict_slashes,
content_type)
def blueprint(self, blueprint, **options): def blueprint(self, blueprint, **options):
"""Register a blueprint on the application. """Register a blueprint on the application.

View File

@ -19,7 +19,7 @@ from sanic.response import file, file_stream, HTTPResponse
def register(app, uri, file_or_directory, pattern, def register(app, uri, file_or_directory, pattern,
use_modified_since, use_content_range, use_modified_since, use_content_range,
stream_large_files, name='static', host=None, stream_large_files, name='static', host=None,
strict_slashes=None): strict_slashes=None, content_type=None):
# TODO: Though sanic is not a file server, I feel like we should at least # TODO: Though sanic is not a file server, I feel like we should at least
# make a good effort here. Modified-since is nice, but we could # make a good effort here. Modified-since is nice, but we could
# also look into etags, expires, and caching # also look into etags, expires, and caching
@ -41,6 +41,7 @@ def register(app, uri, file_or_directory, pattern,
If this is an integer, this represents the If this is an integer, this represents the
threshold size to switch to file_stream() threshold size to switch to file_stream()
:param name: user defined name used for url_for :param name: user defined name used for url_for
:param content_type: user defined content type for header
""" """
# If we're not trying to match a file directly, # If we're not trying to match a file directly,
# serve from the folder # serve from the folder
@ -95,10 +96,10 @@ def register(app, uri, file_or_directory, pattern,
del headers['Content-Length'] del headers['Content-Length']
for key, value in _range.headers.items(): for key, value in _range.headers.items():
headers[key] = value headers[key] = value
headers['Content-Type'] = content_type \
or guess_type(file_path)[0] or 'text/plain'
if request.method == 'HEAD': if request.method == 'HEAD':
return HTTPResponse( return HTTPResponse(headers=headers)
headers=headers,
content_type=guess_type(file_path)[0] or 'text/plain')
else: else:
if stream_large_files: if stream_large_files:
if isinstance(stream_large_files, int): if isinstance(stream_large_files, int):

26
tests/static/test.html Normal file
View File

@ -0,0 +1,26 @@
<html>
<body>
<pre>
▄▄▄▄▄
▀▀▀██████▄▄▄ _______________
▄▄▄▄▄ █████████▄ / \
▀▀▀▀█████▌ ▀▐▄ ▀▐█ | Gotta go fast! |
▀▀█████▄▄ ▀██████▄██ | _________________/
▀▄▄▄▄▄ ▀▀█▄▀█════█▀ |/
▀▀▀▄ ▀▀███ ▀ ▄▄
▄███▀▀██▄████████▄ ▄▀▀▀▀▀▀█▌
██▀▄▄▄██▀▄███▀ ▀▀████ ▄██
▄▀▀▀▄██▄▀▀▌████▒▒▒▒▒▒███ ▌▄▄▀
▌ ▐▀████▐███▒▒▒▒▒▐██▌
▀▄▄▄▄▀ ▀▀████▒▒▒▒▄██▀
▀▀█████████▀
▄▄██▀██████▀█
▄██▀ ▀▀▀ █
▄█ ▐▌
▄▄▄▄█▌ ▀█▄▄▄▄▀▀▄
▌ ▐ ▀▀▄▄▄▀
▀▀▄▄▀
</pre>
</body>
</html>

View File

@ -1,5 +1,6 @@
import asyncio import asyncio
import inspect import inspect
import os
import pytest import pytest
from sanic import Sanic from sanic import Sanic
@ -13,6 +14,14 @@ from sanic.constants import HTTP_METHODS
# GET # GET
# ------------------------------------------------------------ # # ------------------------------------------------------------ #
def get_file_path(static_file_directory, file_name):
return os.path.join(static_file_directory, file_name)
def get_file_content(static_file_directory, file_name):
"""The content of the static file to check"""
with open(get_file_path(static_file_directory, file_name), 'rb') as file:
return file.read()
@pytest.mark.parametrize('method', HTTP_METHODS) @pytest.mark.parametrize('method', HTTP_METHODS)
def test_versioned_routes_get(method): def test_versioned_routes_get(method):
app = Sanic('test_shorhand_routes_get') app = Sanic('test_shorhand_routes_get')
@ -348,6 +357,28 @@ def test_bp_static():
assert response.status == 200 assert response.status == 200
assert response.body == current_file_contents assert response.body == current_file_contents
@pytest.mark.parametrize('file_name', ['test.html'])
def test_bp_static_content_type(file_name):
# This is done here, since no other test loads a file here
current_file = inspect.getfile(inspect.currentframe())
current_directory = os.path.dirname(os.path.abspath(current_file))
static_directory = os.path.join(current_directory, 'static')
app = Sanic('test_static')
blueprint = Blueprint('test_static')
blueprint.static(
'/testing.file',
get_file_path(static_directory, file_name),
content_type='text/html; charset=utf-8'
)
app.blueprint(blueprint)
request, response = app.test_client.get('/testing.file')
assert response.status == 200
assert response.body == get_file_content(static_directory, file_name)
assert response.headers['Content-Type'] == 'text/html; charset=utf-8'
def test_bp_shorthand(): def test_bp_shorthand():
app = Sanic('test_shorhand_routes') app = Sanic('test_shorhand_routes')
blueprint = Blueprint('test_shorhand_routes') blueprint = Blueprint('test_shorhand_routes')
@ -449,41 +480,41 @@ def test_bp_shorthand():
def test_bp_group(): def test_bp_group():
app = Sanic('test_nested_bp_groups') app = Sanic('test_nested_bp_groups')
deep_0 = Blueprint('deep_0', url_prefix='/deep') deep_0 = Blueprint('deep_0', url_prefix='/deep')
deep_1 = Blueprint('deep_1', url_prefix = '/deep1') deep_1 = Blueprint('deep_1', url_prefix = '/deep1')
@deep_0.route('/') @deep_0.route('/')
def handler(request): def handler(request):
return text('D0_OK') return text('D0_OK')
@deep_1.route('/bottom') @deep_1.route('/bottom')
def handler(request): def handler(request):
return text('D1B_OK') return text('D1B_OK')
mid_0 = Blueprint.group(deep_0, deep_1, url_prefix='/mid') mid_0 = Blueprint.group(deep_0, deep_1, url_prefix='/mid')
mid_1 = Blueprint('mid_tier', url_prefix='/mid1') mid_1 = Blueprint('mid_tier', url_prefix='/mid1')
@mid_1.route('/') @mid_1.route('/')
def handler(request): def handler(request):
return text('M1_OK') return text('M1_OK')
top = Blueprint.group(mid_0, mid_1) top = Blueprint.group(mid_0, mid_1)
app.blueprint(top) app.blueprint(top)
@app.route('/') @app.route('/')
def handler(request): def handler(request):
return text('TOP_OK') return text('TOP_OK')
request, response = app.test_client.get('/') request, response = app.test_client.get('/')
assert response.text == 'TOP_OK' assert response.text == 'TOP_OK'
request, response = app.test_client.get('/mid1') request, response = app.test_client.get('/mid1')
assert response.text == 'M1_OK' assert response.text == 'M1_OK'
request, response = app.test_client.get('/mid/deep') request, response = app.test_client.get('/mid/deep')
assert response.text == 'D0_OK' assert response.text == 'D0_OK'
request, response = app.test_client.get('/mid/deep1/bottom') request, response = app.test_client.get('/mid/deep1/bottom')
assert response.text == 'D1B_OK' assert response.text == 'D1B_OK'

View File

@ -36,6 +36,21 @@ def test_static_file(static_file_directory, file_name):
assert response.body == get_file_content(static_file_directory, file_name) assert response.body == get_file_content(static_file_directory, file_name)
@pytest.mark.parametrize('file_name', ['test.html'])
def test_static_file_content_type(static_file_directory, file_name):
app = Sanic('test_static')
app.static(
'/testing.file',
get_file_path(static_file_directory, file_name),
content_type='text/html; charset=utf-8'
)
request, response = app.test_client.get('/testing.file')
assert response.status == 200
assert response.body == get_file_content(static_file_directory, file_name)
assert response.headers['Content-Type'] == 'text/html; charset=utf-8'
@pytest.mark.parametrize('file_name', ['test.file', 'decode me.txt']) @pytest.mark.parametrize('file_name', ['test.file', 'decode me.txt'])
@pytest.mark.parametrize('base_uri', ['/static', '', '/dir']) @pytest.mark.parametrize('base_uri', ['/static', '', '/dir'])
def test_static_directory(file_name, base_uri, static_file_directory): def test_static_directory(file_name, base_uri, static_file_directory):