Open Source Repository

Home /jodd/jodd-3.3.2 | Repository Home



jodd/io/http/HttpTunnel.java
// Copyright (c) 2003-2012, Jodd Team (jodd.org). All Rights Reserved.

package jodd.io.http;

import jodd.io.StreamUtil;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Simple HTTP tunnel base ready to be extended.
 */
public class HttpTunnel {

  /**
   * The number of threads that can be executed in parallel.
   */
  protected int threadPoolSize = 10;

  /**
   * Number of incoming sockets connection that can be hold
   * before processing each.
   */
  protected int socketBacklog = 100;

  /**
   * Tunnel listening port.
   */
  protected int listenPort = 8888;

  /**
   * Target host.
   */
  protected String targetHost = "localhost";
  /**
   * Target port.
   */
  protected int targetPort = 8080;

  protected ExecutorService executorService;
  protected boolean running;
  protected ServerSocket serverSocket;

  /**
   * Starts HTTP tunnel. Method ends when the tunnel is stopped.
   */
  public void start() throws IOException {
    serverSocket = new ServerSocket(listenPort, socketBacklog);
    serverSocket.setReuseAddress(true);
    executorService = Executors.newFixedThreadPool(threadPoolSize);

    running = true;
    while (running) {
      Socket socket = serverSocket.accept();
      socket.setKeepAlive(false);
      executorService.execute(onSocketConnection(socket));
    }
    executorService.shutdown();
  }

  /**
   * Invoked on incoming connection. By default returns {@link HttpTunnelConnection}
   * to handle the connection. May be used to return custom
   * handlers.
   */
  protected Runnable onSocketConnection(Socket socket) {
    return new HttpTunnelConnection(socket);
  }

  /**
   * Stops the tunnel, shutdowns the thread pool and closes server socket.
   */
  public void stop() {
    running = false;
    executorService.shutdown();
    try {
      serverSocket.close();
    catch (IOException ignore) {
    }
  }

  /**
   * Single connection handler that performs the tunneling.
   */
  public class HttpTunnelConnection implements Runnable {

    protected final Socket socket;

    public HttpTunnelConnection(Socket socket) {
      this.socket = socket;
    }

    public void run() {
      try {
        tunnel();
      catch (IOException ioex) {
        ioex.printStackTrace();
      }
    }

    /**
     * Invoked after income connection is parsed. Nothing is
     * changed in the request. Sometimes, it make sense to
     * modify the "Host" header to match the target.
     */
    protected void onRequest(HttpTransfer request) {
    }

    /**
     * Invoked after target response is processed. Response is now
     * ready to be sent back to the client. The following header
     * parameters are changed:
     <li>Transfer-Encoding is removed, as body is returned at once,
     <li>Content-Length is added/update to body size.
     */
    protected void onResponse(HttpTransfer response) {
    }

    /**
     * Performs the tunneling. The following steps occurs:
     <li>read and parse clients request
     <li>open socket to target
     <li>resend request to target
     <li>read targets response
     <li>fix response and resend it to client
     */
    protected void tunnel() throws IOException {

      // read request
      InputStream socketInput = socket.getInputStream();
      HttpTransfer request = Http.readRequest(socketInput);

      // open client socket to target
      Socket clientSocket = new Socket();
      clientSocket.connect(new InetSocketAddress(targetHost, targetPort));

      // do request
      onRequest(request);

      // resend request to target
      OutputStream out = clientSocket.getOutputStream();
      request.send(out);

      // read target response
      InputStream in = clientSocket.getInputStream();
      HttpTransfer response = Http.readResponse(in);

      // close client socket
      StreamUtil.close(in);
      StreamUtil.close(out);
      clientSocket.close();

      // fix response
      if (response.getBody() != null) {
        response.removeHeader("Transfer-Encoding");
        response.addHeader("Content-Length", response.getBody().length);
      }

      // do response
      onResponse(response);

      // send response back
      OutputStream socketOutput = socket.getOutputStream();
      response.send(socketOutput);

      // close socket
      StreamUtil.close(socketInput);
      StreamUtil.close(socketOutput);
      socket.close();
    }
  }

}