Annotation of java/classes/org/w3c/www/protocol/http/HttpManager.java, revision 1.80
1.1 abaird 1: // HttpManager.java
1.80 ! ylafon 2: // $Id: HttpManager.java,v 1.79 2003/04/01 16:45:49 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.38 bmahe 6: package org.w3c.www.protocol.http ;
1.1 abaird 7:
1.65 ylafon 8: import java.util.Enumeration;
9: import java.util.Hashtable;
10: import java.util.Properties;
11:
12: import java.net.URL;
13:
14: import java.io.InputStream;
15: import java.io.PrintStream;
16:
17: import org.w3c.www.mime.MimeHeaderHolder;
18: import org.w3c.www.mime.MimeParser;
19: import org.w3c.www.mime.MimeParserFactory;
20:
21: import org.w3c.util.LRUList;
22: import org.w3c.util.ObservableProperties;
23: import org.w3c.util.PropertyMonitoring;
24: import org.w3c.util.SyncLRUList;
1.1 abaird 25:
1.25 abaird 26: class ManagerDescription {
27: HttpManager manager = null;
28: Properties properties = null;
29:
30: final HttpManager getManager() {
31: return manager;
32: }
33:
34: final boolean sameProperties(Properties props) {
1.54 bmahe 35: if (props.size() != properties.size())
36: return false;
37: Enumeration enum = props.propertyNames();
38: while (enum.hasMoreElements()) {
39: String name = (String) enum.nextElement();
40: String prop = properties.getProperty(name);
41: if ((prop == null) || (! prop.equals(props.getProperty(name))))
42: return false;
43: }
44: return true;
1.25 abaird 45: }
46:
47: ManagerDescription(HttpManager manager, Properties props) {
48: this.manager = manager;
1.54 bmahe 49: this.properties = (Properties)props.clone();
1.25 abaird 50: }
51: }
52:
1.1 abaird 53: class ReplyFactory implements MimeParserFactory {
1.65 ylafon 54:
1.1 abaird 55: public MimeHeaderHolder createHeaderHolder(MimeParser parser) {
56: return new Reply(parser);
57: }
58:
59: }
60:
61: /**
62: * The client side HTTP request manager.
63: * This class is the user interface (along with the other public classes of
64: * this package) for the W3C client side library implementing HTTP.
65: * A typicall request is launched though the following sequence:
66: * <pre>
67: * HttpManager manager = HttpManager.getManager() ;
1.36 ylafon 68: * Request request = manager.createRequest() ;
1.1 abaird 69: * request.setMethod(HTTP.GET) ;
70: * request.setURL(new URL("http://www.w3.org/pub/WWW/"));
71: * Reply reply = manager.runRequest(request) ;
72: * // Get the reply input stream that contains the actual data:
73: * InputStream in = reply.getInputStream() ;
74: * ...
75: * </pre>
76: */
77:
1.13 abaird 78: public class HttpManager implements PropertyMonitoring {
1.45 ylafon 79:
80: private static final boolean debug = false;
81:
1.17 abaird 82: private static final
1.38 bmahe 83: String DEFAULT_SERVER_CLASS = "org.w3c.www.protocol.http.HttpBasicServer";
1.17 abaird 84:
85: /**
86: * The name of the property indicating the class of HttpServer to use.
87: */
88: public static final
1.38 bmahe 89: String SERVER_CLASS_P = "org.w3c.www.protocol.http.server";
1.17 abaird 90:
1.2 abaird 91: /**
92: * The name of the property containing the ProprequestFilter to launch.
93: */
1.12 abaird 94: public static final
1.38 bmahe 95: String FILTERS_PROP_P = "org.w3c.www.protocol.http.filters";
1.5 abaird 96: /**
1.43 ylafon 97: * The maximum number of simultaneous connectionlrus.
1.11 abaird 98: */
99: public static final
1.38 bmahe 100: String CONN_MAX_P = "org.w3c.www.protocol.http.connections.max";
1.11 abaird 101: /**
1.35 ylafon 102: * The SO_TIMEOUT of the client socket.
103: */
104: public static final
1.38 bmahe 105: String TIMEOUT_P = "org.w3c.www.protocol.http.connections.timeout";
1.35 ylafon 106: /**
1.5 abaird 107: * Header properties - The allowed drift for getting cached resources.
108: */
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: */
119: public static final
1.39 ylafon 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: */
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: */
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: */
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: */
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: */
145: public static final
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: */
150: public static final
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.77 ylafon 177: String DEFAULT_USER_AGENT = "Jigsaw/2.2.2";
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: */
189: protected Class serverclass = null;
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: */
197: protected Hashtable servers = null;
198: /**
199: * The template request (the request we will clone to create new requests)
200: */
1.4 abaird 201: protected Request template = null ;
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.9 abaird 212: protected int conn_count = 0;
213: protected int conn_max = 5;
1.62 ylafon 214: protected boolean lenient = true;
1.79 ylafon 215: protected boolean keepbody = false;
1.9 abaird 216:
1.1 abaird 217: /**
1.13 abaird 218: * Update the proxy configuration to match current properties setting.
219: * @return A boolean, <strong>true</strong> if change was done,
220: * <strong>false</strong> otherwise.
221: */
222:
223: protected boolean updateProxy() {
224: boolean set = props.getBoolean(PROXY_SET_P, false);
225: if ( set ) {
226: // Wow using a proxy now !
227: String host = props.getString(PROXY_HOST_P, null);
228: int port = props.getInteger(PROXY_PORT_P, -1);
229: URL proxy = null;
230: try {
231: proxy = new URL("http", host, port, "/");
232: } catch (Exception ex) {
233: return false;
234: }
235: // Now if a proxy...
1.55 bmahe 236: if (( proxy != null ) && (proxy.getHost() != null))
1.24 abaird 237: template.setProxy(proxy);
1.13 abaird 238: } else {
1.24 abaird 239: template.setProxy(null);
1.13 abaird 240: }
241: return true;
242: }
243:
244: /**
1.24 abaird 245: * Get this manager properties.
246: * @return An ObservableProperties instance.
247: */
248:
249: public final ObservableProperties getProperties() {
250: return props;
251: }
252:
253: /**
1.13 abaird 254: * PropertyMonitoring implementation - Update properties on the fly !
255: * @param name The name of the property that has changed.
256: * @return A boolean, <strong>true</strong> if change is accepted,
257: * <strong>false</strong> otherwise.
258: */
259:
260: public boolean propertyChanged(String name) {
1.24 abaird 261: Request tpl = template;
1.13 abaird 262: if ( name.equals(FILTERS_PROP_P) ) {
1.37 ylafon 263: // FIXME
264: return true;
265: // return false;
1.35 ylafon 266: } else if ( name.equals(TIMEOUT_P) ) {
267: setTimeout(props.getInteger(TIMEOUT_P, timeout));
268: return true;
1.13 abaird 269: } else if ( name.equals(CONN_MAX_P) ) {
270: setMaxConnections(props.getInteger(CONN_MAX_P, conn_max));
271: return true;
272: } else if ( name.equals(MAX_STALE_P) ) {
273: int ival = props.getInteger(MAX_STALE_P, -1);
274: if ( ival >= 0 )
275: tpl.setMaxStale(ival);
276: return true;
277: } else if ( name.equals(MIN_FRESH_P) ) {
278: int ival = props.getInteger(MIN_FRESH_P, -1);
279: if ( ival >= 0 )
280: tpl.setMinFresh(ival);
281: return true;
1.62 ylafon 282: } else if ( name.equals(LENIENT_P) ) {
1.78 ylafon 283: lenient = props.getBoolean(LENIENT_P, lenient);
1.62 ylafon 284: return true;
1.79 ylafon 285: } else if ( name.equals(KEEPBODY_P) ) {
286: keepbody = props.getBoolean(KEEPBODY_P, keepbody);
287: return true;
1.62 ylafon 288: } if ( name.equals(ONLY_IF_CACHED_P) ) {
1.13 abaird 289: tpl.setOnlyIfCached(props.getBoolean(ONLY_IF_CACHED_P, false));
290: return true;
291: } else if ( name.equals(USER_AGENT_P) ) {
292: tpl.setValue("user-agent"
293: , props.getString(USER_AGENT_P
294: , DEFAULT_USER_AGENT));
295: return true;
296: } else if ( name.equals(ACCEPT_P) ) {
297: tpl.setValue("accept"
298: , props.getString(ACCEPT_P, DEFAULT_ACCEPT));
299: return true;
300: } else if ( name.equals(ACCEPT_LANGUAGE_P) ) {
301: String sval = props.getString(ACCEPT_LANGUAGE_P, null);
302: if ( sval != null )
303: tpl.setValue("accept-language", sval);
304: return true;
305: } else if ( name.equals(ACCEPT_ENCODING_P) ) {
306: String sval = props.getString(ACCEPT_ENCODING_P, null);
307: if ( sval != null )
308: tpl.setValue("accept-encoding", sval);
309: return true;
310: } else if ( name.equals(PROXY_SET_P)
311: || name.equals(PROXY_HOST_P)
312: || name.equals(PROXY_PORT_P) ) {
313: return updateProxy();
314: } else {
315: return true;
316: }
317: }
318:
319: /**
1.4 abaird 320: * Allow the manager to interact with the user if needed.
321: * This will, for example, allow prompting for paswords, etc.
322: * @param onoff Turn interaction on or off.
323: */
324:
325: public void setAllowUserInteraction(boolean onoff) {
326: template.setAllowUserInteraction(onoff);
327: }
328:
1.66 bmahe 329: protected static synchronized
330: HttpManager getManager(Class managerclass, Properties p)
331: {
1.24 abaird 332: // Does such a manager exists already ?
333: for (int i = 0 ; i < managers.length ; i++) {
334: if ( managers[i] == null )
335: continue;
1.54 bmahe 336: if ( managers[i].sameProperties(p) )
1.25 abaird 337: return managers[i].getManager();
1.24 abaird 338: }
339: // Get the props we will initialize from:
1.28 abaird 340: ObservableProperties props = null;
1.24 abaird 341: if ( p instanceof ObservableProperties )
1.28 abaird 342: props = (ObservableProperties) p;
1.24 abaird 343: else
1.28 abaird 344: props = new ObservableProperties(p);
345: // Create a new manager for this set of properties:
1.66 bmahe 346: HttpManager manager = null;;
347: try {
348: Object o = managerclass.newInstance();
349: if (o instanceof HttpManager) {
350: manager = (HttpManager) o;
351: } else { // default value
352: manager = new HttpManager();
353: }
354: } catch (Exception ex) {
355: ex.printStackTrace();
356: manager = new HttpManager();
357: }
1.28 abaird 358: manager.props = props;
1.24 abaird 359: // Initialize this new manager filters:
360: String filters[] = props.getStringArray(FILTERS_PROP_P, null);
361: if ( filters != null ) {
362: for (int i = 0 ; i < filters.length ; i++) {
363: try {
364: Class c = Class.forName(filters[i]);
365: PropRequestFilter f = null;
366: f = (PropRequestFilter) c.newInstance();
367: f.initialize(manager);
368: } catch (PropRequestFilterException ex) {
369: System.out.println("Couldn't initialize filter \""
370: + filters[i]
371: + "\" init failed: "
372: + ex.getMessage());
373: } catch (Exception ex) {
374: System.err.println("Error initializing prop filters:");
375: System.err.println("Coulnd't initialize ["
376: + filters[i]
377: + "]: " + ex.getMessage());
378: ex.printStackTrace();
379: System.exit(1);
1.2 abaird 380: }
381: }
1.24 abaird 382: }
383: // The factory to create MIME reply holders:
1.66 bmahe 384: manager.factory = manager.getReplyFactory();
1.24 abaird 385: // The class to create HttpServer instances from
386: String c = props.getString(SERVER_CLASS_P, DEFAULT_SERVER_CLASS);
387: try {
1.34 bmahe 388: manager.serverclass = Class.forName(c);
1.24 abaird 389: } catch (Exception ex) {
390: System.err.println("Unable to initialize HttpManager: ");
391: System.err.println("Class \""+c+"\" not found, from property "
392: + SERVER_CLASS_P);
393: ex.printStackTrace();
394: System.exit(1);
395: }
396: // Setup the template request:
397: Request tpl = manager.template;
398: // Set some default headers value (from props)
399: // Check for a proxy ?
400: manager.updateProxy();
401: // CacheControl, only-if-cached
402: tpl.setOnlyIfCached(props.getBoolean(ONLY_IF_CACHED_P, false));
403: // CacheControl, maxstale
404: int ival = props.getInteger(MAX_STALE_P, -1);
405: if ( ival >= 0 )
406: tpl.setMaxStale(ival);
407: // CacheControl, minfresh:
408: ival = props.getInteger(MIN_FRESH_P, -1);
409: if ( ival >= 0 )
410: tpl.setMinFresh(ival);
1.62 ylafon 411: // general, lenient
412: manager.lenient = props.getBoolean(LENIENT_P, true);
1.79 ylafon 413: manager.keepbody = props.getBoolean(KEEPBODY_P, false);
1.24 abaird 414: // General, User agent
1.68 ylafon 415: String sval;
1.24 abaird 416: tpl.setValue("user-agent"
417: , props.getString(USER_AGENT_P
418: , DEFAULT_USER_AGENT));
419: // General, Accept
420: tpl.setValue("accept"
421: , props.getString(ACCEPT_P, DEFAULT_ACCEPT));
422: // General, Accept-Language
1.68 ylafon 423: sval = props.getString(ACCEPT_LANGUAGE_P, null);
1.67 ylafon 424: if ( sval != null ) {
425: if (sval.trim().length() > 0) {
426: tpl.setValue("accept-language", sval);
427: }
428: }
1.24 abaird 429: // General, Accept-Encoding
430: sval = props.getString(ACCEPT_ENCODING_P, null);
1.68 ylafon 431: if ( sval != null ) {
432: if (sval.trim().length() > 0) {
433: tpl.setValue("accept-encoding", sval);
434: }
435: }
1.24 abaird 436: // Maximum number of allowed connections:
437: manager.conn_max = props.getInteger(CONN_MAX_P, 5);
1.58 ylafon 438: // timeout value
1.59 ylafon 439: manager.timeout = props.getInteger(TIMEOUT_P, manager.timeout);
1.24 abaird 440: // Register ourself as a property observer:
441: props.registerObserver(manager);
442: // Register that manager in our knwon managers:
443: for (int i = 0 ; i < managers.length ; i++) {
444: if ( managers[i] == null ) {
1.25 abaird 445: managers[i] = new ManagerDescription(manager, p);
1.24 abaird 446: return manager;
1.17 abaird 447: }
1.1 abaird 448: }
1.25 abaird 449: ManagerDescription nm[] = new ManagerDescription[managers.length << 1];
450: System.arraycopy(managers, 0, nm, 0, managers.length);
451: nm[managers.length] = new ManagerDescription(manager, p);
452: managers = nm;
1.1 abaird 453: return manager;
454: }
1.66 bmahe 455:
456:
457: /**
458: * Get an instance of the HTTP manager.
459: * This method returns an actual instance of the HTTP manager. It may
460: * return different managers, if it decides to distribute the load on
461: * different managers (avoid the HttpManager being a bottleneck).
462: * @return An application wide instance of the HTTP manager.
463: */
464:
465: public static synchronized HttpManager getManager(Properties p) {
466: return getManager(HttpManager.class, p);
467: }
1.1 abaird 468:
1.24 abaird 469: public static HttpManager getManager() {
470: return getManager(System.getProperties());
471: }
1.1 abaird 472:
473: /**
1.32 abaird 474: * Get the String key for the server instance handling that request.
475: * This method takes care of any proxy setting (it will return the key
476: * to the proxy when required.)
477: * @return A uniq identifier for the handling server, as a String.
478: */
479:
480: public final String getServerKey(Request request) {
481: URL proxy = request.getProxy();
482: URL target = request.getURL();
483: String key = null;
484: if ( proxy != null ) {
485: return ((proxy.getPort() == 80)
486: ? proxy.getHost().toLowerCase()
487: : (proxy.getHost().toLowerCase()+":"+proxy.getPort()));
488: } else {
489: return ((target.getPort() == 80)
490: ? target.getHost().toLowerCase()
491: : (target.getHost().toLowerCase()+":"+target.getPort()));
492: }
493: }
494:
495: /**
1.1 abaird 496: * Get the appropriate server object for handling request to given target.
1.32 abaird 497: * @param key The server's key, as returned by <code>getServerKey</code>.
1.1 abaird 498: * @return An object complying to the HttpServer interface.
499: * @exception HttpException If the given host name couldn't be resolved.
500: */
501:
1.76 ylafon 502: protected HttpServer lookupServer(String host, int port)
1.1 abaird 503: throws HttpException
504: {
1.5 abaird 505: int p = (port == -1) ? 80 : port;
1.32 abaird 506: String id = ((p == 80)
507: ? host.toLowerCase()
508: : (host.toLowerCase() +":"+p));
1.1 abaird 509: // Check for an existing server:
510: HttpServer server = (HttpServer) servers.get(id);
511: if ( server != null )
512: return server;
513: // Create and register a new server:
1.17 abaird 514: try {
515: server = (HttpServer) serverclass.newInstance();
516: } catch (Exception ex) {
517: String msg = ("Unable to create an instance of \""
518: + serverclass.getName()
519: + "\", invalid config, check the "
520: + SERVER_CLASS_P + " property.");
1.21 abaird 521: throw new HttpException(ex, msg);
1.17 abaird 522: }
1.35 ylafon 523: server.initialize(this, new HttpServerState(server), host, p, timeout);
1.71 ylafon 524: // FIXME for long running servers, keeping the hastable growing is
525: // a potential leak. This is a hard way of taking care of that
1.72 ylafon 526: if (servers.size() > conn_max) {
1.71 ylafon 527: closeAnyConnection();
1.73 ylafon 528: servers = new Hashtable(conn_max);
1.71 ylafon 529: }
1.1 abaird 530: servers.put(id, server);
531: return server;
532: }
1.5 abaird 533:
1.1 abaird 534: /**
1.60 ylafon 535: * The given connection is about to be used.
536: * Update our list of available servers.
537: * @param conn The idle connection.
538: */
539:
540: public void notifyUse(HttpConnection conn) {
541: if (debug)
542: System.out.println("+++ connection used");
543: connectionsLru.remove(conn);
544: }
545:
546: /**
1.9 abaird 547: * The given connection can be reused, but is now idle.
548: * @param conn The connection that is now idle.
1.4 abaird 549: */
550:
1.60 ylafon 551: public synchronized void notifyIdle(HttpConnection conn) {
1.45 ylafon 552: if (debug)
553: System.out.println("+++ connection idle");
1.9 abaird 554: connectionsLru.toHead(conn);
1.43 ylafon 555: HttpServerState ss = conn.getServer().getState();
556: ss.registerConnection(conn);
1.63 ylafon 557: notifyAll();
1.4 abaird 558: }
559:
560: /**
1.9 abaird 561: * The given connection has just been created.
562: * @param conn The newly created connection.
563: */
564:
565: protected synchronized void notifyConnection(HttpConnection conn) {
1.45 ylafon 566: if (debug)
567: System.out.println("+++ notify conn_count " + (conn_count+1)
568: + " / " + conn_max);
1.9 abaird 569: if ( ++conn_count > conn_max )
570: closeAnyConnection();
571: }
572:
573: /**
574: * The given connection has been deleted.
575: * @param conn The deleted connection.
576: */
577:
1.60 ylafon 578: protected void deleteConnection(HttpConnection conn) {
1.9 abaird 579: HttpServerState ss = conn.getServer().getState();
580: ss.deleteConnection(conn);
1.60 ylafon 581: synchronized(this) {
582: --conn_count;
583: if (debug)
584: System.out.println("+++ delete conn_count: " + conn_count);
1.63 ylafon 585: notifyAll();
1.60 ylafon 586: }
1.9 abaird 587: }
588:
589: protected synchronized boolean tooManyConnections() {
1.51 ylafon 590: return conn_count >= conn_max;
1.9 abaird 591: }
592:
593: /**
594: * Try reusing one of the idle connection of that server, if any.
595: * @param server The target server.
596: * @return An currently idle connection to the given server.
1.4 abaird 597: */
598:
1.60 ylafon 599: protected HttpConnection getConnection(HttpServer server) {
1.9 abaird 600: HttpServerState ss = server.getState();
1.60 ylafon 601: return ss.getConnection();
1.9 abaird 602: }
603:
1.47 ylafon 604: /**
605: * Wait for a connection to come up.
606: * @param server, the target server.
1.48 bmahe 607: * @exception InterruptedException If interrupted..
1.47 ylafon 608: */
609:
1.9 abaird 610: protected synchronized void waitForConnection(HttpServer server)
611: throws InterruptedException
612: {
1.74 ylafon 613: wait(30000); // FIXME should be tunable, now set to 30s
1.4 abaird 614: }
615:
616: /**
1.60 ylafon 617: * Close some connections, but pickling the least recently used ones.
1.45 ylafon 618: * One third of the max number of connection is cut. This is done to
619: * eliminate the old connections that should be broken already.
620: * (no Socket.isAlive());
1.9 abaird 621: * @return A boolean, <strong>true</strong> if a connection was closed
622: * <strong>false</strong> otherwise.
1.4 abaird 623: */
624:
1.75 ylafon 625: protected boolean closeAnyConnection() {
1.45 ylafon 626: boolean saved = false;
1.47 ylafon 627: int max = Math.max(conn_max/3, 1);
1.45 ylafon 628: for (int i=0; i < max; i++) {
629: HttpConnection conn = (HttpConnection) connectionsLru.removeTail();
630: if ( conn != null ) {
631: conn.close();
632: deleteConnection(conn);
633: if (debug)
634: System.out.println("+++ close request");
1.60 ylafon 635: saved = true;
1.47 ylafon 636: } else
637: break;
1.9 abaird 638: }
1.45 ylafon 639: return saved;
1.4 abaird 640: }
641:
642: /**
1.1 abaird 643: * One of our server handler wants to open a connection.
644: * @param block A boolean indicating whether we should block the calling
645: * thread until a token is available (otherwise, the method will just
646: * peek at the connection count, and return the appropriate result).
647: * @return A boolean, <strong>true</strong> if the connection can be
648: * opened straight, <strong>false</strong> otherwise.
649: */
650:
1.9 abaird 651: protected boolean negotiateConnection(HttpServer server) {
652: HttpServerState ss = server.getState();
1.10 abaird 653: if ( ! tooManyConnections() ) {
1.4 abaird 654: return true;
1.10 abaird 655: } else if ( ss.notEnoughConnections() ) {
656: return closeAnyConnection();
1.9 abaird 657: } else if ( servers.size() > conn_max ) {
658: return closeAnyConnection();
1.4 abaird 659: }
1.9 abaird 660: return false;
1.60 ylafon 661: }
662:
663: /**
664: * A new client connection has been established.
665: * This method will try to maintain a maximum number of established
666: * connections, by closing idle connections when possible.
667: * @param server The server that has established a new connection.
668: */
669:
670: protected final synchronized void incrConnCount(HttpServer server) {
671: if ( ++conn_count > conn_max )
672: closeAnyConnection();
673: if (debug)
674: System.out.println("+++ incr conn_count: " + conn_count);
675: }
676:
677: /**
678: * Decrement the number of established connections.
679: * @param server The server that has closed one connection to its target.
680: */
681:
682: protected final synchronized void decrConnCount(HttpServer server) {
683: --conn_count;
684: if (debug)
685: System.out.println("+++ decr conn_count: " + conn_count);
1.1 abaird 686: }
687:
688: /**
689: * Run the given request, in synchronous mode.
690: * This method will launch the given request, and block the calling thread
691: * until the response headers are available.
692: * @param request The request to run.
693: * @return An instance of Reply, containing all the reply
694: * informations.
1.42 bmahe 695: * @exception HttpException If something failed during request processing.
1.1 abaird 696: */
1.65 ylafon 697:
1.1 abaird 698: public Reply runRequest(Request request)
699: throws HttpException
700: {
1.19 abaird 701: Reply reply = null;
702: int fcalls = 0;
1.1 abaird 703: // Now run through the ingoing filters:
704: RequestFilter filters[] = filteng.run(request);
705: if ( filters != null ) {
706: for (int i = 0 ; i < filters.length ; i++) {
1.19 abaird 707: if ((reply = filters[fcalls].ingoingFilter(request)) != null)
708: break;
709: fcalls++;
1.1 abaird 710: }
711: }
1.16 abaird 712: // Locate the appropriate target server:
1.31 abaird 713: URL target = request.getURL();
1.19 abaird 714: if ( reply == null ) {
1.32 abaird 715: HttpServer srv = null;
1.22 ylafon 716: boolean rtry ;
1.21 abaird 717: do {
1.22 ylafon 718: rtry = false;
1.21 abaird 719: try {
1.32 abaird 720: URL proxy = request.getProxy();
721: if ( proxy != null )
722: srv = lookupServer(proxy.getHost(), proxy.getPort());
723: else
724: srv = lookupServer(target.getHost(), target.getPort());
1.30 abaird 725: request.setServer(srv);
1.21 abaird 726: reply = srv.runRequest(request);
727: } catch (HttpException ex) {
728: for (int i = 0; i < fcalls; i++)
729: rtry = rtry || filters[i].exceptionFilter(request, ex);
1.23 abaird 730: if ( ! rtry )
1.22 ylafon 731: throw ex;
1.30 abaird 732: } finally {
733: request.unsetServer();
1.21 abaird 734: }
735: } while (rtry);
1.16 abaird 736: }
1.1 abaird 737: // Apply the filters on the way back:
738: if ( filters != null ) {
1.19 abaird 739: while (--fcalls >= 0) {
740: Reply frep = filters[fcalls].outgoingFilter(request, reply);
741: if ( frep != null ) {
742: reply = frep;
743: break;
744: }
1.3 abaird 745: }
1.1 abaird 746: }
747: return reply;
748: }
749:
750: /**
751: * Get this manager's reply factory.
752: * The Reply factory is used when prsing incomming reply from servers, it
753: * decides what object will be created to hold the actual reply from the
754: * server.
755: * @return An object compatible with the MimeParserFactory interface.
756: */
757:
758: MimeParserFactory factory = null ;
759:
1.9 abaird 760: public MimeParserFactory getReplyFactory() {
1.66 bmahe 761: if (factory == null) {
762: factory = new ReplyFactory();
763: }
1.1 abaird 764: return factory;
765: }
766:
767: /**
768: * Add a new request filter.
769: * Request filters are called <em>before</em> a request is launched, and
770: * <em>after</em> the reply headers are available. They allow applications
771: * to setup specific request headers (such as PICS, or PEP stuff) on the
772: * way in, and check the reply on the way out.
773: * <p>Request filters are application wide: if their scope matches
774: * the current request, then they will always be aplied.
775: * <p>Filter scopes are defined inclusively and exclusively
1.15 abaird 776: * @param incs The URL domains for which the filter should be triggered.
777: * @param exs The URL domains for which the filter should not be triggered.
1.1 abaird 778: * @param filter The request filter to add.
779: */
780:
781: public void setFilter(URL incs[], URL exs[], RequestFilter filter) {
782: if ( incs != null ) {
783: for (int i = 0 ; i < incs.length ; i++)
784: filteng.setFilter(incs[i], true, filter);
785: }
786: if ( exs != null ) {
787: for (int i = 0 ; i < exs.length ; i++)
788: filteng.setFilter(exs[i], false, filter);
789: }
790: return;
791: }
792:
1.15 abaird 793: /**
794: * Add a global filter.
795: * The given filter will <em>always</em> be invoked.
796: * @param filter The filter to install.
797: */
1.65 ylafon 798:
1.1 abaird 799: public void setFilter(RequestFilter filter) {
800: filteng.setFilter(filter);
801: }
802:
803: /**
1.15 abaird 804: * Find back an instance of a global filter.
805: * This methods allow external classes to get a pointer to installed
806: * filters of a given class.
807: * @param cls The class of the filter to look for.
808: * @return A RequestFilter instance, or <strong>null</strong> if not
809: * found.
1.1 abaird 810: */
811:
1.15 abaird 812: public RequestFilter getGlobalFilter(Class cls) {
813: return filteng.getGlobalFilter(cls);
1.1 abaird 814: }
815:
816: /**
817: * Create a new default outgoing request.
818: * This method should <em>always</em> be used to create outgoing requests.
819: * It will initialize the request with appropriate default values for
820: * the various headers, and make sure that the request is enhanced by
821: * the registered request filters.
822: * @return An instance of Request, suitable to be launched.
823: */
824:
825: public Request createRequest() {
826: return (Request) template.getClone() ;
827: }
828:
829: /**
830: * Global settings - Set the max number of allowed connections.
831: * Set the maximum number of simultaneous connections that can remain
832: * opened. The manager will take care of queuing requests if this number
833: * is reached.
834: * <p>This value defaults to the value of the
1.38 bmahe 835: * <code>org.w3c.www.http.maxConnections</code> property.
1.1 abaird 836: * @param max_conn The allowed maximum simultaneous open connections.
837: */
838:
1.13 abaird 839: public synchronized void setMaxConnections(int max_conn) {
840: this.conn_max = max_conn;
1.35 ylafon 841: }
842:
843: /**
844: * Global settings - Set the timeout on the socket
845: *
846: * <p>This value defaults to the value of the
1.38 bmahe 847: * <code>org.w3c.www.http.Timeout</code> property.
1.35 ylafon 848: * @param timeout The allowed maximum microsecond before a timeout.
849: */
850:
851: public synchronized void setTimeout(int timeout) {
852: this.timeout = timeout;
853: Enumeration e = servers.elements();
854: while (e.hasMoreElements()) {
855: ((HttpServer) e.nextElement()).setTimeout(timeout);
856: }
1.62 ylafon 857: }
858:
859: /**
860: * Global settings - set the HTTP parsing lenient or not.
861: * @param lenient, true by default, false to detect wrong servers
862: */
863: public void setLenient(boolean lenient) {
864: this.lenient = lenient;
865: }
866:
867: /**
868: * Is this manager parsing headers in a lenient way?
869: * @return A boolean.
870: */
871: public boolean isLenient() {
872: return lenient;
1.1 abaird 873: }
874:
875: /**
876: * Global settings - Set an optional proxy to use.
877: * Set the proxy to which all requests should be targeted. If the
1.38 bmahe 878: * <code>org.w3c.www.http.proxy</code> property is defined, it will be
1.1 abaird 879: * used as the default value.
880: * @param proxy The URL for the proxy to use.
881: */
882:
883: public void setProxy(URL proxy) {
1.5 abaird 884: template.setProxy(proxy);
1.30 abaird 885: }
886:
887: /**
888: * Does this manager uses a proxy to fulfill requests ?
889: * @return A boolean.
890: */
891:
892: public boolean usingProxy() {
893: return template.hasProxy();
1.1 abaird 894: }
895:
896: /**
897: * Global settings - Set the request timeout.
898: * Once a request has been emited, the HttpManager will sit for this
899: * given number of milliseconds before the request is declared to have
900: * timed-out.
901: * <p>This timeout value defaults to the value of the
1.38 bmahe 902: * <code>org.w3c.www.http.requestTimeout</code> property value.
1.1 abaird 903: * @param ms The timeout value in milliseconds.
904: */
905:
906: public void setRequestTimeout(int ms) {
907: }
908:
909: /**
910: * Global settings - Define a global request header.
911: * Set a default value for some request header. Once defined, the
912: * header will automatically be defined on <em>all</em> outgoing requests
913: * created through the <code>createRequest</code> request.
914: * @param name The name of the header, case insensitive.
915: * @param value It's default value.
916: */
1.65 ylafon 917:
1.1 abaird 918: public void setGlobalHeader(String name, String value) {
919: template.setValue(name, value);
920: }
921:
1.18 abaird 922: /**
923: * Global settings - Get a global request header default value.
924: * @param name The name of the header to get.
925: * @return The value for that header, as a String, or <strong>
926: * null</strong> if undefined.
927: */
928:
1.1 abaird 929: public String getGlobalHeader(String name) {
930: return template.getValue(name);
1.18 abaird 931: }
932:
1.65 ylafon 933:
1.18 abaird 934: /**
935: * Dump all in-memory cached state to persistent storage.
936: */
937:
938: public void sync() {
939: filteng.sync();
1.1 abaird 940: }
941:
942: /**
943: * Create a new HttpManager.
1.33 abaird 944: * FIXME Making this method protected breaks the static method
945: * to create HttpManager instances (should use a factory here)
1.1 abaird 946: * @param props The properties from which the manager should initialize
947: * itself, or <strong>null</strong> if none are available.
948: */
949:
1.33 abaird 950: protected HttpManager() {
1.9 abaird 951: this.template = new Request(this);
952: this.servers = new Hashtable();
953: this.filteng = new FilterEngine();
954: this.connectionsLru = new SyncLRUList();
1.1 abaird 955: }
956:
957: /**
958: * DEBUGGING !
959: */
960:
961: public static void main(String args[]) {
962: try {
963: // Get the manager, and define some global headers:
964: HttpManager manager = HttpManager.getManager();
1.80 ! ylafon 965: manager.setGlobalHeader("User-Agent", "Jigsaw/2.2.2");
1.1 abaird 966: manager.setGlobalHeader("Accept", "*/*;q=1.0");
967: manager.setGlobalHeader("Accept-Encoding", "gzip");
1.34 bmahe 968: PropRequestFilter filter =
1.38 bmahe 969: new org.w3c.www.protocol.http.cookies.CookieFilter();
1.34 bmahe 970: filter.initialize(manager);
1.45 ylafon 971: PropRequestFilter pdebug =
1.38 bmahe 972: new org.w3c.www.protocol.http.DebugFilter();
1.45 ylafon 973: pdebug.initialize(manager);
1.1 abaird 974: Request request = manager.createRequest();
975: request.setURL(new URL(args[0]));
976: request.setMethod("GET");
977: Reply reply = manager.runRequest(request);
1.34 bmahe 978: //Display some infos:
1.1 abaird 979: System.out.println("last-modified: "+reply.getLastModified());
980: System.out.println("length : "+reply.getContentLength());
981: // Display the returned body:
982: InputStream in = reply.getInputStream();
983: byte buf[] = new byte[4096];
984: int cnt = 0;
985: while ((cnt = in.read(buf)) > 0)
1.56 ylafon 986: System.out.print(new String(buf, 0, cnt));
1.1 abaird 987: System.out.println("-");
988: in.close();
1.34 bmahe 989: manager.sync();
1.1 abaird 990: } catch (Exception ex) {
991: ex.printStackTrace();
1.80 ! ylafon 992: if (ex instanceof HttpException) {
! 993: ((HttpException) ex).getException().printStackTrace();
! 994: }
1.1 abaird 995: }
996: System.exit(1);
997: }
998: }
1.34 bmahe 999:
1000:
Webmaster