app: prevent static file reads from blocking the event loop
Perform reads in a thread so File I/O does not block. This patch also disables ETags for static files. Tornado's default behavior of caching file hashes will not work as many of Moonraker's can be updated. The previous workaround to this was to recalculate the checksum if the modified date changed. This is inefficient as its behavior is not much different than using "If-Modified-Since". Signed-off-by: Eric Callahan <arksine.code@gmail.com>
This commit is contained in:
parent
3529acfeec
commit
00f4bd594f
|
@ -9,7 +9,6 @@ import os
|
|||
import mimetypes
|
||||
import logging
|
||||
import json
|
||||
import datetime
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
import traceback
|
||||
import ssl
|
||||
|
@ -39,6 +38,7 @@ from typing import (
|
|||
Union,
|
||||
Dict,
|
||||
List,
|
||||
AsyncGenerator,
|
||||
)
|
||||
if TYPE_CHECKING:
|
||||
from tornado.httpserver import HTTPServer
|
||||
|
@ -628,6 +628,7 @@ class FileRequestHandler(AuthorizedFileHandler):
|
|||
self.modified = self.get_modified_time()
|
||||
self.set_headers()
|
||||
|
||||
self.request.headers.pop("If-None-Match", None)
|
||||
if self.should_return_304():
|
||||
self.set_status(304)
|
||||
return
|
||||
|
@ -682,6 +683,7 @@ class FileRequestHandler(AuthorizedFileHandler):
|
|||
elif end is not None:
|
||||
content_length = end
|
||||
elif start is not None:
|
||||
end = size
|
||||
content_length = size - start
|
||||
else:
|
||||
end = size
|
||||
|
@ -689,10 +691,8 @@ class FileRequestHandler(AuthorizedFileHandler):
|
|||
self.set_header("Content-Length", content_length)
|
||||
|
||||
if include_body:
|
||||
content = self.get_content(self.absolute_path, start, end)
|
||||
if isinstance(content, bytes):
|
||||
content = [content]
|
||||
for chunk in content:
|
||||
content = self.get_content_nonblock(self.absolute_path, start, end)
|
||||
async for chunk in content:
|
||||
try:
|
||||
self.write(chunk)
|
||||
await self.flush()
|
||||
|
@ -707,32 +707,37 @@ class FileRequestHandler(AuthorizedFileHandler):
|
|||
def _escape_filename_to_utf8(self, basename: str) -> str:
|
||||
return urllib.parse.quote(basename, encoding="utf-8")
|
||||
|
||||
@classmethod
|
||||
async def get_content_nonblock(
|
||||
cls, abspath: str, start: Optional[int] = None,
|
||||
end: Optional[int] = None
|
||||
) -> AsyncGenerator[bytes, None]:
|
||||
ioloop = IOLoop.current()
|
||||
with open(abspath, "rb") as file:
|
||||
if start is not None:
|
||||
file.seek(start)
|
||||
if end is not None:
|
||||
remaining = end - (start or 0) # type: Optional[int]
|
||||
else:
|
||||
remaining = None
|
||||
while True:
|
||||
chunk_size = 64 * 1024
|
||||
if remaining is not None and remaining < chunk_size:
|
||||
chunk_size = remaining
|
||||
with ThreadPoolExecutor(max_workers=1) as tpe:
|
||||
chunk = await ioloop.run_in_executor(
|
||||
tpe, file.read, chunk_size)
|
||||
if chunk:
|
||||
if remaining is not None:
|
||||
remaining -= len(chunk)
|
||||
yield chunk
|
||||
else:
|
||||
if remaining is not None:
|
||||
assert remaining == 0
|
||||
return
|
||||
|
||||
@classmethod
|
||||
def _get_cached_version(cls, abs_path: str) -> Optional[str]:
|
||||
with cls._lock:
|
||||
hashes: Dict[str, Dict[str, Any]] = \
|
||||
cls._static_hashes # type: ignore
|
||||
try:
|
||||
mtime = datetime.datetime.fromtimestamp(
|
||||
os.path.getmtime(abs_path), tz=datetime.timezone.utc)
|
||||
except Exception:
|
||||
logging.exception(
|
||||
f"Unable to get modified time for file: {abs_path}")
|
||||
hashes.pop(abs_path, None)
|
||||
return None
|
||||
if abs_path not in hashes or mtime != hashes[abs_path]['modified']:
|
||||
try:
|
||||
hashes[abs_path] = {
|
||||
'modified': mtime,
|
||||
'hash': cls.get_content_version(abs_path)
|
||||
}
|
||||
except Exception:
|
||||
logging.exception(f"Could not open static file {abs_path}")
|
||||
hashes.pop(abs_path, None)
|
||||
return None
|
||||
hsh = hashes.get(abs_path, {}).get('hash')
|
||||
if hsh:
|
||||
return hsh
|
||||
return None
|
||||
|
||||
@tornado.web.stream_request_body
|
||||
|
|
Loading…
Reference in New Issue