Skip to content

Commit f289084

Browse files
authoredFeb 7, 2019
bpo-24209: In http.server script, rely on getaddrinfo to bind to preferred address based on the bind parameter. (#11767)
In http.server script, rely on getaddrinfo to bind to preferred address based on the bind parameter. As a result, now IPv6 is used as the default (including IPv4 on dual-stack systems). Enhanced tests.
1 parent 2848d9d commit f289084

File tree

3 files changed

+76
-23
lines changed

3 files changed

+76
-23
lines changed
 

‎Lib/http/server.py

+20-10
Original file line numberDiff line numberDiff line change
@@ -1224,24 +1224,34 @@ def run_cgi(self):
12241224
self.log_message("CGI script exited OK")
12251225

12261226

1227+
def _get_best_family(*address):
1228+
infos = socket.getaddrinfo(
1229+
*address,
1230+
type=socket.SOCK_STREAM,
1231+
flags=socket.AI_PASSIVE,
1232+
)
1233+
family, type, proto, canonname, sockaddr = next(iter(infos))
1234+
return family, sockaddr
1235+
1236+
12271237
def test(HandlerClass=BaseHTTPRequestHandler,
12281238
ServerClass=ThreadingHTTPServer,
1229-
protocol="HTTP/1.0", port=8000, bind=""):
1239+
protocol="HTTP/1.0", port=8000, bind=None):
12301240
"""Test the HTTP request handler class.
12311241
12321242
This runs an HTTP server on port 8000 (or the port argument).
12331243
12341244
"""
1235-
server_address = (bind, port)
1236-
1237-
if ':' in bind:
1238-
ServerClass.address_family = socket.AF_INET6
1245+
ServerClass.address_family, addr = _get_best_family(bind, port)
12391246

12401247
HandlerClass.protocol_version = protocol
1241-
with ServerClass(server_address, HandlerClass) as httpd:
1242-
sa = httpd.socket.getsockname()
1243-
serve_message = "Serving HTTP on {host} port {port} (http://{host}:{port}/) ..."
1244-
print(serve_message.format(host=sa[0], port=sa[1]))
1248+
with ServerClass(addr, HandlerClass) as httpd:
1249+
host, port = httpd.socket.getsockname()[:2]
1250+
url_host = f'[{host}]' if ':' in host else host
1251+
print(
1252+
f"Serving HTTP on {host} port {port} "
1253+
f"(http://{url_host}:{port}/) ..."
1254+
)
12451255
try:
12461256
httpd.serve_forever()
12471257
except KeyboardInterrupt:
@@ -1254,7 +1264,7 @@ def test(HandlerClass=BaseHTTPRequestHandler,
12541264
parser = argparse.ArgumentParser()
12551265
parser.add_argument('--cgi', action='store_true',
12561266
help='Run as CGI Server')
1257-
parser.add_argument('--bind', '-b', default='', metavar='ADDRESS',
1267+
parser.add_argument('--bind', '-b', metavar='ADDRESS',
12581268
help='Specify alternate bind address '
12591269
'[default: all interfaces]')
12601270
parser.add_argument('--directory', '-d', default=os.getcwd(),

‎Lib/test/test_httpservers.py

+55-13
Original file line numberDiff line numberDiff line change
@@ -1118,21 +1118,63 @@ def test_all(self):
11181118

11191119

11201120
class ScriptTestCase(unittest.TestCase):
1121+
1122+
def mock_server_class(self):
1123+
return mock.MagicMock(
1124+
return_value=mock.MagicMock(
1125+
__enter__=mock.MagicMock(
1126+
return_value=mock.MagicMock(
1127+
socket=mock.MagicMock(
1128+
getsockname=lambda: ('', 0),
1129+
),
1130+
),
1131+
),
1132+
),
1133+
)
1134+
1135+
@mock.patch('builtins.print')
1136+
def test_server_test_unspec(self, _):
1137+
mock_server = self.mock_server_class()
1138+
server.test(ServerClass=mock_server, bind=None)
1139+
self.assertIn(
1140+
mock_server.address_family,
1141+
(socket.AF_INET6, socket.AF_INET),
1142+
)
1143+
1144+
@mock.patch('builtins.print')
1145+
def test_server_test_localhost(self, _):
1146+
mock_server = self.mock_server_class()
1147+
server.test(ServerClass=mock_server, bind="localhost")
1148+
self.assertIn(
1149+
mock_server.address_family,
1150+
(socket.AF_INET6, socket.AF_INET),
1151+
)
1152+
1153+
ipv6_addrs = (
1154+
"::",
1155+
"2001:0db8:85a3:0000:0000:8a2e:0370:7334",
1156+
"::1",
1157+
)
1158+
1159+
ipv4_addrs = (
1160+
"0.0.0.0",
1161+
"8.8.8.8",
1162+
"127.0.0.1",
1163+
)
1164+
11211165
@mock.patch('builtins.print')
11221166
def test_server_test_ipv6(self, _):
1123-
mock_server = mock.MagicMock()
1124-
server.test(ServerClass=mock_server, bind="::")
1125-
self.assertEqual(mock_server.address_family, socket.AF_INET6)
1126-
1127-
mock_server.reset_mock()
1128-
server.test(ServerClass=mock_server,
1129-
bind="2001:0db8:85a3:0000:0000:8a2e:0370:7334")
1130-
self.assertEqual(mock_server.address_family, socket.AF_INET6)
1131-
1132-
mock_server.reset_mock()
1133-
server.test(ServerClass=mock_server,
1134-
bind="::1")
1135-
self.assertEqual(mock_server.address_family, socket.AF_INET6)
1167+
for bind in self.ipv6_addrs:
1168+
mock_server = self.mock_server_class()
1169+
server.test(ServerClass=mock_server, bind=bind)
1170+
self.assertEqual(mock_server.address_family, socket.AF_INET6)
1171+
1172+
@mock.patch('builtins.print')
1173+
def test_server_test_ipv4(self, _):
1174+
for bind in self.ipv4_addrs:
1175+
mock_server = self.mock_server_class()
1176+
server.test(ServerClass=mock_server, bind=bind)
1177+
self.assertEqual(mock_server.address_family, socket.AF_INET)
11361178

11371179

11381180
def test_main(verbose=None):
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
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.

0 commit comments

Comments
 (0)