Annotation of java/classes/org/w3c/www/protocol/http/HttpManager.java, revision 1.9
1.1 abaird 1: // HttpManager.java
1.9 ! abaird 2: // $Id: HttpManager.java,v 1.8 1996/09/12 22:17:08 abaird Exp $
1.1 abaird 3: // (c) COPYRIGHT MIT and INRIA, 1996.
4: // Please first read the full copyright statement in file COPYRIGHT.html
5:
6: package w3c.www.protocol.http ;
7:
8: import java.util.*;
9: import java.net.*;
10: import java.io.*; // FIXME - DEBUG
11:
12: import w3c.www.mime.*;
1.4 abaird 13: import w3c.util.*;
1.1 abaird 14:
15: class ReplyFactory implements MimeParserFactory {
16:
17: public MimeHeaderHolder createHeaderHolder(MimeParser parser) {
18: return new Reply(parser);
19: }
20:
21: }
22:
1.9 ! abaird 23: class HttpServerState {
! 24: HttpServer server = null;
! 25: Vector conns = null;
! 26:
! 27: final HttpServer getServer() {
! 28: return server;
! 29: }
! 30:
! 31: synchronized boolean notEnoughConnections() {
! 32: return (conns == null) || (conns.size() == 1);
! 33: }
! 34:
! 35: void registerConnection(HttpConnection conn) {
! 36: if ( conns == null )
! 37: conns = new Vector(4);
! 38: conns.addElement(conn);
! 39: }
! 40:
! 41: void unregisterConnection(HttpConnection conn) {
! 42: if ( conns != null )
! 43: conns.removeElement(conn);
! 44: }
! 45:
! 46: void deleteConnection(HttpConnection conn) {
! 47: // conn.close();
! 48: if ( conns != null )
! 49: conns.removeElement(conn);
! 50: }
! 51:
! 52: synchronized HttpConnection getConnection() {
! 53: if ((conns != null) && (conns.size() > 0)) {
! 54: HttpConnection conn = (HttpConnection) conns.elementAt(0);
! 55: conns.removeElementAt(0);
! 56: return conn;
! 57: }
! 58: return null;
! 59: }
! 60:
! 61: HttpServerState(HttpServer server) {
! 62: this.server = server;
! 63: }
! 64: }
1.1 abaird 65:
66: /**
67: * The client side HTTP request manager.
68: * This class is the user interface (along with the other public classes of
69: * this package) for the W3C client side library implementing HTTP.
70: * A typicall request is launched though the following sequence:
71: * <pre>
72: * HttpManager manager = HttpManager.getManager() ;
73: * Request request = manager.makeRequest() ;
74: * request.setMethod(HTTP.GET) ;
75: * request.setURL(new URL("http://www.w3.org/pub/WWW/"));
76: * Reply reply = manager.runRequest(request) ;
77: * // Get the reply input stream that contains the actual data:
78: * InputStream in = reply.getInputStream() ;
79: * ...
80: * </pre>
81: */
82:
83: public class HttpManager {
1.4 abaird 84:
1.2 abaird 85: /**
86: * The name of the property containing the ProprequestFilter to launch.
87: */
1.4 abaird 88: public static final String FILTERS_PROP = "w3c.www.protocol.http.filters";
1.5 abaird 89: /**
90: * Header properties - The allowed drift for getting cached resources.
91: */
92: public static final
1.6 abaird 93: String MAX_STALE = "w3c.www.protocol.http.cacheControl.maxStale";
1.5 abaird 94: /**
95: * Header properties - The minium freshness required on cached resources.
96: */
97: public static final
1.6 abaird 98: String MIN_FRESH = "w3c.www.protocol.http.cacheControl.minFresh";
1.5 abaird 99: /**
100: * Header properties - Set the only if cached flag on requests.
101: */
102: public static final
1.6 abaird 103: String ONLY_IF_CACHED = "w3c.www.protocol.http.cacheControl.onlyIfCached";
1.5 abaird 104: /**
105: * Header properties - Set the user agent.
106: */
107: public static final
108: String USER_AGENT = "w3c.www.protocol.http.userAgent";
109: /**
110: * Header properties - Set the accept header.
111: */
112: public static final
113: String ACCEPT = "w3c.www.protocol.http.accept";
114: /**
115: * Header properties - Set the accept language.
116: */
117: public static final
118: String ACCEPT_LANGUAGE = "w3c.www.protocol.http.acceptLanguage";
119: /**
120: * Header properties - Set the accept encodings.
121: */
122: public static final
123: String ACCEPT_ENCODING = "w3c.www.protocol.http.acceptEncoding";
124: /**
125: * Header properties - Should we use a proxy ?
126: */
127: public static final
1.6 abaird 128: String PROXY_SET = "proxySet";
1.5 abaird 129: /**
130: * Header properties - What is the proxy host name.
131: */
132: public static final
1.6 abaird 133: String PROXY_HOST = "proxyHost";
1.5 abaird 134: /**
135: * Header properties - What is the proxy port number.
136: */
137: public static final
1.6 abaird 138: String PROXY_PORT = "proxyPort";
1.2 abaird 139:
1.7 abaird 140: /**
141: * The default value for the <code>Accept</code> header.
142: */
143: public static final
144: String DEFAULT_ACCEPT = "*/*";
145: /**
146: * The default value for the <code>User-Agent</code> header.
147: */
148: public static final
149: String DEFAULT_USER_AGENT = "Jigsaw/1.0a2";
150:
1.1 abaird 151: private static HttpManager manager = null;
152:
153: /**
154: * The server this manager knows about, indexed by FQDN of target servers.
155: */
156: protected Hashtable servers = null;
157: /**
158: * The template request (the request we will clone to create new requests)
159: */
1.4 abaird 160: protected Request template = null ;
161: /**
1.9 ! abaird 162: * The LRU list of connections.
1.4 abaird 163: */
1.9 ! abaird 164: protected LRUList connectionsLru = null;
1.1 abaird 165: /**
166: * The filter engine attached to this manager.
167: */
168: FilterEngine filteng = null;
169:
170:
1.9 ! abaird 171: protected int conn_count = 0;
! 172: protected int conn_max = 5;
! 173:
1.1 abaird 174: /**
1.4 abaird 175: * Allow the manager to interact with the user if needed.
176: * This will, for example, allow prompting for paswords, etc.
177: * @param onoff Turn interaction on or off.
178: */
179:
180: public void setAllowUserInteraction(boolean onoff) {
181: template.setAllowUserInteraction(onoff);
182: }
183:
184: /**
1.1 abaird 185: * Get an instance of the HTTP manager.
186: * This method returns an actual instance of the HTTP manager. It may
187: * return different managers, if it decides to distribute the load on
188: * different managers (avoid the HttpManager being a bottleneck).
189: * @return An application wide instance of the HTTP manager.
190: */
191:
192: public static synchronized HttpManager getManager() {
193: if ( manager == null ) {
194: manager = new HttpManager() ;
1.9 ! abaird 195: // Initialize this new manager filters:
1.2 abaird 196: String filters = System.getProperty(FILTERS_PROP);
197: if ( filters == null )
198: return manager;
199: StringTokenizer st = new StringTokenizer(filters, "|");
200: while (st.hasMoreTokens() ) {
201: String cls = (String) st.nextElement();
202: try {
203: Class c = Class.forName(cls);
204: PropRequestFilter f = (PropRequestFilter) c.newInstance();
205: f.initialize(manager);
206: } catch (Exception ex) {
207: System.err.println("Error initializing prop filters:");
208: System.err.println("Coulnd't initialize ["
209: + cls
210: + "]: " + ex.getMessage());
211: ex.printStackTrace();
212: }
213: }
1.9 ! abaird 214: // The factory to create MIME reply holders:
! 215: manager.factory = new ReplyFactory();
! 216: // Setup the template request:
1.5 abaird 217: Request tpl = manager.template;
218: // Set some default headers value (from props)
219: // Check for a proxy ?
220: String propval = System.getProperty(PROXY_SET);
1.8 abaird 221: if ((propval != null) && propval.equalsIgnoreCase("true")) {
1.5 abaird 222: // Wow using a proxy now !
223: String host = System.getProperty(PROXY_HOST);
224: int port = -1;
225: URL proxy = null;
226: try {
227: propval = System.getProperty(PROXY_PORT);
228: port = Integer.parseInt(propval);
229: proxy = new URL("http", host, port, "/");
230: } catch (Exception ex) {
231: }
232: // Now if a proxy...
233: if ( proxy != null )
234: tpl.setProxy(proxy);
235: }
236: // CacheControl, only-if-cached
237: if (System.getProperty(ONLY_IF_CACHED) != null)
238: tpl.setOnlyIfCached(true);
239: // CacheControl, maxstale
240: propval = System.getProperty(MAX_STALE);
241: if ( propval != null ) {
242: try {
243: int maxstale = Integer.parseInt(propval);
244: tpl.setMaxStale(maxstale);
245: } catch (Exception ex) {
246: }
247: }
248: // CacheControl, minfresh:
249: propval = System.getProperty(MIN_FRESH);
250: if ( propval != null ) {
251: try {
252: int minfresh = Integer.parseInt(propval);
253: tpl.setMinFresh(minfresh);
254: } catch (Exception ex) {
255: }
256: }
257: // General, User agent
258: propval = System.getProperty(USER_AGENT);
259: if ( propval != null )
260: tpl.setValue("user-agent", propval);
1.7 abaird 261: else
262: tpl.setValue("user-agent", DEFAULT_USER_AGENT);
1.5 abaird 263: // General, Accept
264: propval = System.getProperty(ACCEPT);
265: if ( propval != null )
266: tpl.setValue("accept", propval);
1.7 abaird 267: else
268: tpl.setValue("accept", DEFAULT_ACCEPT);
1.5 abaird 269: // General, Accept-Language
270: propval = System.getProperty(ACCEPT_LANGUAGE);
271: if ( propval != null )
272: tpl.setValue("accept-language", propval);
273: // General, Accept-Encoding
274: propval = System.getProperty(ACCEPT_ENCODING);
275: if ( propval != null )
276: tpl.setValue("accept-encoding", propval);
1.1 abaird 277: }
278: return manager;
279: }
280:
281:
282: /**
283: * Get the appropriate server object for handling request to given target.
284: * @param key The server's identifier encoded as a <code>host:port</code>
285: * String.
286: * @return An object complying to the HttpServer interface.
287: * @exception HttpException If the given host name couldn't be resolved.
288: */
289:
1.5 abaird 290: protected synchronized HttpServer lookupServer(String host, int port)
1.1 abaird 291: throws HttpException
292: {
1.5 abaird 293: int p = (port == -1) ? 80 : port;
294: String id = (p == 80) ? host : (host +":"+p);
1.1 abaird 295: // Check for an existing server:
296: HttpServer server = (HttpServer) servers.get(id);
297: if ( server != null )
298: return server;
299: // Create and register a new server:
300: server = new HttpBasicServer();
1.9 ! abaird 301: server.initialize(this, new HttpServerState(server), host, p);
1.1 abaird 302: servers.put(id, server);
303: return server;
304: }
1.5 abaird 305:
1.1 abaird 306: /**
1.9 ! abaird 307: * The given connection is about to be used.
1.4 abaird 308: * Update our list of available servers.
1.9 ! abaird 309: * @param conn The idle connection.
1.4 abaird 310: */
311:
1.9 ! abaird 312: public void notifyUse(HttpConnection conn) {
! 313: connectionsLru.remove(conn);
1.4 abaird 314: }
315:
316: /**
1.9 ! abaird 317: * The given connection can be reused, but is now idle.
! 318: * @param conn The connection that is now idle.
1.4 abaird 319: */
320:
1.9 ! abaird 321: public void notifyIdle(HttpConnection conn) {
! 322: connectionsLru.toHead(conn);
1.4 abaird 323: }
324:
325: /**
1.9 ! abaird 326: * The given connection has just been created.
! 327: * @param conn The newly created connection.
! 328: */
! 329:
! 330: protected synchronized void notifyConnection(HttpConnection conn) {
! 331: System.out.println("[notifyConnection]ConnCount="+(conn_count+1));
! 332: if ( ++conn_count > conn_max )
! 333: closeAnyConnection();
! 334: }
! 335:
! 336: /**
! 337: * The given connection has been deleted.
! 338: * @param conn The deleted connection.
! 339: */
! 340:
! 341: protected void deleteConnection(HttpConnection conn) {
! 342: HttpServerState ss = conn.getServer().getState();
! 343: ss.deleteConnection(conn);
! 344: synchronized(this) {
! 345: --conn_count;
! 346: notifyAll();
! 347: }
! 348: }
! 349:
! 350: protected synchronized boolean tooManyConnections() {
! 351: return conn_count > conn_max;
! 352: }
! 353:
! 354: /**
! 355: * Try reusing one of the idle connection of that server, if any.
! 356: * @param server The target server.
! 357: * @return An currently idle connection to the given server.
1.4 abaird 358: */
359:
1.9 ! abaird 360: protected HttpConnection getConnection(HttpServer server) {
! 361: HttpServerState ss = server.getState();
! 362: return ss.getConnection();
! 363: }
! 364:
! 365: protected synchronized void waitForConnection(HttpServer server)
! 366: throws InterruptedException
! 367: {
! 368: wait();
1.4 abaird 369: }
370:
371: /**
1.9 ! abaird 372: * Close one connection, but pickling the least recently used one.
! 373: * @return A boolean, <strong>true</strong> if a connection was closed
! 374: * <strong>false</strong> otherwise.
1.4 abaird 375: */
376:
1.9 ! abaird 377: protected boolean closeAnyConnection() {
! 378: HttpConnection conn = (HttpConnection) connectionsLru.removeTail();
! 379: if ( conn != null ) {
! 380: conn.close();
! 381: deleteConnection(conn);
! 382: return true;
! 383: } else {
! 384: return false;
! 385: }
1.4 abaird 386: }
387:
388: /**
1.1 abaird 389: * One of our server handler wants to open a connection.
390: * @param block A boolean indicating whether we should block the calling
391: * thread until a token is available (otherwise, the method will just
392: * peek at the connection count, and return the appropriate result).
393: * @return A boolean, <strong>true</strong> if the connection can be
394: * opened straight, <strong>false</strong> otherwise.
395: */
396:
1.9 ! abaird 397: protected boolean negotiateConnection(HttpServer server) {
! 398: HttpServerState ss = server.getState();
! 399: if ( ss.notEnoughConnections() ) {
1.4 abaird 400: return true;
1.9 ! abaird 401: } else if ( ! tooManyConnections() ) {
1.4 abaird 402: return true;
1.9 ! abaird 403: } else if ( servers.size() > conn_max ) {
! 404: return closeAnyConnection();
1.4 abaird 405: }
1.9 ! abaird 406: return false;
! 407: }
! 408:
! 409: /**
! 410: * A new client connection has been established.
! 411: * This method will try to maintain a maximum number of established
! 412: * connections, by closing idle connections when possible.
! 413: * @param server The server that has established a new connection.
! 414: */
! 415:
! 416: protected final synchronized void incrConnCount(HttpServer server) {
! 417: if ( ++conn_count > conn_max )
! 418: closeAnyConnection();
! 419: }
! 420:
! 421: /**
! 422: * Decrement the number of established connections.
! 423: * @param server The server that has closed one connection to its target.
! 424: */
! 425:
! 426: protected final synchronized void decrConnCount(HttpServer server) {
! 427: --conn_count;
1.1 abaird 428: }
429:
430: /**
431: * Run the given request, in synchronous mode.
432: * This method will launch the given request, and block the calling thread
433: * until the response headers are available.
434: * @param request The request to run.
435: * @return An instance of Reply, containing all the reply
436: * informations.
437: * @exception HTTPException If something failed during request processing.
438: */
439:
440: public Reply runRequest(Request request)
441: throws HttpException
442: {
443: URL target = request.getURL();
444: // Locate the appropriate target server:
1.5 abaird 445: HttpServer server = null;
446: URL proxy = request.getProxy();
447: if ( proxy != null ) {
448: server = lookupServer(proxy.getHost(), proxy.getPort());
449: } else {
450: server = lookupServer(target.getHost(), target.getPort());
451: }
1.1 abaird 452: // Now run through the ingoing filters:
453: RequestFilter filters[] = filteng.run(request);
454: if ( filters != null ) {
455: for (int i = 0 ; i < filters.length ; i++) {
1.3 abaird 456: Reply fr = filters[i].ingoingFilter(request);
457: if ( fr != null )
458: return fr;
1.1 abaird 459: }
460: }
461: // Get the server to give back a reply:
462: Reply reply = server.runRequest(request);
463: // Apply the filters on the way back:
464: if ( filters != null ) {
1.3 abaird 465: for (int i = 0 ; i < filters.length ; i++) {
466: Reply fr = filters[i].outgoingFilter(request, reply);
467: if ( fr != null )
468: return fr;
469: }
1.1 abaird 470: }
471: return reply;
472: }
473:
474: /**
475: * Get this manager's reply factory.
476: * The Reply factory is used when prsing incomming reply from servers, it
477: * decides what object will be created to hold the actual reply from the
478: * server.
479: * @return An object compatible with the MimeParserFactory interface.
480: */
481:
482: MimeParserFactory factory = null ;
483:
1.9 ! abaird 484: public MimeParserFactory getReplyFactory() {
1.1 abaird 485: return factory;
486: }
487:
488: /**
489: * Add a new request filter.
490: * Request filters are called <em>before</em> a request is launched, and
491: * <em>after</em> the reply headers are available. They allow applications
492: * to setup specific request headers (such as PICS, or PEP stuff) on the
493: * way in, and check the reply on the way out.
494: * <p>Request filters are application wide: if their scope matches
495: * the current request, then they will always be aplied.
496: * <p>Filter scopes are defined inclusively and exclusively
497: * @param filter The request filter to add.
498: */
499:
500: public void setFilter(URL incs[], URL exs[], RequestFilter filter) {
501: if ( incs != null ) {
502: for (int i = 0 ; i < incs.length ; i++)
503: filteng.setFilter(incs[i], true, filter);
504: }
505: if ( exs != null ) {
506: for (int i = 0 ; i < exs.length ; i++)
507: filteng.setFilter(exs[i], false, filter);
508: }
509: return;
510: }
511:
512: public void setFilter(RequestFilter filter) {
513: filteng.setFilter(filter);
514: }
515:
516: /**
517: * Add a request processor.
518: * Request processors are application wide hooks, able to answer request
519: * by querying a local cache. An application can set as many request
520: * processor as it wants, each of them will be called in trun (in the order
521: * they were registered), if any of them returns a reply, the request
522: * processing will be halted, and the generated reply returned.
523: * <p>Request processors can also be used to query distant cache, through
524: * some home-brew protocols.
525: * @param processor The request processor to be added.
526: */
527:
528: public void addProcessor(RequestProcessor processor) {
529: }
530:
531: /**
532: * Remove a request processor.
533: * Remove the given request processor.
534: * @return A boolean, <strong>true</strong> if the processor was found
535: * and removed, <strong>false</strong> otherwise.
536: */
537:
538: public boolean removeProcessor(RequestProcessor processor) {
539: return false;
540: }
541:
542: /**
543: * Create a new default outgoing request.
544: * This method should <em>always</em> be used to create outgoing requests.
545: * It will initialize the request with appropriate default values for
546: * the various headers, and make sure that the request is enhanced by
547: * the registered request filters.
548: * @return An instance of Request, suitable to be launched.
549: */
550:
551: public Request createRequest() {
552: return (Request) template.getClone() ;
553: }
554:
555: /**
556: * Global settings - Set the max number of allowed connections.
557: * Set the maximum number of simultaneous connections that can remain
558: * opened. The manager will take care of queuing requests if this number
559: * is reached.
560: * <p>This value defaults to the value of the
561: * <code>w3c.www.http.maxConnections</code> property.
562: * @param max_conn The allowed maximum simultaneous open connections.
563: */
564:
565: public void setMaxConnections(int max_conn) {
566: }
567:
568: /**
569: * Global settings - Set an optional proxy to use.
570: * Set the proxy to which all requests should be targeted. If the
571: * <code>w3c.www.http.proxy</code> property is defined, it will be
572: * used as the default value.
573: * @param proxy The URL for the proxy to use.
574: */
575:
576: public void setProxy(URL proxy) {
1.5 abaird 577: template.setProxy(proxy);
1.1 abaird 578: }
579:
580: /**
581: * Global settings - Set the request timeout.
582: * Once a request has been emited, the HttpManager will sit for this
583: * given number of milliseconds before the request is declared to have
584: * timed-out.
585: * <p>This timeout value defaults to the value of the
586: * <code>w3c.www.http.requestTimeout</code> property value.
587: * @param ms The timeout value in milliseconds.
588: */
589:
590: public void setRequestTimeout(int ms) {
591: }
592:
593: /**
594: * Global settings - Define a global request header.
595: * Set a default value for some request header. Once defined, the
596: * header will automatically be defined on <em>all</em> outgoing requests
597: * created through the <code>createRequest</code> request.
598: * @param name The name of the header, case insensitive.
599: * @param value It's default value.
600: */
601:
602: public void setGlobalHeader(String name, String value) {
603: template.setValue(name, value);
604: }
605:
606: public String getGlobalHeader(String name) {
607: return template.getValue(name);
608: }
609:
610: /**
611: * Create a new HttpManager.
612: * This can only be called from this package. The caller must rather
613: * use the <code>getManager</code> method.
614: * @param props The properties from which the manager should initialize
615: * itself, or <strong>null</strong> if none are available.
616: */
617:
618: HttpManager(Properties props) {
1.9 ! abaird 619: this.template = new Request(this);
! 620: this.servers = new Hashtable();
! 621: this.filteng = new FilterEngine();
! 622: this.connectionsLru = new SyncLRUList();
1.1 abaird 623: }
624:
625: HttpManager() {
626: this(System.getProperties());
627: }
628:
629: /**
630: * DEBUGGING !
631: */
632:
633: public static void main(String args[]) {
634: try {
635: // Get the manager, and define some global headers:
636: HttpManager manager = HttpManager.getManager();
637: manager.setGlobalHeader("User-Agent", "Jigsaw/1.0a");
638: manager.setGlobalHeader("Accept", "*/*;q=1.0");
639: manager.setGlobalHeader("Accept-Encoding", "gzip");
640: Request request = manager.createRequest();
641: request.setURL(new URL(args[0]));
642: request.setMethod("GET");
643: Reply reply = manager.runRequest(request);
644: // Display some infos:
645: System.out.println("last-modified: "+reply.getLastModified());
646: System.out.println("length : "+reply.getContentLength());
647: // Display the returned body:
648: InputStream in = reply.getInputStream();
649: byte buf[] = new byte[4096];
650: int cnt = 0;
651: while ((cnt = in.read(buf)) > 0)
652: System.out.print(new String(buf, 0, 0, cnt));
653: System.out.println("-");
654: in.close();
655: } catch (Exception ex) {
656: ex.printStackTrace();
657: }
658: System.exit(1);
659: }
660: }
Webmaster