Skip to content
30 changes: 20 additions & 10 deletions Lib/http/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -1224,24 +1224,34 @@ def run_cgi(self):
self.log_message("CGI script exited OK")


def _get_best_family(*address):
infos = socket.getaddrinfo(
*address,
type=socket.SOCK_STREAM,
flags=socket.AI_PASSIVE,
)
family, type, proto, canonname, sockaddr = next(iter(infos))
return family, sockaddr


def test(HandlerClass=BaseHTTPRequestHandler,
ServerClass=ThreadingHTTPServer,
protocol="HTTP/1.0", port=8000, bind=""):
protocol="HTTP/1.0", port=8000, bind=None):
"""Test the HTTP request handler class.

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

"""
server_address = (bind, port)

if ':' in bind:
ServerClass.address_family = socket.AF_INET6
ServerClass.address_family, addr = _get_best_family(bind, port)

HandlerClass.protocol_version = protocol
with ServerClass(server_address, HandlerClass) as httpd:
sa = httpd.socket.getsockname()
serve_message = "Serving HTTP on {host} port {port} (http://{host}:{port}/) ..."
print(serve_message.format(host=sa[0], port=sa[1]))
with ServerClass(addr, HandlerClass) as httpd:
host, port = httpd.socket.getsockname()[:2]
url_host = f'[{host}]' if ':' in host else host
print(
f"Serving HTTP on {host} port {port} "
f"(http://{url_host}:{port}/) ..."
)
try:
httpd.serve_forever()
except KeyboardInterrupt:
Expand All @@ -1254,7 +1264,7 @@ def test(HandlerClass=BaseHTTPRequestHandler,
parser = argparse.ArgumentParser()
parser.add_argument('--cgi', action='store_true',
help='Run as CGI Server')
parser.add_argument('--bind', '-b', default='', metavar='ADDRESS',
parser.add_argument('--bind', '-b', metavar='ADDRESS',
help='Specify alternate bind address '
'[default: all interfaces]')
parser.add_argument('--directory', '-d', default=os.getcwd(),
Expand Down
68 changes: 55 additions & 13 deletions Lib/test/test_httpservers.py
Original file line number Diff line number Diff line change
Expand Up @@ -1118,21 +1118,63 @@ def test_all(self):


class ScriptTestCase(unittest.TestCase):

def mock_server_class(self):
return mock.MagicMock(
return_value=mock.MagicMock(
__enter__=mock.MagicMock(
return_value=mock.MagicMock(
socket=mock.MagicMock(
getsockname=lambda: ('', 0),
),
),
),
),
)

@mock.patch('builtins.print')
def test_server_test_unspec(self, _):
mock_server = self.mock_server_class()
server.test(ServerClass=mock_server, bind=None)
self.assertIn(
mock_server.address_family,
(socket.AF_INET6, socket.AF_INET),
)

@mock.patch('builtins.print')
def test_server_test_localhost(self, _):
mock_server = self.mock_server_class()
server.test(ServerClass=mock_server, bind="localhost")
self.assertIn(
mock_server.address_family,
(socket.AF_INET6, socket.AF_INET),
)

ipv6_addrs = (
"::",
"2001:0db8:85a3:0000:0000:8a2e:0370:7334",
"::1",
)

ipv4_addrs = (
"0.0.0.0",
"8.8.8.8",
"127.0.0.1",
)

@mock.patch('builtins.print')
def test_server_test_ipv6(self, _):
mock_server = mock.MagicMock()
server.test(ServerClass=mock_server, bind="::")
self.assertEqual(mock_server.address_family, socket.AF_INET6)

mock_server.reset_mock()
server.test(ServerClass=mock_server,
bind="2001:0db8:85a3:0000:0000:8a2e:0370:7334")
self.assertEqual(mock_server.address_family, socket.AF_INET6)

mock_server.reset_mock()
server.test(ServerClass=mock_server,
bind="::1")
self.assertEqual(mock_server.address_family, socket.AF_INET6)
for bind in self.ipv6_addrs:
mock_server = self.mock_server_class()
server.test(ServerClass=mock_server, bind=bind)
self.assertEqual(mock_server.address_family, socket.AF_INET6)

@mock.patch('builtins.print')
def test_server_test_ipv4(self, _):
for bind in self.ipv4_addrs:
mock_server = self.mock_server_class()
server.test(ServerClass=mock_server, bind=bind)
self.assertEqual(mock_server.address_family, socket.AF_INET)


def test_main(verbose=None):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
In http.server script, rely on getaddrinfo to bind to preferred address based on the bind parameter. Now default bind or binding to a name may bind to IPv6 or dual-stack, depending on the environment.