Home > OS >  Java ProxySelector undefined behaviour
Java ProxySelector undefined behaviour

Time:03-20

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

CodePudding user response:

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.

  • Related