2
\$\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\$

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Browse other questions tagged or ask your own question.