Package rabbit.proxy

Source Code of rabbit.proxy.HttpProxy

package rabbit.proxy;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.URL;
import java.net.UnknownHostException;
import java.nio.channels.CancelledKeyException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import rabbit.cache.Cache;
import rabbit.cache.NCache;
import rabbit.filter.HttpFilter;
import rabbit.handler.HandlerFactory;
import rabbit.http.HttpHeader;
import rabbit.http.HttpDateParser;
import rabbit.io.ConnectionHandler;
import rabbit.io.HandlerRegistration;
import rabbit.io.InetAddressListener;
import rabbit.io.Resolver;
import rabbit.io.SocketHandler;
import rabbit.io.WebConnection;
import rabbit.io.WebConnectionListener;
import rabbit.util.Config;
import rabbit.util.Counter;
import rabbit.util.Logger;
import rabbit.util.SProperties;

/** A filtering and caching http proxy.
*
* @author <a href="mailto:robo@khelekore.org">Robert Olofsson</a>
*/
public class HttpProxy implements Runnable, Resolver, TaskRunner {

    /** Current version */
    public static final String VERSION = "RabbIT proxy version 3.2.0";

    /** The current config of this proxy. */
    private Config config;

    /** The time this proxy was started. Time in millis. */
    private long started;

    /** The identity of this server. */
    private String serverIdentity = VERSION;

    /** The logger of this proxy. */
    private ProxyLogger logger = new ProxyLogger ();

    /** The id sequence for acceptors. */
    private static int acceptorId = 0;

    /** The dns handler */
    private DNSHandler dnsHandler;

    /** The socket access controller. */
    private SocketAccessController socketAccessController;
   
    /** The http header filterer. */
    private HttpHeaderFilterer httpHeaderFilterer;
   
    /** The connection handler */
    private ConnectionHandler conhandler;

    /** The local adress of the proxy. */
    private InetAddress localhost;

    /** The port the proxy is using. */
    private int port = -1;

    /** Adress of connected proxy. */
    private InetAddress proxy = null;
    /** Port of the connected proxy. */
    private int proxyport = -1;

    /** Is the proxy currently listening. */
    private boolean accepting = false;

    /** The serversocket the proxy is using. */
    private ServerSocketChannel ssc = null;

    /** The selector the proxy is using. */
    private Selector selector = null;

    /** If this proxy is using strict http parsing. */
    private boolean strictHttp = true;

    /** Maximum number of concurrent connections */
    private int maxConnections = 50;

    /** The counter of events. */
    private Counter counter = new Counter ();

    /** The thread pool */ // TODO: possibility to configure...
    private ExecutorService executorService = Executors.newCachedThreadPool ();
   
    /** The queue to get back on the main thread. */
    private List<Runnable> returnedTasks = new ArrayList<Runnable> ();

    /** The cache-handler */
    private NCache<HttpHeader, HttpHeader> cache;

    /** Are we allowed to proxy ssl? */
    protected boolean proxySSL = false;
    /** The List of acceptable ssl-ports. */
    protected List<Integer> sslports = null;

    /** The handler factory handler. */
    private HandlerFactoryHandler handlerFactoryHandler;

    /** All the currently active connections. */
    private List<Connection> connections = new ArrayList<Connection> ();
   
    /** The total traffic in and out of this proxy. */
    private TrafficLoggerHandler tlh = new TrafficLoggerHandler ();

    /** Is this proxy running. */
    private boolean running = false;

    public HttpProxy () throws UnknownHostException {
  localhost = InetAddress.getLocalHost ();
    }

    /** Set the config file to use for this proxy.
     * @param conf the name of the file to use for proxy configuration.
     */
    public void setConfig (String conf) throws IOException {
  setConfig (new Config (conf));
    }

    private void setupLogging () {
  logger.setup (config.getProperties ("logging"));
    }

    private void setupDateParsing () {
  HttpDateParser.setOffset (getOffset ());
    }

    private void setupDNSHandler () {
  /* DNSJava have problems with international versions of windows.
   * so we default to the default dns handler.
   */
  String osName = System.getProperty ("os.name");
  if (osName.toLowerCase ().indexOf ("windows") > -1) {
      logger.logWarn ("This seems like a windows system, " +
          "will use default sun handler for DNS");
      dnsHandler = new DNSSunHandler ();
  } else {
      String dnsHandlerClass =
    config.getProperty (getClass ().getName (), "dnsHandler",
            "rabbit.proxy.DNSJavaHandler");
      try {
    Class<? extends DNSHandler> clz =
        Class.forName (dnsHandlerClass).asSubclass (DNSHandler.class);
    dnsHandler = clz.newInstance ();     
    dnsHandler.setup (config.getProperties ("dns"), logger);
      } catch (Exception e) {
    logger.logError ("Unable to create and setup dns handler: " + e +
         ", will try to use default instead.");
    dnsHandler = new DNSJavaHandler ();
    dnsHandler.setup (config.getProperties ("dns"), logger);
      }
  }
    }

    /** Configure the chained proxy rabbit is using (if any).
     */
    private void setupProxyConnection () {
  String sec = getClass ().getName ();
  String pname = config.getProperty (sec, "proxyhost", "");
  String pport = config.getProperty (sec, "proxyport", "");
  if (!pname.equals ("") && !pport.equals ("")) {
      try {
    proxy = dnsHandler.getInetAddress (pname);
      } catch (UnknownHostException e) {
    logger.logFatal ("Unknown proxyhost: '" + pname + "' exiting");
      }
      try {
    proxyport = Integer.parseInt (pport.trim ());
      } catch (NumberFormatException e) {
    logger.logFatal ("Strange proxyport: '" + pport + "' exiting");
      }
  }
    }

    private void setupCache () {
  SProperties props =
      config.getProperties (NCache.class.getName ());
  cache = new NCache<HttpHeader, HttpHeader> (getLogger (), props);
    }

    /** Configure the SSL support RabbIT should have.
     */
    private void setupSSLSupport () {
  String ssl = config.getProperty ("sslhandler", "allowSSL", "no");
  ssl = ssl.trim ();
  if (ssl.equals ("no")) {
      proxySSL = false;
  } else if (ssl.equals ("yes")) {
      proxySSL = true;
      sslports = null;
  } else {
      proxySSL = true;     
      // ok, try to get the portnumbers.
      sslports = new ArrayList<Integer> ();
      StringTokenizer st = new StringTokenizer (ssl, ",");
      while (st.hasMoreTokens ()) {
    String s = null;
    try {
        Integer port = new Integer (s = st.nextToken ());
        sslports.add (port);
    } catch (NumberFormatException e) {
        logger.logWarn ("bad number: '" + s +
            "' for ssl port, ignoring.");
    }
      }
  }
    }

    public void setStrictHttp (boolean b) {
  this.strictHttp = b;
    }

    public boolean getStrictHttp () {
  return strictHttp;
    }

    /** Configure the maximum number of simultanious connections we handle
     */
    private void setupMaxConnections () {
  String mc = config.getProperty (getClass ().getName (),
          "maxconnections", "500").trim ();
  try {
      maxConnections = Integer.parseInt (mc);
  } catch (NumberFormatException e) {
      logger.logWarn ("bad number for maxconnections: '" +
          mc + "', using old value: " + maxConnections);
  }
    }

    private void setupConnectionHandler () {
  conhandler = new ConnectionHandler (logger, counter, this, selector);
  String p = conhandler.getClass ().getName ();
  conhandler.setup (logger, config.getProperties (p));
    }
   
    private void setConfig (Config config) {
  this.config = config;
  setupLogging ();
  setupDateParsing ();
  setupDNSHandler ();
  setupProxyConnection ();
  String cn = getClass ().getName ();
  serverIdentity = config.getProperty (cn, "serverIdentity", VERSION);
  String strictHttp = config.getProperty (cn, "StrictHTTP", "true");
  setStrictHttp (strictHttp.equals ("true"));
  setupMaxConnections ()
  setupCache ();
  setupSSLSupport ();
  loadClasses ();
  openSocket ();
  setupConnectionHandler ();
  logger.logMsg ("Configuration loaded: ready for action.");
    }

    /** Open a socket on the specified port
     *  also make the proxy continue accepting connections.
     */
    private void openSocket () {
  int tport =
      Integer.parseInt (config.getProperty (getClass ().getName (),
              "port", "9666").trim ());
  if (tport != port) {
      try {
    port = tport;
    accepting = false;
    closeSocket ();
    ssc = ServerSocketChannel.open ();
    ssc.configureBlocking (false);
    ssc.socket ().bind (new InetSocketAddress (port));
    accepting = true;
    selector = Selector.open ();
    Acceptor acceptor =
        new Acceptor (acceptorId++, this, selector);
    HandlerRegistration hr =
        new HandlerRegistration (acceptor, Long.MAX_VALUE);
    ssc.register (selector, SelectionKey.OP_ACCEPT, hr);
      } catch (IOException e) {
    logger.logFatal ("Failed to open serversocket on port " +
         port);
    stop ();
      }
  }
    }
   
    /** Closes the serversocket and makes the proxy stop listening for
     *  connections.
     */
    private void closeSocket () {
  try {
      accepting = false;
      if (selector != null) {
    selector.close ();
    selector = null;
      }     
      if (ssc != null) {
    ssc.close ();
    ssc = null;
      }
  } catch (IOException e) {
      logger.logFatal ("Failed to close serversocket on port " + port);
      stop ();
  }
    }

    /** Make sure all filters and handlers are available
     */
    private void loadClasses () {
  SProperties hProps = config.getProperties ("Handlers");
  SProperties chProps = config.getProperties ("CacheHandlers");
  handlerFactoryHandler =
      new HandlerFactoryHandler (hProps, chProps, config, getLogger ());

  String filters = config.getProperty ("Filters", "accessfilters","");
  socketAccessController =
      new SocketAccessController (filters, config, logger);
 
  String in = config.getProperty ("Filters", "httpinfilters","");
  String out = config.getProperty ("Filters", "httpoutfilters","");
  httpHeaderFilterer =
      new HttpHeaderFilterer (in, out, config, this);
    }

   
    /** Run the proxy in a separate thread. */
    public void start () {
  started = System.currentTimeMillis ()
  Thread t = new Thread (this, VERSION);
  running = true;
  t.start ();
    }

    /** Run the proxy in a separate thread. */
    public void stop () {
  // TODO: what level do we want here?
  getLogger ().logFatal ("HttpProxy.stop() called, shutting down");
  closeSocket ();
  // TODO: wait for remaining connections.
  // TODO: as it is now, it will just close connections in the middle.
  executorService.shutdown ()
  logger.close ();
  cache.flush ();
  cache.stop ()
  running = false;
    }

    public void run () {
  while (running) {
      while (!accepting || !selector.isOpen ()) {
    try {
        // wait for reconfigure
        Thread.sleep (2 * 1000);
    } catch (InterruptedException e) {
        // ignore
    }
      }
      try {
    int num = selector.select (10 * 1000);
    if (selector.isOpen ()) {
        cancelTimeouts ();
        handleSelects ();
        runReturnedTasks ();
    }
      } catch (IOException e) {
    logger.logError ("Failed to accept, " +
         "trying to restart serversocket: " + e);
    closeSocket ();
    openSocket ();
      } catch (Exception e) {
    logger.logError ("Unknown error: " + e +
         " attemting to ignore");
    e.printStackTrace ();
      }
  } 
    }

    private void cancelTimeouts () throws IOException {
  long now = System.currentTimeMillis ();
  for (SelectionKey sk : selector.keys ()) {
      Object a = sk.attachment ();
      if (a instanceof String) {
    // ignore, this is used for status.
      } else {
    HandlerRegistration hr = (HandlerRegistration)a;
    if (hr != null && now - hr.when > 60 * 1000) {
        cancelKeyAndCloseChannel (sk);
        hr.handler.timeout ();
    }
      }
  }
    }
   
    /** Close down a client that has timed out.
     */
    private void cancelKeyAndCloseChannel (SelectionKey sk) {
  sk.cancel ();
  try {
      SocketChannel sc = (SocketChannel)sk.channel ();
      sc.close ();
  } catch (IOException e) {
      logger.logError ("failed to shutdown and close socket: " + e);
  }
    }
   
    private void handleSelects () throws IOException {
  Set<SelectionKey> selected = selector.selectedKeys ();
  for (Iterator<SelectionKey> i = selected.iterator (); i.hasNext (); ) {
      SelectionKey sk = i.next ();
      Object a = sk.attachment ();
      if (a != null && a instanceof HandlerRegistration) {
    HandlerRegistration hr = (HandlerRegistration)a;
    if (hr != null && hr.handler != null)
        handle (sk, hr.handler);
      } else if (a == null) {
    logger.logWarn ("No handler for:" + sk);
      } else {
    // Ok, something is very bad here, try to shutdown the channel
    // and hope that we handle it ok elsewhere...
    logger.logError ("Bad handler for:" + sk + ": " + a);
    sk.cancel ();
    sk.channel ().close ();
      }
  }
  selected.clear ();
    }

    private void handle (SelectionKey sk, SocketHandler handler) {
  if (handler.useSeparateThread ()) {
      // need to cancel so that we do not get multiple selects...
      sk.cancel ();
      executorService.execute (handler);
  } else {
      handler.run ();
  }
    }

    private void runReturnedTasks () {
  synchronized (returnedTasks) {
      for (Runnable r : returnedTasks)
    r.run ();
      returnedTasks.clear ();
  }
    }

    public void runMainTask (Runnable r) {
  synchronized (returnedTasks) {
      returnedTasks.add (r);
      selector.wakeup ();
  }
    }

    public void runThreadTask (Runnable run) {
  executorService.execute (run);
    }

    public Cache<HttpHeader, HttpHeader> getCache () {
  return cache;
    }
   
    public Logger getLogger () {
  return logger;
    }

    public long getOffset () {
  return logger.getOffset ();
    }

    public long getStartTime () {
  return started;
    }

    ConnectionLogger getConnectionLogger () {
  return logger;
    }

    ServerSocketChannel getServerSocketChannel () {
  return ssc;
    }

    public Counter getCounter () {
  return counter;
    }

    SocketAccessController getSocketAccessController () {
  return socketAccessController;
    }

    HttpHeaderFilterer getHttpHeaderFilterer () {
  return httpHeaderFilterer;
    }

    public Config getConfig () {
  return config;
    }

    HandlerFactory getHandlerFactory (String mime) {
  return handlerFactoryHandler.getHandlerFactory (mime);
    }

    HandlerFactory getCacheHandlerFactory (String mime) {
  return handlerFactoryHandler.getCacheHandlerFactory (mime);
    }
   
    public String getVersion () {
  return VERSION;
    }

    public String getServerIdentity () {
  return serverIdentity;
    }

    /** Get the local host.
     * @return the InetAddress of the host the proxy is running on.
     */
    public InetAddress getHost () {
  return localhost;
    }

    /** Get the port this proxy is using.
     * @return the port number the proxy is listening on.
     */
    public int getPort () {
  return port;
    }

    /** Get the InetAddress for a given url.
     *  We do dns lookups on a separate thread until we have an
     *  asyncronous dns library.
     *  We jump back on the main thread before telling the listener.
     */
    public void getInetAddress (final URL url, final InetAddressListener ial) {
  if (isProxyConnected ()) {
      ial.lookupDone (proxy);
      return;
  }
  Runnable r = new Runnable () {
    public void run () {
        try {
      final InetAddress ia = dnsHandler.getInetAddress (url);
      runMainTask (new Runnable () {
        public void run () {
            ial.lookupDone (ia);
        }
          });
        } catch (final UnknownHostException e) {
      runMainTask (new Runnable () {
        public void run () {
            ial.unknownHost (e);
        }
          });
        }
    }
      };
  executorService.execute (r);
    }

    /** Get the port to connect to.
     * @param port the port we want to connect to.
     * @return the port to connect to.
     */
    public int getConnectPort (int port) {
  if (isProxyConnected ())    // are we talking through another proxy?
      return proxyport;
  return port;
    }

    /** Try hard to check if the given address matches the proxy.
     *  Will use the localhost name and all ip addresses.
     */
    public boolean isSelf (String uhost, int urlport) {
  if (urlport == getPort ()) {   
      String proxyhost = getHost ().getHostName ();     
      if (uhost.equalsIgnoreCase (proxyhost))
    return true;
      try {
    Enumeration<NetworkInterface> e =
        NetworkInterface.getNetworkInterfaces();
    while (e.hasMoreElements ()) {
        NetworkInterface ni = e.nextElement ();
        Enumeration<InetAddress> ei = ni.getInetAddresses ();
        while (ei.hasMoreElements ()) {
      InetAddress ia = ei.nextElement ();
      if (ia.getHostAddress ().equals (uhost))
          return true;
        }
    }
      } catch (SocketException e) {
    logger.logWarn ("failed to get network interfaces: " + e);
      }
  } 
  return false;
    }

    /** Is this proxy chained to another proxy?
     * @return true if the proxy is connected to another proxy.
     */
    public boolean isProxyConnected () {
  return proxy != null;
    }

    /** Get the authenticationstring to use for proxy.
     * @return an authentication string.
     */
    public String getProxyAuthString () {
  return config.getProperty (getClass ().getName (), "proxyauth");
    }

    /** Get a WebConnection.
     * @param header the http header to get the host and port from
     * @param wcl the listener that wants to get the connection.
     */
    public void getWebConnection (HttpHeader header,
          WebConnectionListener wcl) {
  conhandler.getConnection (header, wcl);
    }
   
    /** Release a WebConnection so that it may be reused if possible.
     * @param wc the WebConnection to release.
     */
    public void releaseWebConnection (WebConnection wc) {
  conhandler.releaseConnection (wc);
    }
   
    /** Mark a WebConnection for pipelining.
     * @param wc the WebConnection to mark.
     */
    public void markForPipelining (WebConnection wc) {
  conhandler.markForPipelining (wc);
    }

    /** Add a current connection
     * @param con the connection
     */
    public void addCurrentConnection (Connection con) {
  connections.add (con);
    }

    /** Remove a current connection.
     * @param con the connection
     */
    public void removeCurrentConnection (Connection con) {
  connections.remove (con);
    }

    /** Get the connection handler.
     */
    public ConnectionHandler getConnectionHandler () {
  return conhandler;
    }

    /** Get all the current connections
     */
    public List<Connection> getCurrentConnections () {
  return Collections.unmodifiableList (connections);
    }

    /** Update the currently transferred traffic statistics.
     */
    protected void updateTrafficLog (TrafficLoggerHandler tlh) {
  synchronized (this.tlh) {
      tlh.addTo (this.tlh);
  }
    }

    /** Get the currently transferred traffic statistics.
     */
    public TrafficLoggerHandler getTrafficLoggerHandler () {
  return tlh;
    }
}
TOP

Related Classes of rabbit.proxy.HttpProxy

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.