Skip to content

Commit 5019131

Browse files
authored
Merge branch 'main' into warsaw/829
2 parents e34dd6f + 0bf6e31 commit 5019131

28 files changed

Lines changed: 500 additions & 111 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ Tools/unicode/data/
140140
/.ccache
141141
/cross-build*/
142142
/jit_stencils*.h
143+
/jit_unwind_info*.h
143144
/platform
144145
/profile-clean-stamp
145146
/profile-run-stamp

Doc/library/http.server.rst

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,8 @@ instantiation, of which this module provides three different variants:
366366
delays, it now always returns the IP address.
367367

368368

369-
.. class:: SimpleHTTPRequestHandler(request, client_address, server, directory=None)
369+
.. class:: SimpleHTTPRequestHandler(request, client_address, server, \
370+
*, directory=None, extra_response_headers=None)
370371
371372
This class serves files from the directory *directory* and below,
372373
or the current directory if *directory* is not provided, directly
@@ -378,6 +379,9 @@ instantiation, of which this module provides three different variants:
378379
.. versionchanged:: 3.9
379380
The *directory* parameter accepts a :term:`path-like object`.
380381

382+
.. versionchanged:: next
383+
Added *extra_response_headers* parameter.
384+
381385
A lot of the work, such as parsing the request, is done by the base class
382386
:class:`BaseHTTPRequestHandler`. This class implements the :func:`do_GET`
383387
and :func:`do_HEAD` functions.
@@ -408,6 +412,15 @@ instantiation, of which this module provides three different variants:
408412
This dictionary is no longer filled with the default system mappings,
409413
but only contains overrides.
410414

415+
.. attribute:: extra_response_headers
416+
417+
A sequence of ``(name, value)`` pairs containing user-defined extra HTTP
418+
response headers to add to each successful HTTP status 200 response. These
419+
headers are not included in other status code responses.
420+
421+
Headers that the server sends automatically such as ``Content-Type``
422+
will not be overwritten by :attr:`!extra_response_headers`.
423+
411424
The :class:`SimpleHTTPRequestHandler` class defines the following methods:
412425

413426
.. method:: do_HEAD()
@@ -440,6 +453,9 @@ instantiation, of which this module provides three different variants:
440453
followed by a ``'Content-Length:'`` header with the file's size and a
441454
``'Last-Modified:'`` header with the file's modification time.
442455

456+
The instance attribute :attr:`extra_response_headers` is a sequence of
457+
``(name, value)`` pairs containing user-defined extra response headers.
458+
443459
Then follows a blank line signifying the end of the headers, and then the
444460
contents of the file are output.
445461

@@ -581,6 +597,15 @@ The following options are accepted:
581597

582598
.. versionadded:: 3.14
583599

600+
.. option:: -H, --header <header> <value>
601+
602+
Specify an additional extra HTTP Response Header to send on successful HTTP
603+
200 responses. Can be used multiple times to send additional custom response
604+
headers. Headers that are sent automatically by the server (for instance
605+
Content-Type) will not be overwritten by the server.
606+
607+
.. versionadded:: next
608+
584609

585610
.. _http.server-security:
586611

Doc/library/unittest.rst

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1095,6 +1095,13 @@ Test cases
10951095
self.assertIn('myfile.py', cm.filename)
10961096
self.assertEqual(320, cm.lineno)
10971097

1098+
The context managers can be nested to test that multiple different
1099+
warnings are emitted::
1100+
1101+
with (self.assertWarns(SomeWarning),
1102+
self.assertWarns(OtherWarning)):
1103+
do_something()
1104+
10981105
This method works regardless of the warning filters in place when it
10991106
is called.
11001107

@@ -1103,6 +1110,10 @@ Test cases
11031110
.. versionchanged:: 3.3
11041111
Added the *msg* keyword argument when used as a context manager.
11051112

1113+
.. versionchanged:: next
1114+
Warnings that do not match the specified category are no longer
1115+
swallowed.
1116+
Nested context managers are now supported.
11061117

11071118
.. method:: assertWarnsRegex(warning, regex, callable, *args, **kwds)
11081119
assertWarnsRegex(warning, regex, *, msg=None)
@@ -1121,11 +1132,23 @@ Test cases
11211132
with self.assertWarnsRegex(RuntimeWarning, 'unsafe frobnicating'):
11221133
frobnicate('/etc/passwd')
11231134

1135+
The context managers can be nested to test that multiple different
1136+
warnings are emitted::
1137+
1138+
with (self.assertWarns(SomeWarning, regex1),
1139+
self.assertWarns(OtherWarning, regex2)):
1140+
do_something()
1141+
11241142
.. versionadded:: 3.2
11251143

11261144
.. versionchanged:: 3.3
11271145
Added the *msg* keyword argument when used as a context manager.
11281146

1147+
.. versionchanged:: next
1148+
Warnings that do not match the specified category or regex are
1149+
no longer swallowed.
1150+
Nested context managers are now supported.
1151+
11291152
.. method:: assertLogs(logger=None, level=None, formatter=None)
11301153

11311154
A context manager to test that at least one message is logged on

Doc/whatsnew/3.15.rst

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -975,6 +975,15 @@ http.server
975975
for files with unknown extensions.
976976
(Contributed by John Comeau and Hugo van Kemenade in :gh:`113471`.)
977977

978+
* Add a new ``extra_response_headers`` keyword argument to
979+
:class:`~http.server.SimpleHTTPRequestHandler` to support custom headers in
980+
HTTP responses.
981+
(Contributed by Anton I. Sipos in :gh:`135057`.)
982+
983+
* Add a ``-H/--header`` option to the :program:`python -m http.server`
984+
command-line interface to support custom headers in HTTP responses.
985+
(Contributed by Anton I. Sipos in :gh:`135057`.)
986+
978987

979988
inspect
980989
-------
@@ -1472,10 +1481,16 @@ unicodedata
14721481
unittest
14731482
--------
14741483

1475-
* :func:`unittest.TestCase.assertLogs` will now accept a formatter
1484+
* :meth:`unittest.TestCase.assertLogs` will now accept a formatter
14761485
to control how messages are formatted.
14771486
(Contributed by Garry Cairns in :gh:`134567`.)
14781487

1488+
* :meth:`unittest.TestCase.assertWarns` and
1489+
:meth:`unittest.TestCase.assertWarnsRegex` no longer swallow warnings that
1490+
do not match the specified category or regex.
1491+
Nested context managers are now supported.
1492+
(Contributed by Serhiy Storchaka in :gh:`143231`.)
1493+
14791494

14801495
urllib.parse
14811496
------------
@@ -2353,3 +2368,11 @@ that may require changes to your code.
23532368
with argument ``altchars=b'-_'`` (this works with older Python versions)
23542369
to make padding required.
23552370
(Contributed by Serhiy Storchaka in :gh:`73613`.)
2371+
2372+
* Since :meth:`unittest.TestCase.assertWarns` and
2373+
:meth:`unittest.TestCase.assertWarnsRegex` no longer swallow warnings that
2374+
do not match the specified category or regex, your tests may start leaking
2375+
some warnings that were previously masked.
2376+
Use warning filters to silence them or additional :meth:`!assertWarns*`
2377+
to catch and check them.
2378+
(Contributed by Serhiy Storchaka in :gh:`143231`.)

Include/internal/pycore_opcode_metadata.h

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Lib/_py_warnings.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -620,30 +620,33 @@ def warn_explicit(message, category, filename, lineno,
620620
linecache.getlines(filename, module_globals)
621621

622622
# Print message and context
623-
msg = _wm.WarningMessage(message, category, filename, lineno, source=source)
623+
msg = _wm.WarningMessage(message, category, filename, lineno,
624+
module=module, source=source)
624625
_wm._showwarnmsg(msg)
625626

626627

627628
class WarningMessage(object):
628629

629630
_WARNING_DETAILS = ("message", "category", "filename", "lineno", "file",
630-
"line", "source")
631+
"line", "source", "module")
631632

632633
def __init__(self, message, category, filename, lineno, file=None,
633-
line=None, source=None):
634+
line=None, source=None, module=None):
634635
self.message = message
635636
self.category = category
636637
self.filename = filename
637638
self.lineno = lineno
638639
self.file = file
639640
self.line = line
640641
self.source = source
642+
self.module = module
641643
self._category_name = category.__name__ if category else None
642644

643645
def __str__(self):
644-
return ("{message : %r, category : %r, filename : %r, lineno : %s, "
645-
"line : %r}" % (self.message, self._category_name,
646-
self.filename, self.lineno, self.line))
646+
return ("{message : %r, category : %r, module : %r, "
647+
"filename : %r, lineno : %s, line : %r}" % (
648+
self.message, self._category_name, self.module,
649+
self.filename, self.lineno, self.line))
647650

648651
def __repr__(self):
649652
return f'<{type(self).__qualname__} {self}>'

Lib/http/server.py

Lines changed: 49 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -551,13 +551,17 @@ def send_response_only(self, code, message=None):
551551
(self.protocol_version, code, message)).encode(
552552
'latin-1', 'strict'))
553553

554-
def send_header(self, keyword, value):
554+
def send_header(self, keyword, value, *, _is_extra=False):
555555
"""Send a MIME header to the headers buffer."""
556556
if self.request_version != 'HTTP/0.9':
557557
if not hasattr(self, '_headers_buffer'):
558558
self._headers_buffer = []
559559
self._headers_buffer.append(
560560
("%s: %s\r\n" % (keyword, value)).encode('latin-1', 'strict'))
561+
if not hasattr(self, '_default_response_headers'):
562+
self._default_response_headers = []
563+
if not _is_extra:
564+
self._default_response_headers.append((keyword, value))
561565

562566
if keyword.lower() == 'connection':
563567
if value.lower() == 'close':
@@ -575,6 +579,8 @@ def flush_headers(self):
575579
if hasattr(self, '_headers_buffer'):
576580
self.wfile.write(b"".join(self._headers_buffer))
577581
self._headers_buffer = []
582+
if hasattr(self, '_default_response_headers'):
583+
self._default_response_headers = []
578584

579585
def _colorize_request(self, code, size, t):
580586
try:
@@ -736,10 +742,11 @@ class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
736742
'.xz': 'application/x-xz',
737743
}
738744

739-
def __init__(self, *args, directory=None, **kwargs):
745+
def __init__(self, *args, directory=None, extra_response_headers=None, **kwargs):
740746
if directory is None:
741747
directory = os.getcwd()
742748
self.directory = os.fspath(directory)
749+
self.extra_response_headers = extra_response_headers
743750
super().__init__(*args, **kwargs)
744751

745752
def do_GET(self):
@@ -757,6 +764,16 @@ def do_HEAD(self):
757764
if f:
758765
f.close()
759766

767+
def _send_extra_response_headers(self):
768+
"""Send the headers stored in self.extra_response_headers."""
769+
if self.extra_response_headers is not None:
770+
default_headers = {h.lower() for h, _ in self._default_response_headers}
771+
for header, value in self.extra_response_headers:
772+
# Don't send the header if it's already sent
773+
# as part of the default response headers
774+
if header.lower() not in default_headers:
775+
self.send_header(header, value, _is_extra=True)
776+
760777
def send_head(self):
761778
"""Common code for GET and HEAD commands.
762779
@@ -839,6 +856,7 @@ def send_head(self):
839856
self.send_header("Content-Length", str(fs[6]))
840857
self.send_header("Last-Modified",
841858
self.date_time_string(fs.st_mtime))
859+
self._send_extra_response_headers()
842860
self.end_headers()
843861
return f
844862
except:
@@ -903,6 +921,7 @@ def list_directory(self, path):
903921
self.send_response(HTTPStatus.OK)
904922
self.send_header("Content-type", "text/html; charset=%s" % enc)
905923
self.send_header("Content-Length", str(len(encoded)))
924+
self._send_extra_response_headers()
906925
self.end_headers()
907926
return f
908927

@@ -1011,6 +1030,22 @@ def _get_best_family(*address):
10111030
return family, sockaddr
10121031

10131032

1033+
def _make_server(HandlerClass=BaseHTTPRequestHandler,
1034+
ServerClass=ThreadingHTTPServer,
1035+
protocol="HTTP/1.0", port=8000, bind=None,
1036+
tls_cert=None, tls_key=None, tls_password=None,
1037+
default_content_type=SimpleHTTPRequestHandler.default_content_type):
1038+
ServerClass.address_family, addr = _get_best_family(bind, port)
1039+
HandlerClass.protocol_version = protocol
1040+
HandlerClass.default_content_type = default_content_type
1041+
1042+
if tls_cert:
1043+
return ServerClass(addr, HandlerClass, certfile=tls_cert,
1044+
keyfile=tls_key, password=tls_password)
1045+
else:
1046+
return ServerClass(addr, HandlerClass)
1047+
1048+
10141049
def test(HandlerClass=SimpleHTTPRequestHandler,
10151050
ServerClass=ThreadingHTTPServer,
10161051
protocol="HTTP/1.0", port=8000, bind=None,
@@ -1019,19 +1054,13 @@ def test(HandlerClass=SimpleHTTPRequestHandler,
10191054
"""Test the HTTP request handler class.
10201055
10211056
This runs an HTTP server on port 8000 (or the port argument).
1022-
10231057
"""
1024-
ServerClass.address_family, addr = _get_best_family(bind, port)
1025-
HandlerClass.protocol_version = protocol
1026-
HandlerClass.default_content_type = content_type
1027-
1028-
if tls_cert:
1029-
server = ServerClass(addr, HandlerClass, certfile=tls_cert,
1030-
keyfile=tls_key, password=tls_password)
1031-
else:
1032-
server = ServerClass(addr, HandlerClass)
1033-
1034-
with server as httpd:
1058+
with _make_server(
1059+
HandlerClass=HandlerClass, ServerClass=ServerClass,
1060+
protocol=protocol, port=port, bind=bind,
1061+
tls_cert=tls_cert, tls_key=tls_key, tls_password=tls_password,
1062+
default_content_type=content_type,
1063+
) as httpd:
10351064
host, port = httpd.socket.getsockname()[:2]
10361065
url_host = f'[{host}]' if ':' in host else host
10371066
protocol = 'HTTPS' if tls_cert else 'HTTP'
@@ -1076,6 +1105,10 @@ def _main(args=None):
10761105
parser.add_argument('port', default=8000, type=int, nargs='?',
10771106
help='bind to this port '
10781107
'(default: %(default)s)')
1108+
parser.add_argument('-H', '--header', nargs=2, action='append',
1109+
metavar=('HEADER', 'VALUE'),
1110+
help='Add a custom response header '
1111+
'(can be specified multiple times)')
10791112
args = parser.parse_args(args)
10801113

10811114
if not args.tls_cert and args.tls_key:
@@ -1104,7 +1137,8 @@ def server_bind(self):
11041137

11051138
def finish_request(self, request, client_address):
11061139
self.RequestHandlerClass(request, client_address, self,
1107-
directory=args.directory)
1140+
directory=args.directory,
1141+
extra_response_headers=args.header)
11081142

11091143
class HTTPDualStackServer(DualStackServerMixin, ThreadingHTTPServer):
11101144
pass

Lib/test/test_complex.py

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -504,17 +504,25 @@ def check(z, x, y):
504504
with self.assertWarnsRegex(DeprecationWarning,
505505
"argument 'imag' must be a real number, not complex"):
506506
check(complex(0.0, 4.25j), -4.25, 0.0)
507-
with self.assertWarnsRegex(DeprecationWarning,
508-
"argument 'real' must be a real number, not complex"):
507+
with (self.assertWarnsRegex(DeprecationWarning,
508+
"argument 'real' must be a real number, not complex"),
509+
self.assertWarnsRegex(DeprecationWarning,
510+
"argument 'imag' must be a real number, not complex")):
509511
check(complex(4.25+0j, 0j), 4.25, 0.0)
510-
with self.assertWarnsRegex(DeprecationWarning,
511-
"argument 'real' must be a real number, not complex"):
512+
with (self.assertWarnsRegex(DeprecationWarning,
513+
"argument 'real' must be a real number, not complex"),
514+
self.assertWarnsRegex(DeprecationWarning,
515+
"argument 'imag' must be a real number, not complex")):
512516
check(complex(4.25j, 0j), 0.0, 4.25)
513-
with self.assertWarnsRegex(DeprecationWarning,
514-
"argument 'real' must be a real number, not complex"):
517+
with (self.assertWarnsRegex(DeprecationWarning,
518+
"argument 'real' must be a real number, not complex"),
519+
self.assertWarnsRegex(DeprecationWarning,
520+
"argument 'imag' must be a real number, not complex")):
515521
check(complex(0j, 4.25+0j), 0.0, 4.25)
516-
with self.assertWarnsRegex(DeprecationWarning,
517-
"argument 'real' must be a real number, not complex"):
522+
with (self.assertWarnsRegex(DeprecationWarning,
523+
"argument 'real' must be a real number, not complex"),
524+
self.assertWarnsRegex(DeprecationWarning,
525+
"argument 'imag' must be a real number, not complex")):
518526
check(complex(0j, 4.25j), -4.25, 0.0)
519527

520528
check(complex(real=4.25), 4.25, 0.0)

0 commit comments

Comments
 (0)