mirror of
https://github.com/python/cpython.git
synced 2025-09-27 10:50:04 +00:00
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:
parent
2848d9d299
commit
f289084c83
3 changed files with 75 additions and 22 deletions
|
@ -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(),
|
||||||
|
|
|
@ -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()
|
||||||
self.assertEqual(mock_server.address_family, socket.AF_INET6)
|
server.test(ServerClass=mock_server, bind=bind)
|
||||||
|
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):
|
||||||
|
|
|
@ -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.
|
Loading…
Add table
Add a link
Reference in a new issue