Skip to content

Commit cc5f8b5

Browse files
jcomeauictxserhiy-storchakapicnixzdonbarboshugovk
authored
gh-113471: Add custom default Content-Type to http.server (#113475)
Co-authored-by: Serhiy Storchaka <storchaka@gmail.com> Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> Co-authored-by: donBarbos <donbarbos@proton.me> Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Co-authored-by: blurb-it[bot] <43283697+blurb-it[bot]@users.noreply.github.com>
1 parent f0e90d7 commit cc5f8b5

5 files changed

Lines changed: 49 additions & 2 deletions

File tree

Doc/library/http.server.rst

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,14 @@ instantiation, of which this module provides three different variants:
390390
This will be ``"SimpleHTTP/" + __version__``, where ``__version__`` is
391391
defined at the module level.
392392

393+
.. attribute:: default_content_type
394+
395+
Specifies the Content-Type header value sent when the MIME type
396+
cannot be guessed from the file extension of the requested URL.
397+
By default, it is set to ``'application/octet-stream'``.
398+
399+
.. versionadded:: next
400+
393401
.. attribute:: extensions_map
394402

395403
A dictionary mapping suffixes into MIME types, contains custom overrides
@@ -528,6 +536,18 @@ The following options are accepted:
528536
529537
.. versionadded:: 3.11
530538

539+
.. option:: --content-type <content_type>
540+
541+
Specifies the default Content-Type HTTP header used when the MIME type
542+
cannot be guessed from the URL's file extension. By default, the server
543+
uses ``'application/octet-stream'``:
544+
545+
.. code-block:: bash
546+
547+
python -m http.server --content-type text/html
548+
549+
.. versionadded:: next
550+
531551
.. option:: --tls-cert
532552

533553
Specifies a TLS certificate chain for HTTPS connections:

Doc/whatsnew/3.15.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -964,6 +964,12 @@ http.server
964964
<using-on-controlling-color>`.
965965
(Contributed by Hugo van Kemenade in :gh:`146292`.)
966966

967+
* Added :attr:`~http.server.SimpleHTTPRequestHandler.default_content_type`
968+
and the :option:`--content-type <http.server --content-type>` command-line
969+
option to allow customizing the default ``Content-Type`` header
970+
for files with unknown extensions.
971+
(Contributed by John Comeau and Hugo van Kemenade in :gh:`113471`.)
972+
967973

968974
inspect
969975
-------

Lib/http/server.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -727,6 +727,7 @@ class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
727727
"""
728728

729729
server_version = "SimpleHTTP"
730+
default_content_type = "application/octet-stream"
730731
index_pages = ("index.html", "index.htm")
731732
extensions_map = _encodings_map_default = {
732733
'.gz': 'application/gzip',
@@ -974,7 +975,7 @@ def guess_type(self, path):
974975
guess, _ = mimetypes.guess_file_type(path)
975976
if guess:
976977
return guess
977-
return 'application/octet-stream'
978+
return self.default_content_type
978979

979980

980981
nobody = None
@@ -1010,9 +1011,10 @@ def _get_best_family(*address):
10101011
return family, sockaddr
10111012

10121013

1013-
def test(HandlerClass=BaseHTTPRequestHandler,
1014+
def test(HandlerClass=SimpleHTTPRequestHandler,
10141015
ServerClass=ThreadingHTTPServer,
10151016
protocol="HTTP/1.0", port=8000, bind=None,
1017+
content_type=SimpleHTTPRequestHandler.default_content_type,
10161018
tls_cert=None, tls_key=None, tls_password=None):
10171019
"""Test the HTTP request handler class.
10181020
@@ -1021,6 +1023,7 @@ def test(HandlerClass=BaseHTTPRequestHandler,
10211023
"""
10221024
ServerClass.address_family, addr = _get_best_family(bind, port)
10231025
HandlerClass.protocol_version = protocol
1026+
HandlerClass.default_content_type = content_type
10241027

10251028
if tls_cert:
10261029
server = ServerClass(addr, HandlerClass, certfile=tls_cert,
@@ -1060,6 +1063,10 @@ def _main(args=None):
10601063
default='HTTP/1.0',
10611064
help='conform to this HTTP version '
10621065
'(default: %(default)s)')
1066+
parser.add_argument('--content-type',
1067+
default=SimpleHTTPRequestHandler.default_content_type,
1068+
help='default content type for unknown extensions '
1069+
'(default: %(default)s)')
10631070
parser.add_argument('--tls-cert', metavar='PATH',
10641071
help='path to the TLS certificate chain file')
10651072
parser.add_argument('--tls-key', metavar='PATH',
@@ -1112,6 +1119,7 @@ class HTTPSDualStackServer(DualStackServerMixin, ThreadingHTTPSServer):
11121119
port=args.port,
11131120
bind=args.bind,
11141121
protocol=args.protocol,
1122+
content_type=args.content_type,
11151123
tls_cert=args.tls_cert,
11161124
tls_key=args.tls_key,
11171125
tls_password=tls_key_password,

Lib/test/test_httpservers.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1379,6 +1379,7 @@ class CommandLineTestCase(unittest.TestCase):
13791379
'protocol': default_protocol,
13801380
'port': default_port,
13811381
'bind': default_bind,
1382+
'content_type': 'application/octet-stream',
13821383
'tls_cert': None,
13831384
'tls_key': None,
13841385
'tls_password': None,
@@ -1447,6 +1448,16 @@ def test_protocol_flag(self, mock_func):
14471448
mock_func.assert_called_once_with(**call_args)
14481449
mock_func.reset_mock()
14491450

1451+
@mock.patch('http.server.test')
1452+
def test_content_type_flag(self, mock_func):
1453+
content_types = ['text/html', 'text/plain', 'application/json']
1454+
for content_type in content_types:
1455+
with self.subTest(content_type=content_type):
1456+
self.invoke_httpd('--content-type', content_type)
1457+
call_args = self.args | dict(content_type=content_type)
1458+
mock_func.assert_called_once_with(**call_args)
1459+
mock_func.reset_mock()
1460+
14501461
@unittest.skipIf(ssl is None, "requires ssl")
14511462
@mock.patch('http.server.test')
14521463
def test_tls_cert_and_key_flags(self, mock_func):
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Allow :mod:`http.server` to set a default content-type when serving
2+
files with an unknown or missing extension.

0 commit comments

Comments
 (0)