Java ProxySelector 未定义行为

Java ProxySelector undefined behaviour

我正在 java 网络中试验代理。我有 read 有关它们的文档,目前正在测试 ProxySelector。

我注意到此 ProxySelector 在与 HttpURLConnection 一起使用时和与 Socket 一起使用时有两种行为 class

使用此代码与 HttpUrlConnection 一起使用时

  class CustomSelector extends ProxySelector
  {
   @Override
   public List<Proxy> select(URI uri)
   {
    System.out.println("Selecting");
    System.out.println("===============");
    
    return List.of
    (
      new Proxy(Proxy.Type.HTTP,new InetSocketAddress("localhost",5000))
     ,new Proxy(Proxy.Type.HTTP,new InetSocketAddress("localhost",8000))
     ,Proxy.NO_PROXY
    ); 
   }

   @Override
   public void connectFailed(URI uri, SocketAddress sa, IOException ioe) 
   {
    System.out.println("Failed:"+uri);
    System.out.println("Address:"+sa);
    System.out.println("Exception:"+sa);
    System.out.println("=========================");
   }
  } 

 public static void main(String[] args)throws Exception
 {
  ProxySelector.setDefault(new CustomSelector());
  
  HttpURLConnection con=(HttpURLConnection)new URL("http://192.168.1.2:2000/Test")
                        .openConnection();
 
  System.out.println(con.getResponseMessage());
  
  con.disconnect();
 }  

我得到了预期的输出

Selecting
===============
Failed:http://192.168.1.2:2000/Test
Address:localhost/127.0.0.1:5000
Exception:localhost/127.0.0.1:5000
=========================
Failed:http://192.168.1.2:2000/Test
Address:localhost/127.0.0.1:8000
Exception:localhost/127.0.0.1:8000
=========================
Not-Implemented

这是有道理的,因为端口 5000 和 8000 只是虚拟端口,上面没有服务器 运行,因此连接失败,最终转到 NO_PROXY,它直接连接到我的自定义 HttpServer 运行 在端口 2000 上 returns 没有为所有的东西实现

现在我使用相同的过程使用套接字。我再次验证我的服务器是 运行 端口 2000

  class CustomSelector extends ProxySelector
  {
   @Override
   public List<Proxy> select(URI uri)
   {
    System.out.println("Selecting");
    System.out.println("===============");
    
     return List.of
    (
       new Proxy(Proxy.Type.SOCKS,new InetSocketAddress("localhost",5000))
      ,new Proxy(Proxy.Type.SOCKS,new InetSocketAddress("localhost",8000))
     ,Proxy.NO_PROXY 
   ); 
   }

   @Override
   public void connectFailed(URI uri, SocketAddress sa, IOException ioe) 
   {
    System.out.println("Failed:"+uri);
    System.out.println("Address:"+sa);
    System.out.println("Exception:"+sa);
    System.out.println("=========================");
   }
  } 

 public static void main(String[] args)throws Exception
 {
  ProxySelector.setDefault(new CustomSelector());
  
  try(Socket client=new Socket())
  {
   System.out.println("Connecting");
  
   client.connect(new InetSocketAddress(InetAddress.getLocalHost(),2000));
  
   System.out.println("Connected");
   } 
  }     

我得到这个输出

Connecting
Selecting
===============
Failed:socket://DESKTOP-1N0I046:2000
Address:localhost/127.0.0.1:5000
Exception:localhost/127.0.0.1:5000
=========================
Failed:socket://DESKTOP-1N0I046:2000
Address:localhost/127.0.0.1:8000
Exception:localhost/127.0.0.1:8000
=========================
Exception in thread "main" java.net.SocketException: Socket closed
    at java.base/sun.nio.ch.NioSocketImpl.beginConnect(NioSocketImpl.java:498)
    at java.base/sun.nio.ch.NioSocketImpl.connect(NioSocketImpl.java:580)
    at java.base/java.net.SocksSocketImpl.connect(SocksSocketImpl.java:327)
    at java.base/java.net.Socket.connect(Socket.java:633)
    at java.base/java.net.Socket.connect(Socket.java:583)
    at n_networking.proxy.TCPClient.main(TCPClient.java:237)

这不应该发生,因为代理列表中的最后一个选项是 NO_PROXY,这意味着没有任何应该成功的代理的直接连接,但似乎 ProxySelector 从未使用列表中的最后一个选项

更奇怪的是,如果我将 ProxyType 从 SOCKS 更改为 HTTP,如下所示。我知道这在这种情况下没有意义,但这只是为了测试目的

   @Override
   public List<Proxy> select(URI uri)
   {
    System.out.println("Selecting");
    System.out.println("===============");
    
     return List.of
     (
       new Proxy(Proxy.Type.HTTP,new InetSocketAddress("localhost",5000))
      ,new Proxy(Proxy.Type.HTTP,new InetSocketAddress("localhost",8000))
      ,Proxy.NO_PROXY 
     ); 
   } 

然后一切正常

输出:

Connecting
Selecting
===============
Connected

出于某种原因,它会跳过所有 HTTP 代理类型,甚至不会进行测试。

我已经将 Sockets 与 Proxy.HTTP 一起使用,它工作得很好,因为它在发送数据之前先发出 CONNECT 命令。

这是我用于这两个测试用例的虚拟服务器

 public static void main(String[] args)throws Exception
 {
  try(ServerSocket server=new ServerSocket(2000,0,InetAddress.getLocalHost()))
  {
   System.out.println("Main Server Started");
   
   try(Socket socket=server.accept())
   {
    System.out.println("Accepted");

    socket.getOutputStream().write("HTTP/1.1 501 Not-Implemented\r\n\r\n".getBytes());

    socket.getOutputStream().flush();
   }
 }
}

为什么会有这些差异?我正在使用 jdk 17.0.2 和 windows 10

这似乎是 JDK 中的错误:JDK-7141231

尽管理论上 java.net.SocksSocketImpl supporting proxy failover;实际上,这显然行不通,因为在第一次失败的连接尝试后,套接字已关闭,但相同的已关闭套接字用于任何后续连接尝试,因此失败并显示“套接字关闭”(您正在看到)。

之所以将代理类型更改为 HTTP“有效”是因为它执行直接连接,ignoring all other specified proxies