Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ Tools/unicode/data/
/.ccache
/cross-build*/
/jit_stencils*.h
/jit_unwind_info*.h
/platform
/profile-clean-stamp
/profile-run-stamp
Expand Down
27 changes: 26 additions & 1 deletion Doc/library/http.server.rst
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,8 @@ instantiation, of which this module provides three different variants:
delays, it now always returns the IP address.


.. class:: SimpleHTTPRequestHandler(request, client_address, server, directory=None)
.. class:: SimpleHTTPRequestHandler(request, client_address, server, \
*, directory=None, extra_response_headers=None)

This class serves files from the directory *directory* and below,
or the current directory if *directory* is not provided, directly
Expand All @@ -378,6 +379,9 @@ instantiation, of which this module provides three different variants:
.. versionchanged:: 3.9
The *directory* parameter accepts a :term:`path-like object`.

.. versionchanged:: next
Added *extra_response_headers* parameter.

A lot of the work, such as parsing the request, is done by the base class
:class:`BaseHTTPRequestHandler`. This class implements the :func:`do_GET`
and :func:`do_HEAD` functions.
Expand Down Expand Up @@ -408,6 +412,15 @@ instantiation, of which this module provides three different variants:
This dictionary is no longer filled with the default system mappings,
but only contains overrides.

.. attribute:: extra_response_headers

A sequence of ``(name, value)`` pairs containing user-defined extra HTTP
response headers to add to each successful HTTP status 200 response. These
headers are not included in other status code responses.

Headers that the server sends automatically such as ``Content-Type``
will not be overwritten by :attr:`!extra_response_headers`.

The :class:`SimpleHTTPRequestHandler` class defines the following methods:

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

The instance attribute :attr:`extra_response_headers` is a sequence of
``(name, value)`` pairs containing user-defined extra response headers.

Then follows a blank line signifying the end of the headers, and then the
contents of the file are output.

Expand Down Expand Up @@ -581,6 +597,15 @@ The following options are accepted:

.. versionadded:: 3.14

.. option:: -H, --header <header> <value>

Specify an additional extra HTTP Response Header to send on successful HTTP
200 responses. Can be used multiple times to send additional custom response
headers. Headers that are sent automatically by the server (for instance
Content-Type) will not be overwritten by the server.

.. versionadded:: next


.. _http.server-security:

Expand Down
23 changes: 23 additions & 0 deletions Doc/library/unittest.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1095,6 +1095,13 @@ Test cases
self.assertIn('myfile.py', cm.filename)
self.assertEqual(320, cm.lineno)

The context managers can be nested to test that multiple different
warnings are emitted::

with (self.assertWarns(SomeWarning),
self.assertWarns(OtherWarning)):
do_something()

This method works regardless of the warning filters in place when it
is called.

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

.. versionchanged:: next
Warnings that do not match the specified category are no longer
swallowed.
Nested context managers are now supported.

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

The context managers can be nested to test that multiple different
warnings are emitted::

with (self.assertWarns(SomeWarning, regex1),
self.assertWarns(OtherWarning, regex2)):
do_something()

.. versionadded:: 3.2

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

.. versionchanged:: next
Warnings that do not match the specified category or regex are
no longer swallowed.
Nested context managers are now supported.

.. method:: assertLogs(logger=None, level=None, formatter=None)

A context manager to test that at least one message is logged on
Expand Down
25 changes: 24 additions & 1 deletion Doc/whatsnew/3.15.rst
Original file line number Diff line number Diff line change
Expand Up @@ -974,6 +974,15 @@ http.server
for files with unknown extensions.
(Contributed by John Comeau and Hugo van Kemenade in :gh:`113471`.)

* Add a new ``extra_response_headers`` keyword argument to
:class:`~http.server.SimpleHTTPRequestHandler` to support custom headers in
HTTP responses.
(Contributed by Anton I. Sipos in :gh:`135057`.)

* Add a ``-H/--header`` option to the :program:`python -m http.server`
command-line interface to support custom headers in HTTP responses.
(Contributed by Anton I. Sipos in :gh:`135057`.)


inspect
-------
Expand Down Expand Up @@ -1471,10 +1480,16 @@ unicodedata
unittest
--------

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

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


urllib.parse
------------
Expand Down Expand Up @@ -2352,3 +2367,11 @@ that may require changes to your code.
with argument ``altchars=b'-_'`` (this works with older Python versions)
to make padding required.
(Contributed by Serhiy Storchaka in :gh:`73613`.)

* Since :meth:`unittest.TestCase.assertWarns` and
:meth:`unittest.TestCase.assertWarnsRegex` no longer swallow warnings that
do not match the specified category or regex, your tests may start leaking
some warnings that were previously masked.
Use warning filters to silence them or additional :meth:`!assertWarns*`
to catch and check them.
(Contributed by Serhiy Storchaka in :gh:`143231`.)
2 changes: 1 addition & 1 deletion Include/internal/pycore_opcode_metadata.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 9 additions & 6 deletions Lib/_py_warnings.py
Original file line number Diff line number Diff line change
Expand Up @@ -620,30 +620,33 @@ def warn_explicit(message, category, filename, lineno,
linecache.getlines(filename, module_globals)

# Print message and context
msg = _wm.WarningMessage(message, category, filename, lineno, source=source)
msg = _wm.WarningMessage(message, category, filename, lineno,
module=module, source=source)
_wm._showwarnmsg(msg)


class WarningMessage(object):

_WARNING_DETAILS = ("message", "category", "filename", "lineno", "file",
"line", "source")
"line", "source", "module")

def __init__(self, message, category, filename, lineno, file=None,
line=None, source=None):
line=None, source=None, module=None):
self.message = message
self.category = category
self.filename = filename
self.lineno = lineno
self.file = file
self.line = line
self.source = source
self.module = module
self._category_name = category.__name__ if category else None

def __str__(self):
return ("{message : %r, category : %r, filename : %r, lineno : %s, "
"line : %r}" % (self.message, self._category_name,
self.filename, self.lineno, self.line))
return ("{message : %r, category : %r, module : %r, "
"filename : %r, lineno : %s, line : %r}" % (
self.message, self._category_name, self.module,
self.filename, self.lineno, self.line))

def __repr__(self):
return f'<{type(self).__qualname__} {self}>'
Expand Down
64 changes: 49 additions & 15 deletions Lib/http/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -551,13 +551,17 @@ def send_response_only(self, code, message=None):
(self.protocol_version, code, message)).encode(
'latin-1', 'strict'))

def send_header(self, keyword, value):
def send_header(self, keyword, value, *, _is_extra=False):
"""Send a MIME header to the headers buffer."""
if self.request_version != 'HTTP/0.9':
if not hasattr(self, '_headers_buffer'):
self._headers_buffer = []
self._headers_buffer.append(
("%s: %s\r\n" % (keyword, value)).encode('latin-1', 'strict'))
if not hasattr(self, '_default_response_headers'):
self._default_response_headers = []
if not _is_extra:
self._default_response_headers.append((keyword, value))

if keyword.lower() == 'connection':
if value.lower() == 'close':
Expand All @@ -575,6 +579,8 @@ def flush_headers(self):
if hasattr(self, '_headers_buffer'):
self.wfile.write(b"".join(self._headers_buffer))
self._headers_buffer = []
if hasattr(self, '_default_response_headers'):
self._default_response_headers = []

def _colorize_request(self, code, size, t):
try:
Expand Down Expand Up @@ -736,10 +742,11 @@ class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
'.xz': 'application/x-xz',
}

def __init__(self, *args, directory=None, **kwargs):
def __init__(self, *args, directory=None, extra_response_headers=None, **kwargs):
if directory is None:
directory = os.getcwd()
self.directory = os.fspath(directory)
self.extra_response_headers = extra_response_headers
super().__init__(*args, **kwargs)

def do_GET(self):
Expand All @@ -757,6 +764,16 @@ def do_HEAD(self):
if f:
f.close()

def _send_extra_response_headers(self):
"""Send the headers stored in self.extra_response_headers."""
if self.extra_response_headers is not None:
default_headers = {h.lower() for h, _ in self._default_response_headers}
for header, value in self.extra_response_headers:
# Don't send the header if it's already sent
# as part of the default response headers
if header.lower() not in default_headers:
self.send_header(header, value, _is_extra=True)

def send_head(self):
"""Common code for GET and HEAD commands.

Expand Down Expand Up @@ -839,6 +856,7 @@ def send_head(self):
self.send_header("Content-Length", str(fs[6]))
self.send_header("Last-Modified",
self.date_time_string(fs.st_mtime))
self._send_extra_response_headers()
self.end_headers()
return f
except:
Expand Down Expand Up @@ -903,6 +921,7 @@ def list_directory(self, path):
self.send_response(HTTPStatus.OK)
self.send_header("Content-type", "text/html; charset=%s" % enc)
self.send_header("Content-Length", str(len(encoded)))
self._send_extra_response_headers()
self.end_headers()
return f

Expand Down Expand Up @@ -1011,6 +1030,22 @@ def _get_best_family(*address):
return family, sockaddr


def _make_server(HandlerClass=BaseHTTPRequestHandler,
ServerClass=ThreadingHTTPServer,
protocol="HTTP/1.0", port=8000, bind=None,
tls_cert=None, tls_key=None, tls_password=None,
default_content_type=SimpleHTTPRequestHandler.default_content_type):
ServerClass.address_family, addr = _get_best_family(bind, port)
HandlerClass.protocol_version = protocol
HandlerClass.default_content_type = default_content_type

if tls_cert:
return ServerClass(addr, HandlerClass, certfile=tls_cert,
keyfile=tls_key, password=tls_password)
else:
return ServerClass(addr, HandlerClass)


def test(HandlerClass=SimpleHTTPRequestHandler,
ServerClass=ThreadingHTTPServer,
protocol="HTTP/1.0", port=8000, bind=None,
Expand All @@ -1019,19 +1054,13 @@ def test(HandlerClass=SimpleHTTPRequestHandler,
"""Test the HTTP request handler class.

This runs an HTTP server on port 8000 (or the port argument).

"""
ServerClass.address_family, addr = _get_best_family(bind, port)
HandlerClass.protocol_version = protocol
HandlerClass.default_content_type = content_type

if tls_cert:
server = ServerClass(addr, HandlerClass, certfile=tls_cert,
keyfile=tls_key, password=tls_password)
else:
server = ServerClass(addr, HandlerClass)

with server as httpd:
with _make_server(
HandlerClass=HandlerClass, ServerClass=ServerClass,
protocol=protocol, port=port, bind=bind,
tls_cert=tls_cert, tls_key=tls_key, tls_password=tls_password,
default_content_type=content_type,
) as httpd:
host, port = httpd.socket.getsockname()[:2]
url_host = f'[{host}]' if ':' in host else host
protocol = 'HTTPS' if tls_cert else 'HTTP'
Expand Down Expand Up @@ -1076,6 +1105,10 @@ def _main(args=None):
parser.add_argument('port', default=8000, type=int, nargs='?',
help='bind to this port '
'(default: %(default)s)')
parser.add_argument('-H', '--header', nargs=2, action='append',
metavar=('HEADER', 'VALUE'),
help='Add a custom response header '
'(can be specified multiple times)')
args = parser.parse_args(args)

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

def finish_request(self, request, client_address):
self.RequestHandlerClass(request, client_address, self,
directory=args.directory)
directory=args.directory,
extra_response_headers=args.header)

class HTTPDualStackServer(DualStackServerMixin, ThreadingHTTPServer):
pass
Expand Down
24 changes: 16 additions & 8 deletions Lib/test/test_complex.py
Original file line number Diff line number Diff line change
Expand Up @@ -504,17 +504,25 @@ def check(z, x, y):
with self.assertWarnsRegex(DeprecationWarning,
"argument 'imag' must be a real number, not complex"):
check(complex(0.0, 4.25j), -4.25, 0.0)
with self.assertWarnsRegex(DeprecationWarning,
"argument 'real' must be a real number, not complex"):
with (self.assertWarnsRegex(DeprecationWarning,
"argument 'real' must be a real number, not complex"),
self.assertWarnsRegex(DeprecationWarning,
"argument 'imag' must be a real number, not complex")):
check(complex(4.25+0j, 0j), 4.25, 0.0)
with self.assertWarnsRegex(DeprecationWarning,
"argument 'real' must be a real number, not complex"):
with (self.assertWarnsRegex(DeprecationWarning,
"argument 'real' must be a real number, not complex"),
self.assertWarnsRegex(DeprecationWarning,
"argument 'imag' must be a real number, not complex")):
check(complex(4.25j, 0j), 0.0, 4.25)
with self.assertWarnsRegex(DeprecationWarning,
"argument 'real' must be a real number, not complex"):
with (self.assertWarnsRegex(DeprecationWarning,
"argument 'real' must be a real number, not complex"),
self.assertWarnsRegex(DeprecationWarning,
"argument 'imag' must be a real number, not complex")):
check(complex(0j, 4.25+0j), 0.0, 4.25)
with self.assertWarnsRegex(DeprecationWarning,
"argument 'real' must be a real number, not complex"):
with (self.assertWarnsRegex(DeprecationWarning,
"argument 'real' must be a real number, not complex"),
self.assertWarnsRegex(DeprecationWarning,
"argument 'imag' must be a real number, not complex")):
check(complex(0j, 4.25j), -4.25, 0.0)

check(complex(real=4.25), 4.25, 0.0)
Expand Down
Loading
Loading