Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 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
35 changes: 35 additions & 0 deletions Doc/library/http.server.rst
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,30 @@ 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)

This class is a :class:`HTTPServer` subclass with a wrapped socket using the
:mod:`ssl`, if the :mod:`ssl` module is not available the class will not
initialize. The *certfile* argument is required and is the path to the SSL
certificate chain file. The *keyfile* is the path to its private key. But
private keys are often protected and wrapped with PKCS #8, so we provide
*password* argument for that case.

.. versionadded:: 3.14

.. 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 using the :class:`~socketserver.ThreadingMixIn`. This is
analogue of :class:`ThreadingHTTPServer` class only using
:class:`HTTPSServer`.

.. versionadded:: 3.14

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

This class is used to handle the HTTP requests that arrive at the server. By
Expand Down Expand Up @@ -462,6 +486,17 @@ following command runs an HTTP/1.1 conformant server::
.. versionchanged:: 3.11
Added the ``--protocol`` option.

The server can also support TLS encryption. The options ``--tls-cert`` and
``--tls-key`` allow specifying a TLS certificate chain and private key for
secure HTTPS connections. And ``--tls-password`` option has been added to
``http.server`` to support password-protected private keys. For example, the
following command runs the server with TLS enabled::

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

.. versionchanged:: 3.14
Added the ``--tls-cert``, ``--tls-key`` and ``--tls-password`` options.

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

This class is used to serve either files or output of CGI scripts from the
Expand Down
84 changes: 79 additions & 5 deletions Lib/http/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,8 @@

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

import copy
Expand All @@ -105,6 +106,12 @@
import time
import urllib.parse

try:
import ssl
except ImportError:
ssl = None

from getpass import getpass
from http import HTTPStatus


Expand Down Expand Up @@ -1251,6 +1258,42 @@ def run_cgi(self):
self.log_message("CGI script exited OK")


class HTTPSServer(HTTPServer):
def __init__(self, server_address, RequestHandlerClass,
bind_and_activate=True, *, certfile, keyfile=None,
password=None, alpn_protocols=None):
if ssl is None:
raise ImportError("SSL support missing")
if not certfile:
raise TypeError("__init__() missing required argument 'certfile'")

self.certfile = certfile
self.keyfile = keyfile
self.password = password
# Support by default HTTP/1.1
self.alpn_protocols = alpn_protocols or ["http/1.1"]

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

def server_activate(self):
"""Wrap the socket in SSLSocket."""
if ssl is None:
raise ImportError("SSL support missing")

super().server_activate()

context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
context.load_cert_chain(certfile=self.certfile,
keyfile=self.keyfile,
password=self.password)
context.set_alpn_protocols(self.alpn_protocols)
self.socket = context.wrap_socket(self.socket, server_side=True)


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


def _get_best_family(*address):
infos = socket.getaddrinfo(
*address,
Expand All @@ -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 not tls_cert:
server = ServerClass(addr, HandlerClass)
else:
server = ThreadingHTTPSServer(addr, HandlerClass, certfile=tls_cert,
keyfile=tls_key, password=tls_password)

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 @@ -1288,6 +1340,8 @@ def test(HandlerClass=BaseHTTPRequestHandler,
import argparse
import contextlib

PASSWORD_EMPTY = object()

parser = argparse.ArgumentParser()
parser.add_argument('--cgi', action='store_true',
help='run as CGI server')
Expand All @@ -1301,10 +1355,27 @@ 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')
parser.add_argument('--tls-key', metavar='PATH',
help='path to the TLS key')
parser.add_argument('--tls-password', metavar='PASSWORD', nargs='?',
default=None, const=PASSWORD_EMPTY,
help='password for the TLS key '
'(default: empty)')
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')

if not args.tls_key and args.tls_password:
parser.error("--tls-password requires --tls-key to be set")
elif args.tls_password is PASSWORD_EMPTY:
args.tls_password = getpass("Enter the password for the TLS key: ")

if args.cgi:
handler_class = CGIHTTPRequestHandler
else:
Expand All @@ -1330,4 +1401,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=args.tls_password,
)
48 changes: 44 additions & 4 deletions Lib/test/test_httpservers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
Josip Dzolonga, and Michael Otteneder for the 2007/08 GHOP contest.
"""
from collections import OrderedDict
from http.server import BaseHTTPRequestHandler, HTTPServer, \
from http.server import BaseHTTPRequestHandler, HTTPServer, HTTPSServer, \
SimpleHTTPRequestHandler, CGIHTTPRequestHandler
from http import server, HTTPStatus

Expand Down Expand Up @@ -34,6 +34,11 @@
is_apple, os_helper, requires_subprocess, threading_helper
)

try:
import ssl
except ImportError:
ssl = None

support.requires_working_socket(module=True)

class NoLogRequestHandler:
Expand All @@ -46,13 +51,22 @@ def read(self, n=None):


class TestServerThread(threading.Thread):
def __init__(self, test_object, request_handler):
def __init__(self, test_object, request_handler, tls=None):
threading.Thread.__init__(self)
self.request_handler = request_handler
self.test_object = test_object
self.tls = tls

def run(self):
self.server = HTTPServer(('localhost', 0), self.request_handler)
if self.tls:
self.server = HTTPSServer(
('localhost', 0),
self.request_handler,
certfile=self.tls[0],
keyfile=self.tls[1],
)
else:
self.server = HTTPServer(('localhost', 0), self.request_handler)
self.test_object.HOST, self.test_object.PORT = self.server.socket.getsockname()
self.test_object.server_started.set()
self.test_object = None
Expand All @@ -67,11 +81,13 @@ def stop(self):


class BaseTestCase(unittest.TestCase):
tls = None

def setUp(self):
self._threads = threading_helper.threading_setup()
os.environ = os_helper.EnvironmentVarGuard()
self.server_started = threading.Event()
self.thread = TestServerThread(self, self.request_handler)
self.thread = TestServerThread(self, self.request_handler, self.tls)
self.thread.start()
self.server_started.wait()

Expand Down Expand Up @@ -315,6 +331,30 @@ def test_head_via_send_error(self):
self.assertEqual(b'', data)


@unittest.skipIf(ssl is None, 'No ssl module')
class BaseHTTPSServerTestCase(BaseTestCase):
tls = (
os.path.join(os.path.dirname(__file__), "certdata", "ssl_cert.pem"),
os.path.join(os.path.dirname(__file__), "certdata", "ssl_key.pem"),
)

class request_handler(NoLogRequestHandler, SimpleHTTPRequestHandler):
pass

def test_get(self):
response = self.request('/')
self.assertEqual(response.status, HTTPStatus.OK)

def request(self, uri, method='GET', body=None, headers={}):
self.connection = http.client.HTTPSConnection(
self.HOST,
self.PORT,
context=ssl._create_unverified_context()
)
self.connection.request(method, uri, body, headers)
return self.connection.getresponse()


class RequestHandlerLoggingTestCase(BaseTestCase):
class request_handler(BaseHTTPRequestHandler):
protocol_version = 'HTTP/1.1'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
The :mod:`http.server` module now includes built-in support for HTTPS
server. New :class:`http.server.HTTPSServer` class is an implementation of
HTTPS server that uses :mod:`ssl` module by providing a certificate and
private key. The ``--tls-cert``, ``--tls-key`` and ``--tls-password``
arguments have been added to ``python -m http.server``. Patch by Semyon
Moroz.
Loading