1
\$\begingroup\$

I've been messing around with protocol handlers in Java, going so far as to write my own using a custom URLStreamHandler and URLConnection. I was implementing the loose methods when I came across URLStreamHandler::openConnection(URL, Proxy). Since I'd already written my logic using Sockets without Proxy support, I decided to write a simple static drop-in proxy layer.

import java.io.IOException;
import java.io.InputStream;
import java.net.*;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Base64;

public class SocketProxy{
    public static Socket proxy(InetSocketAddress destination, Proxy proxy) throws IOException{
        if(proxy == null) proxy = Proxy.NO_PROXY;
        InetSocketAddress proxyAddress = (InetSocketAddress) proxy.address();

        switch(proxy.type()){
            case DIRECT:    // No connection
                Socket directSocket = new Socket();
                directSocket.connect(destination);
                return directSocket;
            case SOCKS:     // Simple SOCKS tunnel
                Socket socksProxySocket = new Socket(proxy);
                socksProxySocket.connect(destination);
                return socksProxySocket;
            case HTTP:      // HTTP Direct
                StringBuilder headers = new StringBuilder("CONNECT ")
                        .append(destination.getHostName()).append(':').append(destination.getPort());

                PasswordAuthentication auth = Authenticator.requestPasswordAuthentication(proxyAddress.getHostName(),
                        proxyAddress.getAddress(), proxyAddress.getPort(), "http",
                        proxyAddress.getHostString(), "http");
                if(auth != null){
                    headers.append(" HTTP/1.0\nProxy-Authorization:Basic ");
                    byte[] userBytes = (auth.getUserName() + ":").getBytes("UTF-8");
                    ByteBuffer passBytes = StandardCharsets.UTF_8.encode(CharBuffer.wrap(auth.getPassword()));
                    ByteBuffer authBytes = ByteBuffer.allocate(userBytes.length + passBytes.capacity());
                    authBytes.put(userBytes);
                    authBytes.put(passBytes);
                    headers.append(Base64.getEncoder().encode(authBytes));
                }
                headers.append("\n\n");

                Socket httpProxySocket = new Socket();
                httpProxySocket.connect(proxyAddress);
                httpProxySocket.getOutputStream().write(headers.toString().getBytes());

                InputStream proxyInputStream = httpProxySocket.getInputStream();
                String response = ConnectionUtils.streamToString(proxyInputStream);
                if(response.length() == 0)
                    throw new SocketException("HTTP CONNECT not supported");
                if(response.contains("200")){
                    proxyInputStream.skip(proxyInputStream.available());
                    return httpProxySocket;
                }
                throw new SocketException("HTTP CONNECT failed; check authorization for realm " +
                        proxyAddress.getHostString());
            default:
                throw new SocketException("Cannot create socket for connection type: " + proxy.type());
        }
    }
}

ConnectionUtils (because someone's going to ask):

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;

public class ConnectionUtils{
    public static String streamToString(InputStream stream) throws IOException{
        ByteArrayOutputStream result = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int length;
        while((length = stream.read(buffer)) != -1){
            result.write(buffer, 0, length);
        }
        return result.toString("UTF-8");
    }
}

Sample use case:

protected URLConnection openConnection(URL u, Proxy p) throws IOException{
    return new URLConnection(u){
        private InputStream stream;

        @Override
        public void connect() throws IOException{
            InetSocketAddress address =
                    new InetSocketAddress(url.getHost(), url.getPort() == -1 ? url.getDefaultPort() : url.getPort());
            Socket socket = SocketProxy.proxy(address, p);

            // Request page
            // ...

            stream = socket.getInputStream();
            connected = true;
        }

        @Override
        public InputStream getInputStream() throws IOException{
            if(!connected) connect();
            return stream;
        }
    };
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.