Annotation of java/classes/org/w3c/www/protocol/http/HttpManager.java, revision 1.96
1.1 abaird 1: // HttpManager.java
1.96 ! ylafon 2: // $Id: HttpManager.java,v 1.95 2012/06/26 09:47:29 ylafon Exp $
1.1 abaird 3: // (c) COPYRIGHT MIT and INRIA, 1996.
4: // Please first read the full copyright statement in file COPYRIGHT.html
5:
1.94 ylafon 6: package org.w3c.www.protocol.http;
1.1 abaird 7:
1.96 ! ylafon 8: import java.io.InputStream;
! 9: import java.net.URL;
1.65 ylafon 10: import java.util.Enumeration;
11: import java.util.Hashtable;
12: import java.util.Properties;
13: import org.w3c.util.LRUList;
14: import org.w3c.util.ObservableProperties;
15: import org.w3c.util.PropertyMonitoring;
16: import org.w3c.util.SyncLRUList;
1.96 ! ylafon 17: import org.w3c.www.mime.MimeHeaderHolder;
! 18: import org.w3c.www.mime.MimeParser;
! 19: import org.w3c.www.mime.MimeParserFactory;
1.1 abaird 20:
1.25 abaird 21: class ManagerDescription {
22: HttpManager manager = null;
1.94 ylafon 23: Properties properties = null;
1.25 abaird 24:
25: final HttpManager getManager() {
1.94 ylafon 26: return manager;
1.25 abaird 27: }
28:
29: final boolean sameProperties(Properties props) {
1.94 ylafon 30: if (props.size() != properties.size())
31: return false;
1.96 ! ylafon 32: Enumeration<String> e = (Enumeration<String>) props.propertyNames();
1.94 ylafon 33: while (e.hasMoreElements()) {
1.96 ! ylafon 34: String name = e.nextElement();
1.94 ylafon 35: String prop = properties.getProperty(name);
36: if ((prop == null) || (!prop.equals(props.getProperty(name))))
37: return false;
38: }
39: return true;
1.25 abaird 40: }
41:
42: ManagerDescription(HttpManager manager, Properties props) {
1.94 ylafon 43: this.manager = manager;
44: this.properties = (Properties) props.clone();
1.25 abaird 45: }
46: }
47:
1.1 abaird 48: class ReplyFactory implements MimeParserFactory {
1.65 ylafon 49:
1.1 abaird 50: public MimeHeaderHolder createHeaderHolder(MimeParser parser) {
1.94 ylafon 51: return new Reply(parser);
1.1 abaird 52: }
53:
54: }
55:
56: /**
57: * The client side HTTP request manager.
58: * This class is the user interface (along with the other public classes of
1.94 ylafon 59: * this package) for the W3C client side library implementing HTTP.
1.89 ylafon 60: * A typical request is launched though the following sequence:
1.1 abaird 61: * <pre>
62: * HttpManager manager = HttpManager.getManager() ;
1.36 ylafon 63: * Request request = manager.createRequest() ;
1.1 abaird 64: * request.setMethod(HTTP.GET) ;
65: * request.setURL(new URL("http://www.w3.org/pub/WWW/"));
66: * Reply reply = manager.runRequest(request) ;
67: * // Get the reply input stream that contains the actual data:
68: * InputStream in = reply.getInputStream() ;
69: * ...
70: * </pre>
71: */
72:
1.13 abaird 73: public class HttpManager implements PropertyMonitoring {
1.45 ylafon 74:
75: private static final boolean debug = false;
76:
1.94 ylafon 77: private static final
1.38 bmahe 78: String DEFAULT_SERVER_CLASS = "org.w3c.www.protocol.http.HttpBasicServer";
1.17 abaird 79:
80: /**
81: * The name of the property indicating the class of HttpServer to use.
82: */
83: public static final
1.38 bmahe 84: String SERVER_CLASS_P = "org.w3c.www.protocol.http.server";
1.17 abaird 85:
1.2 abaird 86: /**
87: * The name of the property containing the ProprequestFilter to launch.
88: */
1.94 ylafon 89: public static final
1.38 bmahe 90: String FILTERS_PROP_P = "org.w3c.www.protocol.http.filters";
1.5 abaird 91: /**
1.43 ylafon 92: * The maximum number of simultaneous connectionlrus.
1.11 abaird 93: */
94: public static final
1.38 bmahe 95: String CONN_MAX_P = "org.w3c.www.protocol.http.connections.max";
1.11 abaird 96: /**
1.35 ylafon 97: * The SO_TIMEOUT of the client socket.
98: */
99: public static final
1.38 bmahe 100: String TIMEOUT_P = "org.w3c.www.protocol.http.connections.timeout";
1.35 ylafon 101: /**
1.82 ylafon 102: * The connection timeout of the client socket.
103: */
104: public static final
1.94 ylafon 105: String CONN_TIMEOUT_P = "org.w3c.www.protocol.http.connections.connTimeout";
1.82 ylafon 106: /**
1.5 abaird 107: * Header properties - The allowed drift for getting cached resources.
108: */
1.94 ylafon 109: public static final
1.38 bmahe 110: String MAX_STALE_P = "org.w3c.www.protocol.http.cacheControl.maxStale";
1.5 abaird 111: /**
112: * Header properties - The minium freshness required on cached resources.
113: */
114: public static final
1.38 bmahe 115: String MIN_FRESH_P = "org.w3c.www.protocol.http.cacheControl.minFresh";
1.5 abaird 116: /**
117: * Header properties - Set the only if cached flag on requests.
118: */
1.94 ylafon 119: public static final
120: String ONLY_IF_CACHED_P =
121: "org.w3c.www.protocol.http.cacheControl.onlyIfCached";
1.5 abaird 122: /**
123: * Header properties - Set the user agent.
124: */
1.94 ylafon 125: public static final
1.38 bmahe 126: String USER_AGENT_P = "org.w3c.www.protocol.http.userAgent";
1.5 abaird 127: /**
128: * Header properties - Set the accept header.
129: */
1.94 ylafon 130: public static final
1.38 bmahe 131: String ACCEPT_P = "org.w3c.www.protocol.http.accept";
1.5 abaird 132: /**
133: * Header properties - Set the accept language.
134: */
1.94 ylafon 135: public static final
1.38 bmahe 136: String ACCEPT_LANGUAGE_P = "org.w3c.www.protocol.http.acceptLanguage";
1.5 abaird 137: /**
138: * Header properties - Set the accept encodings.
139: */
1.94 ylafon 140: public static final
1.38 bmahe 141: String ACCEPT_ENCODING_P = "org.w3c.www.protocol.http.acceptEncoding";
1.5 abaird 142: /**
1.62 ylafon 143: * Header properties - are we parsing answers in a lenient way?
144: */
1.94 ylafon 145: public static final
1.62 ylafon 146: String LENIENT_P = "org.w3c.www.protocol.http.lenient";
147: /**
1.79 ylafon 148: * Header properties - should we reuse a connection for POST?
149: */
1.94 ylafon 150: public static final
1.79 ylafon 151: String KEEPBODY_P = "org.w3c.www.protocol.http.keepbody";
152: /**
1.5 abaird 153: * Header properties - Should we use a proxy ?
154: */
155: public static final
1.12 abaird 156: String PROXY_SET_P = "proxySet";
1.5 abaird 157: /**
158: * Header properties - What is the proxy host name.
159: */
160: public static final
1.12 abaird 161: String PROXY_HOST_P = "proxyHost";
1.5 abaird 162: /**
163: * Header properties - What is the proxy port number.
164: */
165: public static final
1.12 abaird 166: String PROXY_PORT_P = "proxyPort";
1.2 abaird 167:
1.7 abaird 168: /**
169: * The default value for the <code>Accept</code> header.
170: */
171: public static final
172: String DEFAULT_ACCEPT = "*/*";
173: /**
174: * The default value for the <code>User-Agent</code> header.
175: */
176: public static final
1.93 ylafon 177: String DEFAULT_USER_AGENT = "Jigsaw/2.3.0-beta2";
1.7 abaird 178:
1.24 abaird 179: /**
180: * This array keeps track of all the created managers.
181: * A new manager (kind of HTTP client side context) is created for each
182: * diffferent set of properties.
183: */
1.25 abaird 184: private static ManagerDescription managers[] = new ManagerDescription[4];
1.65 ylafon 185:
1.1 abaird 186: /**
1.17 abaird 187: * The class to instantiate to create new HttpServer instances.
188: */
1.96 ! ylafon 189: protected Class<?> serverclass = null;
1.17 abaird 190: /**
1.13 abaird 191: * The properties we initialized from.
192: */
193: ObservableProperties props = null;
194: /**
1.1 abaird 195: * The server this manager knows about, indexed by FQDN of target servers.
196: */
1.96 ! ylafon 197: protected Hashtable<String, HttpServer> servers = null;
1.1 abaird 198: /**
199: * The template request (the request we will clone to create new requests)
200: */
1.94 ylafon 201: protected Request template = null;
1.4 abaird 202: /**
1.9 abaird 203: * The LRU list of connections.
1.4 abaird 204: */
1.9 abaird 205: protected LRUList connectionsLru = null;
1.1 abaird 206: /**
207: * The filter engine attached to this manager.
208: */
209: FilterEngine filteng = null;
210:
1.35 ylafon 211: protected int timeout = 300000;
1.85 ylafon 212: protected int conn_timeout = 3000;
1.9 abaird 213: protected int conn_count = 0;
214: protected int conn_max = 5;
1.62 ylafon 215: protected boolean lenient = true;
1.79 ylafon 216: protected boolean keepbody = false;
1.9 abaird 217:
1.96 ! ylafon 218: protected Hashtable<String,HttpServer> _tmp_servers = null; // synced during creation
1.83 ylafon 219:
1.1 abaird 220: /**
1.13 abaird 221: * Update the proxy configuration to match current properties setting.
1.94 ylafon 222: *
1.13 abaird 223: * @return A boolean, <strong>true</strong> if change was done,
1.94 ylafon 224: * <strong>false</strong> otherwise.
1.13 abaird 225: */
226:
227: protected boolean updateProxy() {
1.94 ylafon 228: boolean set = props.getBoolean(PROXY_SET_P, false);
229: if (set) {
230: // Wow using a proxy now !
231: String host = props.getString(PROXY_HOST_P, null);
232: int port = props.getInteger(PROXY_PORT_P, -1);
233: URL proxy = null;
234: try {
235: proxy = new URL("http", host, port, "/");
236: } catch (Exception ex) {
237: return false;
238: }
239: // Now if a proxy...
240: if ((proxy != null) && (proxy.getHost() != null))
241: template.setProxy(proxy);
242: } else {
243: template.setProxy(null);
244: }
245: return true;
1.13 abaird 246: }
247:
248: /**
1.24 abaird 249: * Get this manager properties.
1.94 ylafon 250: *
1.24 abaird 251: * @return An ObservableProperties instance.
252: */
253:
254: public final ObservableProperties getProperties() {
1.94 ylafon 255: return props;
1.24 abaird 256: }
257:
258: /**
1.13 abaird 259: * PropertyMonitoring implementation - Update properties on the fly !
1.94 ylafon 260: *
1.13 abaird 261: * @param name The name of the property that has changed.
262: * @return A boolean, <strong>true</strong> if change is accepted,
1.94 ylafon 263: * <strong>false</strong> otherwise.
1.13 abaird 264: */
265:
266: public boolean propertyChanged(String name) {
1.94 ylafon 267: Request tpl = template;
268: if (name.equals(FILTERS_PROP_P)) {
269: // FIXME
270: return true;
271: // return false;
272: } else if (name.equals(TIMEOUT_P)) {
273: setTimeout(props.getInteger(TIMEOUT_P, timeout));
274: return true;
275: } else if (name.equals(CONN_TIMEOUT_P)) {
276: setConnTimeout(props.getInteger(CONN_TIMEOUT_P, conn_timeout));
277: return true;
278: } else if (name.equals(CONN_MAX_P)) {
279: setMaxConnections(props.getInteger(CONN_MAX_P, conn_max));
280: return true;
281: } else if (name.equals(MAX_STALE_P)) {
282: int ival = props.getInteger(MAX_STALE_P, -1);
283: if (ival >= 0)
284: tpl.setMaxStale(ival);
285: return true;
286: } else if (name.equals(MIN_FRESH_P)) {
287: int ival = props.getInteger(MIN_FRESH_P, -1);
288: if (ival >= 0)
289: tpl.setMinFresh(ival);
290: return true;
291: } else if (name.equals(LENIENT_P)) {
292: lenient = props.getBoolean(LENIENT_P, lenient);
293: return true;
294: } else if (name.equals(KEEPBODY_P)) {
295: keepbody = props.getBoolean(KEEPBODY_P, keepbody);
296: return true;
297: }
298: if (name.equals(ONLY_IF_CACHED_P)) {
299: tpl.setOnlyIfCached(props.getBoolean(ONLY_IF_CACHED_P, false));
300: return true;
301: } else if (name.equals(USER_AGENT_P)) {
302: tpl.setValue("user-agent"
303: , props.getString(USER_AGENT_P
304: , DEFAULT_USER_AGENT));
305: return true;
306: } else if (name.equals(ACCEPT_P)) {
307: tpl.setValue("accept"
308: , props.getString(ACCEPT_P, DEFAULT_ACCEPT));
309: return true;
310: } else if (name.equals(ACCEPT_LANGUAGE_P)) {
311: String sval = props.getString(ACCEPT_LANGUAGE_P, null);
312: if (sval != null)
313: tpl.setValue("accept-language", sval);
314: return true;
315: } else if (name.equals(ACCEPT_ENCODING_P)) {
316: String sval = props.getString(ACCEPT_ENCODING_P, null);
317: if (sval != null)
318: tpl.setValue("accept-encoding", sval);
319: return true;
320: } else if (name.equals(PROXY_SET_P)
321: || name.equals(PROXY_HOST_P)
322: || name.equals(PROXY_PORT_P)) {
323: return updateProxy();
324: } else {
325: return true;
326: }
1.13 abaird 327: }
328:
329: /**
1.4 abaird 330: * Allow the manager to interact with the user if needed.
331: * This will, for example, allow prompting for paswords, etc.
1.94 ylafon 332: *
1.4 abaird 333: * @param onoff Turn interaction on or off.
334: */
335:
336: public void setAllowUserInteraction(boolean onoff) {
1.94 ylafon 337: template.setAllowUserInteraction(onoff);
1.4 abaird 338: }
339:
1.96 ! ylafon 340: protected static synchronized HttpManager getManager(Class<?> managerclass, Properties p) {
1.94 ylafon 341: // Does such a manager exists already ?
342: for (ManagerDescription descr : managers) {
343: if (descr != null && descr.sameProperties(p)) {
344: return descr.getManager();
345: }
346: }
347: // Get the props we will initialize from:
348: ObservableProperties props = null;
349: if (p instanceof ObservableProperties)
350: props = (ObservableProperties) p;
351: else
352: props = new ObservableProperties(p);
353: // Create a new manager for this set of properties:
354: HttpManager manager = null;
355: try {
356: Object o = managerclass.newInstance();
357: if (o instanceof HttpManager) {
358: manager = (HttpManager) o;
359: } else { // default value
360: manager = new HttpManager();
361: }
362: } catch (Exception ex) {
363: ex.printStackTrace();
364: manager = new HttpManager();
365: }
366: manager.props = props;
367: // Initialize this new manager filters:
368: String filters[] = props.getStringArray(FILTERS_PROP_P, null);
369: if (filters != null) {
370: for (String filter : filters) {
371: try {
1.96 ! ylafon 372: Class<?> c = Class.forName(filter);
1.94 ylafon 373: PropRequestFilter f = null;
374: f = (PropRequestFilter) c.newInstance();
375: f.initialize(manager);
376: } catch (PropRequestFilterException ex) {
377: System.out.println("Couldn't initialize filter \""
378: + filter
379: + "\" init failed: "
380: + ex.getMessage());
381: } catch (Exception ex) {
382: System.err.println("Error initializing prop filters:");
383: System.err.println("Coulnd't initialize ["
384: + filter
385: + "]: " + ex.getMessage());
386: ex.printStackTrace();
387: System.exit(1);
388: }
389: }
390: }
391: // The factory to create MIME reply holders:
392: manager.factory = manager.getReplyFactory();
393: // The class to create HttpServer instances from
394: String c = props.getString(SERVER_CLASS_P, DEFAULT_SERVER_CLASS);
395: try {
396: manager.serverclass = Class.forName(c);
397: } catch (Exception ex) {
398: System.err.println("Unable to initialize HttpManager: ");
399: System.err.println("Class \"" + c + "\" not found, from property "
400: + SERVER_CLASS_P);
401: ex.printStackTrace();
402: System.exit(1);
403: }
404: // Setup the template request:
405: Request tpl = manager.template;
406: // Set some default headers value (from props)
407: // Check for a proxy ?
408: manager.updateProxy();
409: // CacheControl, only-if-cached
410: tpl.setOnlyIfCached(props.getBoolean(ONLY_IF_CACHED_P, false));
411: // CacheControl, maxstale
412: int ival = props.getInteger(MAX_STALE_P, -1);
413: if (ival >= 0)
414: tpl.setMaxStale(ival);
415: // CacheControl, minfresh:
416: ival = props.getInteger(MIN_FRESH_P, -1);
417: if (ival >= 0)
418: tpl.setMinFresh(ival);
419: // general, lenient
420: manager.lenient = props.getBoolean(LENIENT_P, true);
421: manager.keepbody = props.getBoolean(KEEPBODY_P, false);
422: // General, User agent
423: String sval;
424: tpl.setValue("user-agent"
425: , props.getString(USER_AGENT_P
426: , DEFAULT_USER_AGENT));
427: // General, Accept
428: tpl.setValue("accept"
429: , props.getString(ACCEPT_P, DEFAULT_ACCEPT));
430: // General, Accept-Language
431: sval = props.getString(ACCEPT_LANGUAGE_P, null);
432: if (sval != null) {
433: if (sval.trim().length() > 0) {
434: tpl.setValue("accept-language", sval);
435: }
436: }
437: // General, Accept-Encoding
438: sval = props.getString(ACCEPT_ENCODING_P, null);
439: if (sval != null) {
440: if (sval.trim().length() > 0) {
441: tpl.setValue("accept-encoding", sval);
442: }
443: }
444: // Maximum number of allowed connections:
445: manager.conn_max = props.getInteger(CONN_MAX_P, 5);
446: // timeout value
447: manager.timeout = props.getInteger(TIMEOUT_P, manager.timeout);
448: // connection timeout
449: manager.conn_timeout = props.getInteger(CONN_TIMEOUT_P,
450: manager.conn_timeout);
451: // Register ourself as a property observer:
452: props.registerObserver(manager);
453: // Register that manager in our knwon managers:
454: for (int i = 0; i < managers.length; i++) {
455: if (managers[i] == null) {
456: managers[i] = new ManagerDescription(manager, p);
457: return manager;
458: }
459: }
460: ManagerDescription nm[] = new ManagerDescription[managers.length << 1];
461: System.arraycopy(managers, 0, nm, 0, managers.length);
462: nm[managers.length] = new ManagerDescription(manager, p);
463: managers = nm;
464: return manager;
1.1 abaird 465: }
1.94 ylafon 466:
1.66 bmahe 467:
468: /**
469: * Get an instance of the HTTP manager.
470: * This method returns an actual instance of the HTTP manager. It may
471: * return different managers, if it decides to distribute the load on
472: * different managers (avoid the HttpManager being a bottleneck).
1.94 ylafon 473: *
1.66 bmahe 474: * @return An application wide instance of the HTTP manager.
475: */
476:
477: public static synchronized HttpManager getManager(Properties p) {
1.94 ylafon 478: return getManager(HttpManager.class, p);
1.66 bmahe 479: }
1.1 abaird 480:
1.24 abaird 481: public static HttpManager getManager() {
1.94 ylafon 482: return getManager(System.getProperties());
1.24 abaird 483: }
1.1 abaird 484:
485: /**
1.32 abaird 486: * Get the String key for the server instance handling that request.
487: * This method takes care of any proxy setting (it will return the key
488: * to the proxy when required.)
1.94 ylafon 489: *
1.32 abaird 490: * @return A uniq identifier for the handling server, as a String.
491: */
492:
493: public final String getServerKey(Request request) {
1.94 ylafon 494: URL proxy = request.getProxy();
495: URL target = request.getURL();
496: String key = null;
497: if (proxy != null) {
498: return ((proxy.getPort() == 80)
499: ? proxy.getHost().toLowerCase()
500: : (proxy.getHost().toLowerCase() + ":" + proxy.getPort()));
501: } else {
502: return ((target.getPort() == 80)
503: ? target.getHost().toLowerCase()
504: : (target.getHost().toLowerCase() + ":" + target.getPort()));
505: }
1.32 abaird 506: }
507:
508: /**
1.1 abaird 509: * Get the appropriate server object for handling request to given target.
1.94 ylafon 510: *
511: * @param host The server's key, as returned by <code>getServerKey</code>.
512: * @param port The server's port
1.1 abaird 513: * @return An object complying to the HttpServer interface.
1.94 ylafon 514: * @throws HttpException If the given host name couldn't be resolved.
1.1 abaird 515: */
516:
1.76 ylafon 517: protected HttpServer lookupServer(String host, int port)
1.94 ylafon 518: throws HttpException {
519: int p = (port == -1) ? 80 : port;
520: String id = ((p == 80)
521: ? host.toLowerCase()
522: : (host.toLowerCase() + ":" + p));
523: // Check for an existing server:
524: HttpServer server = (HttpServer) servers.get(id);
525: if (server != null) {
526: return server;
527: }
528: // Create and register a new server:
529: synchronized (_tmp_servers) {
530: if (_tmp_servers.containsKey(id)) {
531: server = (HttpServer) _tmp_servers.get(id);
532: synchronized (server) {
533: while (server.state == null ||
534: server.state.state != HttpServerState.PREINIT) {
535: try {
536: wait(100);
537: } catch (InterruptedException ex) {
538: } catch (IllegalMonitorStateException ex) {
539: break;
540: }
541: }
542: }
543: if (server.state.state == HttpServerState.OK) {
544: return server;
545: } else if (server.state.ex != null) {
546: throw server.state.ex;
547: } else {
548: throw new RuntimeException("Unexpected error " +
549: "in lookupServer");
550: }
551: } else {
552: try {
553: server = (HttpServer) serverclass.newInstance();
554: } catch (Exception ex) {
555: String msg = ("Unable to create an instance of \""
556: + serverclass.getName()
557: + "\", invalid config, check the "
558: + SERVER_CLASS_P + " property.");
559: throw new HttpException(ex, msg);
560: }
561: }
562: }
563: try {
564: synchronized (server) {
565: server.initialize(this, new HttpServerState(server), host, p,
566: timeout, conn_timeout);
567: try {
568: notifyAll();
569: } catch (IllegalMonitorStateException imse) {
570: }
571: }
572: // FIXME for long running servers the growing hashtable is
573: // a potential leak. This is a hard way of taking care of that
1.83 ylafon 574: // if (servers.size() > conn_max) {
575: // closeAnyConnection();
576: // servers = new Hashtable(conn_max);
577: // }
1.94 ylafon 578: } finally {
579: synchronized (_tmp_servers) {
580: if (server.state.state == HttpServerState.OK) {
581: if (!servers.containsKey(id)) {
582: servers.put(id, server);
583: }
584: } else {
1.83 ylafon 585: // System.err.println("ERROR State is "+server.state.state
586: // +" for " + server);
1.94 ylafon 587: }
588: _tmp_servers.remove(id);
589: }
590: }
591: return server;
1.1 abaird 592: }
1.5 abaird 593:
1.1 abaird 594: /**
1.60 ylafon 595: * The given connection is about to be used.
596: * Update our list of available servers.
1.94 ylafon 597: *
1.60 ylafon 598: * @param conn The idle connection.
599: */
600:
1.83 ylafon 601: public synchronized void notifyUse(HttpConnection conn) {
1.94 ylafon 602: if (debug)
603: System.out.println(conn + "+++ connection used");
604: connectionsLru.remove(conn);
1.60 ylafon 605: }
606:
607: /**
1.9 abaird 608: * The given connection can be reused, but is now idle.
1.94 ylafon 609: *
1.9 abaird 610: * @param conn The connection that is now idle.
1.4 abaird 611: */
612:
1.60 ylafon 613: public synchronized void notifyIdle(HttpConnection conn) {
1.94 ylafon 614: if (debug)
615: System.out.println(conn + "+++ connection idle");
616: connectionsLru.toHead(conn);
617: notifyAll();
1.4 abaird 618: }
619:
620: /**
1.9 abaird 621: * The given connection has just been created.
1.94 ylafon 622: *
1.9 abaird 623: * @param conn The newly created connection.
624: */
625:
626: protected synchronized void notifyConnection(HttpConnection conn) {
1.94 ylafon 627: if (debug)
628: System.out.println(conn + "+++ notify conn_count " + (conn_count + 1)
629: + " / " + conn_max);
630: if (++conn_count > conn_max)
631: closeAnyConnection();
1.9 abaird 632: }
633:
634: /**
635: * The given connection has been deleted.
1.94 ylafon 636: *
1.9 abaird 637: * @param conn The deleted connection.
638: */
639:
1.83 ylafon 640: protected synchronized void deleteConnection(HttpConnection conn) {
1.94 ylafon 641: --conn_count;
642: connectionsLru.remove(conn);
643: if (debug)
644: System.out.println(conn + "+++ delete conn_count: " + conn_count);
645: notifyAll();
1.9 abaird 646: }
647:
648: protected synchronized boolean tooManyConnections() {
1.94 ylafon 649: return conn_count >= conn_max;
1.9 abaird 650: }
651:
652: /**
653: * Try reusing one of the idle connection of that server, if any.
1.94 ylafon 654: *
1.9 abaird 655: * @param server The target server.
656: * @return An currently idle connection to the given server.
1.4 abaird 657: */
658:
1.83 ylafon 659: protected synchronized HttpConnection getConnection(HttpServer server) {
1.94 ylafon 660: HttpServerState ss = server.getState();
661: HttpConnection hcn = ss.getConnection();
662: if (hcn != null) {
663: notifyUse(hcn);
664: }
665: return hcn;
1.9 abaird 666: }
667:
1.47 ylafon 668: /**
669: * Wait for a connection to come up.
1.94 ylafon 670: *
1.47 ylafon 671: * @param server, the target server.
1.94 ylafon 672: * @throws InterruptedException If interrupted..
1.47 ylafon 673: */
674:
1.9 abaird 675: protected synchronized void waitForConnection(HttpServer server)
1.94 ylafon 676: throws InterruptedException {
677: wait(30000); // FIXME should be tunable, now set to 30s
1.4 abaird 678: }
679:
680: /**
1.60 ylafon 681: * Close some connections, but pickling the least recently used ones.
1.94 ylafon 682: * One third of the max number of connection is cut. This is done to
1.45 ylafon 683: * eliminate the old connections that should be broken already.
684: * (no Socket.isAlive());
1.94 ylafon 685: *
1.9 abaird 686: * @return A boolean, <strong>true</strong> if a connection was closed
1.94 ylafon 687: * <strong>false</strong> otherwise.
1.4 abaird 688: */
689:
1.83 ylafon 690: protected synchronized boolean closeAnyConnection() {
1.94 ylafon 691: boolean saved = false;
692: int max = Math.max(conn_max / 3, 1);
693: for (int i = 0; i < max; i++) {
694: HttpConnection conn = (HttpConnection) connectionsLru.removeTail();
695: if (conn != null) {
696: conn.close();
697: if (debug)
698: System.out.println("+++ close request");
699: saved = true;
700: } else {
701: break;
702: }
703: }
704: // now purge the server Hashtable
705: synchronized (servers) {
1.96 ! ylafon 706: Enumeration<String> e = servers.keys();
1.94 ylafon 707: if (debug) {
708: System.out.println("+++ hashtable purge starting: "
709: + servers.size() + " entries");
710: }
711: int nbconn = 0;
712: int rnbconn = 0;
713: int nbkept = 0;
714: while (e.hasMoreElements()) {
1.96 ! ylafon 715: String id = e.nextElement();
1.94 ylafon 716: if (id != null) {
1.96 ! ylafon 717: HttpServer server = servers.get(id);
1.94 ylafon 718: int conn_count = server.state.getConnectionCount();
719: if (conn_count <= 0) {
720: if (debug) {
721: System.out.println("+++ hashtable purge: " + id);
722: }
723: servers.remove(id);
724: } else {
725: if (debug) {
726: nbkept++;
727: nbconn += server.state.getConnectionCount();
728: if (server.state.conns != null) {
729: rnbconn += server.state.conns.size();
730: }
731: System.out.println("+++ hashtable keep: " + id
732: + " ( "
733: + server.state.getConnectionCount()
734: + ((server.state.conns != null) ?
735: " idle) ( real: " + server.state.conns.size() + ")" :
736: " idle)")
737: + server.state);
738: }
739: }
740: }
741: }
742: if (debug) {
743: System.out.println("+++ hashtable purge done, keeping "
744: + servers.size() + " entries");
745: System.out.println("+++ hashtable stats, keeping "
746: + nbconn + " ( " + rnbconn
747: + " ) connections for " + nbkept
748: + " servers ( "
749: + ((float) nbconn / (float) nbkept) + " )");
750: }
751: }
752: return saved;
1.4 abaird 753: }
754:
755: /**
1.1 abaird 756: * One of our server handler wants to open a connection.
1.94 ylafon 757: *
1.96 ! ylafon 758: * @param server the server
1.1 abaird 759: * @return A boolean, <strong>true</strong> if the connection can be
1.94 ylafon 760: * opened straight, <strong>false</strong> otherwise.
1.1 abaird 761: */
762:
1.9 abaird 763: protected boolean negotiateConnection(HttpServer server) {
1.94 ylafon 764: HttpServerState ss = server.getState();
765: if (!tooManyConnections()) {
766: return true;
767: } else if (ss.notEnoughConnections()) {
768: return closeAnyConnection();
769: } else if (servers.size() > conn_max) {
770: return closeAnyConnection();
771: }
772: return false;
1.60 ylafon 773: }
774:
775: /**
776: * A new client connection has been established.
777: * This method will try to maintain a maximum number of established
778: * connections, by closing idle connections when possible.
1.94 ylafon 779: *
1.60 ylafon 780: * @param server The server that has established a new connection.
781: */
782:
783: protected final synchronized void incrConnCount(HttpServer server) {
1.94 ylafon 784: if (++conn_count > conn_max)
785: closeAnyConnection();
786: if (debug)
787: System.out.println("+++ incr conn_count: " + conn_count);
1.60 ylafon 788: }
789:
790: /**
791: * Decrement the number of established connections.
1.94 ylafon 792: *
1.60 ylafon 793: * @param server The server that has closed one connection to its target.
794: */
795:
796: protected final synchronized void decrConnCount(HttpServer server) {
1.94 ylafon 797: --conn_count;
798: if (debug)
799: System.out.println("+++ decr conn_count: " + conn_count);
800: if (conn_count < 0) {
801: System.err.println(this);
802: }
1.1 abaird 803: }
804:
805: /**
806: * Run the given request, in synchronous mode.
807: * This method will launch the given request, and block the calling thread
808: * until the response headers are available.
1.94 ylafon 809: *
1.1 abaird 810: * @param request The request to run.
1.94 ylafon 811: * @return An instance of Reply, containing all the reply
812: * informations.
813: * @throws HttpException If something failed during request processing.
1.1 abaird 814: */
1.65 ylafon 815:
1.1 abaird 816: public Reply runRequest(Request request)
1.94 ylafon 817: throws HttpException {
818: Reply reply = null;
819: int fcalls = 0;
820: // Now run through the ingoing filters:
821: RequestFilter filters[] = filteng.run(request);
822: if (filters != null) {
823: for (RequestFilter filter : filters) {
824: if ((reply = filter.ingoingFilter(request)) != null)
825: break;
826: fcalls++;
827: }
828: }
829: // Locate the appropriate target server:
830: URL target = request.getURL();
831: if (reply == null) {
832: HttpServer srv = null;
833: boolean rtry;
834: do {
835: rtry = false;
836: try {
837: URL proxy = request.getProxy();
838: if (proxy != null)
839: srv = lookupServer(proxy.getHost(), proxy.getPort());
840: else
841: srv = lookupServer(target.getHost(), target.getPort());
842: request.setServer(srv);
843: reply = srv.runRequest(request);
844: } catch (HttpException ex) {
845: for (int i = 0; i < fcalls; i++)
846: rtry = rtry || filters[i].exceptionFilter(request, ex);
847: if (!rtry)
848: throw ex;
849: // } finally {
1.88 ylafon 850: // request.unsetServer();
1.94 ylafon 851: }
852: } while (rtry);
853: }
854: // Apply the filters on the way back:
855: if (filters != null) {
856: while (--fcalls >= 0) {
857: Reply frep = filters[fcalls].outgoingFilter(request, reply);
858: if (frep != null) {
859: reply = frep;
860: break;
861: }
862: }
863: }
864: return reply;
1.1 abaird 865: }
866:
867: /**
868: * Get this manager's reply factory.
869: * The Reply factory is used when prsing incomming reply from servers, it
1.94 ylafon 870: * decides what object will be created to hold the actual reply from the
1.1 abaird 871: * server.
1.94 ylafon 872: *
1.1 abaird 873: * @return An object compatible with the MimeParserFactory interface.
874: */
875:
1.94 ylafon 876: MimeParserFactory factory = null;
1.1 abaird 877:
1.9 abaird 878: public MimeParserFactory getReplyFactory() {
1.94 ylafon 879: if (factory == null) {
880: factory = new ReplyFactory();
881: }
882: return factory;
1.1 abaird 883: }
884:
885: /**
886: * Add a new request filter.
887: * Request filters are called <em>before</em> a request is launched, and
888: * <em>after</em> the reply headers are available. They allow applications
889: * to setup specific request headers (such as PICS, or PEP stuff) on the
890: * way in, and check the reply on the way out.
891: * <p>Request filters are application wide: if their scope matches
892: * the current request, then they will always be aplied.
893: * <p>Filter scopes are defined inclusively and exclusively
1.94 ylafon 894: *
895: * @param incs The URL domains for which the filter should be triggered.
896: * @param exs The URL domains for which the filter should not be triggered.
1.1 abaird 897: * @param filter The request filter to add.
898: */
899:
900: public void setFilter(URL incs[], URL exs[], RequestFilter filter) {
1.94 ylafon 901: if (incs != null) {
902: for (URL inc : incs) {
903: filteng.setFilter(inc, true, filter);
904: }
905: }
906: if (exs != null) {
907: for (URL ex : exs) {
908: filteng.setFilter(ex, false, filter);
909: }
910: }
1.1 abaird 911: }
912:
1.15 abaird 913: /**
914: * Add a global filter.
915: * The given filter will <em>always</em> be invoked.
1.94 ylafon 916: *
1.15 abaird 917: * @param filter The filter to install.
918: */
1.65 ylafon 919:
1.1 abaird 920: public void setFilter(RequestFilter filter) {
1.94 ylafon 921: filteng.setFilter(filter);
1.1 abaird 922: }
923:
924: /**
1.15 abaird 925: * Find back an instance of a global filter.
926: * This methods allow external classes to get a pointer to installed
927: * filters of a given class.
1.94 ylafon 928: *
1.15 abaird 929: * @param cls The class of the filter to look for.
930: * @return A RequestFilter instance, or <strong>null</strong> if not
1.94 ylafon 931: * found.
1.1 abaird 932: */
933:
1.96 ! ylafon 934: public RequestFilter getGlobalFilter(Class<?> cls) {
1.94 ylafon 935: return filteng.getGlobalFilter(cls);
1.1 abaird 936: }
937:
938: /**
939: * Create a new default outgoing request.
940: * This method should <em>always</em> be used to create outgoing requests.
1.94 ylafon 941: * It will initialize the request with appropriate default values for
1.1 abaird 942: * the various headers, and make sure that the request is enhanced by
943: * the registered request filters.
1.94 ylafon 944: *
1.1 abaird 945: * @return An instance of Request, suitable to be launched.
946: */
947:
948: public Request createRequest() {
1.94 ylafon 949: return (Request) template.getDeeperClone();
1.1 abaird 950: }
951:
952: /**
953: * Global settings - Set the max number of allowed connections.
954: * Set the maximum number of simultaneous connections that can remain
955: * opened. The manager will take care of queuing requests if this number
956: * is reached.
1.94 ylafon 957: * <p>This value defaults to the value of the
1.82 ylafon 958: * <code>org.w3c.www.http.connections.max</code> property.
1.94 ylafon 959: *
1.1 abaird 960: * @param max_conn The allowed maximum simultaneous open connections.
961: */
962:
1.13 abaird 963: public synchronized void setMaxConnections(int max_conn) {
1.94 ylafon 964: this.conn_max = max_conn;
1.35 ylafon 965: }
966:
967: /**
968: * Global settings - Set the timeout on the socket
1.94 ylafon 969: * <p/>
970: * <p>This value defaults to the value of the
971: * <code>org.w3c.www.http.connections.timeout</code> property.
1.35 ylafon 972: *
973: * @param timeout The allowed maximum microsecond before a timeout.
974: */
975:
976: public synchronized void setTimeout(int timeout) {
1.94 ylafon 977: this.timeout = timeout;
1.96 ! ylafon 978: Enumeration<HttpServer> e = servers.elements();
1.94 ylafon 979: while (e.hasMoreElements()) {
1.96 ! ylafon 980: e.nextElement().setTimeout(timeout);
1.94 ylafon 981: }
1.62 ylafon 982: }
983:
984: /**
1.82 ylafon 985: * Global settings - Set the connection timeout for the socket
1.94 ylafon 986: * <p/>
987: * <p>This value defaults to the value of the
988: * <code>org.w3c.www.protocol.http.connections.connTimeout</code> property
1.82 ylafon 989: *
1.94 ylafon 990: * @param conn_timeout The allowed maximum microsecond before a timeout.
1.82 ylafon 991: */
992:
993: public synchronized void setConnTimeout(int conn_timeout) {
1.94 ylafon 994: this.conn_timeout = conn_timeout;
1.96 ! ylafon 995: Enumeration<HttpServer> e = servers.elements();
1.94 ylafon 996: while (e.hasMoreElements()) {
1.96 ! ylafon 997: e.nextElement().setConnTimeout(conn_timeout);
1.94 ylafon 998: }
1.82 ylafon 999: }
1000:
1001: /**
1.62 ylafon 1002: * Global settings - set the HTTP parsing lenient or not.
1.94 ylafon 1003: *
1.62 ylafon 1004: * @param lenient, true by default, false to detect wrong servers
1005: */
1006: public void setLenient(boolean lenient) {
1.94 ylafon 1007: this.lenient = lenient;
1.62 ylafon 1008: }
1009:
1010: /**
1011: * Is this manager parsing headers in a lenient way?
1.94 ylafon 1012: *
1.62 ylafon 1013: * @return A boolean.
1014: */
1015: public boolean isLenient() {
1.94 ylafon 1016: return lenient;
1.1 abaird 1017: }
1018:
1019: /**
1020: * Global settings - Set an optional proxy to use.
1021: * Set the proxy to which all requests should be targeted. If the
1.38 bmahe 1022: * <code>org.w3c.www.http.proxy</code> property is defined, it will be
1.1 abaird 1023: * used as the default value.
1.94 ylafon 1024: *
1.1 abaird 1025: * @param proxy The URL for the proxy to use.
1026: */
1027:
1028: public void setProxy(URL proxy) {
1.94 ylafon 1029: template.setProxy(proxy);
1.30 abaird 1030: }
1031:
1032: /**
1033: * Does this manager uses a proxy to fulfill requests ?
1.94 ylafon 1034: *
1.30 abaird 1035: * @return A boolean.
1036: */
1037:
1038: public boolean usingProxy() {
1.94 ylafon 1039: return template.hasProxy();
1.1 abaird 1040: }
1041:
1042: /**
1043: * Global settings - Set the request timeout.
1.94 ylafon 1044: * Once a request has been emited, the HttpManager will sit for this
1.1 abaird 1045: * given number of milliseconds before the request is declared to have
1046: * timed-out.
1047: * <p>This timeout value defaults to the value of the
1.38 bmahe 1048: * <code>org.w3c.www.http.requestTimeout</code> property value.
1.94 ylafon 1049: *
1.1 abaird 1050: * @param ms The timeout value in milliseconds.
1051: */
1052:
1053: public void setRequestTimeout(int ms) {
1054: }
1055:
1056: /**
1057: * Global settings - Define a global request header.
1058: * Set a default value for some request header. Once defined, the
1059: * header will automatically be defined on <em>all</em> outgoing requests
1060: * created through the <code>createRequest</code> request.
1.94 ylafon 1061: *
1062: * @param name The name of the header, case insensitive.
1.1 abaird 1063: * @param value It's default value.
1064: */
1.65 ylafon 1065:
1.1 abaird 1066: public void setGlobalHeader(String name, String value) {
1.94 ylafon 1067: template.setValue(name, value);
1.1 abaird 1068: }
1069:
1.18 abaird 1070: /**
1071: * Global settings - Get a global request header default value.
1.94 ylafon 1072: *
1.18 abaird 1073: * @param name The name of the header to get.
1074: * @return The value for that header, as a String, or <strong>
1.94 ylafon 1075: * null</strong> if undefined.
1.18 abaird 1076: */
1077:
1.1 abaird 1078: public String getGlobalHeader(String name) {
1.94 ylafon 1079: return template.getValue(name);
1.18 abaird 1080: }
1081:
1.94 ylafon 1082:
1.18 abaird 1083: /**
1084: * Dump all in-memory cached state to persistent storage.
1085: */
1086:
1087: public void sync() {
1.94 ylafon 1088: filteng.sync();
1.1 abaird 1089: }
1090:
1091: /**
1092: * Create a new HttpManager.
1.33 abaird 1093: * FIXME Making this method protected breaks the static method
1094: * to create HttpManager instances (should use a factory here)
1.1 abaird 1095: */
1096:
1.33 abaird 1097: protected HttpManager() {
1.94 ylafon 1098: this.template = new Request(this);
1.96 ! ylafon 1099: this.servers = new Hashtable<String, HttpServer>();
! 1100: this._tmp_servers = new Hashtable<String,HttpServer>();
1.94 ylafon 1101: this.filteng = new FilterEngine();
1102: this.connectionsLru = new SyncLRUList();
1.1 abaird 1103: }
1104:
1.83 ylafon 1105:
1.1 abaird 1106: /**
1107: * DEBUGGING !
1108: */
1109:
1.83 ylafon 1110: public synchronized String toString() {
1.94 ylafon 1111: StringBuilder sb = new StringBuilder();
1112: HttpConnection hcn = (HttpConnection) connectionsLru.getHead();
1113: sb.append("Connections: ");
1114: sb.append(conn_count);
1115: sb.append(" out of ");
1116: sb.append(conn_max);
1117: sb.append("\n\n");
1118: if (hcn != null) {
1119: sb.append("**** Idle Connections list ****\n");
1120: while (hcn != null) {
1121: sb.append(" ");
1122: sb.append(hcn.toString());
1123: sb.append('\n');
1124: try {
1125: hcn = (HttpConnection) hcn.getNext();
1126: } catch (ClassCastException ccex) {
1127: break;
1128: }
1129: }
1130: } else {
1131: sb.append("*** NO IDLE CONNECTIONS ***\n");
1132: }
1133: sb.append(servers);
1134: return sb.toString();
1.83 ylafon 1135: }
1136:
1.1 abaird 1137: public static void main(String args[]) {
1.94 ylafon 1138: try {
1139: // Get the manager, and define some global headers:
1140: HttpManager manager = HttpManager.getManager();
1141: manager.setGlobalHeader("User-Agent", DEFAULT_USER_AGENT);
1142: manager.setGlobalHeader("Accept", "*/*;q=1.0");
1143: manager.setGlobalHeader("Accept-Encoding", "gzip");
1144: PropRequestFilter filter =
1145: new org.w3c.www.protocol.http.cookies.CookieFilter();
1146: filter.initialize(manager);
1147: PropRequestFilter pdebug =
1148: new org.w3c.www.protocol.http.DebugFilter();
1149: pdebug.initialize(manager);
1150: Request request = manager.createRequest();
1151: request.setURL(new URL(args[0]));
1152: request.setMethod("GET");
1153: Reply reply = manager.runRequest(request);
1154: //Display some infos:
1155: System.out.println("last-modified: " + reply.getLastModified());
1156: System.out.println("length : " + reply.getContentLength());
1157: // Display the returned body:
1158: InputStream in = reply.getInputStream();
1159: byte buf[] = new byte[4096];
1160: int cnt = 0;
1161: while ((cnt = in.read(buf)) >= 0) {
1.83 ylafon 1162: // System.out.print(new String(buf, 0, cnt));
1.94 ylafon 1163: }
1164: System.out.println("-");
1165: in.close();
1166: manager.sync();
1167: System.err.println(manager);
1168: } catch (Exception ex) {
1169: ex.printStackTrace();
1170: if (ex instanceof HttpException) {
1171: ((HttpException) ex).getException().printStackTrace();
1172: }
1173: }
1174: System.exit(1);
1.1 abaird 1175: }
1176: }
1.34 bmahe 1177:
1178:
Webmaster