[Python-checkins] bpo-24209: In http.server script, rely on getaddrinfo to bind to preferred address based on the bind parameter. (#11767)

Jason R. Coombs webhook-mailer at python.org
Thu Feb 7 08:22:50 EST 2019


https://github.com/python/cpython/commit/f289084c83190cc72db4a70c58f007ec62e75247
commit: f289084c83190cc72db4a70c58f007ec62e75247
branch: master
author: Jason R. Coombs <jaraco at jaraco.com>
committer: GitHub <noreply at github.com>
date: 2019-02-07T08:22:45-05:00
summary:

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.

files:
A Misc/NEWS.d/next/Library/2019-02-06-01-40-55.bpo-24209.awtwPD.rst
M Lib/http/server.py
M Lib/test/test_httpservers.py

diff --git a/Lib/http/server.py b/Lib/http/server.py
index 29c720ea7ea8..b247675ec45e 100644
--- a/Lib/http/server.py
+++ b/Lib/http/server.py
@@ -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:
@@ -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(),
diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py
index 3d8e0af8b45c..8357ee9145d7 100644
--- a/Lib/test/test_httpservers.py
+++ b/Lib/test/test_httpservers.py
@@ -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):
diff --git a/Misc/NEWS.d/next/Library/2019-02-06-01-40-55.bpo-24209.awtwPD.rst b/Misc/NEWS.d/next/Library/2019-02-06-01-40-55.bpo-24209.awtwPD.rst
new file mode 100644
index 000000000000..4d555fd4125a
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2019-02-06-01-40-55.bpo-24209.awtwPD.rst
@@ -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.
\ No newline at end of file



More information about the Python-checkins mailing list