Java ProxySelector 未定义行为

发布于 2025-01-13 22:52:37 字数 4943 浏览 3 评论 0原文

我正在尝试java网络中的代理。我已经阅读有关它们的文档并且目前正在测试 ProxySelector。

我注意到此 ProxySelector 在与 HttpURLConnection 和 HttpURLConnection 一起使用时有两种类型的行为:当与 Socket 类一起使用时

当与 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,它直接连接到在端口 2000 上运行的自定义 HttpServer,该服务器返回未实现的所有内容

现在我使用相同的过程使用套接字。我再次验证我的服务器正在端口 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();
   }
 }
}

为什么会有这些差异?我在 Windows 10 上使用 jdk 17.0.2

I am experimenting with Proxies in java networking. I have read the documentation regarding them and am currently testing ProxySelector.

I notice 2 types of behaviour of this ProxySelector when using with HttpURLConnection & when using with Socket class

When using with HttpUrlConnection with this code

  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();
 }  

I get the expected output

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

This makes sense because ports 5000 & 8000 are just dummy ports with no server running on it hence connection fails on them and it finally goes to NO_PROXY which directly connects to my custom HttpServer running on port 2000 which returns not implemented for everything

Now i work with Sockets using the same procedure . Again i have verified that my server is running on port 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");
   } 
  }     

I get this output

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)

This should not happen as the last option in the Proxy List is NO_PROXY which means direct connection without any proxy which should succeed but it seems that the ProxySelector never uses that last option in the list

Whats more bizarre is that if i change my ProxyType from SOCKS to HTTP as follows. I know it doesn't make sense in this context but this is just for testing purposes

   @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 
     ); 
   } 

Then everything works

Output :

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

For some reason it skips all HTTP proxy types and not even test then.

I have used Sockets with Proxy.HTTP and it works perfectly as it issues an CONNECT command first before sending data.

Here is my dummy server i use for both these test cases

 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();
   }
 }
}

Why these differences? I am using jdk 17.0.2 with windows 10

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

美羊羊 2025-01-20 22:52:37

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

尽管 java.net.SocksSocketImpl 理论上支持代理故障转移;实际上,这显然不起作用,因为在第一次失败的连接尝试之后,套接字被关闭,但相同的关闭套接字用于任何后续的连接尝试,因此会失败并显示“套接字已关闭”(您所看到的)。

将代理类型更改为 HTTP 之所以“有效”,是因为它执行直接连接,忽略所有其他指定的代理

This seems to be a bug in the JDK: JDK-7141231

Despite java.net.SocksSocketImpl in theory supporting proxy failover; in reality this does apparently not work because after the first failed connection attempt the socket is closed, but that same closed socket is used for any subsequent connection attemps, which therefore fail with "Socket closed" (which you are seeing).

The reason why changing the proxy type to HTTP "works" is because it performs a direct connection, ignoring all other specified proxies.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文