]> granicus.if.org Git - python/commitdiff
bpo-24209: In http.server script, rely on getaddrinfo to bind to preferred address...
authorJason R. Coombs <jaraco@jaraco.com>
Thu, 7 Feb 2019 13:22:45 +0000 (08:22 -0500)
committerGitHub <noreply@github.com>
Thu, 7 Feb 2019 13:22:45 +0000 (08:22 -0500)
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.

Lib/http/server.py
Lib/test/test_httpservers.py
Misc/NEWS.d/next/Library/2019-02-06-01-40-55.bpo-24209.awtwPD.rst [new file with mode: 0644]

index 29c720ea7ea87eccd3f12944907a925af8e4f4dc..b247675ec45e81c52c3c7f25462fab85b5894a1a 100644 (file)
@@ -1224,24 +1224,34 @@ class CGIHTTPRequestHandler(SimpleHTTPRequestHandler):
                 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 @@ if __name__ == '__main__':
     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(),
index 3d8e0af8b45c274f3d5a8f802fb87a47761db3e9..8357ee9145d7e5b17086592476be79e540e9a18c 100644 (file)
@@ -1118,21 +1118,63 @@ class MiscTestCase(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')
     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 (file)
index 0000000..4d555fd
--- /dev/null
@@ -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