-
-
Notifications
You must be signed in to change notification settings - Fork 32.3k
bpo-29654 : Support If-Modified-Since HTTP header (browser cache) #298
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
20cc898
f1d8a48
c4403ec
3225080
415e4af
8595735
2be2f8c
c3337f4
143e1e9
42edfe3
1e68be9
fc20596
9436e45
708ff03
6a58894
db46671
Change order of imports.
Mar 1, 2017
41f74ef
204c503
e2fee49
6dadf6e
c389bf6
3cc4f6d
294e164
6c5ba39
1e0f3e3
2503add
36a8e2a
c8b108f
bd91346
d3238d2
16f9e11
c669909
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
…Match is present. Add tests.
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -687,25 +687,30 @@ def send_head(self): | |
except OSError: | ||
self.send_error(HTTPStatus.NOT_FOUND, "File not found") | ||
return None | ||
|
||
fs = os.fstat(f.fileno()) | ||
|
||
try: | ||
# Use browser cache if possible | ||
if "If-Modified-Since" in self.headers: | ||
if "If-Modified-Since" in self.headers \ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is more preferable to use parenthesis rather than line continuation. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
and not "If-None-Match" in self.headers: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done too |
||
# compare If-Modified-Since and date of last file modification | ||
fs = os.stat(path) | ||
ims = email.utils.parsedate(self.headers["If-Modified-Since"]) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This email function accepts a timezone (e.g. from the documentation: -0500), and seems to ignore it. But it may make more sense for a HTTP server to either adjust the date according to the timezone (e.g. add 5 hours), or treat it as an error (required by RFC 7232). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It looks a problem to me that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, the latest commit uses |
||
if ims is not None: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done in commit fc20596 |
||
ims_datetime = datetime.datetime(*ims[:7]) | ||
ims_dtstring = ims_datetime.strftime("%d %b %Y %H:%M:%S") | ||
last_modif = datetime.datetime.utcfromtimestamp( | ||
fs.st_mtime).strftime("%d %b %Y %H:%M:%S") | ||
if last_modif <= ims_dtstring: | ||
# If-Modified-Since is UTC, rounded to the second | ||
tzinfo = datetime.timezone(datetime.timedelta(hours=0)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
But maybe use naive datetime objects? |
||
ims_datetime = datetime.datetime(*ims[:7], tzinfo=tzinfo) | ||
# compare to UTC datetime of last modification, also | ||
# rounded to the second | ||
mtime = int(fs.st_mtime) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. “Int” rounds towards 1970. The actual rounding that “date_time_string” uses seems to be a combination of rounding half to even microseconds (thanks to datetime), and then rounding down (always backwards in time) to seconds. It might be more sensible to call datetime.replace(microseconds=0). |
||
last_modif = datetime.datetime.fromtimestamp(mtime, tzinfo) | ||
if last_modif <= ims_datetime: | ||
self.send_response(HTTPStatus.NOT_MODIFIED) | ||
self.end_headers() | ||
f.close() | ||
return | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
self.send_response(HTTPStatus.OK) | ||
self.send_header("Content-type", ctype) | ||
fs = os.fstat(f.fileno()) | ||
self.send_header("Content-Length", str(fs[6])) | ||
self.send_header("Last-Modified", self.date_time_string(fs.st_mtime)) | ||
self.end_headers() | ||
|
@@ -1229,4 +1234,3 @@ def test(HandlerClass=BaseHTTPRequestHandler, | |
handler_class = SimpleHTTPRequestHandler | ||
test(HandlerClass=handler_class, port=args.port, bind=args.bind) | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unrelated changes. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't add this line. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done in commit fc20596 |
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -448,16 +448,42 @@ def test_browser_cache(self): | |
#constructs the path relative to the root directory of the HTTPServer | ||
response = self.request(self.base_url + '/test') | ||
self.check_status_and_reason(response, HTTPStatus.OK, data=self.data) | ||
# get Last-Modified response heaer | ||
last_modif = response.headers['Last-modified'] | ||
|
||
# send new request to the same url with request header | ||
# If-Modified-Since | ||
# If-Modified-Since set to Last-Modified : must return 304 | ||
from email.message import Message | ||
headers = Message() | ||
headers['If-Modified-Since'] = last_modif | ||
response = self.request(self.base_url + '/test', headers=headers) | ||
self.check_status_and_reason(response, HTTPStatus.NOT_MODIFIED) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please repeat the test with an earlier date, verifying that a response body is returned. |
||
|
||
# if If-None-Match header is present, ignore If-Modified-Since | ||
headers['If-None-Match'] = "*" | ||
response = self.request(self.base_url + '/test', headers=headers) | ||
self.check_status_and_reason(response, HTTPStatus.OK) | ||
|
||
# with If-Modified-Since earlier than Last-Modified, must return 200 | ||
import datetime | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Import at module level. Importing inside functions is used in exceptional cases. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done in commit 42edfe3 |
||
import email.utils | ||
dt = email.utils.parsedate(last_modif) | ||
# build datetime object : one year before last modification | ||
old_dt = [dt[0] - 1] + list(dt[1:7]) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Assuming the date reflects when the test suite is run, this will fail on a leap day (e.g. 2020-02-29). Perhaps try something like “datetime(*old_dt) - timedelta(days=365)”. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Correct, I have modified the test. |
||
old_dt = datetime.datetime(*old_dt) | ||
headers = Message() | ||
headers['If-Modified-Since'] = email.utils.format_datetime(old_dt) | ||
response = self.request(self.base_url + '/test', headers=headers) | ||
self.check_status_and_reason(response, HTTPStatus.OK) | ||
|
||
# build datetime object : one hours after last modification | ||
new_dt = [dt[0]] + [dt[1] + 1] + list(dt[2:7]) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Dt[1] is attribute tm_mon, but the comment suggests you want to adjust by one hour. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I changed the implementation with |
||
new_dt = datetime.datetime(*new_dt) | ||
headers = Message() | ||
headers['If-Modified-Since'] = email.utils.format_datetime(new_dt) | ||
response = self.request(self.base_url + '/test', headers=headers) | ||
self.check_status_and_reason(response, HTTPStatus.NOT_MODIFIED) | ||
|
||
|
||
def test_invalid_requests(self): | ||
response = self.request('/', method='FOO') | ||
self.check_status_and_reason(response, HTTPStatus.NOT_IMPLEMENTED) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
f
is leaked ifos.fstat()
raises an exception.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done