How do I create a proxy server using ProxySelector and Socket in Java?

Creating a proxy server using ProxySelector and Socket in Java involves a few steps. ProxySelector is used to determine which proxy to use for connections, while Socket facilitates communication with the client and/or the target server behind the proxy.

Here’s a step-by-step guide:


1. Setup ProxySelector

A custom ProxySelector can be created to define the proxy behavior. The select(URI uri) method of ProxySelector decides which proxy should be used for a given URI.

2. Create a Server Socket

A ServerSocket will listen for incoming client requests. Once a client is connected, communication between client and the target server through the proxy is handled via sockets.

3. Communicate between the Client and Remote Server

You’ll use a Socket to connect to the remote server via the proxy. The data from the client is forwarded to the remote server, and the response from the remote server is sent back to the client.

Example Code:

package org.kodejava.net;

import java.io.*;
import java.net.*;
import java.util.ArrayList;
import java.util.List;

public class ProxyServerExample {

   public static void main(String[] args) {
      int localPort = 8888; // The port your proxy server will listen on

      try (ServerSocket serverSocket = new ServerSocket(localPort)) {
         System.out.println("Proxy server is running on port: " + localPort);

         // Custom ProxySelector
         ProxySelector.setDefault(new CustomProxySelector());

         while (true) {
            // Accept incoming client connections
            Socket clientSocket = serverSocket.accept();
            System.out.println("Accepted connection from: " + clientSocket.getRemoteSocketAddress());

            // Handle the connection in a separate thread
            new Thread(new ProxyHandler(clientSocket)).start();
         }
      } catch (IOException e) {
         e.printStackTrace();
      }
   }

   // Custom ProxySelector class
   static class CustomProxySelector extends ProxySelector {
      @Override
      public List<Proxy> select(URI uri) {
         List<Proxy> proxies = new ArrayList<>();
         // Specify your proxy details here
         proxies.add(new Proxy(Proxy.Type.HTTP, new InetSocketAddress("proxy.example.com", 8080)));
         return proxies;
      }

      @Override
      public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
         System.err.printf("Failed to connect to proxy: %s%n", sa);
      }
   }

   // ProxyHandler to manage client-server communication
   static class ProxyHandler implements Runnable {
      private final Socket clientSocket;

      public ProxyHandler(Socket clientSocket) {
         this.clientSocket = clientSocket;
      }

      @Override
      public void run() {
         try (InputStream clientIn = clientSocket.getInputStream();
              OutputStream clientOut = clientSocket.getOutputStream()) {

            BufferedReader clientReader = new BufferedReader(new InputStreamReader(clientIn));
            OutputStreamWriter clientWriter = new OutputStreamWriter(clientOut);

            // Read the first line from the client (usually HTTP request line)
            String clientRequestLine = clientReader.readLine();
            System.out.println("Client Request: " + clientRequestLine);

            if (clientRequestLine != null) {
               // Extract the target host and port from the client request
               String[] requestParts = clientRequestLine.split(" ");
               String targetHost = new URI(requestParts[1]).getHost();
               int targetPort = new URI(requestParts[1]).getPort() != -1 ? new URI(requestParts[1]).getPort() : 80;

               // Connect to the target server through the proxy
               List<Proxy> proxies = ProxySelector.getDefault().select(new URI(requestParts[1]));
               Proxy proxy = proxies.isEmpty() ? Proxy.NO_PROXY : proxies.get(0);

               System.out.println("Connecting to: " + targetHost + ":" + targetPort + " via " + proxy);

               // Connect to the target server
               Socket serverSocket;
               if (proxy.type().equals(Proxy.Type.DIRECT)) {
                  // A direct connection to the target host
                  serverSocket = new Socket(targetHost, targetPort);
               } else if (proxy.type().equals(Proxy.Type.HTTP)) {
                  // Connect through an HTTP proxy
                  InetSocketAddress proxyAddress = (InetSocketAddress) proxy.address();
                  serverSocket = new Socket(proxyAddress.getHostName(), proxyAddress.getPort());
               } else {
                  throw new IOException("Unsupported proxy type: " + proxy.type());
               }

               // Forward client's request to the server
               try (OutputStream serverOut = serverSocket.getOutputStream();
                    InputStream serverIn = serverSocket.getInputStream()) {

                  serverOut.write((clientRequestLine + "\r\n").getBytes());
                  serverOut.flush();

                  // Forward other client headers to the server
                  String line;
                  while ((line = clientReader.readLine()) != null && !line.isEmpty()) {
                     serverOut.write((line + "\r\n").getBytes());
                     serverOut.flush();
                  }
                  serverOut.write("\r\n".getBytes());
                  serverOut.flush();

                  // Read server response and forward to the client
                  byte[] buffer = new byte[8192];
                  int bytesRead;
                  while ((bytesRead = serverIn.read(buffer)) != -1) {
                     clientOut.write(buffer, 0, bytesRead);
                     clientOut.flush();
                  }
               }

               serverSocket.close();
            }

         } catch (Exception e) {
            e.printStackTrace();
         } finally {
            try {
               clientSocket.close();
            } catch (IOException e) {
               e.printStackTrace();
            }
         }
      }
   }
}

How It Works:

  1. ProxySelector:
    • CustomProxySelector determines which proxy to use for a particular URI.
    • Modify the proxy host and port in CustomProxySelector to suit your requirements.
  2. Proxy Server:
    • The proxy server listens for incoming client requests on a specified port using ServerSocket.
  3. Handling Requests:
    • A new thread is started for each incoming client connection to handle the client’s request and forward it to the target server via the proxy.
  4. Forwarding Traffic:
    • ProxyHandler reads data from the client, connects to the target server through the proxy (if required), forwards the data, and sends the response back to the client.

Key Considerations:

  • Validate input to ensure the proxy server cannot be exploited.
  • Add proper error handling/logging.
  • Consider using a library like Netty or Jetty for production-grade performance.
  • This example is a basic proxy that works for simple HTTP requests. For HTTPS, consider HTTP CONNECT tunneling.

This structure forms a good starting point for a custom proxy server in Java!

Wayan

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.