/rabbit4/src/rabbit/io/ConnectionHandler.java
# · Java · 314 lines · 243 code · 39 blank · 32 comment · 32 complexity · bf81bfc96ea055fd7eaf0820ad47359c MD5 · raw file
- package rabbit.io;
- import java.io.IOException;
- import java.net.InetAddress;
- import java.net.MalformedURLException;
- import java.net.URL;
- import java.util.ArrayList;
- import java.util.Collections;
- import java.util.List;
- import java.util.Map;
- import java.util.HashMap;
- import java.util.concurrent.ConcurrentHashMap;
- import java.util.logging.Level;
- import java.util.logging.Logger;
- import rabbit.http.HttpHeader;
- import rabbit.nio.NioHandler;
- import rabbit.nio.ReadHandler;
- import rabbit.util.Counter;
- import rabbit.util.SProperties;
- /** A class to handle the connections to the net.
- * Tries to reuse connections whenever possible.
- *
- * @author <a href="mailto:robo@khelekore.org">Robert Olofsson</a>
- */
- public class ConnectionHandler {
- // The logger to use
- private final Logger logger = Logger.getLogger (getClass ().getName ());
- // The counter to use.
- private final Counter counter;
- // The resolver to use
- private final Resolver resolver;
- // The available connections.
- private final Map<Address, List<WebConnection>> activeConnections;
- // The channels waiting for closing
- private final Map<WebConnection, CloseListener> wc2closer;
- // the keepalivetime.
- private long keepaliveTime = 1000;
- // should we use pipelining...
- private boolean usePipelining = true;
- // the nio handler
- private final NioHandler nioHandler;
- public ConnectionHandler (Counter counter, Resolver resolver,
- NioHandler nioHandler) {
- this.counter = counter;
- this.resolver = resolver;
- this.nioHandler = nioHandler;
- activeConnections = new HashMap<Address, List<WebConnection>> ();
- wc2closer = new ConcurrentHashMap<WebConnection, CloseListener> ();
- }
- /** Set the keep alive time for this handler.
- * @param milis the keep alive time in miliseconds.
- */
- public void setKeepaliveTime (long milis) {
- keepaliveTime = milis;
- }
- /** Get the current keep alive time.
- * @return the keep alive time in miliseconds.
- */
- public long getKeepaliveTime () {
- return keepaliveTime;
- }
- public Map<Address, List<WebConnection>> getActiveConnections () {
- return Collections.unmodifiableMap (activeConnections);
- }
- /** Get a WebConnection for the given header.
- * @param header the HttpHeader containing the URL to connect to.
- * @param wcl the Listener that wants the connection.
- */
- public void getConnection (final HttpHeader header,
- final WebConnectionListener wcl) {
- // TODO: should we use the Host: header if its available? probably...
- String requri = header.getRequestURI ();
- URL url = null;
- try {
- url = new URL (requri);
- } catch (MalformedURLException e) {
- wcl.failed (e);
- return;
- }
- int port = url.getPort () > 0 ? url.getPort () : 80;
- final int rport = resolver.getConnectPort (port);
- resolver.getInetAddress (url, new InetAddressListener () {
- public void lookupDone (InetAddress ia) {
- Address a = new Address (ia, rport);
- getConnection (header, wcl, a);
- }
- public void unknownHost (Exception e) {
- wcl.failed (e);
- }
- });
- }
- private void getConnection (HttpHeader header,
- WebConnectionListener wcl,
- Address a) {
- WebConnection wc = null;
- counter.inc ("WebConnections used");
- String method = header.getMethod ();
- if (method != null) {
- // since we should not retry POST (and other) we
- // have to get a fresh connection for them..
- method = method.trim ();
- if (!(method.equals ("GET") || method.equals ("HEAD"))) {
- wc = new WebConnection (a, counter);
- } else {
- wc = getPooledConnection (a, activeConnections);
- if (wc == null)
- wc = new WebConnection (a, counter);
- }
- try {
- wc.connect (nioHandler, wcl);
- } catch (IOException e) {
- wcl.failed (e);
- }
- } else {
- String err = "No method specified: " + header;
- wcl.failed (new IllegalArgumentException (err));
- }
- }
- private WebConnection
- getPooledConnection (Address a, Map<Address, List<WebConnection>> conns) {
- synchronized (conns) {
- List<WebConnection> pool = conns.get (a);
- if (pool != null) {
- if (pool.size () > 0) {
- WebConnection wc = pool.remove (pool.size () - 1);
- if (pool.isEmpty ())
- conns.remove (a);
- return unregister (wc);
- }
- }
- }
- return null;
- }
- private WebConnection unregister (WebConnection wc) {
- CloseListener closer = null;
- closer = wc2closer.remove (wc);
- if (closer != null)
- nioHandler.cancel (wc.getChannel (), closer);
- return wc;
- }
- private void removeFromPool (WebConnection wc,
- Map<Address, List<WebConnection>> conns) {
- synchronized (conns) {
- List<WebConnection> pool = conns.get (wc.getAddress ());
- if (pool != null) {
- pool.remove (wc);
- if (pool.isEmpty ())
- conns.remove (wc.getAddress ());
- }
- }
- }
- /** Return a WebConnection to the pool so that it may be reused.
- * @param wc the WebConnection to return.
- */
- public void releaseConnection (WebConnection wc) {
- counter.inc ("WebConnections released");
- if (!wc.getChannel ().isOpen ()) {
- return;
- }
- Address a = wc.getAddress ();
- if (!wc.getKeepalive ()) {
- closeWebConnection (wc);
- return;
- }
- synchronized (wc) {
- wc.setReleased ();
- }
- synchronized (activeConnections) {
- List<WebConnection> pool = activeConnections.get (a);
- if (pool == null) {
- pool = new ArrayList<WebConnection> ();
- activeConnections.put (a, pool);
- } else {
- if (pool.contains (wc)) {
- String err =
- "web connection already added to pool: " + wc;
- throw new IllegalStateException (err);
- }
- }
- try {
- pool.add (wc);
- CloseListener cl = new CloseListener (wc);
- wc2closer.put (wc, cl);
- cl.register ();
- } catch (IOException e) {
- logger.log (Level.WARNING,
- "Get IOException when setting up a CloseListener: ",
- e);
- closeWebConnection (wc);
- }
- }
- }
- private void closeWebConnection (WebConnection wc) {
- if (wc == null)
- return;
- if (!wc.getChannel ().isOpen ())
- return;
- try {
- wc.close ();
- } catch (IOException e) {
- logger.warning ("Failed to close WebConnection: " + wc);
- }
- }
- private class CloseListener implements ReadHandler {
- private WebConnection wc;
- private Long timeout;
- public CloseListener (WebConnection wc) throws IOException {
- this.wc = wc;
- }
- public void register () {
- timeout = nioHandler.getDefaultTimeout ();
- nioHandler.waitForRead (wc.getChannel (), this);
- }
- public void read () {
- closeChannel ();
- }
- public void closed () {
- closeChannel ();
- }
- public void timeout () {
- closeChannel ();
- }
- public Long getTimeout () {
- return timeout;
- }
- private void closeChannel () {
- try {
- wc2closer.remove (wc);
- removeFromPool (wc, activeConnections);
- wc.close ();
- } catch (IOException e) {
- String err =
- "CloseListener: Failed to close web connection: " + e;
- logger.warning (err);
- }
- }
- public boolean useSeparateThread () {
- return false;
- }
- public String getDescription () {
- return "ConnectionHandler$CloseListener: address: " +
- wc.getAddress ();
- }
- @Override public String toString () {
- return getClass ().getSimpleName () + "{wc: " + wc + "}@" +
- Integer.toString (hashCode (), 16);
- }
- }
- /** Mark a WebConnection ready for pipelining.
- * @param wc the WebConnection to mark ready for pipelining.
- */
- public void markForPipelining (WebConnection wc) {
- if (!usePipelining)
- return;
- synchronized (wc) {
- if (wc.getKeepalive ())
- wc.setMayPipeline (true);
- }
- }
- public void setup (SProperties config) {
- if (config == null)
- return;
- String kat = config.getProperty ("keepalivetime", "1000");
- try {
- setKeepaliveTime (Long.parseLong (kat));
- } catch (NumberFormatException e) {
- String err =
- "Bad number for ConnectionHandler keepalivetime: '" + kat + "'";
- logger.warning (err);
- }
- String up = config.get ("usepipelining");
- if (up == null)
- up = "true";
- usePipelining = up.equalsIgnoreCase ("true");
- }
- }