http 304: if-range, backdating

add support for the `If-Range` header which is generally used to
prevent resuming a partial download after the source file on the
server has been modified, by returning HTTP 200 instead of a 206

also simplifies `If-Modified-Since` and `If-Range` handling;
previously this was a spec-compliant lexical comparison,
now it's a basic string-comparison instead. The server will now
reply 200 also when the server mtime is older than the client's.
This is technically not according to spec, but should be safer,
as it allows backdating timestamps without purging client cache
This commit is contained in:
ed 2024-10-25 22:05:59 +00:00
parent 7678a91b0e
commit 159f51b12b

View file

@ -2,7 +2,6 @@
from __future__ import print_function, unicode_literals
import argparse # typechk
import calendar
import copy
import errno
import gzip
@ -19,7 +18,6 @@ import threading # typechk
import time
import uuid
from datetime import datetime
from email.utils import parsedate
from operator import itemgetter
import jinja2 # typechk
@ -3409,26 +3407,26 @@ class HttpCli(object):
self.reply(response.encode("utf-8"))
return True
def _chk_lastmod(self, file_ts: int) -> tuple[str, bool]:
def _chk_lastmod(self, file_ts: int) -> tuple[str, bool, bool]:
# ret: lastmod, do_send, can_range
file_lastmod = formatdate(file_ts)
cli_lastmod = self.headers.get("if-modified-since")
if cli_lastmod:
try:
# some browser append "; length=573"
cli_lastmod = cli_lastmod.split(";")[0].strip()
cli_dt = parsedate(cli_lastmod)
assert cli_dt # !rm
cli_ts = calendar.timegm(cli_dt)
return file_lastmod, int(file_ts) > int(cli_ts)
except Exception as ex:
self.log(
"lastmod {}\nremote: [{}]\n local: [{}]".format(
repr(ex), cli_lastmod, file_lastmod
)
)
return file_lastmod, file_lastmod != cli_lastmod
c_ifrange = self.headers.get("if-range")
c_lastmod = self.headers.get("if-modified-since")
return file_lastmod, True
if not c_ifrange and not c_lastmod:
return file_lastmod, True, True
if c_ifrange and c_ifrange != file_lastmod:
t = "sending entire file due to If-Range; cli(%s) file(%s)"
self.log(t % (c_ifrange, file_lastmod), 6)
return file_lastmod, True, False
do_send = c_lastmod != file_lastmod
if do_send and c_lastmod:
t = "sending body due to If-Modified-Since cli(%s) file(%s)"
self.log(t % (c_lastmod, file_lastmod), 6)
return file_lastmod, do_send, True
def _use_dirkey(self, vn: VFS, ap: str) -> bool:
if self.can_read or not self.can_get:
@ -3578,7 +3576,7 @@ class HttpCli(object):
# if-modified
if file_ts > 0:
file_lastmod, do_send = self._chk_lastmod(int(file_ts))
file_lastmod, do_send, _ = self._chk_lastmod(int(file_ts))
self.out_headers["Last-Modified"] = file_lastmod
if not do_send:
status = 304
@ -3740,7 +3738,7 @@ class HttpCli(object):
#
# if-modified
file_lastmod, do_send = self._chk_lastmod(int(file_ts))
file_lastmod, do_send, can_range = self._chk_lastmod(int(file_ts))
self.out_headers["Last-Modified"] = file_lastmod
if not do_send:
status = 304
@ -3784,7 +3782,14 @@ class HttpCli(object):
# let's not support 206 with compression
# and multirange / multipart is also not-impl (mostly because calculating contentlength is a pain)
if do_send and not is_compressed and hrange and file_sz and "," not in hrange:
if (
do_send
and not is_compressed
and hrange
and can_range
and file_sz
and "," not in hrange
):
try:
if not hrange.lower().startswith("bytes"):
raise Exception()
@ -4255,7 +4260,7 @@ class HttpCli(object):
sz_md = len(lead) + len(fullfile)
file_ts = int(max(ts_md, self.E.t0))
file_lastmod, do_send = self._chk_lastmod(file_ts)
file_lastmod, do_send, _ = self._chk_lastmod(file_ts)
self.out_headers["Last-Modified"] = file_lastmod
self.out_headers.update(NO_CACHE)
status = 200 if do_send else 304