--- /dev/null
+// HttpManager.java\r
+// $Id: HttpManager.java,v 1.2 2010/06/15 17:53:12 smhuang Exp $\r
+// (c) COPYRIGHT MIT and INRIA, 1996.\r
+// Please first read the full copyright statement in file COPYRIGHT.html\r
+\r
+package org.w3c.www.protocol.http ;\r
+\r
+import java.util.Enumeration;\r
+import java.util.Hashtable;\r
+import java.util.Properties;\r
+\r
+import java.net.URL;\r
+\r
+import java.io.InputStream;\r
+import java.io.PrintStream;\r
+\r
+import org.w3c.www.mime.MimeHeaderHolder;\r
+import org.w3c.www.mime.MimeParser;\r
+import org.w3c.www.mime.MimeParserFactory;\r
+\r
+import org.w3c.util.LRUList;\r
+import org.w3c.util.ObservableProperties;\r
+import org.w3c.util.PropertyMonitoring;\r
+import org.w3c.util.SyncLRUList;\r
+\r
+class ManagerDescription {\r
+ HttpManager manager = null;\r
+ Properties properties = null;\r
+\r
+ final HttpManager getManager() {\r
+ return manager;\r
+ }\r
+\r
+ final boolean sameProperties(Properties props) {\r
+ if (props.size() != properties.size())\r
+ return false;\r
+ Enumeration e = props.propertyNames();\r
+ while (e.hasMoreElements()) {\r
+ String name = (String) e.nextElement();\r
+ String prop = properties.getProperty(name);\r
+ if ((prop == null) || (! prop.equals(props.getProperty(name))))\r
+ return false;\r
+ }\r
+ return true;\r
+ }\r
+\r
+ ManagerDescription(HttpManager manager, Properties props) {\r
+ this.manager = manager;\r
+ this.properties = (Properties)props.clone();\r
+ }\r
+}\r
+\r
+class ReplyFactory implements MimeParserFactory {\r
+\r
+ public MimeHeaderHolder createHeaderHolder(MimeParser parser) {\r
+ return new Reply(parser);\r
+ }\r
+\r
+}\r
+\r
+/**\r
+ * The client side HTTP request manager.\r
+ * This class is the user interface (along with the other public classes of\r
+ * this package) for the W3C client side library implementing HTTP. \r
+ * A typical request is launched though the following sequence:\r
+ * <pre>\r
+ * HttpManager manager = HttpManager.getManager() ;\r
+ * Request request = manager.createRequest() ;\r
+ * request.setMethod(HTTP.GET) ;\r
+ * request.setURL(new URL("http://www.w3.org/pub/WWW/"));\r
+ * Reply reply = manager.runRequest(request) ;\r
+ * // Get the reply input stream that contains the actual data:\r
+ * InputStream in = reply.getInputStream() ;\r
+ * ...\r
+ * </pre>\r
+ */\r
+\r
+public class HttpManager implements PropertyMonitoring {\r
+\r
+ private static final boolean debug = false;\r
+\r
+ private static final \r
+ String DEFAULT_SERVER_CLASS = "org.w3c.www.protocol.http.HttpBasicServer";\r
+\r
+ /**\r
+ * The name of the property indicating the class of HttpServer to use.\r
+ */\r
+ public static final\r
+ String SERVER_CLASS_P = "org.w3c.www.protocol.http.server";\r
+\r
+ /**\r
+ * The name of the property containing the ProprequestFilter to launch.\r
+ */\r
+ public static final \r
+ String FILTERS_PROP_P = "org.w3c.www.protocol.http.filters";\r
+ /**\r
+ * The maximum number of simultaneous connectionlrus.\r
+ */\r
+ public static final\r
+ String CONN_MAX_P = "org.w3c.www.protocol.http.connections.max";\r
+ /**\r
+ * The SO_TIMEOUT of the client socket.\r
+ */\r
+ public static final\r
+ String TIMEOUT_P = "org.w3c.www.protocol.http.connections.timeout";\r
+ /**\r
+ * The connection timeout of the client socket.\r
+ */\r
+ public static final\r
+ String CONN_TIMEOUT_P ="org.w3c.www.protocol.http.connections.connTimeout";\r
+ /**\r
+ * Header properties - The allowed drift for getting cached resources.\r
+ */\r
+ public static final \r
+ String MAX_STALE_P = "org.w3c.www.protocol.http.cacheControl.maxStale";\r
+ /**\r
+ * Header properties - The minium freshness required on cached resources.\r
+ */\r
+ public static final\r
+ String MIN_FRESH_P = "org.w3c.www.protocol.http.cacheControl.minFresh";\r
+ /**\r
+ * Header properties - Set the only if cached flag on requests.\r
+ */\r
+ public static final \r
+ String ONLY_IF_CACHED_P=\r
+ "org.w3c.www.protocol.http.cacheControl.onlyIfCached";\r
+ /**\r
+ * Header properties - Set the user agent.\r
+ */\r
+ public static final \r
+ String USER_AGENT_P = "org.w3c.www.protocol.http.userAgent";\r
+ /**\r
+ * Header properties - Set the accept header.\r
+ */\r
+ public static final \r
+ String ACCEPT_P = "org.w3c.www.protocol.http.accept";\r
+ /**\r
+ * Header properties - Set the accept language.\r
+ */\r
+ public static final \r
+ String ACCEPT_LANGUAGE_P = "org.w3c.www.protocol.http.acceptLanguage";\r
+ /**\r
+ * Header properties - Set the accept encodings.\r
+ */\r
+ public static final \r
+ String ACCEPT_ENCODING_P = "org.w3c.www.protocol.http.acceptEncoding";\r
+ /**\r
+ * Header properties - are we parsing answers in a lenient way?\r
+ */\r
+ public static final \r
+ String LENIENT_P = "org.w3c.www.protocol.http.lenient";\r
+ /**\r
+ * Header properties - should we reuse a connection for POST?\r
+ */\r
+ public static final \r
+ String KEEPBODY_P = "org.w3c.www.protocol.http.keepbody";\r
+ /**\r
+ * Header properties - Should we use a proxy ?\r
+ */\r
+ public static final\r
+ String PROXY_SET_P = "proxySet";\r
+ /**\r
+ * Header properties - What is the proxy host name.\r
+ */\r
+ public static final\r
+ String PROXY_HOST_P = "proxyHost";\r
+ /**\r
+ * Header properties - What is the proxy port number.\r
+ */\r
+ public static final\r
+ String PROXY_PORT_P = "proxyPort";\r
+\r
+ /**\r
+ * The default value for the <code>Accept</code> header.\r
+ */\r
+ public static final\r
+ String DEFAULT_ACCEPT = "*/*";\r
+ /**\r
+ * The default value for the <code>User-Agent</code> header.\r
+ */\r
+ public static final\r
+ String DEFAULT_USER_AGENT = "Jigsaw/2.2.6";\r
+\r
+ /**\r
+ * This array keeps track of all the created managers.\r
+ * A new manager (kind of HTTP client side context) is created for each\r
+ * diffferent set of properties.\r
+ */\r
+ private static ManagerDescription managers[] = new ManagerDescription[4];\r
+\r
+ /**\r
+ * The class to instantiate to create new HttpServer instances.\r
+ */\r
+ protected Class serverclass = null;\r
+ /**\r
+ * The properties we initialized from.\r
+ */\r
+ ObservableProperties props = null;\r
+ /**\r
+ * The server this manager knows about, indexed by FQDN of target servers.\r
+ */\r
+ protected Hashtable servers = null;\r
+ /**\r
+ * The template request (the request we will clone to create new requests)\r
+ */\r
+ protected Request template = null ;\r
+ /**\r
+ * The LRU list of connections.\r
+ */\r
+ protected LRUList connectionsLru = null;\r
+ /**\r
+ * The filter engine attached to this manager.\r
+ */\r
+ FilterEngine filteng = null;\r
+\r
+ protected int timeout = 300000;\r
+ protected int conn_timeout = 3000;\r
+ protected int conn_count = 0;\r
+ protected int conn_max = 5;\r
+ protected boolean lenient = true;\r
+ protected boolean keepbody = false;\r
+\r
+ protected Hashtable _tmp_servers = null; // synced during creation\r
+\r
+ /**\r
+ * Update the proxy configuration to match current properties setting.\r
+ * @return A boolean, <strong>true</strong> if change was done,\r
+ * <strong>false</strong> otherwise.\r
+ */\r
+\r
+ protected boolean updateProxy() {\r
+ boolean set = props.getBoolean(PROXY_SET_P, false);\r
+ if ( set ) {\r
+ // Wow using a proxy now !\r
+ String host = props.getString(PROXY_HOST_P, null);\r
+ int port = props.getInteger(PROXY_PORT_P, -1);\r
+ URL proxy = null;\r
+ try {\r
+ proxy = new URL("http", host, port, "/");\r
+ } catch (Exception ex) {\r
+ return false;\r
+ }\r
+ // Now if a proxy...\r
+ if (( proxy != null ) && (proxy.getHost() != null))\r
+ template.setProxy(proxy);\r
+ } else {\r
+ template.setProxy(null);\r
+ }\r
+ return true;\r
+ }\r
+\r
+ /**\r
+ * Get this manager properties.\r
+ * @return An ObservableProperties instance.\r
+ */\r
+\r
+ public final ObservableProperties getProperties() {\r
+ return props;\r
+ }\r
+\r
+ /**\r
+ * PropertyMonitoring implementation - Update properties on the fly !\r
+ * @param name The name of the property that has changed.\r
+ * @return A boolean, <strong>true</strong> if change is accepted,\r
+ * <strong>false</strong> otherwise.\r
+ */\r
+\r
+ public boolean propertyChanged(String name) {\r
+ Request tpl = template;\r
+ if ( name.equals(FILTERS_PROP_P) ) {\r
+ // FIXME\r
+ return true;\r
+ // return false;\r
+ } else if ( name.equals(TIMEOUT_P) ) {\r
+ setTimeout(props.getInteger(TIMEOUT_P, timeout));\r
+ return true;\r
+ } else if ( name.equals(CONN_TIMEOUT_P) ) {\r
+ setConnTimeout(props.getInteger(CONN_TIMEOUT_P, conn_timeout));\r
+ return true;\r
+ } else if ( name.equals(CONN_MAX_P) ) {\r
+ setMaxConnections(props.getInteger(CONN_MAX_P, conn_max));\r
+ return true;\r
+ } else if ( name.equals(MAX_STALE_P) ) {\r
+ int ival = props.getInteger(MAX_STALE_P, -1);\r
+ if ( ival >= 0 )\r
+ tpl.setMaxStale(ival);\r
+ return true;\r
+ } else if ( name.equals(MIN_FRESH_P) ) {\r
+ int ival = props.getInteger(MIN_FRESH_P, -1);\r
+ if ( ival >= 0 )\r
+ tpl.setMinFresh(ival);\r
+ return true;\r
+ } else if ( name.equals(LENIENT_P) ) {\r
+ lenient = props.getBoolean(LENIENT_P, lenient);\r
+ return true;\r
+ } else if ( name.equals(KEEPBODY_P) ) {\r
+ keepbody = props.getBoolean(KEEPBODY_P, keepbody);\r
+ return true;\r
+ } if ( name.equals(ONLY_IF_CACHED_P) ) {\r
+ tpl.setOnlyIfCached(props.getBoolean(ONLY_IF_CACHED_P, false));\r
+ return true;\r
+ } else if ( name.equals(USER_AGENT_P) ) {\r
+ tpl.setValue("user-agent"\r
+ , props.getString(USER_AGENT_P\r
+ , DEFAULT_USER_AGENT));\r
+ return true;\r
+ } else if ( name.equals(ACCEPT_P) ) {\r
+ tpl.setValue("accept" \r
+ , props.getString(ACCEPT_P, DEFAULT_ACCEPT));\r
+ return true;\r
+ } else if ( name.equals(ACCEPT_LANGUAGE_P) ) {\r
+ String sval = props.getString(ACCEPT_LANGUAGE_P, null);\r
+ if ( sval != null )\r
+ tpl.setValue("accept-language", sval);\r
+ return true;\r
+ } else if ( name.equals(ACCEPT_ENCODING_P) ) {\r
+ String sval = props.getString(ACCEPT_ENCODING_P, null);\r
+ if ( sval != null )\r
+ tpl.setValue("accept-encoding", sval);\r
+ return true;\r
+ } else if ( name.equals(PROXY_SET_P)\r
+ || name.equals(PROXY_HOST_P)\r
+ || name.equals(PROXY_PORT_P) ) {\r
+ return updateProxy();\r
+ } else {\r
+ return true;\r
+ } \r
+ }\r
+\r
+ /**\r
+ * Allow the manager to interact with the user if needed.\r
+ * This will, for example, allow prompting for paswords, etc.\r
+ * @param onoff Turn interaction on or off.\r
+ */\r
+\r
+ public void setAllowUserInteraction(boolean onoff) {\r
+ template.setAllowUserInteraction(onoff);\r
+ }\r
+\r
+ protected static synchronized \r
+ HttpManager getManager(Class managerclass, Properties p) \r
+ {\r
+ // Does such a manager exists already ?\r
+ for (int i = 0 ; i < managers.length ; i++) {\r
+ if ( managers[i] == null )\r
+ continue;\r
+ if ( managers[i].sameProperties(p) )\r
+ return managers[i].getManager();\r
+ }\r
+ // Get the props we will initialize from:\r
+ ObservableProperties props = null;\r
+ if ( p instanceof ObservableProperties )\r
+ props = (ObservableProperties) p;\r
+ else\r
+ props = new ObservableProperties(p);\r
+ // Create a new manager for this set of properties:\r
+ HttpManager manager = null;;\r
+ try {\r
+ Object o = managerclass.newInstance();\r
+ if (o instanceof HttpManager) {\r
+ manager = (HttpManager) o;\r
+ } else { // default value\r
+ manager = new HttpManager();\r
+ }\r
+ } catch (Exception ex) {\r
+ ex.printStackTrace();\r
+ manager = new HttpManager();\r
+ }\r
+ manager.props = props;\r
+ // Initialize this new manager filters:\r
+ String filters[] = props.getStringArray(FILTERS_PROP_P, null);\r
+ if ( filters != null ) {\r
+ for (int i = 0 ; i < filters.length ; i++) {\r
+ try {\r
+ Class c = Class.forName(filters[i]);\r
+ PropRequestFilter f = null;\r
+ f = (PropRequestFilter) c.newInstance();\r
+ //Added by Jeff Huang\r
+ //TODO: FIXIT\r
+ f.initialize(manager);\r
+ } catch (PropRequestFilterException ex) {\r
+ System.out.println("Couldn't initialize filter \""\r
+ + filters[i]\r
+ + "\" init failed: "\r
+ + ex.getMessage());\r
+ } catch (Exception ex) {\r
+ System.err.println("Error initializing prop filters:");\r
+ System.err.println("Coulnd't initialize ["\r
+ + filters[i]\r
+ + "]: " + ex.getMessage());\r
+ ex.printStackTrace();\r
+ System.exit(1);\r
+ }\r
+ }\r
+ }\r
+ // The factory to create MIME reply holders:\r
+ manager.factory = manager.getReplyFactory();\r
+ // The class to create HttpServer instances from\r
+ String c = props.getString(SERVER_CLASS_P, DEFAULT_SERVER_CLASS);\r
+ try {\r
+ manager.serverclass = Class.forName(c);\r
+ //Added by Jeff Huang\r
+ //TODO: FIXIT\r
+ } catch (Exception ex) {\r
+ System.err.println("Unable to initialize HttpManager: ");\r
+ System.err.println("Class \""+c+"\" not found, from property "\r
+ + SERVER_CLASS_P);\r
+ ex.printStackTrace();\r
+ System.exit(1);\r
+ }\r
+ // Setup the template request:\r
+ Request tpl = manager.template;\r
+ // Set some default headers value (from props)\r
+ // Check for a proxy ?\r
+ manager.updateProxy();\r
+ // CacheControl, only-if-cached\r
+ tpl.setOnlyIfCached(props.getBoolean(ONLY_IF_CACHED_P, false));\r
+ // CacheControl, maxstale\r
+ int ival = props.getInteger(MAX_STALE_P, -1);\r
+ if ( ival >= 0 )\r
+ tpl.setMaxStale(ival);\r
+ // CacheControl, minfresh:\r
+ ival = props.getInteger(MIN_FRESH_P, -1);\r
+ if ( ival >= 0 )\r
+ tpl.setMinFresh(ival);\r
+ // general, lenient\r
+ manager.lenient = props.getBoolean(LENIENT_P, true);\r
+ manager.keepbody = props.getBoolean(KEEPBODY_P, false);\r
+ // General, User agent\r
+ String sval;\r
+ tpl.setValue("user-agent"\r
+ , props.getString(USER_AGENT_P\r
+ , DEFAULT_USER_AGENT));\r
+ // General, Accept\r
+ tpl.setValue("accept" \r
+ , props.getString(ACCEPT_P, DEFAULT_ACCEPT));\r
+ // General, Accept-Language\r
+ sval = props.getString(ACCEPT_LANGUAGE_P, null);\r
+ if ( sval != null ) {\r
+ if (sval.trim().length() > 0) {\r
+ tpl.setValue("accept-language", sval);\r
+ }\r
+ }\r
+ // General, Accept-Encoding\r
+ sval = props.getString(ACCEPT_ENCODING_P, null);\r
+ if ( sval != null ) {\r
+ if (sval.trim().length() > 0) {\r
+ tpl.setValue("accept-encoding", sval);\r
+ }\r
+ }\r
+ // Maximum number of allowed connections:\r
+ manager.conn_max = props.getInteger(CONN_MAX_P, 5);\r
+ // timeout value\r
+ manager.timeout = props.getInteger(TIMEOUT_P, manager.timeout);\r
+ // connection timeout\r
+ manager.conn_timeout = props.getInteger(CONN_TIMEOUT_P, \r
+ manager.conn_timeout);\r
+ // Register ourself as a property observer:\r
+ props.registerObserver(manager);\r
+ // Register that manager in our knwon managers:\r
+ for (int i = 0 ; i < managers.length ; i++) {\r
+ if ( managers[i] == null ) {\r
+ managers[i] = new ManagerDescription(manager, p);\r
+ return manager;\r
+ }\r
+ }\r
+ ManagerDescription nm[] = new ManagerDescription[managers.length << 1];\r
+ System.arraycopy(managers, 0, nm, 0, managers.length);\r
+ nm[managers.length] = new ManagerDescription(manager, p);\r
+ managers = nm;\r
+ return manager;\r
+ }\r
+ \r
+\r
+ /**\r
+ * Get an instance of the HTTP manager.\r
+ * This method returns an actual instance of the HTTP manager. It may\r
+ * return different managers, if it decides to distribute the load on\r
+ * different managers (avoid the HttpManager being a bottleneck).\r
+ * @return An application wide instance of the HTTP manager.\r
+ */\r
+\r
+ public static synchronized HttpManager getManager(Properties p) {\r
+ return getManager(HttpManager.class, p);\r
+ }\r
+\r
+ public static HttpManager getManager() {\r
+ return getManager(System.getProperties());\r
+ }\r
+\r
+ /**\r
+ * Get the String key for the server instance handling that request.\r
+ * This method takes care of any proxy setting (it will return the key\r
+ * to the proxy when required.)\r
+ * @return A uniq identifier for the handling server, as a String.\r
+ */\r
+\r
+ public final String getServerKey(Request request) {\r
+ URL proxy = request.getProxy();\r
+ URL target = request.getURL();\r
+ String key = null;\r
+ if ( proxy != null ) {\r
+ return ((proxy.getPort() == 80)\r
+ ? proxy.getHost().toLowerCase()\r
+ : (proxy.getHost().toLowerCase()+":"+proxy.getPort()));\r
+ } else {\r
+ return ((target.getPort() == 80)\r
+ ? target.getHost().toLowerCase()\r
+ : (target.getHost().toLowerCase()+":"+target.getPort()));\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Get the appropriate server object for handling request to given target.\r
+ * @param key The server's key, as returned by <code>getServerKey</code>.\r
+ * @return An object complying to the HttpServer interface.\r
+ * @exception HttpException If the given host name couldn't be resolved.\r
+ */\r
+\r
+ protected HttpServer lookupServer(String host, int port)\r
+ throws HttpException\r
+ {\r
+ int p = (port == -1) ? 80 : port;\r
+ String id = ((p == 80) \r
+ ? host.toLowerCase() \r
+ : (host.toLowerCase() +":"+p));\r
+ // Check for an existing server:\r
+ HttpServer server = (HttpServer) servers.get(id);\r
+ if ( server != null ) {\r
+ return server;\r
+ }\r
+ // Create and register a new server:\r
+ synchronized (_tmp_servers) {\r
+ if (_tmp_servers.containsKey(id)) {\r
+ server = (HttpServer) _tmp_servers.get(id);\r
+ synchronized (server) {\r
+ while (server.state == null || \r
+ server.state.state != HttpServerState.PREINIT) {\r
+ try {\r
+ wait(100);\r
+ } catch (InterruptedException ex) {\r
+ } catch (IllegalMonitorStateException ex) {\r
+ break;\r
+ }\r
+ }\r
+ }\r
+ if (server.state.state == HttpServerState.OK) {\r
+ return server;\r
+ } else if (server.state.ex != null) {\r
+ throw server.state.ex;\r
+ } else {\r
+ throw new RuntimeException("Unexpected error "+\r
+ "in lookupServer");\r
+ }\r
+ } else {\r
+ try {\r
+ server = (HttpServer) serverclass.newInstance();\r
+ } catch (Exception ex) {\r
+ String msg = ("Unable to create an instance of \""\r
+ + serverclass.getName()\r
+ + "\", invalid config, check the "\r
+ + SERVER_CLASS_P + " property.");\r
+ throw new HttpException(ex, msg);\r
+ } \r
+ }\r
+ }\r
+ try {\r
+ synchronized (server) {\r
+ server.initialize(this, new HttpServerState(server), host, p,\r
+ timeout, conn_timeout);\r
+ try {\r
+ notifyAll();\r
+ } catch (IllegalMonitorStateException imse) {};\r
+ }\r
+ // FIXME for long running servers the growing hashtable is\r
+ // a potential leak. This is a hard way of taking care of that\r
+// if (servers.size() > conn_max) {\r
+// closeAnyConnection();\r
+// servers = new Hashtable(conn_max);\r
+// }\r
+ } finally {\r
+ synchronized (_tmp_servers) {\r
+ if (server.state.state == HttpServerState.OK) {\r
+ if (!servers.containsKey(id)) {\r
+ servers.put(id, server);\r
+ }\r
+ } else {\r
+// System.err.println("ERROR State is "+server.state.state\r
+// +" for " + server);\r
+ }\r
+ _tmp_servers.remove(id);\r
+ }\r
+ }\r
+ return server;\r
+ }\r
+\r
+ /**\r
+ * The given connection is about to be used.\r
+ * Update our list of available servers.\r
+ * @param conn The idle connection.\r
+ */\r
+\r
+ public synchronized void notifyUse(HttpConnection conn) {\r
+ if (debug)\r
+ System.out.println("+++ connection used");\r
+ connectionsLru.remove(conn);\r
+ }\r
+\r
+ /**\r
+ * The given connection can be reused, but is now idle.\r
+ * @param conn The connection that is now idle.\r
+ */\r
+\r
+ public synchronized void notifyIdle(HttpConnection conn) {\r
+ if (debug)\r
+ System.out.println("+++ connection idle");\r
+ connectionsLru.toHead(conn);\r
+ notifyAll();\r
+ }\r
+\r
+ /**\r
+ * The given connection has just been created.\r
+ * @param conn The newly created connection.\r
+ */\r
+\r
+ protected synchronized void notifyConnection(HttpConnection conn) {\r
+ if (debug)\r
+ System.out.println("+++ notify conn_count " + (conn_count+1)\r
+ + " / " + conn_max);\r
+ if ( ++conn_count > conn_max )\r
+ closeAnyConnection();\r
+ }\r
+\r
+ /**\r
+ * The given connection has been deleted.\r
+ * @param conn The deleted connection.\r
+ */\r
+\r
+ protected synchronized void deleteConnection(HttpConnection conn) {\r
+ --conn_count;\r
+ connectionsLru.remove(conn);\r
+ if (debug)\r
+ System.out.println("+++ delete conn_count: " + conn_count);\r
+ notifyAll();\r
+ }\r
+\r
+ protected synchronized boolean tooManyConnections() {\r
+ return conn_count >= conn_max;\r
+ }\r
+\r
+ /**\r
+ * Try reusing one of the idle connection of that server, if any.\r
+ * @param server The target server.\r
+ * @return An currently idle connection to the given server.\r
+ */\r
+\r
+ protected synchronized HttpConnection getConnection(HttpServer server) {\r
+ HttpServerState ss = server.getState();\r
+ HttpConnection hcn = ss.getConnection();\r
+ if (hcn != null) {\r
+ notifyUse(hcn);\r
+ }\r
+ return hcn;\r
+ }\r
+\r
+ /**\r
+ * Wait for a connection to come up.\r
+ * @param server, the target server.\r
+ * @exception InterruptedException If interrupted..\r
+ */\r
+\r
+ protected synchronized void waitForConnection(HttpServer server)\r
+ throws InterruptedException\r
+ {\r
+ wait(30000); // FIXME should be tunable, now set to 30s\r
+ }\r
+\r
+ /**\r
+ * Close some connections, but pickling the least recently used ones.\r
+ * One third of the max number of connection is cut. This is done to \r
+ * eliminate the old connections that should be broken already.\r
+ * (no Socket.isAlive());\r
+ * @return A boolean, <strong>true</strong> if a connection was closed\r
+ * <strong>false</strong> otherwise.\r
+ */\r
+\r
+ protected synchronized boolean closeAnyConnection() {\r
+ boolean saved = false;\r
+ int max = Math.max(conn_max/3, 1);\r
+ for (int i=0; i < max; i++) {\r
+ HttpConnection conn = (HttpConnection) connectionsLru.removeTail();\r
+ if ( conn != null ) {\r
+ conn.close();\r
+ if (debug)\r
+ System.out.println("+++ close request");\r
+ saved = true;\r
+ } else {\r
+ break;\r
+ }\r
+ }\r
+ // now purge the server Hashtable\r
+ synchronized (servers) {\r
+ Enumeration e = servers.keys();\r
+ if (debug) {\r
+ System.out.println("+++ hashtable purge starting: "\r
+ + servers.size() + " entries");\r
+ }\r
+ int nbconn = 0;\r
+ int rnbconn = 0;\r
+ int nbkept = 0;\r
+ while (e.hasMoreElements()) {\r
+ String id = (String) e.nextElement();\r
+ if (id != null) {\r
+ HttpServer server = (HttpServer) servers.get(id);\r
+ int conn_count = server.state.getConnectionCount();\r
+ if (conn_count <= 0) {\r
+ if (debug) {\r
+ System.out.println("+++ hashtable purge: "+id);\r
+ } \r
+ servers.remove(id);\r
+ } else {\r
+ if (debug) {\r
+ nbkept++;\r
+ nbconn += server.state.getConnectionCount();\r
+ if (server.state.conns != null) {\r
+ rnbconn += server.state.conns.size();\r
+ }\r
+ System.out.println("+++ hashtable keep: "+id\r
+ +" ( "\r
+ +server.state.getConnectionCount()\r
+ +" )");\r
+ }\r
+ }\r
+ }\r
+ }\r
+ if (debug) {\r
+ System.out.println("+++ hashtable purge done, keeping "\r
+ + servers.size() + " entries");\r
+ System.out.println("+++ hashtable stats, keeping "\r
+ + nbconn + " ( "+ rnbconn\r
+ + " ) connections for " + nbkept \r
+ + " servers ( "\r
+ + ((float)nbconn / (float)nbkept) + " )");\r
+ }\r
+ \r
+ }\r
+ return saved;\r
+ }\r
+\r
+ /**\r
+ * One of our server handler wants to open a connection.\r
+ * @param block A boolean indicating whether we should block the calling\r
+ * thread until a token is available (otherwise, the method will just\r
+ * peek at the connection count, and return the appropriate result).\r
+ * @return A boolean, <strong>true</strong> if the connection can be\r
+ * opened straight, <strong>false</strong> otherwise.\r
+ */\r
+\r
+ protected boolean negotiateConnection(HttpServer server) {\r
+ HttpServerState ss = server.getState();\r
+ if ( ! tooManyConnections() ) {\r
+ return true;\r
+ } else if ( ss.notEnoughConnections() ) {\r
+ return closeAnyConnection();\r
+ } else if ( servers.size() > conn_max ) {\r
+ return closeAnyConnection();\r
+ }\r
+ return false;\r
+ }\r
+\r
+ /**\r
+ * A new client connection has been established.\r
+ * This method will try to maintain a maximum number of established\r
+ * connections, by closing idle connections when possible.\r
+ * @param server The server that has established a new connection.\r
+ */\r
+\r
+ protected final synchronized void incrConnCount(HttpServer server) {\r
+ if ( ++conn_count > conn_max )\r
+ closeAnyConnection();\r
+ if (debug)\r
+ System.out.println("+++ incr conn_count: " + conn_count);\r
+ }\r
+\r
+ /**\r
+ * Decrement the number of established connections.\r
+ * @param server The server that has closed one connection to its target.\r
+ */\r
+\r
+ protected final synchronized void decrConnCount(HttpServer server) {\r
+ --conn_count;\r
+ if (debug)\r
+ System.out.println("+++ decr conn_count: " + conn_count);\r
+ if (conn_count < 0) {\r
+ System.err.println(this);\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Run the given request, in synchronous mode.\r
+ * This method will launch the given request, and block the calling thread\r
+ * until the response headers are available.\r
+ * @param request The request to run.\r
+ * @return An instance of Reply, containing all the reply \r
+ * informations.\r
+ * @exception HttpException If something failed during request processing.\r
+ */\r
+\r
+ public Reply runRequest(Request request)\r
+ throws HttpException\r
+ {\r
+ Reply reply = null;\r
+ int fcalls = 0;\r
+ // Now run through the ingoing filters:\r
+ RequestFilter filters[] = filteng.run(request);\r
+ if ( filters != null ) {\r
+ for (int i = 0 ; i < filters.length ; i++) {\r
+ if ((reply = filters[fcalls].ingoingFilter(request)) != null)\r
+ break;\r
+ fcalls++;\r
+ }\r
+ }\r
+ // Locate the appropriate target server:\r
+ URL target = request.getURL();\r
+ if ( reply == null ) {\r
+ HttpServer srv = null;\r
+ boolean rtry ;\r
+ do {\r
+ rtry = false;\r
+ try {\r
+ URL proxy = request.getProxy();\r
+ if ( proxy != null ) \r
+ srv = lookupServer(proxy.getHost(), proxy.getPort());\r
+ else\r
+ srv = lookupServer(target.getHost(), target.getPort());\r
+ request.setServer(srv);\r
+ reply = srv.runRequest(request);\r
+ } catch (HttpException ex) {\r
+ for (int i = 0; i < fcalls; i++)\r
+ rtry = rtry || filters[i].exceptionFilter(request, ex);\r
+ if ( ! rtry )\r
+ throw ex;\r
+ } finally {\r
+// request.unsetServer();\r
+ }\r
+ } while (rtry);\r
+ }\r
+ // Apply the filters on the way back:\r
+ if ( filters != null ) {\r
+ while (--fcalls >= 0) {\r
+ Reply frep = filters[fcalls].outgoingFilter(request, reply);\r
+ if ( frep != null ) {\r
+ reply = frep;\r
+ break;\r
+ }\r
+ }\r
+ }\r
+ return reply;\r
+ }\r
+\r
+ /**\r
+ * Get this manager's reply factory.\r
+ * The Reply factory is used when prsing incomming reply from servers, it\r
+ * decides what object will be created to hold the actual reply from the \r
+ * server.\r
+ * @return An object compatible with the MimeParserFactory interface.\r
+ */\r
+\r
+ MimeParserFactory factory = null ;\r
+\r
+ public MimeParserFactory getReplyFactory() {\r
+ if (factory == null) {\r
+ factory = new ReplyFactory();\r
+ }\r
+ return factory;\r
+ }\r
+\r
+ /**\r
+ * Add a new request filter.\r
+ * Request filters are called <em>before</em> a request is launched, and\r
+ * <em>after</em> the reply headers are available. They allow applications\r
+ * to setup specific request headers (such as PICS, or PEP stuff) on the\r
+ * way in, and check the reply on the way out.\r
+ * <p>Request filters are application wide: if their scope matches\r
+ * the current request, then they will always be aplied.\r
+ * <p>Filter scopes are defined inclusively and exclusively\r
+ * @param incs The URL domains for which the filter should be triggered.\r
+ * @param exs The URL domains for which the filter should not be triggered.\r
+ * @param filter The request filter to add.\r
+ */\r
+\r
+ public void setFilter(URL incs[], URL exs[], RequestFilter filter) {\r
+ if ( incs != null ) {\r
+ for (int i = 0 ; i < incs.length ; i++)\r
+ filteng.setFilter(incs[i], true, filter);\r
+ }\r
+ if ( exs != null ) {\r
+ for (int i = 0 ; i < exs.length ; i++)\r
+ filteng.setFilter(exs[i], false, filter);\r
+ }\r
+ return;\r
+ }\r
+\r
+ /**\r
+ * Add a global filter.\r
+ * The given filter will <em>always</em> be invoked.\r
+ * @param filter The filter to install.\r
+ */\r
+\r
+ public void setFilter(RequestFilter filter) {\r
+ filteng.setFilter(filter);\r
+ }\r
+\r
+ /**\r
+ * Find back an instance of a global filter.\r
+ * This methods allow external classes to get a pointer to installed\r
+ * filters of a given class.\r
+ * @param cls The class of the filter to look for.\r
+ * @return A RequestFilter instance, or <strong>null</strong> if not\r
+ * found.\r
+ */\r
+\r
+ public RequestFilter getGlobalFilter(Class cls) {\r
+ return filteng.getGlobalFilter(cls);\r
+ }\r
+\r
+ /**\r
+ * Create a new default outgoing request.\r
+ * This method should <em>always</em> be used to create outgoing requests.\r
+ * It will initialize the request with appropriate default values for \r
+ * the various headers, and make sure that the request is enhanced by\r
+ * the registered request filters.\r
+ * @return An instance of Request, suitable to be launched.\r
+ */\r
+\r
+ public Request createRequest() {\r
+ return (Request) template.getDeeperClone() ;\r
+ }\r
+\r
+ /**\r
+ * Global settings - Set the max number of allowed connections.\r
+ * Set the maximum number of simultaneous connections that can remain\r
+ * opened. The manager will take care of queuing requests if this number\r
+ * is reached.\r
+ * <p>This value defaults to the value of the \r
+ * <code>org.w3c.www.http.connections.max</code> property.\r
+ * @param max_conn The allowed maximum simultaneous open connections.\r
+ */\r
+\r
+ public synchronized void setMaxConnections(int max_conn) {\r
+ this.conn_max = max_conn;\r
+ }\r
+\r
+ /**\r
+ * Global settings - Set the timeout on the socket\r
+ *\r
+ * <p>This value defaults to the value of the \r
+ * <code>org.w3c.www.http.connections.timeout</code> property.\r
+ * @param timeout The allowed maximum microsecond before a timeout.\r
+ */\r
+\r
+ public synchronized void setTimeout(int timeout) {\r
+ this.timeout = timeout;\r
+ Enumeration e = servers.elements();\r
+ while (e.hasMoreElements()) {\r
+ ((HttpServer) e.nextElement()).setTimeout(timeout);\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Global settings - Set the connection timeout for the socket\r
+ *\r
+ * <p>This value defaults to the value of the \r
+ * <code>org.w3c.www.protocol.http.connections.connTimeout</code> property\r
+ * @param timeout The allowed maximum microsecond before a timeout.\r
+ */\r
+\r
+ public synchronized void setConnTimeout(int conn_timeout) {\r
+ this.conn_timeout = conn_timeout;\r
+ Enumeration e = servers.elements();\r
+ while (e.hasMoreElements()) {\r
+ ((HttpServer) e.nextElement()).setConnTimeout(conn_timeout);\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Global settings - set the HTTP parsing lenient or not.\r
+ * @param lenient, true by default, false to detect wrong servers\r
+ */\r
+ public void setLenient(boolean lenient) {\r
+ this.lenient = lenient;\r
+ }\r
+\r
+ /**\r
+ * Is this manager parsing headers in a lenient way?\r
+ * @return A boolean.\r
+ */\r
+ public boolean isLenient() {\r
+ return lenient;\r
+ }\r
+\r
+ /**\r
+ * Global settings - Set an optional proxy to use.\r
+ * Set the proxy to which all requests should be targeted. If the\r
+ * <code>org.w3c.www.http.proxy</code> property is defined, it will be\r
+ * used as the default value.\r
+ * @param proxy The URL for the proxy to use.\r
+ */\r
+\r
+ public void setProxy(URL proxy) {\r
+ template.setProxy(proxy);\r
+ }\r
+\r
+ /**\r
+ * Does this manager uses a proxy to fulfill requests ?\r
+ * @return A boolean.\r
+ */\r
+\r
+ public boolean usingProxy() {\r
+ return template.hasProxy();\r
+ }\r
+\r
+ /**\r
+ * Global settings - Set the request timeout.\r
+ * Once a request has been emited, the HttpManager will sit for this \r
+ * given number of milliseconds before the request is declared to have\r
+ * timed-out.\r
+ * <p>This timeout value defaults to the value of the\r
+ * <code>org.w3c.www.http.requestTimeout</code> property value.\r
+ * @param ms The timeout value in milliseconds.\r
+ */\r
+\r
+ public void setRequestTimeout(int ms) {\r
+ }\r
+\r
+ /**\r
+ * Global settings - Define a global request header.\r
+ * Set a default value for some request header. Once defined, the\r
+ * header will automatically be defined on <em>all</em> outgoing requests\r
+ * created through the <code>createRequest</code> request.\r
+ * @param name The name of the header, case insensitive.\r
+ * @param value It's default value.\r
+ */\r
+\r
+ public void setGlobalHeader(String name, String value) {\r
+org.w3c.util.Trace.showTrace();\r
+System.err.println("**** " + name + " : " + value);\r
+ template.setValue(name, value);\r
+ }\r
+\r
+ /**\r
+ * Global settings - Get a global request header default value.\r
+ * @param name The name of the header to get.\r
+ * @return The value for that header, as a String, or <strong>\r
+ * null</strong> if undefined.\r
+ */\r
+\r
+ public String getGlobalHeader(String name) {\r
+ return template.getValue(name);\r
+ }\r
+\r
+ \r
+ /**\r
+ * Dump all in-memory cached state to persistent storage.\r
+ */\r
+\r
+ public void sync() {\r
+ filteng.sync();\r
+ }\r
+\r
+ /**\r
+ * Create a new HttpManager.\r
+ * FIXME Making this method protected breaks the static method\r
+ * to create HttpManager instances (should use a factory here)\r
+ * @param props The properties from which the manager should initialize \r
+ * itself, or <strong>null</strong> if none are available.\r
+ */\r
+\r
+ protected HttpManager() {\r
+ this.template = new Request(this);\r
+ this.servers = new Hashtable();\r
+ this._tmp_servers = new Hashtable();\r
+ this.filteng = new FilterEngine();\r
+ this.connectionsLru = new SyncLRUList();\r
+ }\r
+\r
+\r
+ /**\r
+ * DEBUGGING !\r
+ */\r
+\r
+ public synchronized String toString() {\r
+ StringBuffer sb = new StringBuffer();\r
+ HttpConnection hcn = (HttpConnection) connectionsLru.getHead();\r
+ sb.append("Connections: ");\r
+ sb.append(conn_count);\r
+ sb.append(" out of ");\r
+ sb.append(conn_max);\r
+ sb.append("\n\n");\r
+ if (hcn != null) {\r
+ sb.append("**** Idle Connections list ****\n");\r
+ while (hcn != null) {\r
+ sb.append(" ");\r
+ sb.append(hcn.toString());\r
+ sb.append('\n');\r
+ try {\r
+ hcn = (HttpConnection) hcn.getNext();\r
+ } catch (ClassCastException ccex) {\r
+ break;\r
+ }\r
+ }\r
+ } else { \r
+ sb.append ("*** NO IDLE CONNECTIONS ***\n");\r
+ }\r
+ sb.append(servers);\r
+ return sb.toString();\r
+ }\r
+\r
+ public static void main(String args[]) {\r
+ try {\r
+ // Get the manager, and define some global headers:\r
+ HttpManager manager = HttpManager.getManager();\r
+ manager.setGlobalHeader("User-Agent", DEFAULT_USER_AGENT);\r
+ manager.setGlobalHeader("Accept", "*/*;q=1.0");\r
+ manager.setGlobalHeader("Accept-Encoding", "gzip");\r
+ PropRequestFilter filter = \r
+ new org.w3c.www.protocol.http.cookies.CookieFilter();\r
+ filter.initialize(manager);\r
+ PropRequestFilter pdebug = \r
+ new org.w3c.www.protocol.http.DebugFilter();\r
+ pdebug.initialize(manager);\r
+ Request request = manager.createRequest();\r
+ request.setURL(new URL(args[0]));\r
+ request.setMethod("GET");\r
+ Reply reply = manager.runRequest(request);\r
+ //Display some infos:\r
+ System.out.println("last-modified: "+reply.getLastModified());\r
+ System.out.println("length : "+reply.getContentLength());\r
+ // Display the returned body:\r
+ InputStream in = reply.getInputStream();\r
+ byte buf[] = new byte[4096];\r
+ int cnt = 0;\r
+ while ((cnt = in.read(buf)) >= 0) {\r
+// System.out.print(new String(buf, 0, cnt));\r
+ }\r
+ System.out.println("-");\r
+ in.close();\r
+ manager.sync();\r
+ System.err.println(manager);\r
+ } catch (Exception ex) {\r
+ ex.printStackTrace();\r
+ if (ex instanceof HttpException) {\r
+ ((HttpException) ex).getException().printStackTrace();\r
+ }\r
+ }\r
+ System.exit(1);\r
+ }\r
+}\r
+\r
+\r