From 20cc898cdd943a3a1d7628d88fd0c8ddbc450aaa Mon Sep 17 00:00:00 2001 From: Pierre Quentel Date: Sat, 25 Feb 2017 21:44:56 +0100 Subject: [PATCH 01/22] Support If-Modified-Since HTTP headers, return 304 response if file was not modified --- Lib/http/server.py | 18 ++++++++++++++++++ Lib/test/test_httpservers.py | 14 ++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/Lib/http/server.py b/Lib/http/server.py index 61ddecc7efe4e3..6c0ceef38e701b 100644 --- a/Lib/http/server.py +++ b/Lib/http/server.py @@ -100,6 +100,7 @@ import socketserver import sys import time +import datetime import urllib.parse import copy import argparse @@ -687,6 +688,21 @@ def send_head(self): self.send_error(HTTPStatus.NOT_FOUND, "File not found") return None try: + # Use browser cache if possible + if "If-Modified-Since" in self.headers: + # compare If-Modified-Since and date of last file modification + fs = os.stat(path) + ims = email.utils.parsedate(self.headers["If-Modified-Since"]) + if ims is not None: + 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: + self.send_response(HTTPStatus.NOT_MODIFIED) + self.end_headers() + f.close() + return self.send_response(HTTPStatus.OK) self.send_header("Content-type", ctype) fs = os.fstat(f.fileno()) @@ -1212,3 +1228,5 @@ def test(HandlerClass=BaseHTTPRequestHandler, else: handler_class = SimpleHTTPRequestHandler test(HandlerClass=handler_class, port=args.port, bind=args.bind) + + diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py index 5049538e664181..9a4326e6005678 100644 --- a/Lib/test/test_httpservers.py +++ b/Lib/test/test_httpservers.py @@ -444,6 +444,20 @@ def test_head(self): self.assertEqual(response.getheader('content-type'), 'application/octet-stream') + 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 + 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) + def test_invalid_requests(self): response = self.request('/', method='FOO') self.check_status_and_reason(response, HTTPStatus.NOT_IMPLEMENTED) From f1d8a484dfa2812f57decb259f6bbcde72d59c05 Mon Sep 17 00:00:00 2001 From: Pierre Quentel Date: Sun, 26 Feb 2017 09:53:42 +0100 Subject: [PATCH 02/22] Fix bug in datetime comparisons. Ignore If-Modified-Since if If-None-Match is present. Add tests. --- Lib/http/server.py | 22 +++++++++++++--------- Lib/test/test_httpservers.py | 30 ++++++++++++++++++++++++++++-- 2 files changed, 41 insertions(+), 11 deletions(-) diff --git a/Lib/http/server.py b/Lib/http/server.py index 6c0ceef38e701b..ca1c5fdcab2507 100644 --- a/Lib/http/server.py +++ b/Lib/http/server.py @@ -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 \ + and not "If-None-Match" in self.headers: # compare If-Modified-Since and date of last file modification - fs = os.stat(path) ims = email.utils.parsedate(self.headers["If-Modified-Since"]) if ims is not None: - 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)) + 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) + last_modif = datetime.datetime.fromtimestamp(mtime, tzinfo) + if last_modif <= ims_datetime: self.send_response(HTTPStatus.NOT_MODIFIED) self.end_headers() f.close() return 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) - diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py index 9a4326e6005678..0ed2f7578936ab 100644 --- a/Lib/test/test_httpservers.py +++ b/Lib/test/test_httpservers.py @@ -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) + # 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 + 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]) + 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]) + 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) From c4403eca213a40d99c046b17900bacf279cb3eaa Mon Sep 17 00:00:00 2001 From: Pierre Quentel Date: Sun, 26 Feb 2017 10:22:43 +0100 Subject: [PATCH 03/22] Update Misc/NEWS and What's New --- Doc/whatsnew/3.7.rst | 6 ++++++ Misc/NEWS | 3 +++ 2 files changed, 9 insertions(+) diff --git a/Doc/whatsnew/3.7.rst b/Doc/whatsnew/3.7.rst index 35eea84e0a6c19..55f3042de026f9 100644 --- a/Doc/whatsnew/3.7.rst +++ b/Doc/whatsnew/3.7.rst @@ -110,6 +110,12 @@ urllib.parse adding `~` to the set of characters that is never quoted by default. (Contributed by Christian Theune and Ratnadeep Debnath in :issue:`16285`.) +http.server +----------- +:class:`SimpleHTTPRequestHandler` supports the HTTP If-Modified-Since header. +The server returns the 304 response status if the target file was not +modified after the datetime specified in the header. +(Contributed by Pierre Quentel in :issue:`29654`.) Optimizations ============= diff --git a/Misc/NEWS b/Misc/NEWS index 74ec8c3bdf26e3..d9088d724c1872 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -666,6 +666,9 @@ Library - Issue #24142: Reading a corrupt config file left configparser in an invalid state. Original patch by Florian Höch. +- bpo-29654 : Support If-Modified-Since HTTP header (browser cache). Patch + by Pierre Quentel. + Windows ------- From 3225080814bda8dd7670bed78fa766bcfeb6e1b5 Mon Sep 17 00:00:00 2001 From: Pierre Quentel Date: Sun, 26 Feb 2017 10:53:35 +0100 Subject: [PATCH 04/22] Fix typo --- Lib/test/test_httpservers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py index 0ed2f7578936ab..732d2a1b1c882e 100644 --- a/Lib/test/test_httpservers.py +++ b/Lib/test/test_httpservers.py @@ -475,7 +475,7 @@ def test_browser_cache(self): 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 + # build datetime object : one hour after last modification new_dt = [dt[0]] + [dt[1] + 1] + list(dt[2:7]) new_dt = datetime.datetime(*new_dt) headers = Message() From 415e4af46a0fbea4593507b73b4b4be1a57e4f17 Mon Sep 17 00:00:00 2001 From: Pierre Quentel Date: Sun, 26 Feb 2017 11:47:06 +0100 Subject: [PATCH 05/22] Use parsedate_to_datetime to extract datetime from If-Modified-Since, check that it's a valid HTTP date with timezone set to UTC. --- Lib/http/server.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Lib/http/server.py b/Lib/http/server.py index ca1c5fdcab2507..c54273c27d663c 100644 --- a/Lib/http/server.py +++ b/Lib/http/server.py @@ -692,19 +692,19 @@ def send_head(self): try: # Use browser cache if possible - if "If-Modified-Since" in self.headers \ - and not "If-None-Match" in self.headers: - # compare If-Modified-Since and date of last file modification - ims = email.utils.parsedate(self.headers["If-Modified-Since"]) - if ims is not None: - # If-Modified-Since is UTC, rounded to the second - tzinfo = datetime.timezone(datetime.timedelta(hours=0)) - ims_datetime = datetime.datetime(*ims[:7], tzinfo=tzinfo) - # compare to UTC datetime of last modification, also + if ("If-Modified-Since" in self.headers + and "If-None-Match" not in self.headers): + # compare If-Modified-Since and time of last file modification + ims = email.utils.parsedate_to_datetime( + self.headers["If-Modified-Since"]) + if (ims is not None and + ims.tzinfo is datetime.timezone.utc): + # compare to UTC datetime of last modification, # rounded to the second mtime = int(fs.st_mtime) - last_modif = datetime.datetime.fromtimestamp(mtime, tzinfo) - if last_modif <= ims_datetime: + last_modif = datetime.datetime.fromtimestamp(mtime, + ims.tzinfo) + if last_modif <= ims: self.send_response(HTTPStatus.NOT_MODIFIED) self.end_headers() f.close() From 8595735d8fdab76cc017c169344ecd4a0fb7f3d7 Mon Sep 17 00:00:00 2001 From: Pierre Quentel Date: Sun, 26 Feb 2017 11:48:14 +0100 Subject: [PATCH 06/22] Change computing of dates used to test browser cache --- Lib/test/test_httpservers.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py index 732d2a1b1c882e..791760df1f3752 100644 --- a/Lib/test/test_httpservers.py +++ b/Lib/test/test_httpservers.py @@ -449,7 +449,7 @@ def test_browser_cache(self): response = self.request(self.base_url + '/test') self.check_status_and_reason(response, HTTPStatus.OK, data=self.data) last_modif = response.headers['Last-modified'] - + # send new request to the same url with request header # If-Modified-Since set to Last-Modified : must return 304 from email.message import Message @@ -466,23 +466,22 @@ def test_browser_cache(self): # with If-Modified-Since earlier than Last-Modified, must return 200 import datetime 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]) - old_dt = datetime.datetime(*old_dt) + dt = email.utils.parsedate_to_datetime(last_modif) + # build datetime object : 365 days before last modification + old_dt = dt - datetime.timedelta(days=365) headers = Message() - headers['If-Modified-Since'] = email.utils.format_datetime(old_dt) + headers['If-Modified-Since'] = email.utils.format_datetime(old_dt, + usegmt=True) response = self.request(self.base_url + '/test', headers=headers) self.check_status_and_reason(response, HTTPStatus.OK) - # build datetime object : one hour after last modification - new_dt = [dt[0]] + [dt[1] + 1] + list(dt[2:7]) - new_dt = datetime.datetime(*new_dt) + # one hour after last modification : must return 304 + new_dt = dt + datetime.timedelta(hours=1) headers = Message() - headers['If-Modified-Since'] = email.utils.format_datetime(new_dt) + headers['If-Modified-Since'] = email.utils.format_datetime(new_dt, + usegmt=True) response = self.request(self.base_url + '/test', headers=headers) - self.check_status_and_reason(response, HTTPStatus.NOT_MODIFIED) - + self.check_status_and_reason(response, HTTPStatus.NOT_MODIFIED) def test_invalid_requests(self): response = self.request('/', method='FOO') From 2be2f8c523fd5a4ed2d4154ebb8336943a71f5f8 Mon Sep 17 00:00:00 2001 From: Pierre Quentel Date: Sun, 26 Feb 2017 12:01:15 +0100 Subject: [PATCH 07/22] Remove microseconds from time of last modification with .replace(microsecond=0) instead of int() --- Lib/http/server.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Lib/http/server.py b/Lib/http/server.py index c54273c27d663c..622d582879623c 100644 --- a/Lib/http/server.py +++ b/Lib/http/server.py @@ -699,11 +699,11 @@ def send_head(self): self.headers["If-Modified-Since"]) if (ims is not None and ims.tzinfo is datetime.timezone.utc): - # compare to UTC datetime of last modification, - # rounded to the second - mtime = int(fs.st_mtime) - last_modif = datetime.datetime.fromtimestamp(mtime, + # compare to UTC datetime of last modification + last_modif = datetime.datetime.fromtimestamp(fs.st_mtime, ims.tzinfo) + # remove microseconds, like in If-Modified-Since + last_modif = last_modif.replace(microsecond=0) if last_modif <= ims: self.send_response(HTTPStatus.NOT_MODIFIED) self.end_headers() From c3337f40207a402944b61e89b617b21599b0519e Mon Sep 17 00:00:00 2001 From: Pierre Quentel Date: Sun, 26 Feb 2017 15:20:23 +0100 Subject: [PATCH 08/22] Put os.fstat() call inside the try block ; handle obsolete HTTP date format ; ignore ill-formed values in If-Modified-Since header. --- Lib/http/server.py | 44 +++++++++++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/Lib/http/server.py b/Lib/http/server.py index 622d582879623c..656084bec1a955 100644 --- a/Lib/http/server.py +++ b/Lib/http/server.py @@ -688,31 +688,41 @@ def send_head(self): self.send_error(HTTPStatus.NOT_FOUND, "File not found") return None - fs = os.fstat(f.fileno()) - try: + fs = os.fstat(f.fileno()) # Use browser cache if possible if ("If-Modified-Since" in self.headers and "If-None-Match" not in self.headers): # compare If-Modified-Since and time of last file modification - ims = email.utils.parsedate_to_datetime( - self.headers["If-Modified-Since"]) - if (ims is not None and - ims.tzinfo is datetime.timezone.utc): - # compare to UTC datetime of last modification - last_modif = datetime.datetime.fromtimestamp(fs.st_mtime, - ims.tzinfo) - # remove microseconds, like in If-Modified-Since - last_modif = last_modif.replace(microsecond=0) - if last_modif <= ims: - self.send_response(HTTPStatus.NOT_MODIFIED) - self.end_headers() - f.close() - return + try: + ims = email.utils.parsedate_to_datetime( + self.headers["If-Modified-Since"]) + except: + # ignore ill-formed values + ims = None + if ims is not None: + if ims.tzinfo is None: + # obsolete format with no timezone, cf. + # https://tools.ietf.org/html/rfc7231#section-7.1.1.1 + ims = ims.replace(tzinfo=datetime.timezone.utc) + if ims.tzinfo is datetime.timezone.utc: + # compare to UTC datetime of last modification + last_modif = datetime.datetime.fromtimestamp( + fs.st_mtime, datetime.timezone.utc) + # remove microseconds, like in If-Modified-Since + last_modif = last_modif.replace(microsecond=0) + + if last_modif <= ims: + self.send_response(HTTPStatus.NOT_MODIFIED) + self.end_headers() + f.close() + return None + self.send_response(HTTPStatus.OK) self.send_header("Content-type", ctype) self.send_header("Content-Length", str(fs[6])) - self.send_header("Last-Modified", self.date_time_string(fs.st_mtime)) + self.send_header("Last-Modified", + self.date_time_string(fs.st_mtime)) self.end_headers() return f except: From 143e1e92324a60c800dceb6c6310e540e42c1e84 Mon Sep 17 00:00:00 2001 From: Pierre Quentel Date: Sun, 26 Feb 2017 15:23:24 +0100 Subject: [PATCH 09/22] Restore alphabetical order in section Improved Modules --- Doc/whatsnew/3.7.rst | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Doc/whatsnew/3.7.rst b/Doc/whatsnew/3.7.rst index 55f3042de026f9..4b7736137eca09 100644 --- a/Doc/whatsnew/3.7.rst +++ b/Doc/whatsnew/3.7.rst @@ -96,6 +96,13 @@ New Modules Improved Modules ================ +http.server +----------- +:class:`SimpleHTTPRequestHandler` supports the HTTP If-Modified-Since header. +The server returns the 304 response status if the target file was not +modified after the datetime specified in the header. +(Contributed by Pierre Quentel in :issue:`29654`.) + unittest.mock ------------- @@ -110,13 +117,6 @@ urllib.parse adding `~` to the set of characters that is never quoted by default. (Contributed by Christian Theune and Ratnadeep Debnath in :issue:`16285`.) -http.server ------------ -:class:`SimpleHTTPRequestHandler` supports the HTTP If-Modified-Since header. -The server returns the 304 response status if the target file was not -modified after the datetime specified in the header. -(Contributed by Pierre Quentel in :issue:`29654`.) - Optimizations ============= From 42edfe3856e0ae683dbffe3f7a75b9daec0898e8 Mon Sep 17 00:00:00 2001 From: Pierre Quentel Date: Sun, 26 Feb 2017 16:46:56 +0100 Subject: [PATCH 10/22] Store last modification date in setUp ; split browser cache tests in several methods ; add a test for Last-Modified header. --- Lib/test/test_httpservers.py | 64 +++++++++++++++++++++++------------- 1 file changed, 41 insertions(+), 23 deletions(-) diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py index 791760df1f3752..182454e3c4ebec 100644 --- a/Lib/test/test_httpservers.py +++ b/Lib/test/test_httpservers.py @@ -17,8 +17,11 @@ import urllib.parse import html import http.client +import email.message +import email.utils import tempfile import time +import datetime from io import BytesIO import unittest @@ -333,6 +336,13 @@ def setUp(self): self.base_url = '/' + self.tempdir_name with open(os.path.join(self.tempdir, 'test'), 'wb') as temp: temp.write(self.data) + mtime = os.fstat(temp.fileno()).st_mtime + # compute last modification datetime for browser cache tests + last_modif = datetime.datetime.fromtimestamp(mtime, + datetime.timezone.utc) + self.last_modif_datetime = last_modif.replace(microsecond=0) + self.last_modif_header = email.utils.formatdate( + last_modif.timestamp(), usegmt=True) def tearDown(self): try: @@ -445,43 +455,42 @@ def test_head(self): 'application/octet-stream') 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) - last_modif = response.headers['Last-modified'] - - # send new request to the same url with request header - # If-Modified-Since set to Last-Modified : must return 304 - from email.message import Message - headers = Message() - headers['If-Modified-Since'] = last_modif + """Check that when a request to /test is sent with the request header + If-Modified-Since set to date of last modification, the server returns + status code 304, not 200 + """ + headers = email.message.Message() + headers['If-Modified-Since'] = self.last_modif_header response = self.request(self.base_url + '/test', headers=headers) self.check_status_and_reason(response, HTTPStatus.NOT_MODIFIED) - # if If-None-Match header is present, ignore If-Modified-Since - headers['If-None-Match'] = "*" + # one hour after last modification : must return 304 + new_dt = self.last_modif_datetime + datetime.timedelta(hours=1) + headers = email.message.Message() + headers['If-Modified-Since'] = email.utils.format_datetime(new_dt, + usegmt=True) response = self.request(self.base_url + '/test', headers=headers) - self.check_status_and_reason(response, HTTPStatus.OK) + self.check_status_and_reason(response, HTTPStatus.NOT_MODIFIED) + def test_browser_cache_file_changed(self): # with If-Modified-Since earlier than Last-Modified, must return 200 - import datetime - import email.utils - dt = email.utils.parsedate_to_datetime(last_modif) + dt = self.last_modif_datetime # build datetime object : 365 days before last modification old_dt = dt - datetime.timedelta(days=365) - headers = Message() + headers = email.message.Message() headers['If-Modified-Since'] = email.utils.format_datetime(old_dt, usegmt=True) response = self.request(self.base_url + '/test', headers=headers) self.check_status_and_reason(response, HTTPStatus.OK) - # one hour after last modification : must return 304 - new_dt = dt + datetime.timedelta(hours=1) - headers = Message() - headers['If-Modified-Since'] = email.utils.format_datetime(new_dt, - usegmt=True) + def test_browser_cache_with_If_None_Match_header(self): + # if If-None-Match header is present, ignore If-Modified-Since + + headers = email.message.Message() + headers['If-Modified-Since'] = self.last_modif_header + headers['If-None-Match'] = "*" response = self.request(self.base_url + '/test', headers=headers) - self.check_status_and_reason(response, HTTPStatus.NOT_MODIFIED) + self.check_status_and_reason(response, HTTPStatus.OK) def test_invalid_requests(self): response = self.request('/', method='FOO') @@ -492,6 +501,15 @@ def test_invalid_requests(self): response = self.request('/', method='GETs') self.check_status_and_reason(response, HTTPStatus.NOT_IMPLEMENTED) + def test_last_modified(self): + """Checks that the datetime returned in Last-Modified response header + is the actual datetime of last modification, rounded to the second + """ + response = self.request(self.base_url + '/test') + self.check_status_and_reason(response, HTTPStatus.OK, data=self.data) + last_modif_header = response.headers['Last-modified'] + self.assertEqual(last_modif_header, self.last_modif_header) + def test_path_without_leading_slash(self): response = self.request(self.tempdir_name + '/test') self.check_status_and_reason(response, HTTPStatus.OK, data=self.data) From 1e68be9e1e68e5c483f01043ba0d081a2e538290 Mon Sep 17 00:00:00 2001 From: Pierre Quentel Date: Mon, 27 Feb 2017 09:08:11 +0100 Subject: [PATCH 11/22] Specify the exceptions to catch in parsedate_to_datetime --- Lib/http/server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/http/server.py b/Lib/http/server.py index 656084bec1a955..702cdbb7d09c7f 100644 --- a/Lib/http/server.py +++ b/Lib/http/server.py @@ -697,7 +697,7 @@ def send_head(self): try: ims = email.utils.parsedate_to_datetime( self.headers["If-Modified-Since"]) - except: + except (TypeError, IndexError, OverflowError, ValueError): # ignore ill-formed values ims = None if ims is not None: From fc2059693a12601302d6eb5c084b2cf01dffc3c5 Mon Sep 17 00:00:00 2001 From: Pierre Quentel Date: Mon, 27 Feb 2017 09:40:10 +0100 Subject: [PATCH 12/22] Use except/else for parsedate_to_datetime exception handling --- Lib/http/server.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Lib/http/server.py b/Lib/http/server.py index 702cdbb7d09c7f..a164eea08acf72 100644 --- a/Lib/http/server.py +++ b/Lib/http/server.py @@ -699,8 +699,8 @@ def send_head(self): self.headers["If-Modified-Since"]) except (TypeError, IndexError, OverflowError, ValueError): # ignore ill-formed values - ims = None - if ims is not None: + pass + else: if ims.tzinfo is None: # obsolete format with no timezone, cf. # https://tools.ietf.org/html/rfc7231#section-7.1.1.1 @@ -1243,4 +1243,3 @@ def test(HandlerClass=BaseHTTPRequestHandler, else: handler_class = SimpleHTTPRequestHandler test(HandlerClass=handler_class, port=args.port, bind=args.bind) - From 9436e45fd7238c83be1546bf96f289c712cf4b6a Mon Sep 17 00:00:00 2001 From: Pierre Quentel Date: Tue, 28 Feb 2017 08:47:38 +0100 Subject: [PATCH 13/22] Restore blank line --- Doc/whatsnew/3.7.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/Doc/whatsnew/3.7.rst b/Doc/whatsnew/3.7.rst index 4b7736137eca09..f5b92b31374902 100644 --- a/Doc/whatsnew/3.7.rst +++ b/Doc/whatsnew/3.7.rst @@ -117,6 +117,7 @@ urllib.parse adding `~` to the set of characters that is never quoted by default. (Contributed by Christian Theune and Ratnadeep Debnath in :issue:`16285`.) + Optimizations ============= From 708ff034a7055f34a1845bd5fce7bb2efed14164 Mon Sep 17 00:00:00 2001 From: Pierre Quentel Date: Wed, 1 Mar 2017 13:44:21 +0100 Subject: [PATCH 14/22] Put imports in alphabetical order. Change version to 0.7. --- Lib/http/server.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Lib/http/server.py b/Lib/http/server.py index a164eea08acf72..36e1ecc6ab1791 100644 --- a/Lib/http/server.py +++ b/Lib/http/server.py @@ -80,13 +80,16 @@ # (Actually, the latter is only true if you know the server configuration # at the time the request was made!) -__version__ = "0.6" +__version__ = "0.7" __all__ = [ "HTTPServer", "BaseHTTPRequestHandler", "SimpleHTTPRequestHandler", "CGIHTTPRequestHandler", ] +import argparse +import copy +import datetime import email.utils import html import http.client @@ -100,10 +103,7 @@ import socketserver import sys import time -import datetime import urllib.parse -import copy -import argparse from http import HTTPStatus From 6a58894e49915ef11339d4e31d4f544ff324f0ec Mon Sep 17 00:00:00 2001 From: Pierre Quentel Date: Wed, 1 Mar 2017 16:32:37 +0100 Subject: [PATCH 15/22] Presentation changes. --- Doc/whatsnew/3.7.rst | 6 +++--- Misc/NEWS | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Doc/whatsnew/3.7.rst b/Doc/whatsnew/3.7.rst index f5b92b31374902..288213723ed3dc 100644 --- a/Doc/whatsnew/3.7.rst +++ b/Doc/whatsnew/3.7.rst @@ -98,9 +98,9 @@ Improved Modules http.server ----------- -:class:`SimpleHTTPRequestHandler` supports the HTTP If-Modified-Since header. -The server returns the 304 response status if the target file was not -modified after the datetime specified in the header. +:class:`~http.server.SimpleHTTPRequestHandler` supports the HTTP +If-Modified-Since header. The server returns the 304 response status if the +target file was not modified after the datetime specified in the header. (Contributed by Pierre Quentel in :issue:`29654`.) unittest.mock diff --git a/Misc/NEWS b/Misc/NEWS index d9088d724c1872..bd654f96383a63 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -249,6 +249,9 @@ Extension Modules Library ------- +- bpo-29654: Support If-Modified-Since HTTP header (browser cache). Patch + by Pierre Quentel. + - Issue #16285: urrlib.parse.quote is now based on RFC 3986 and hence includes '~' in the set of characters that is not quoted by default. Patch by Christian Theune and Ratnadeep Debnath. @@ -666,9 +669,6 @@ Library - Issue #24142: Reading a corrupt config file left configparser in an invalid state. Original patch by Florian Höch. -- bpo-29654 : Support If-Modified-Since HTTP header (browser cache). Patch - by Pierre Quentel. - Windows ------- From db46671509567a026c32c5a33b0ef6c709dcc903 Mon Sep 17 00:00:00 2001 From: Pierre Quentel Date: Wed, 1 Mar 2017 16:41:21 +0100 Subject: [PATCH 16/22] Change order of imports. --- Lib/test/test_httpservers.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py index 182454e3c4ebec..dafcb0cbd56ba3 100644 --- a/Lib/test/test_httpservers.py +++ b/Lib/test/test_httpservers.py @@ -14,11 +14,11 @@ import base64 import ntpath import shutil -import urllib.parse -import html -import http.client import email.message import email.utils +import html +import http.client +import urllib.parse import tempfile import time import datetime From 41f74efd9676b2df67a6af6b0626f184fb37d334 Mon Sep 17 00:00:00 2001 From: Pierre Quentel Date: Thu, 2 Mar 2017 09:10:12 +0100 Subject: [PATCH 17/22] Update Misc/NEWS (conflict) --- Misc/NEWS | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/Misc/NEWS b/Misc/NEWS index bd654f96383a63..cf2ff39af6b533 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -10,10 +10,14 @@ What's New in Python 3.7.0 alpha 1? Core and Builtins ----------------- +- bpo-29684: Fix minor regression of PyEval_CallObjectWithKeywords. + It should raise TypeError when kwargs is not a dict. But it might + cause segv when args=NULL and kwargs is not a dict. + - bpo-28598: Support __rmod__ for subclasses of str being called before str.__mod__. Patch by Martijn Pieters. -- bpo-29607: Fix stack_effect computation for CALL_FUNCTION_EX. +- bpo-29607: Fix stack_effect computation for CALL_FUNCTION_EX. Patch by Matthieu Dartiailh. - bpo-29602: Fix incorrect handling of signed zeros in complex constructor for @@ -251,6 +255,21 @@ Library - bpo-29654: Support If-Modified-Since HTTP header (browser cache). Patch by Pierre Quentel. + +- bpo-29615: SimpleXMLRPCDispatcher no longer chains KeyError (or any other + exception) to exception(s) raised in the dispatched methods. + Patch by Petr Motejlek. + +- bpo-7769: Method register_function() of xmlrpc.server.SimpleXMLRPCDispatcher + and its subclasses can now be used as a decorator. + +- bpo-29376: Fix assertion error in threading._DummyThread.is_alive(). + +- bpo-28624: Add a test that checks that cwd parameter of Popen() accepts + PathLike objects. Patch by Sayan Chowdhury. + +- bpo-28518: Start a transaction implicitly before a DML statement. + Patch by Aviv Palivoda. - Issue #16285: urrlib.parse.quote is now based on RFC 3986 and hence includes '~' in the set of characters that is not quoted by default. Patch by @@ -1246,6 +1265,9 @@ Core and Builtins Library ------- +- Issue #26128: Added keyword-only arguments support for + subprocess.STARTUPINFO + - Issue #27517: LZMA compressor and decompressor no longer raise exceptions if given empty data twice. Patch by Benjamin Fogle. From 6dadf6ec3fd18cf12d56155fcf4275557cc62391 Mon Sep 17 00:00:00 2001 From: Pierre Quentel Date: Tue, 7 Mar 2017 08:30:08 +0100 Subject: [PATCH 18/22] Restore version number 0.6 --- Lib/http/server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/http/server.py b/Lib/http/server.py index 36e1ecc6ab1791..429490b73a88b7 100644 --- a/Lib/http/server.py +++ b/Lib/http/server.py @@ -80,7 +80,7 @@ # (Actually, the latter is only true if you know the server configuration # at the time the request was made!) -__version__ = "0.7" +__version__ = "0.6" __all__ = [ "HTTPServer", "BaseHTTPRequestHandler", From c389bf69fd79ee1208d3f18918e901ff3b4c3550 Mon Sep 17 00:00:00 2001 From: Pierre Quentel Date: Tue, 7 Mar 2017 08:30:32 +0100 Subject: [PATCH 19/22] Replace "datetime" by "time" --- Doc/whatsnew/3.7.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/whatsnew/3.7.rst b/Doc/whatsnew/3.7.rst index 288213723ed3dc..ed1c4fb44b1724 100644 --- a/Doc/whatsnew/3.7.rst +++ b/Doc/whatsnew/3.7.rst @@ -100,7 +100,7 @@ http.server ----------- :class:`~http.server.SimpleHTTPRequestHandler` supports the HTTP If-Modified-Since header. The server returns the 304 response status if the -target file was not modified after the datetime specified in the header. +target file was not modified after the time specified in the header. (Contributed by Pierre Quentel in :issue:`29654`.) unittest.mock From 294e1648c4f06694715b9d9081018460afbc4854 Mon Sep 17 00:00:00 2001 From: Pierre Quentel Date: Tue, 7 Mar 2017 08:43:20 +0100 Subject: [PATCH 20/22] Proposal for an update of the http.server module documentation --- Doc/library/http.server.rst | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Doc/library/http.server.rst b/Doc/library/http.server.rst index fb5c1df611d8f2..2487cbff0578ae 100644 --- a/Doc/library/http.server.rst +++ b/Doc/library/http.server.rst @@ -343,11 +343,13 @@ of which this module provides three different variants: :func:`os.listdir` to scan the directory, and returns a ``404`` error response if the :func:`~os.listdir` fails. - If the request was mapped to a file, it is opened and the contents are - returned. Any :exc:`OSError` exception in opening the requested file is - mapped to a ``404``, ``'File not found'`` error. Otherwise, the content + If the request was mapped to a file, it is opened. Any :exc:`OSError` + exception in opening the requested file is mapped to a ``404``, + ``'File not found'`` error. If there was a ``'If-Modified-Since'`` + header in the request, and the file was not modified after this time, + a ``304``, ``'Not Modified'`` response is sent. Otherwise, the content type is guessed by calling the :meth:`guess_type` method, which in turn - uses the *extensions_map* variable. + uses the *extensions_map* variable, and the file contents are returned. A ``'Content-type:'`` header with the guessed content type is output, followed by a ``'Content-Length:'`` header with the file's size and a From c8b108fc3c2ab66cb218c0f4e5da39d40c5fa035 Mon Sep 17 00:00:00 2001 From: Pierre Quentel Date: Fri, 24 Mar 2017 08:56:46 +0100 Subject: [PATCH 21/22] Add a "Changed in version 3.7" comment --- Doc/library/http.server.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Doc/library/http.server.rst b/Doc/library/http.server.rst index 2487cbff0578ae..303ef778ad349b 100644 --- a/Doc/library/http.server.rst +++ b/Doc/library/http.server.rst @@ -362,6 +362,8 @@ of which this module provides three different variants: For example usage, see the implementation of the :func:`test` function invocation in the :mod:`http.server` module. + .. versionchanged:: 3.7 + Support of the ``'If-Modified-Since'`` header. The :class:`SimpleHTTPRequestHandler` class can be used in the following manner in order to create a very basic webserver serving files relative to From 16f9e1139202bfedb9314bdac8e5d8fef0d2f4f2 Mon Sep 17 00:00:00 2001 From: Pierre Quentel Date: Sat, 1 Apr 2017 22:28:51 +0200 Subject: [PATCH 22/22] Remove trailing whitespaces --- Doc/library/http.server.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Doc/library/http.server.rst b/Doc/library/http.server.rst index 303ef778ad349b..ee1c37c631941b 100644 --- a/Doc/library/http.server.rst +++ b/Doc/library/http.server.rst @@ -343,9 +343,9 @@ of which this module provides three different variants: :func:`os.listdir` to scan the directory, and returns a ``404`` error response if the :func:`~os.listdir` fails. - If the request was mapped to a file, it is opened. Any :exc:`OSError` - exception in opening the requested file is mapped to a ``404``, - ``'File not found'`` error. If there was a ``'If-Modified-Since'`` + If the request was mapped to a file, it is opened. Any :exc:`OSError` + exception in opening the requested file is mapped to a ``404``, + ``'File not found'`` error. If there was a ``'If-Modified-Since'`` header in the request, and the file was not modified after this time, a ``304``, ``'Not Modified'`` response is sent. Otherwise, the content type is guessed by calling the :meth:`guess_type` method, which in turn