Fix static _handler pickling error.
Moves the subfunction _handler out to a module-level function, and parameterizes it with functools.partial(). Fixes the case when picking a sanic app which has a registered static route handler. This is usually encountered when attempting to use multiprocessing or auto_reload on OSX or Windows. Fixes #1774
This commit is contained in:
parent
ae1874ce34
commit
aacbd022cf
|
@ -1,3 +1,4 @@
|
||||||
|
from functools import partial, wraps
|
||||||
from mimetypes import guess_type
|
from mimetypes import guess_type
|
||||||
from os import path
|
from os import path
|
||||||
from re import sub
|
from re import sub
|
||||||
|
@ -15,48 +16,15 @@ from sanic.handlers import ContentRangeHandler
|
||||||
from sanic.response import HTTPResponse, file, file_stream
|
from sanic.response import HTTPResponse, file, file_stream
|
||||||
|
|
||||||
|
|
||||||
def register(
|
async def _static_request_handler(
|
||||||
app,
|
|
||||||
uri,
|
|
||||||
file_or_directory,
|
file_or_directory,
|
||||||
pattern,
|
|
||||||
use_modified_since,
|
use_modified_since,
|
||||||
use_content_range,
|
use_content_range,
|
||||||
stream_large_files,
|
stream_large_files,
|
||||||
name="static",
|
request,
|
||||||
host=None,
|
|
||||||
strict_slashes=None,
|
|
||||||
content_type=None,
|
content_type=None,
|
||||||
|
file_uri=None,
|
||||||
):
|
):
|
||||||
# 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
|
|
||||||
# also look into etags, expires, and caching
|
|
||||||
"""
|
|
||||||
Register a static directory handler with Sanic by adding a route to the
|
|
||||||
router and registering a handler.
|
|
||||||
|
|
||||||
:param app: Sanic
|
|
||||||
:param file_or_directory: File or directory path to serve from
|
|
||||||
:param uri: URL to serve from
|
|
||||||
:param pattern: regular expression used to match files in the URL
|
|
||||||
:param use_modified_since: If true, send file modified time, and return
|
|
||||||
not modified if the browser's matches the
|
|
||||||
server's
|
|
||||||
:param use_content_range: If true, process header for range requests
|
|
||||||
and sends the file part that is requested
|
|
||||||
:param stream_large_files: If true, use the file_stream() handler rather
|
|
||||||
than the file() handler to send the file
|
|
||||||
If this is an integer, this represents the
|
|
||||||
threshold size to switch to file_stream()
|
|
||||||
: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,
|
|
||||||
# serve from the folder
|
|
||||||
if not path.isfile(file_or_directory):
|
|
||||||
uri += "<file_uri:" + pattern + ">"
|
|
||||||
|
|
||||||
async def _handler(request, file_uri=None):
|
|
||||||
# Using this to determine if the URL is trying to break out of the path
|
# Using this to determine if the URL is trying to break out of the path
|
||||||
# served. os.path.realpath seems to be very slow
|
# served. os.path.realpath seems to be very slow
|
||||||
if file_uri and "../" in file_uri:
|
if file_uri and "../" in file_uri:
|
||||||
|
@ -66,9 +34,7 @@ def register(
|
||||||
# from herping a derp and treating the uri as an absolute path
|
# from herping a derp and treating the uri as an absolute path
|
||||||
root_path = file_path = file_or_directory
|
root_path = file_path = file_or_directory
|
||||||
if file_uri:
|
if file_uri:
|
||||||
file_path = path.join(
|
file_path = path.join(file_or_directory, sub("^[/]*", "", file_uri))
|
||||||
file_or_directory, sub("^[/]*", "", file_uri)
|
|
||||||
)
|
|
||||||
|
|
||||||
# URL decode the path sent by the browser otherwise we won't be able to
|
# URL decode the path sent by the browser otherwise we won't be able to
|
||||||
# match filenames which got encoded (filenames with spaces etc)
|
# match filenames which got encoded (filenames with spaces etc)
|
||||||
|
@ -132,10 +98,63 @@ def register(
|
||||||
"File not found", path=file_or_directory, relative_url=file_uri
|
"File not found", path=file_or_directory, relative_url=file_uri
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def register(
|
||||||
|
app,
|
||||||
|
uri,
|
||||||
|
file_or_directory,
|
||||||
|
pattern,
|
||||||
|
use_modified_since,
|
||||||
|
use_content_range,
|
||||||
|
stream_large_files,
|
||||||
|
name="static",
|
||||||
|
host=None,
|
||||||
|
strict_slashes=None,
|
||||||
|
content_type=None,
|
||||||
|
):
|
||||||
|
# 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
|
||||||
|
# also look into etags, expires, and caching
|
||||||
|
"""
|
||||||
|
Register a static directory handler with Sanic by adding a route to the
|
||||||
|
router and registering a handler.
|
||||||
|
|
||||||
|
:param app: Sanic
|
||||||
|
:param file_or_directory: File or directory path to serve from
|
||||||
|
:param uri: URL to serve from
|
||||||
|
:param pattern: regular expression used to match files in the URL
|
||||||
|
:param use_modified_since: If true, send file modified time, and return
|
||||||
|
not modified if the browser's matches the
|
||||||
|
server's
|
||||||
|
:param use_content_range: If true, process header for range requests
|
||||||
|
and sends the file part that is requested
|
||||||
|
:param stream_large_files: If true, use the file_stream() handler rather
|
||||||
|
than the file() handler to send the file
|
||||||
|
If this is an integer, this represents the
|
||||||
|
threshold size to switch to file_stream()
|
||||||
|
: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,
|
||||||
|
# serve from the folder
|
||||||
|
if not path.isfile(file_or_directory):
|
||||||
|
uri += "<file_uri:" + pattern + ">"
|
||||||
|
|
||||||
# special prefix for static files
|
# special prefix for static files
|
||||||
if not name.startswith("_static_"):
|
if not name.startswith("_static_"):
|
||||||
name = f"_static_{name}"
|
name = f"_static_{name}"
|
||||||
|
|
||||||
|
_handler = wraps(_static_request_handler)(
|
||||||
|
partial(
|
||||||
|
_static_request_handler,
|
||||||
|
file_or_directory,
|
||||||
|
use_modified_since,
|
||||||
|
use_content_range,
|
||||||
|
stream_large_files,
|
||||||
|
content_type=content_type,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
app.route(
|
app.route(
|
||||||
uri,
|
uri,
|
||||||
methods=["GET", "HEAD"],
|
methods=["GET", "HEAD"],
|
||||||
|
|
|
@ -87,3 +87,14 @@ def test_pickle_app_with_bp(app, protocol):
|
||||||
request, response = up_p_app.test_client.get("/")
|
request, response = up_p_app.test_client.get("/")
|
||||||
assert up_p_app.is_request_stream is False
|
assert up_p_app.is_request_stream is False
|
||||||
assert response.text == "Hello"
|
assert response.text == "Hello"
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("protocol", [3, 4])
|
||||||
|
def test_pickle_app_with_static(app, protocol):
|
||||||
|
app.route("/")(handler)
|
||||||
|
app.static('/static', "/tmp/static")
|
||||||
|
p_app = pickle.dumps(app, protocol=protocol)
|
||||||
|
del app
|
||||||
|
up_p_app = pickle.loads(p_app)
|
||||||
|
assert up_p_app
|
||||||
|
request, response = up_p_app.test_client.get("/static/missing.txt")
|
||||||
|
assert response.status == 404
|
||||||
|
|
Loading…
Reference in New Issue
Block a user