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.
This commit is contained in:
Jason R. Coombs 2019-02-07 08:22:45 -05:00 committed by GitHub
parent 2848d9d299
commit f289084c83
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 75 additions and 22 deletions

View file

@ -1224,24 +1224,34 @@ class CGIHTTPRequestHandler(SimpleHTTPRequestHandler):
self.log_message("CGI script exited OK") 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, def test(HandlerClass=BaseHTTPRequestHandler,
ServerClass=ThreadingHTTPServer, ServerClass=ThreadingHTTPServer,
protocol="HTTP/1.0", port=8000, bind=""): protocol="HTTP/1.0", port=8000, bind=None):
"""Test the HTTP request handler class. """Test the HTTP request handler class.
This runs an HTTP server on port 8000 (or the port argument). This runs an HTTP server on port 8000 (or the port argument).
""" """
server_address = (bind, port) ServerClass.address_family, addr = _get_best_family(bind, port)
if ':' in bind:
ServerClass.address_family = socket.AF_INET6
HandlerClass.protocol_version = protocol HandlerClass.protocol_version = protocol
with ServerClass(server_address, HandlerClass) as httpd: with ServerClass(addr, HandlerClass) as httpd:
sa = httpd.socket.getsockname() host, port = httpd.socket.getsockname()[:2]
serve_message = "Serving HTTP on {host} port {port} (http://{host}:{port}/) ..." url_host = f'[{host}]' if ':' in host else host
print(serve_message.format(host=sa[0], port=sa[1])) print(
f"Serving HTTP on {host} port {port} "
f"(http://{url_host}:{port}/) ..."
)
try: try:
httpd.serve_forever() httpd.serve_forever()
except KeyboardInterrupt: except KeyboardInterrupt:
@ -1254,7 +1264,7 @@ if __name__ == '__main__':
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument('--cgi', action='store_true', parser.add_argument('--cgi', action='store_true',
help='Run as CGI Server') 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 ' help='Specify alternate bind address '
'[default: all interfaces]') '[default: all interfaces]')
parser.add_argument('--directory', '-d', default=os.getcwd(), parser.add_argument('--directory', '-d', default=os.getcwd(),

View file

@ -1118,21 +1118,63 @@ class MiscTestCase(unittest.TestCase):
class ScriptTestCase(unittest.TestCase): 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') @mock.patch('builtins.print')
def test_server_test_ipv6(self, _): def test_server_test_ipv6(self, _):
mock_server = mock.MagicMock() for bind in self.ipv6_addrs:
server.test(ServerClass=mock_server, bind="::") mock_server = self.mock_server_class()
server.test(ServerClass=mock_server, bind=bind)
self.assertEqual(mock_server.address_family, socket.AF_INET6) self.assertEqual(mock_server.address_family, socket.AF_INET6)
mock_server.reset_mock() @mock.patch('builtins.print')
server.test(ServerClass=mock_server, def test_server_test_ipv4(self, _):
bind="2001:0db8:85a3:0000:0000:8a2e:0370:7334") for bind in self.ipv4_addrs:
self.assertEqual(mock_server.address_family, socket.AF_INET6) mock_server = self.mock_server_class()
server.test(ServerClass=mock_server, bind=bind)
mock_server.reset_mock() self.assertEqual(mock_server.address_family, socket.AF_INET)
server.test(ServerClass=mock_server,
bind="::1")
self.assertEqual(mock_server.address_family, socket.AF_INET6)
def test_main(verbose=None): def test_main(verbose=None):

View file

@ -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.