Skip to content

gh-85162: Add HTTPSServer to http.server #129607

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

Merged
merged 54 commits into from
Apr 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
5bfc939
Add support HTTPS in http.server
donBarbos Feb 2, 2025
b382985
Correct style code
donBarbos Feb 2, 2025
4cc80c5
Add tests for HTTPSServer
donBarbos Feb 2, 2025
75fff2b
Update options
donBarbos Feb 3, 2025
64c3070
Update docs
donBarbos Feb 3, 2025
e4652a7
Merge branch 'main' into issue-85162
donBarbos Feb 3, 2025
abd949c
Revert "Correct style code"
donBarbos Feb 3, 2025
8fc2311
Merge branch 'main' into issue-85162
donBarbos Feb 3, 2025
4f587bd
Update docs and correct raising errors
donBarbos Feb 3, 2025
db796cd
Add helper method _create_context
donBarbos Feb 3, 2025
96d4a68
Update docs and replace password option
donBarbos Feb 4, 2025
947f581
Update Lib/http/server.py
donBarbos Feb 15, 2025
b8ba151
Update Doc/library/http.server.rst
donBarbos Feb 15, 2025
97e2032
Update Doc/library/http.server.rst
donBarbos Feb 15, 2025
bd97fd6
Update Doc/library/http.server.rst
donBarbos Feb 15, 2025
15b2581
Update Doc/library/http.server.rst
donBarbos Feb 15, 2025
1951e22
Update Doc/library/http.server.rst
donBarbos Feb 15, 2025
b4e1eba
Update Lib/http/server.py
donBarbos Feb 15, 2025
4838ff8
Update Lib/http/server.py
donBarbos Feb 15, 2025
3a7821f
Update Lib/http/server.py
donBarbos Feb 15, 2025
85ee1b5
Update Lib/http/server.py
donBarbos Feb 15, 2025
196e71d
Update Doc/library/http.server.rst
donBarbos Feb 15, 2025
efd44a4
Update Doc/library/http.server.rst
donBarbos Feb 15, 2025
4b33ecc
Update Doc/whatsnew/3.14.rst
donBarbos Feb 15, 2025
5fcc947
Update Doc/whatsnew/3.14.rst
donBarbos Feb 15, 2025
4df61de
Update Lib/http/server.py
donBarbos Feb 15, 2025
08a5720
Add suggestions
donBarbos Feb 15, 2025
43ae6b8
Merge branch 'main' into issue-85162
donBarbos Feb 15, 2025
6cff350
Update 2025-02-02-00-30-09.gh-issue-85162.BNF_aJ.rst
donBarbos Feb 15, 2025
0b2d50a
Update http.server.rst
donBarbos Feb 15, 2025
8a7f316
Move function back
donBarbos Feb 15, 2025
e7d9250
Add test case for pass certdata
donBarbos Feb 15, 2025
1b64e3d
Update test_httpservers.py
donBarbos Feb 15, 2025
c004b71
Update test_httpservers.py
donBarbos Feb 15, 2025
b6ba37f
Update test_httpservers.py
donBarbos Feb 15, 2025
bf86a0d
Update test_httpservers.py
donBarbos Feb 15, 2025
b89f4c4
Update test_httpservers.py
donBarbos Feb 15, 2025
c6879de
Update test_httpservers.py
donBarbos Feb 15, 2025
1ee542f
Update test_httpservers.py
donBarbos Feb 15, 2025
0c40dd7
Add more suggestions
donBarbos Feb 15, 2025
6e51ec3
Update docs
donBarbos Feb 15, 2025
4b85253
Update
donBarbos Feb 15, 2025
09d32b3
Update tests
donBarbos Feb 15, 2025
4b8786f
Correct style code
donBarbos Feb 15, 2025
96ba50d
Wrap the lines
donBarbos Feb 15, 2025
5d87f80
Wrap again
donBarbos Feb 15, 2025
05f5f65
Add seealso section
donBarbos Feb 15, 2025
e7a42f7
Update http.server.rst
donBarbos Feb 15, 2025
4c68c27
Merge branch 'main' into issue-85162
donBarbos Mar 16, 2025
3ca55d1
Update cli description
donBarbos Mar 16, 2025
3daf484
Update doc
donBarbos Mar 16, 2025
8b84be2
Update docs
donBarbos Apr 4, 2025
50e0ed5
Update Doc/whatsnew/3.14.rst
picnixz Apr 5, 2025
4f36fbf
Update Doc/whatsnew/3.14.rst
picnixz Apr 5, 2025
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
75 changes: 72 additions & 3 deletions Doc/library/http.server.rst
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,49 @@ handler. Code to create and run the server looks like this::
.. versionadded:: 3.7


The :class:`HTTPServer` and :class:`ThreadingHTTPServer` must be given
a *RequestHandlerClass* on instantiation, of which this module
provides three different variants:
.. class:: HTTPSServer(server_address, RequestHandlerClass,\
bind_and_activate=True, *, certfile, keyfile=None,\
password=None, alpn_protocols=None)

Subclass of :class:`HTTPServer` with a wrapped socket using the :mod:`ssl` module.
If the :mod:`ssl` module is not available, instantiating a :class:`!HTTPSServer`
object fails with a :exc:`RuntimeError`.

The *certfile* argument is the path to the SSL certificate chain file,
and the *keyfile* is the path to file containing the private key.

A *password* can be specified for files protected and wrapped with PKCS#8,
but beware that this could possibly expose hardcoded passwords in clear.

.. seealso::

See :meth:`ssl.SSLContext.load_cert_chain` for additional
information on the accepted values for *certfile*, *keyfile*
and *password*.

When specified, the *alpn_protocols* argument must be a sequence of strings
specifying the "Application-Layer Protocol Negotiation" (ALPN) protocols
supported by the server. ALPN allows the server and the client to negotiate
the application protocol during the TLS handshake.

By default, it is set to ``["http/1.1"]``, meaning the server supports HTTP/1.1.

.. versionadded:: next

.. class:: ThreadingHTTPSServer(server_address, RequestHandlerClass,\
bind_and_activate=True, *, certfile, keyfile=None,\
password=None, alpn_protocols=None)

This class is identical to :class:`HTTPSServer` but uses threads to handle
requests by inheriting from :class:`~socketserver.ThreadingMixIn`. This is
analogous to :class:`ThreadingHTTPServer` only using :class:`HTTPSServer`.

.. versionadded:: next


The :class:`HTTPServer`, :class:`ThreadingHTTPServer`, :class:`HTTPSServer` and
:class:`ThreadingHTTPSServer` must be given a *RequestHandlerClass* on
instantiation, of which this module provides three different variants:

.. class:: BaseHTTPRequestHandler(request, client_address, server)

Expand Down Expand Up @@ -542,6 +582,35 @@ The following options are accepted:
are not intended for use by untrusted clients and may be vulnerable
to exploitation. Always use within a secure environment.

.. option:: --tls-cert

Specifies a TLS certificate chain for HTTPS connections::

python -m http.server --tls-cert fullchain.pem

.. versionadded:: next

.. option:: --tls-key

Specifies a private key file for HTTPS connections.

This option requires ``--tls-cert`` to be specified.

.. versionadded:: next

.. option:: --tls-password-file

Specifies the password file for password-protected private keys::

python -m http.server \
--tls-cert cert.pem \
--tls-key key.pem \
--tls-password-file password.txt

This option requires `--tls-cert`` to be specified.

.. versionadded:: next


.. _http.server-security:

Expand Down
11 changes: 11 additions & 0 deletions Doc/whatsnew/3.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -599,6 +599,17 @@ http
module allow the browser to apply its default dark mode.
(Contributed by Yorik Hansen in :gh:`123430`.)

* The :mod:`http.server` module now supports serving over HTTPS using the
:class:`http.server.HTTPSServer` class. This functionality is exposed by
the command-line interface (``python -m http.server``) through the following
options:

* ``--tls-cert <path>``: Path to the TLS certificate file.
* ``--tls-key <path>``: Optional path to the private key file.
* ``--tls-password-file <path>``: Optional path to the password file for the private key.

(Contributed by Semyon Moroz in :gh:`85162`.)


imaplib
-------
Expand Down
88 changes: 82 additions & 6 deletions Lib/http/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,10 @@
__version__ = "0.6"

__all__ = [
"HTTPServer", "ThreadingHTTPServer", "BaseHTTPRequestHandler",
"SimpleHTTPRequestHandler", "CGIHTTPRequestHandler",
"HTTPServer", "ThreadingHTTPServer",
"HTTPSServer", "ThreadingHTTPSServer",
"BaseHTTPRequestHandler", "SimpleHTTPRequestHandler",
"CGIHTTPRequestHandler",
]

import copy
Expand Down Expand Up @@ -149,6 +151,47 @@ class ThreadingHTTPServer(socketserver.ThreadingMixIn, HTTPServer):
daemon_threads = True


class HTTPSServer(HTTPServer):
def __init__(self, server_address, RequestHandlerClass,
bind_and_activate=True, *, certfile, keyfile=None,
password=None, alpn_protocols=None):
try:
import ssl
except ImportError:
raise RuntimeError("SSL module is missing; "
"HTTPS support is unavailable")

self.ssl = ssl
self.certfile = certfile
self.keyfile = keyfile
self.password = password
# Support by default HTTP/1.1
self.alpn_protocols = (
["http/1.1"] if alpn_protocols is None else alpn_protocols
)

super().__init__(server_address,
RequestHandlerClass,
bind_and_activate)

def server_activate(self):
"""Wrap the socket in SSLSocket."""
super().server_activate()
context = self._create_context()
self.socket = context.wrap_socket(self.socket, server_side=True)

def _create_context(self):
"""Create a secure SSL context."""
context = self.ssl.create_default_context(self.ssl.Purpose.CLIENT_AUTH)
context.load_cert_chain(self.certfile, self.keyfile, self.password)
context.set_alpn_protocols(self.alpn_protocols)
return context


class ThreadingHTTPSServer(socketserver.ThreadingMixIn, HTTPSServer):
daemon_threads = True


class BaseHTTPRequestHandler(socketserver.StreamRequestHandler):

"""HTTP request handler base class.
Expand Down Expand Up @@ -1263,20 +1306,29 @@ def _get_best_family(*address):

def test(HandlerClass=BaseHTTPRequestHandler,
ServerClass=ThreadingHTTPServer,
protocol="HTTP/1.0", port=8000, bind=None):
protocol="HTTP/1.0", port=8000, bind=None,
tls_cert=None, tls_key=None, tls_password=None):
"""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
with ServerClass(addr, HandlerClass) as httpd:

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

with server as httpd:
host, port = httpd.socket.getsockname()[:2]
url_host = f'[{host}]' if ':' in host else host
protocol = 'HTTPS' if tls_cert else 'HTTP'
print(
f"Serving HTTP on {host} port {port} "
f"(http://{url_host}:{port}/) ..."
f"Serving {protocol} on {host} port {port} "
f"({protocol.lower()}://{url_host}:{port}/) ..."
)
try:
httpd.serve_forever()
Expand All @@ -1301,10 +1353,31 @@ def test(HandlerClass=BaseHTTPRequestHandler,
default='HTTP/1.0',
help='conform to this HTTP version '
'(default: %(default)s)')
parser.add_argument('--tls-cert', metavar='PATH',
help='path to the TLS certificate chain file')
parser.add_argument('--tls-key', metavar='PATH',
help='path to the TLS key file')
parser.add_argument('--tls-password-file', metavar='PATH',
help='path to the password file for the TLS key')
parser.add_argument('port', default=8000, type=int, nargs='?',
help='bind to this port '
'(default: %(default)s)')
args = parser.parse_args()

if not args.tls_cert and args.tls_key:
parser.error("--tls-key requires --tls-cert to be set")

tls_key_password = None
if args.tls_password_file:
if not args.tls_cert:
parser.error("--tls-password-file requires --tls-cert to be set")

try:
with open(args.tls_password_file, "r", encoding="utf-8") as f:
tls_key_password = f.read().strip()
except OSError as e:
parser.error(f"Failed to read TLS password file: {e}")

if args.cgi:
handler_class = CGIHTTPRequestHandler
else:
Expand All @@ -1330,4 +1403,7 @@ def finish_request(self, request, client_address):
port=args.port,
bind=args.bind,
protocol=args.protocol,
tls_cert=args.tls_cert,
tls_key=args.tls_key,
tls_password=tls_key_password,
)
Loading
Loading