0001: // HttpManager.java
0002: // $Id: HttpManager.java,v 1.90 2007/04/10 13:22:22 ylafon Exp $
0003: // (c) COPYRIGHT MIT and INRIA, 1996.
0004: // Please first read the full copyright statement in file COPYRIGHT.html
0005:
0006: package org.w3c.www.protocol.http;
0007:
0008: import java.util.Enumeration;
0009: import java.util.Hashtable;
0010: import java.util.Properties;
0011:
0012: import java.net.URL;
0013:
0014: import java.io.InputStream;
0015: import java.io.PrintStream;
0016:
0017: import org.w3c.www.mime.MimeHeaderHolder;
0018: import org.w3c.www.mime.MimeParser;
0019: import org.w3c.www.mime.MimeParserFactory;
0020:
0021: import org.w3c.util.LRUList;
0022: import org.w3c.util.ObservableProperties;
0023: import org.w3c.util.PropertyMonitoring;
0024: import org.w3c.util.SyncLRUList;
0025:
0026: class ManagerDescription {
0027: HttpManager manager = null;
0028: Properties properties = null;
0029:
0030: final HttpManager getManager() {
0031: return manager;
0032: }
0033:
0034: final boolean sameProperties(Properties props) {
0035: if (props.size() != properties.size())
0036: return false;
0037: Enumeration e = props.propertyNames();
0038: while (e.hasMoreElements()) {
0039: String name = (String) e.nextElement();
0040: String prop = properties.getProperty(name);
0041: if ((prop == null)
0042: || (!prop.equals(props.getProperty(name))))
0043: return false;
0044: }
0045: return true;
0046: }
0047:
0048: ManagerDescription(HttpManager manager, Properties props) {
0049: this .manager = manager;
0050: this .properties = (Properties) props.clone();
0051: }
0052: }
0053:
0054: class ReplyFactory implements MimeParserFactory {
0055:
0056: public MimeHeaderHolder createHeaderHolder(MimeParser parser) {
0057: return new Reply(parser);
0058: }
0059:
0060: }
0061:
0062: /**
0063: * The client side HTTP request manager.
0064: * This class is the user interface (along with the other public classes of
0065: * this package) for the W3C client side library implementing HTTP.
0066: * A typical request is launched though the following sequence:
0067: * <pre>
0068: * HttpManager manager = HttpManager.getManager() ;
0069: * Request request = manager.createRequest() ;
0070: * request.setMethod(HTTP.GET) ;
0071: * request.setURL(new URL("http://www.w3.org/pub/WWW/"));
0072: * Reply reply = manager.runRequest(request) ;
0073: * // Get the reply input stream that contains the actual data:
0074: * InputStream in = reply.getInputStream() ;
0075: * ...
0076: * </pre>
0077: */
0078:
0079: public class HttpManager implements PropertyMonitoring {
0080:
0081: private static final boolean debug = false;
0082:
0083: private static final String DEFAULT_SERVER_CLASS = "org.w3c.www.protocol.http.HttpBasicServer";
0084:
0085: /**
0086: * The name of the property indicating the class of HttpServer to use.
0087: */
0088: public static final String SERVER_CLASS_P = "org.w3c.www.protocol.http.server";
0089:
0090: /**
0091: * The name of the property containing the ProprequestFilter to launch.
0092: */
0093: public static final String FILTERS_PROP_P = "org.w3c.www.protocol.http.filters";
0094: /**
0095: * The maximum number of simultaneous connectionlrus.
0096: */
0097: public static final String CONN_MAX_P = "org.w3c.www.protocol.http.connections.max";
0098: /**
0099: * The SO_TIMEOUT of the client socket.
0100: */
0101: public static final String TIMEOUT_P = "org.w3c.www.protocol.http.connections.timeout";
0102: /**
0103: * The connection timeout of the client socket.
0104: */
0105: public static final String CONN_TIMEOUT_P = "org.w3c.www.protocol.http.connections.connTimeout";
0106: /**
0107: * Header properties - The allowed drift for getting cached resources.
0108: */
0109: public static final String MAX_STALE_P = "org.w3c.www.protocol.http.cacheControl.maxStale";
0110: /**
0111: * Header properties - The minium freshness required on cached resources.
0112: */
0113: public static final String MIN_FRESH_P = "org.w3c.www.protocol.http.cacheControl.minFresh";
0114: /**
0115: * Header properties - Set the only if cached flag on requests.
0116: */
0117: public static final String ONLY_IF_CACHED_P = "org.w3c.www.protocol.http.cacheControl.onlyIfCached";
0118: /**
0119: * Header properties - Set the user agent.
0120: */
0121: public static final String USER_AGENT_P = "org.w3c.www.protocol.http.userAgent";
0122: /**
0123: * Header properties - Set the accept header.
0124: */
0125: public static final String ACCEPT_P = "org.w3c.www.protocol.http.accept";
0126: /**
0127: * Header properties - Set the accept language.
0128: */
0129: public static final String ACCEPT_LANGUAGE_P = "org.w3c.www.protocol.http.acceptLanguage";
0130: /**
0131: * Header properties - Set the accept encodings.
0132: */
0133: public static final String ACCEPT_ENCODING_P = "org.w3c.www.protocol.http.acceptEncoding";
0134: /**
0135: * Header properties - are we parsing answers in a lenient way?
0136: */
0137: public static final String LENIENT_P = "org.w3c.www.protocol.http.lenient";
0138: /**
0139: * Header properties - should we reuse a connection for POST?
0140: */
0141: public static final String KEEPBODY_P = "org.w3c.www.protocol.http.keepbody";
0142: /**
0143: * Header properties - Should we use a proxy ?
0144: */
0145: public static final String PROXY_SET_P = "proxySet";
0146: /**
0147: * Header properties - What is the proxy host name.
0148: */
0149: public static final String PROXY_HOST_P = "proxyHost";
0150: /**
0151: * Header properties - What is the proxy port number.
0152: */
0153: public static final String PROXY_PORT_P = "proxyPort";
0154:
0155: /**
0156: * The default value for the <code>Accept</code> header.
0157: */
0158: public static final String DEFAULT_ACCEPT = "*/*";
0159: /**
0160: * The default value for the <code>User-Agent</code> header.
0161: */
0162: public static final String DEFAULT_USER_AGENT = "Jigsaw/2.2.6";
0163:
0164: /**
0165: * This array keeps track of all the created managers.
0166: * A new manager (kind of HTTP client side context) is created for each
0167: * diffferent set of properties.
0168: */
0169: private static ManagerDescription managers[] = new ManagerDescription[4];
0170:
0171: /**
0172: * The class to instantiate to create new HttpServer instances.
0173: */
0174: protected Class serverclass = null;
0175: /**
0176: * The properties we initialized from.
0177: */
0178: ObservableProperties props = null;
0179: /**
0180: * The server this manager knows about, indexed by FQDN of target servers.
0181: */
0182: protected Hashtable servers = null;
0183: /**
0184: * The template request (the request we will clone to create new requests)
0185: */
0186: protected Request template = null;
0187: /**
0188: * The LRU list of connections.
0189: */
0190: protected LRUList connectionsLru = null;
0191: /**
0192: * The filter engine attached to this manager.
0193: */
0194: FilterEngine filteng = null;
0195:
0196: protected int timeout = 300000;
0197: protected int conn_timeout = 3000;
0198: protected int conn_count = 0;
0199: protected int conn_max = 5;
0200: protected boolean lenient = true;
0201: protected boolean keepbody = false;
0202:
0203: protected Hashtable _tmp_servers = null; // synced during creation
0204:
0205: /**
0206: * Update the proxy configuration to match current properties setting.
0207: * @return A boolean, <strong>true</strong> if change was done,
0208: * <strong>false</strong> otherwise.
0209: */
0210:
0211: protected boolean updateProxy() {
0212: boolean set = props.getBoolean(PROXY_SET_P, false);
0213: if (set) {
0214: // Wow using a proxy now !
0215: String host = props.getString(PROXY_HOST_P, null);
0216: int port = props.getInteger(PROXY_PORT_P, -1);
0217: URL proxy = null;
0218: try {
0219: proxy = new URL("http", host, port, "/");
0220: } catch (Exception ex) {
0221: return false;
0222: }
0223: // Now if a proxy...
0224: if ((proxy != null) && (proxy.getHost() != null))
0225: template.setProxy(proxy);
0226: } else {
0227: template.setProxy(null);
0228: }
0229: return true;
0230: }
0231:
0232: /**
0233: * Get this manager properties.
0234: * @return An ObservableProperties instance.
0235: */
0236:
0237: public final ObservableProperties getProperties() {
0238: return props;
0239: }
0240:
0241: /**
0242: * PropertyMonitoring implementation - Update properties on the fly !
0243: * @param name The name of the property that has changed.
0244: * @return A boolean, <strong>true</strong> if change is accepted,
0245: * <strong>false</strong> otherwise.
0246: */
0247:
0248: public boolean propertyChanged(String name) {
0249: Request tpl = template;
0250: if (name.equals(FILTERS_PROP_P)) {
0251: // FIXME
0252: return true;
0253: // return false;
0254: } else if (name.equals(TIMEOUT_P)) {
0255: setTimeout(props.getInteger(TIMEOUT_P, timeout));
0256: return true;
0257: } else if (name.equals(CONN_TIMEOUT_P)) {
0258: setConnTimeout(props.getInteger(CONN_TIMEOUT_P,
0259: conn_timeout));
0260: return true;
0261: } else if (name.equals(CONN_MAX_P)) {
0262: setMaxConnections(props.getInteger(CONN_MAX_P, conn_max));
0263: return true;
0264: } else if (name.equals(MAX_STALE_P)) {
0265: int ival = props.getInteger(MAX_STALE_P, -1);
0266: if (ival >= 0)
0267: tpl.setMaxStale(ival);
0268: return true;
0269: } else if (name.equals(MIN_FRESH_P)) {
0270: int ival = props.getInteger(MIN_FRESH_P, -1);
0271: if (ival >= 0)
0272: tpl.setMinFresh(ival);
0273: return true;
0274: } else if (name.equals(LENIENT_P)) {
0275: lenient = props.getBoolean(LENIENT_P, lenient);
0276: return true;
0277: } else if (name.equals(KEEPBODY_P)) {
0278: keepbody = props.getBoolean(KEEPBODY_P, keepbody);
0279: return true;
0280: }
0281: if (name.equals(ONLY_IF_CACHED_P)) {
0282: tpl.setOnlyIfCached(props.getBoolean(ONLY_IF_CACHED_P,
0283: false));
0284: return true;
0285: } else if (name.equals(USER_AGENT_P)) {
0286: tpl.setValue("user-agent", props.getString(USER_AGENT_P,
0287: DEFAULT_USER_AGENT));
0288: return true;
0289: } else if (name.equals(ACCEPT_P)) {
0290: tpl.setValue("accept", props.getString(ACCEPT_P,
0291: DEFAULT_ACCEPT));
0292: return true;
0293: } else if (name.equals(ACCEPT_LANGUAGE_P)) {
0294: String sval = props.getString(ACCEPT_LANGUAGE_P, null);
0295: if (sval != null)
0296: tpl.setValue("accept-language", sval);
0297: return true;
0298: } else if (name.equals(ACCEPT_ENCODING_P)) {
0299: String sval = props.getString(ACCEPT_ENCODING_P, null);
0300: if (sval != null)
0301: tpl.setValue("accept-encoding", sval);
0302: return true;
0303: } else if (name.equals(PROXY_SET_P)
0304: || name.equals(PROXY_HOST_P)
0305: || name.equals(PROXY_PORT_P)) {
0306: return updateProxy();
0307: } else {
0308: return true;
0309: }
0310: }
0311:
0312: /**
0313: * Allow the manager to interact with the user if needed.
0314: * This will, for example, allow prompting for paswords, etc.
0315: * @param onoff Turn interaction on or off.
0316: */
0317:
0318: public void setAllowUserInteraction(boolean onoff) {
0319: template.setAllowUserInteraction(onoff);
0320: }
0321:
0322: protected static synchronized HttpManager getManager(
0323: Class managerclass, Properties p) {
0324: // Does such a manager exists already ?
0325: for (int i = 0; i < managers.length; i++) {
0326: if (managers[i] == null)
0327: continue;
0328: if (managers[i].sameProperties(p))
0329: return managers[i].getManager();
0330: }
0331: // Get the props we will initialize from:
0332: ObservableProperties props = null;
0333: if (p instanceof ObservableProperties)
0334: props = (ObservableProperties) p;
0335: else
0336: props = new ObservableProperties(p);
0337: // Create a new manager for this set of properties:
0338: HttpManager manager = null;
0339: ;
0340: try {
0341: Object o = managerclass.newInstance();
0342: if (o instanceof HttpManager) {
0343: manager = (HttpManager) o;
0344: } else { // default value
0345: manager = new HttpManager();
0346: }
0347: } catch (Exception ex) {
0348: ex.printStackTrace();
0349: manager = new HttpManager();
0350: }
0351: manager.props = props;
0352: // Initialize this new manager filters:
0353: String filters[] = props.getStringArray(FILTERS_PROP_P, null);
0354: if (filters != null) {
0355: for (int i = 0; i < filters.length; i++) {
0356: try {
0357: Class c = Class.forName(filters[i]);
0358: PropRequestFilter f = null;
0359: f = (PropRequestFilter) c.newInstance();
0360: f.initialize(manager);
0361: } catch (PropRequestFilterException ex) {
0362: System.out.println("Couldn't initialize filter \""
0363: + filters[i] + "\" init failed: "
0364: + ex.getMessage());
0365: } catch (Exception ex) {
0366: System.err
0367: .println("Error initializing prop filters:");
0368: System.err.println("Coulnd't initialize ["
0369: + filters[i] + "]: " + ex.getMessage());
0370: ex.printStackTrace();
0371: System.exit(1);
0372: }
0373: }
0374: }
0375: // The factory to create MIME reply holders:
0376: manager.factory = manager.getReplyFactory();
0377: // The class to create HttpServer instances from
0378: String c = props
0379: .getString(SERVER_CLASS_P, DEFAULT_SERVER_CLASS);
0380: try {
0381: manager.serverclass = Class.forName(c);
0382: } catch (Exception ex) {
0383: System.err.println("Unable to initialize HttpManager: ");
0384: System.err.println("Class \"" + c
0385: + "\" not found, from property " + SERVER_CLASS_P);
0386: ex.printStackTrace();
0387: System.exit(1);
0388: }
0389: // Setup the template request:
0390: Request tpl = manager.template;
0391: // Set some default headers value (from props)
0392: // Check for a proxy ?
0393: manager.updateProxy();
0394: // CacheControl, only-if-cached
0395: tpl.setOnlyIfCached(props.getBoolean(ONLY_IF_CACHED_P, false));
0396: // CacheControl, maxstale
0397: int ival = props.getInteger(MAX_STALE_P, -1);
0398: if (ival >= 0)
0399: tpl.setMaxStale(ival);
0400: // CacheControl, minfresh:
0401: ival = props.getInteger(MIN_FRESH_P, -1);
0402: if (ival >= 0)
0403: tpl.setMinFresh(ival);
0404: // general, lenient
0405: manager.lenient = props.getBoolean(LENIENT_P, true);
0406: manager.keepbody = props.getBoolean(KEEPBODY_P, false);
0407: // General, User agent
0408: String sval;
0409: tpl.setValue("user-agent", props.getString(USER_AGENT_P,
0410: DEFAULT_USER_AGENT));
0411: // General, Accept
0412: tpl.setValue("accept", props
0413: .getString(ACCEPT_P, DEFAULT_ACCEPT));
0414: // General, Accept-Language
0415: sval = props.getString(ACCEPT_LANGUAGE_P, null);
0416: if (sval != null) {
0417: if (sval.trim().length() > 0) {
0418: tpl.setValue("accept-language", sval);
0419: }
0420: }
0421: // General, Accept-Encoding
0422: sval = props.getString(ACCEPT_ENCODING_P, null);
0423: if (sval != null) {
0424: if (sval.trim().length() > 0) {
0425: tpl.setValue("accept-encoding", sval);
0426: }
0427: }
0428: // Maximum number of allowed connections:
0429: manager.conn_max = props.getInteger(CONN_MAX_P, 5);
0430: // timeout value
0431: manager.timeout = props.getInteger(TIMEOUT_P, manager.timeout);
0432: // connection timeout
0433: manager.conn_timeout = props.getInteger(CONN_TIMEOUT_P,
0434: manager.conn_timeout);
0435: // Register ourself as a property observer:
0436: props.registerObserver(manager);
0437: // Register that manager in our knwon managers:
0438: for (int i = 0; i < managers.length; i++) {
0439: if (managers[i] == null) {
0440: managers[i] = new ManagerDescription(manager, p);
0441: return manager;
0442: }
0443: }
0444: ManagerDescription nm[] = new ManagerDescription[managers.length << 1];
0445: System.arraycopy(managers, 0, nm, 0, managers.length);
0446: nm[managers.length] = new ManagerDescription(manager, p);
0447: managers = nm;
0448: return manager;
0449: }
0450:
0451: /**
0452: * Get an instance of the HTTP manager.
0453: * This method returns an actual instance of the HTTP manager. It may
0454: * return different managers, if it decides to distribute the load on
0455: * different managers (avoid the HttpManager being a bottleneck).
0456: * @return An application wide instance of the HTTP manager.
0457: */
0458:
0459: public static synchronized HttpManager getManager(Properties p) {
0460: return getManager(HttpManager.class, p);
0461: }
0462:
0463: public static HttpManager getManager() {
0464: return getManager(System.getProperties());
0465: }
0466:
0467: /**
0468: * Get the String key for the server instance handling that request.
0469: * This method takes care of any proxy setting (it will return the key
0470: * to the proxy when required.)
0471: * @return A uniq identifier for the handling server, as a String.
0472: */
0473:
0474: public final String getServerKey(Request request) {
0475: URL proxy = request.getProxy();
0476: URL target = request.getURL();
0477: String key = null;
0478: if (proxy != null) {
0479: return ((proxy.getPort() == 80) ? proxy.getHost()
0480: .toLowerCase() : (proxy.getHost().toLowerCase()
0481: + ":" + proxy.getPort()));
0482: } else {
0483: return ((target.getPort() == 80) ? target.getHost()
0484: .toLowerCase() : (target.getHost().toLowerCase()
0485: + ":" + target.getPort()));
0486: }
0487: }
0488:
0489: /**
0490: * Get the appropriate server object for handling request to given target.
0491: * @param key The server's key, as returned by <code>getServerKey</code>.
0492: * @return An object complying to the HttpServer interface.
0493: * @exception HttpException If the given host name couldn't be resolved.
0494: */
0495:
0496: protected HttpServer lookupServer(String host, int port)
0497: throws HttpException {
0498: int p = (port == -1) ? 80 : port;
0499: String id = ((p == 80) ? host.toLowerCase() : (host
0500: .toLowerCase()
0501: + ":" + p));
0502: // Check for an existing server:
0503: HttpServer server = (HttpServer) servers.get(id);
0504: if (server != null) {
0505: return server;
0506: }
0507: // Create and register a new server:
0508: synchronized (_tmp_servers) {
0509: if (_tmp_servers.containsKey(id)) {
0510: server = (HttpServer) _tmp_servers.get(id);
0511: synchronized (server) {
0512: while (server.state == null
0513: || server.state.state != HttpServerState.PREINIT) {
0514: try {
0515: wait(100);
0516: } catch (InterruptedException ex) {
0517: } catch (IllegalMonitorStateException ex) {
0518: break;
0519: }
0520: }
0521: }
0522: if (server.state.state == HttpServerState.OK) {
0523: return server;
0524: } else if (server.state.ex != null) {
0525: throw server.state.ex;
0526: } else {
0527: throw new RuntimeException("Unexpected error "
0528: + "in lookupServer");
0529: }
0530: } else {
0531: try {
0532: server = (HttpServer) serverclass.newInstance();
0533: } catch (Exception ex) {
0534: String msg = ("Unable to create an instance of \""
0535: + serverclass.getName()
0536: + "\", invalid config, check the "
0537: + SERVER_CLASS_P + " property.");
0538: throw new HttpException(ex, msg);
0539: }
0540: }
0541: }
0542: try {
0543: synchronized (server) {
0544: server.initialize(this , new HttpServerState(server),
0545: host, p, timeout, conn_timeout);
0546: try {
0547: notifyAll();
0548: } catch (IllegalMonitorStateException imse) {
0549: }
0550: ;
0551: }
0552: // FIXME for long running servers the growing hashtable is
0553: // a potential leak. This is a hard way of taking care of that
0554: // if (servers.size() > conn_max) {
0555: // closeAnyConnection();
0556: // servers = new Hashtable(conn_max);
0557: // }
0558: } finally {
0559: synchronized (_tmp_servers) {
0560: if (server.state.state == HttpServerState.OK) {
0561: if (!servers.containsKey(id)) {
0562: servers.put(id, server);
0563: }
0564: } else {
0565: // System.err.println("ERROR State is "+server.state.state
0566: // +" for " + server);
0567: }
0568: _tmp_servers.remove(id);
0569: }
0570: }
0571: return server;
0572: }
0573:
0574: /**
0575: * The given connection is about to be used.
0576: * Update our list of available servers.
0577: * @param conn The idle connection.
0578: */
0579:
0580: public synchronized void notifyUse(HttpConnection conn) {
0581: if (debug)
0582: System.out.println("+++ connection used");
0583: connectionsLru.remove(conn);
0584: }
0585:
0586: /**
0587: * The given connection can be reused, but is now idle.
0588: * @param conn The connection that is now idle.
0589: */
0590:
0591: public synchronized void notifyIdle(HttpConnection conn) {
0592: if (debug)
0593: System.out.println("+++ connection idle");
0594: connectionsLru.toHead(conn);
0595: notifyAll();
0596: }
0597:
0598: /**
0599: * The given connection has just been created.
0600: * @param conn The newly created connection.
0601: */
0602:
0603: protected synchronized void notifyConnection(HttpConnection conn) {
0604: if (debug)
0605: System.out.println("+++ notify conn_count "
0606: + (conn_count + 1) + " / " + conn_max);
0607: if (++conn_count > conn_max)
0608: closeAnyConnection();
0609: }
0610:
0611: /**
0612: * The given connection has been deleted.
0613: * @param conn The deleted connection.
0614: */
0615:
0616: protected synchronized void deleteConnection(HttpConnection conn) {
0617: --conn_count;
0618: connectionsLru.remove(conn);
0619: if (debug)
0620: System.out.println("+++ delete conn_count: " + conn_count);
0621: notifyAll();
0622: }
0623:
0624: protected synchronized boolean tooManyConnections() {
0625: return conn_count >= conn_max;
0626: }
0627:
0628: /**
0629: * Try reusing one of the idle connection of that server, if any.
0630: * @param server The target server.
0631: * @return An currently idle connection to the given server.
0632: */
0633:
0634: protected synchronized HttpConnection getConnection(
0635: HttpServer server) {
0636: HttpServerState ss = server.getState();
0637: HttpConnection hcn = ss.getConnection();
0638: if (hcn != null) {
0639: notifyUse(hcn);
0640: }
0641: return hcn;
0642: }
0643:
0644: /**
0645: * Wait for a connection to come up.
0646: * @param server, the target server.
0647: * @exception InterruptedException If interrupted..
0648: */
0649:
0650: protected synchronized void waitForConnection(HttpServer server)
0651: throws InterruptedException {
0652: wait(30000); // FIXME should be tunable, now set to 30s
0653: }
0654:
0655: /**
0656: * Close some connections, but pickling the least recently used ones.
0657: * One third of the max number of connection is cut. This is done to
0658: * eliminate the old connections that should be broken already.
0659: * (no Socket.isAlive());
0660: * @return A boolean, <strong>true</strong> if a connection was closed
0661: * <strong>false</strong> otherwise.
0662: */
0663:
0664: protected synchronized boolean closeAnyConnection() {
0665: boolean saved = false;
0666: int max = Math.max(conn_max / 3, 1);
0667: for (int i = 0; i < max; i++) {
0668: HttpConnection conn = (HttpConnection) connectionsLru
0669: .removeTail();
0670: if (conn != null) {
0671: conn.close();
0672: if (debug)
0673: System.out.println("+++ close request");
0674: saved = true;
0675: } else {
0676: break;
0677: }
0678: }
0679: // now purge the server Hashtable
0680: synchronized (servers) {
0681: Enumeration e = servers.keys();
0682: if (debug) {
0683: System.out.println("+++ hashtable purge starting: "
0684: + servers.size() + " entries");
0685: }
0686: int nbconn = 0;
0687: int rnbconn = 0;
0688: int nbkept = 0;
0689: while (e.hasMoreElements()) {
0690: String id = (String) e.nextElement();
0691: if (id != null) {
0692: HttpServer server = (HttpServer) servers.get(id);
0693: int conn_count = server.state.getConnectionCount();
0694: if (conn_count <= 0) {
0695: if (debug) {
0696: System.out.println("+++ hashtable purge: "
0697: + id);
0698: }
0699: servers.remove(id);
0700: } else {
0701: if (debug) {
0702: nbkept++;
0703: nbconn += server.state.getConnectionCount();
0704: if (server.state.conns != null) {
0705: rnbconn += server.state.conns.size();
0706: }
0707: System.out.println("+++ hashtable keep: "
0708: + id + " ( "
0709: + server.state.getConnectionCount()
0710: + " )");
0711: }
0712: }
0713: }
0714: }
0715: if (debug) {
0716: System.out.println("+++ hashtable purge done, keeping "
0717: + servers.size() + " entries");
0718: System.out.println("+++ hashtable stats, keeping "
0719: + nbconn + " ( " + rnbconn
0720: + " ) connections for " + nbkept
0721: + " servers ( "
0722: + ((float) nbconn / (float) nbkept) + " )");
0723: }
0724:
0725: }
0726: return saved;
0727: }
0728:
0729: /**
0730: * One of our server handler wants to open a connection.
0731: * @param block A boolean indicating whether we should block the calling
0732: * thread until a token is available (otherwise, the method will just
0733: * peek at the connection count, and return the appropriate result).
0734: * @return A boolean, <strong>true</strong> if the connection can be
0735: * opened straight, <strong>false</strong> otherwise.
0736: */
0737:
0738: protected boolean negotiateConnection(HttpServer server) {
0739: HttpServerState ss = server.getState();
0740: if (!tooManyConnections()) {
0741: return true;
0742: } else if (ss.notEnoughConnections()) {
0743: return closeAnyConnection();
0744: } else if (servers.size() > conn_max) {
0745: return closeAnyConnection();
0746: }
0747: return false;
0748: }
0749:
0750: /**
0751: * A new client connection has been established.
0752: * This method will try to maintain a maximum number of established
0753: * connections, by closing idle connections when possible.
0754: * @param server The server that has established a new connection.
0755: */
0756:
0757: protected final synchronized void incrConnCount(HttpServer server) {
0758: if (++conn_count > conn_max)
0759: closeAnyConnection();
0760: if (debug)
0761: System.out.println("+++ incr conn_count: " + conn_count);
0762: }
0763:
0764: /**
0765: * Decrement the number of established connections.
0766: * @param server The server that has closed one connection to its target.
0767: */
0768:
0769: protected final synchronized void decrConnCount(HttpServer server) {
0770: --conn_count;
0771: if (debug)
0772: System.out.println("+++ decr conn_count: " + conn_count);
0773: if (conn_count < 0) {
0774: System.err.println(this );
0775: }
0776: }
0777:
0778: /**
0779: * Run the given request, in synchronous mode.
0780: * This method will launch the given request, and block the calling thread
0781: * until the response headers are available.
0782: * @param request The request to run.
0783: * @return An instance of Reply, containing all the reply
0784: * informations.
0785: * @exception HttpException If something failed during request processing.
0786: */
0787:
0788: public Reply runRequest(Request request) throws HttpException {
0789: Reply reply = null;
0790: int fcalls = 0;
0791: // Now run through the ingoing filters:
0792: RequestFilter filters[] = filteng.run(request);
0793: if (filters != null) {
0794: for (int i = 0; i < filters.length; i++) {
0795: if ((reply = filters[fcalls].ingoingFilter(request)) != null)
0796: break;
0797: fcalls++;
0798: }
0799: }
0800: // Locate the appropriate target server:
0801: URL target = request.getURL();
0802: if (reply == null) {
0803: HttpServer srv = null;
0804: boolean rtry;
0805: do {
0806: rtry = false;
0807: try {
0808: URL proxy = request.getProxy();
0809: if (proxy != null)
0810: srv = lookupServer(proxy.getHost(), proxy
0811: .getPort());
0812: else
0813: srv = lookupServer(target.getHost(), target
0814: .getPort());
0815: request.setServer(srv);
0816: reply = srv.runRequest(request);
0817: } catch (HttpException ex) {
0818: for (int i = 0; i < fcalls; i++)
0819: rtry = rtry
0820: || filters[i].exceptionFilter(request,
0821: ex);
0822: if (!rtry)
0823: throw ex;
0824: } finally {
0825: // request.unsetServer();
0826: }
0827: } while (rtry);
0828: }
0829: // Apply the filters on the way back:
0830: if (filters != null) {
0831: while (--fcalls >= 0) {
0832: Reply frep = filters[fcalls].outgoingFilter(request,
0833: reply);
0834: if (frep != null) {
0835: reply = frep;
0836: break;
0837: }
0838: }
0839: }
0840: return reply;
0841: }
0842:
0843: /**
0844: * Get this manager's reply factory.
0845: * The Reply factory is used when prsing incomming reply from servers, it
0846: * decides what object will be created to hold the actual reply from the
0847: * server.
0848: * @return An object compatible with the MimeParserFactory interface.
0849: */
0850:
0851: MimeParserFactory factory = null;
0852:
0853: public MimeParserFactory getReplyFactory() {
0854: if (factory == null) {
0855: factory = new ReplyFactory();
0856: }
0857: return factory;
0858: }
0859:
0860: /**
0861: * Add a new request filter.
0862: * Request filters are called <em>before</em> a request is launched, and
0863: * <em>after</em> the reply headers are available. They allow applications
0864: * to setup specific request headers (such as PICS, or PEP stuff) on the
0865: * way in, and check the reply on the way out.
0866: * <p>Request filters are application wide: if their scope matches
0867: * the current request, then they will always be aplied.
0868: * <p>Filter scopes are defined inclusively and exclusively
0869: * @param incs The URL domains for which the filter should be triggered.
0870: * @param exs The URL domains for which the filter should not be triggered.
0871: * @param filter The request filter to add.
0872: */
0873:
0874: public void setFilter(URL incs[], URL exs[], RequestFilter filter) {
0875: if (incs != null) {
0876: for (int i = 0; i < incs.length; i++)
0877: filteng.setFilter(incs[i], true, filter);
0878: }
0879: if (exs != null) {
0880: for (int i = 0; i < exs.length; i++)
0881: filteng.setFilter(exs[i], false, filter);
0882: }
0883: return;
0884: }
0885:
0886: /**
0887: * Add a global filter.
0888: * The given filter will <em>always</em> be invoked.
0889: * @param filter The filter to install.
0890: */
0891:
0892: public void setFilter(RequestFilter filter) {
0893: filteng.setFilter(filter);
0894: }
0895:
0896: /**
0897: * Find back an instance of a global filter.
0898: * This methods allow external classes to get a pointer to installed
0899: * filters of a given class.
0900: * @param cls The class of the filter to look for.
0901: * @return A RequestFilter instance, or <strong>null</strong> if not
0902: * found.
0903: */
0904:
0905: public RequestFilter getGlobalFilter(Class cls) {
0906: return filteng.getGlobalFilter(cls);
0907: }
0908:
0909: /**
0910: * Create a new default outgoing request.
0911: * This method should <em>always</em> be used to create outgoing requests.
0912: * It will initialize the request with appropriate default values for
0913: * the various headers, and make sure that the request is enhanced by
0914: * the registered request filters.
0915: * @return An instance of Request, suitable to be launched.
0916: */
0917:
0918: public Request createRequest() {
0919: return (Request) template.getDeeperClone();
0920: }
0921:
0922: /**
0923: * Global settings - Set the max number of allowed connections.
0924: * Set the maximum number of simultaneous connections that can remain
0925: * opened. The manager will take care of queuing requests if this number
0926: * is reached.
0927: * <p>This value defaults to the value of the
0928: * <code>org.w3c.www.http.connections.max</code> property.
0929: * @param max_conn The allowed maximum simultaneous open connections.
0930: */
0931:
0932: public synchronized void setMaxConnections(int max_conn) {
0933: this .conn_max = max_conn;
0934: }
0935:
0936: /**
0937: * Global settings - Set the timeout on the socket
0938: *
0939: * <p>This value defaults to the value of the
0940: * <code>org.w3c.www.http.connections.timeout</code> property.
0941: * @param timeout The allowed maximum microsecond before a timeout.
0942: */
0943:
0944: public synchronized void setTimeout(int timeout) {
0945: this .timeout = timeout;
0946: Enumeration e = servers.elements();
0947: while (e.hasMoreElements()) {
0948: ((HttpServer) e.nextElement()).setTimeout(timeout);
0949: }
0950: }
0951:
0952: /**
0953: * Global settings - Set the connection timeout for the socket
0954: *
0955: * <p>This value defaults to the value of the
0956: * <code>org.w3c.www.protocol.http.connections.connTimeout</code> property
0957: * @param timeout The allowed maximum microsecond before a timeout.
0958: */
0959:
0960: public synchronized void setConnTimeout(int conn_timeout) {
0961: this .conn_timeout = conn_timeout;
0962: Enumeration e = servers.elements();
0963: while (e.hasMoreElements()) {
0964: ((HttpServer) e.nextElement()).setConnTimeout(conn_timeout);
0965: }
0966: }
0967:
0968: /**
0969: * Global settings - set the HTTP parsing lenient or not.
0970: * @param lenient, true by default, false to detect wrong servers
0971: */
0972: public void setLenient(boolean lenient) {
0973: this .lenient = lenient;
0974: }
0975:
0976: /**
0977: * Is this manager parsing headers in a lenient way?
0978: * @return A boolean.
0979: */
0980: public boolean isLenient() {
0981: return lenient;
0982: }
0983:
0984: /**
0985: * Global settings - Set an optional proxy to use.
0986: * Set the proxy to which all requests should be targeted. If the
0987: * <code>org.w3c.www.http.proxy</code> property is defined, it will be
0988: * used as the default value.
0989: * @param proxy The URL for the proxy to use.
0990: */
0991:
0992: public void setProxy(URL proxy) {
0993: template.setProxy(proxy);
0994: }
0995:
0996: /**
0997: * Does this manager uses a proxy to fulfill requests ?
0998: * @return A boolean.
0999: */
1000:
1001: public boolean usingProxy() {
1002: return template.hasProxy();
1003: }
1004:
1005: /**
1006: * Global settings - Set the request timeout.
1007: * Once a request has been emited, the HttpManager will sit for this
1008: * given number of milliseconds before the request is declared to have
1009: * timed-out.
1010: * <p>This timeout value defaults to the value of the
1011: * <code>org.w3c.www.http.requestTimeout</code> property value.
1012: * @param ms The timeout value in milliseconds.
1013: */
1014:
1015: public void setRequestTimeout(int ms) {
1016: }
1017:
1018: /**
1019: * Global settings - Define a global request header.
1020: * Set a default value for some request header. Once defined, the
1021: * header will automatically be defined on <em>all</em> outgoing requests
1022: * created through the <code>createRequest</code> request.
1023: * @param name The name of the header, case insensitive.
1024: * @param value It's default value.
1025: */
1026:
1027: public void setGlobalHeader(String name, String value) {
1028: org.w3c.util.Trace.showTrace();
1029: System.err.println("**** " + name + " : " + value);
1030: template.setValue(name, value);
1031: }
1032:
1033: /**
1034: * Global settings - Get a global request header default value.
1035: * @param name The name of the header to get.
1036: * @return The value for that header, as a String, or <strong>
1037: * null</strong> if undefined.
1038: */
1039:
1040: public String getGlobalHeader(String name) {
1041: return template.getValue(name);
1042: }
1043:
1044: /**
1045: * Dump all in-memory cached state to persistent storage.
1046: */
1047:
1048: public void sync() {
1049: filteng.sync();
1050: }
1051:
1052: /**
1053: * Create a new HttpManager.
1054: * FIXME Making this method protected breaks the static method
1055: * to create HttpManager instances (should use a factory here)
1056: * @param props The properties from which the manager should initialize
1057: * itself, or <strong>null</strong> if none are available.
1058: */
1059:
1060: protected HttpManager() {
1061: this .template = new Request(this );
1062: this .servers = new Hashtable();
1063: this ._tmp_servers = new Hashtable();
1064: this .filteng = new FilterEngine();
1065: this .connectionsLru = new SyncLRUList();
1066: }
1067:
1068: /**
1069: * DEBUGGING !
1070: */
1071:
1072: public synchronized String toString() {
1073: StringBuffer sb = new StringBuffer();
1074: HttpConnection hcn = (HttpConnection) connectionsLru.getHead();
1075: sb.append("Connections: ");
1076: sb.append(conn_count);
1077: sb.append(" out of ");
1078: sb.append(conn_max);
1079: sb.append("\n\n");
1080: if (hcn != null) {
1081: sb.append("**** Idle Connections list ****\n");
1082: while (hcn != null) {
1083: sb.append(" ");
1084: sb.append(hcn.toString());
1085: sb.append('\n');
1086: try {
1087: hcn = (HttpConnection) hcn.getNext();
1088: } catch (ClassCastException ccex) {
1089: break;
1090: }
1091: }
1092: } else {
1093: sb.append("*** NO IDLE CONNECTIONS ***\n");
1094: }
1095: sb.append(servers);
1096: return sb.toString();
1097: }
1098:
1099: public static void main(String args[]) {
1100: try {
1101: // Get the manager, and define some global headers:
1102: HttpManager manager = HttpManager.getManager();
1103: manager.setGlobalHeader("User-Agent", DEFAULT_USER_AGENT);
1104: manager.setGlobalHeader("Accept", "*/*;q=1.0");
1105: manager.setGlobalHeader("Accept-Encoding", "gzip");
1106: PropRequestFilter filter = new org.w3c.www.protocol.http.cookies.CookieFilter();
1107: filter.initialize(manager);
1108: PropRequestFilter pdebug = new org.w3c.www.protocol.http.DebugFilter();
1109: pdebug.initialize(manager);
1110: Request request = manager.createRequest();
1111: request.setURL(new URL(args[0]));
1112: request.setMethod("GET");
1113: Reply reply = manager.runRequest(request);
1114: //Display some infos:
1115: System.out.println("last-modified: "
1116: + reply.getLastModified());
1117: System.out.println("length : "
1118: + reply.getContentLength());
1119: // Display the returned body:
1120: InputStream in = reply.getInputStream();
1121: byte buf[] = new byte[4096];
1122: int cnt = 0;
1123: while ((cnt = in.read(buf)) >= 0) {
1124: // System.out.print(new String(buf, 0, cnt));
1125: }
1126: System.out.println("-");
1127: in.close();
1128: manager.sync();
1129: System.err.println(manager);
1130: } catch (Exception ex) {
1131: ex.printStackTrace();
1132: if (ex instanceof HttpException) {
1133: ((HttpException) ex).getException().printStackTrace();
1134: }
1135: }
1136: System.exit(1);
1137: }
1138: }
|