001: // ICPFilter.java
002: // $Id: ICPFilter.java,v 1.13 2000/08/16 21:38:04 ylafon Exp $
003: // (c) COPYRIGHT MIT and INRIA, 1996.
004: // please first read the full copyright statement in file COPYRIGHT.HTML
005:
006: package org.w3c.www.protocol.http.icp;
007:
008: import java.util.Hashtable;
009: import java.util.StringTokenizer;
010:
011: import java.net.DatagramSocket;
012: import java.net.InetAddress;
013: import java.net.MalformedURLException;
014: import java.net.SocketException;
015: import java.net.URL;
016: import java.net.UnknownHostException;
017:
018: import java.io.BufferedInputStream;
019: import java.io.DataInputStream;
020: import java.io.File;
021: import java.io.FileInputStream;
022: import java.io.FilterInputStream;
023: import java.io.IOException;
024: import java.io.PrintStream;
025:
026: import org.w3c.util.ObservableProperties;
027: import org.w3c.util.PropertyMonitoring;
028:
029: import org.w3c.www.protocol.http.HttpException;
030: import org.w3c.www.protocol.http.HttpManager;
031: import org.w3c.www.protocol.http.PropRequestFilter;
032: import org.w3c.www.protocol.http.PropRequestFilterException;
033: import org.w3c.www.protocol.http.Reply;
034: import org.w3c.www.protocol.http.Request;
035:
036: import org.w3c.www.protocol.http.cache.CacheFilter;
037:
038: public class ICPFilter implements PropRequestFilter, PropertyMonitoring {
039: /**
040: * Properties - Our debug flag.
041: */
042: public static final String DEBUG_P = "org.w3c.www.protocol.http.icp.debug";
043: /**
044: * Properties - Our configuration file.
045: */
046: public static final String CONFIG_P = "org.w3c.www.protocol.http.icp.config";
047: /**
048: * Properties - Our own UDP port number.
049: */
050: public static final String PORT_P = "org.w3c.www.protocol.http.icp.port";
051: /**
052: * Properties - Our default timeout value.
053: */
054: public static final String TIMEOUT_P = "org.w3c.www.protocol.http.icp.timeout";
055: /**
056: * Properties - disable caching when fetching from a neighbour proxy.
057: */
058: public static final String DISABLE_CACHE_P = "org.w3c.www.protocol.http.icp.disable-cache";
059: /**
060: * The properties we are initialized from.
061: */
062: protected ObservableProperties props = null;
063: /**
064: * Our ICP engine.
065: */
066: protected ICPReceiver icp = null;
067: /**
068: * Our ICP neighbors.
069: */
070: ICPSender senders[] = null;
071: /**
072: * Our senders, indexed by InetAddress.
073: */
074: protected Hashtable friends = null;
075: /**
076: * Our default timeout value for waiting for replies (in ms).
077: */
078: protected long timeoutValue = 500;
079: /**
080: * Our we in debug mode ?
081: */
082: protected boolean debug = false;
083: /**
084: * Our sending and source port.
085: */
086: int port = -1;
087: /**
088: * Should we disablecaching when fetching through a proxy ?
089: */
090: protected boolean disableCache = true;
091:
092: public boolean propertyChanged(String name) {
093: System.out.println("ICPFilter:" + name + ": property changed.");
094: return true;
095: }
096:
097: protected DatagramSocket getSocket() {
098: return icp.getSocket();
099: }
100:
101: protected void createICPSender(String host, int dstport, String http)
102: throws UnknownHostException, MalformedURLException,
103: SocketException {
104: InetAddress addr = InetAddress.getByName(host);
105: URL url = new URL(http);
106: ICPSender sender = new ICPSender(this , port, addr, dstport, url);
107: // Add it to the array of senders:
108: if (senders == null) {
109: senders = new ICPSender[1];
110: senders[0] = sender;
111: } else {
112: ICPSender ns[] = new ICPSender[senders.length + 1];
113: System.arraycopy(senders, 0, ns, 0, senders.length);
114: ns[senders.length] = sender;
115: senders = ns;
116: }
117: // Add it to our hashtable of hosts:
118: byte baddr[] = addr.getAddress();
119: Long key = new Long((((long) dstport) << 32)
120: + ((baddr[0] & 0xff) << 24) + ((baddr[1] & 0xff) << 16)
121: + ((baddr[2] & 0xff) << 8) + (baddr[3] & 0xff));
122: friends.put(key, sender);
123: if (debug)
124: System.out.println("icp: friend " + key + " http=" + http);
125: }
126:
127: /**
128: * Parse the configuration file.
129: */
130:
131: protected void parseConfiguration() {
132: DataInputStream in = null;
133: String host = null;
134: int port = -1;
135: String http = null;
136: File file = props.getFile(CONFIG_P, new File("icp.conf"));
137: try {
138: in = (new DataInputStream(new BufferedInputStream(
139: new FileInputStream(file))));
140: } catch (IOException ex) {
141: System.out.println("*** ICP, unable to read config file "
142: + file.getAbsolutePath());
143: return;
144: }
145: // Parse the file in:
146: try {
147: for (String line = null; (line = in.readLine()) != null;) {
148: // Syntax (FIXME)
149: // host udp-port http-location
150: // | '#' comments
151: if (line.startsWith("#") || line.length() == 0)
152: continue;
153: StringTokenizer st = new StringTokenizer(line, " \t");
154: host = st.nextToken();
155: port = Integer.parseInt(st.nextToken());
156: http = st.nextToken();
157: createICPSender(host, port, http);
158: }
159: } catch (Exception ex) {
160: System.out
161: .println("*** ICP, unable to create " + host + "@"
162: + port + "[" + http + "]: "
163: + ex.getMessage());
164: } finally {
165: try {
166: if (in != null)
167: in.close();
168: } catch (IOException ex) {
169: }
170: }
171: }
172:
173: /**
174: * Get the sender object for the given InetAddress instance.
175: * @param addr The InetAddress of the sender.
176: * @return An ICPSender instance, if available, <strong>null</strong>
177: * otherwise.
178: */
179:
180: public ICPSender getSender(InetAddress addr, int port) {
181: byte baddr[] = addr.getAddress();
182: Long key = new Long((((long) port) << 32)
183: + ((baddr[0] & 0xff) << 24) + ((baddr[1] & 0xff) << 16)
184: + ((baddr[2] & 0xff) << 8) + (baddr[3] & 0xff));
185: return (ICPSender) friends.get(key);
186: }
187:
188: /**
189: * Locate the HTTP service of the proxy that has emitted that reply.
190: * @param reply The reply emitted by the host that alos host the HTTP
191: * service we are looking for.
192: * @return The URL of the proxy, or <strong>null</strong> if no matching
193: * proxy was found.
194: */
195:
196: protected URL locateProxy(ICPReply reply) {
197: ICPSender sender = getSender(reply.getSenderAddress(), reply
198: .getSenderPort());
199: return (sender != null) ? sender.getProxyLocation() : null;
200: }
201:
202: /**
203: * Send the given query to all our neighbors.
204: * @return The number of times we emitted the query.
205: */
206:
207: protected int sendQuery(ICPQuery query) {
208: int count = 0;
209: if (senders != null) {
210: for (int i = 0; i < senders.length; i++) {
211: if (debug)
212: System.out.println("icp: query@"
213: + senders[i].getAddress() + "/"
214: + senders[i].getPort() + " for "
215: + query.getURL());
216: if (senders[i].send(query))
217: count++;
218: }
219: }
220: return count;
221: }
222:
223: /**
224: * Run the ICP query, and return the proxy we should go to.
225: * @param url The URL we are looking for.
226: * @return The URL of the proxy we should go to for that URL, or <strong>
227: * null</strong> if none was found.
228: */
229:
230: protected URL runQuery(ICPQuery query) {
231: // Create a new waiter block for this query, and register it:
232: ICPWaiter waiter = new ICPWaiter(query.getIdentifier());
233: icp.addReplyWaiter(waiter);
234: // Emit the query, and wait for a suitable reply:
235: try {
236: long curtime = -1;
237: long nxttime = -1;
238: long timeout = timeoutValue;
239: int sent = sendQuery(query);
240: while ((sent > 0) && (timeout > 0)) {
241: ICPReply reply = waiter.getNextReply(timeout);
242: if (reply != null) {
243: sent--;
244: if (reply.isHit()) {
245: return locateProxy(reply);
246: }
247: } else {
248: // Our timeout has expired, notify failure
249: return null;
250: }
251: nxttime = System.currentTimeMillis();
252: timeout -= (nxttime - curtime);
253: curtime = nxttime;
254: }
255: } finally {
256: icp.removeReplyWaiter(waiter);
257: }
258: return null;
259: }
260:
261: /**
262: * This filter doesn't handle exceptions.
263: * @param request The request that triggered the exception.
264: * @param ex The triggered exception.
265: * @return Always <strong>false</strong>.
266: */
267:
268: public boolean exceptionFilter(Request request, HttpException ex) {
269: return false;
270: }
271:
272: /**
273: * Our ingoingFilter method.
274: * This method emits (only for GET requestst currently) an ICP query
275: * to all our neighbors, and wait for either one of them to
276: * reply with a hit, or, our timeout value to expire.
277: * <p>If a hit reply is received, we then use the corresponding proxy
278: * to fullfill the request.
279: * @param request The request that is about to be emitted.
280: * @return Always <strong>null</strong>.
281: */
282:
283: public Reply ingoingFilter(Request request) {
284: if (request.getMethod().equals("GET")
285: && (!request.hasState(CacheFilter.STATE_NOCACHE))
286: && (!request.hasState(CacheFilter.STATE_REVALIDATION))) {
287: ICPQuery query = icp.createQuery(request.getURL());
288: URL proxy = runQuery(query);
289: if (proxy != null) {
290: if (debug)
291: System.out.println("*** routing "
292: + request.getURL() + " to " + proxy);
293: // Disable caching and set proxy:
294: if (disableCache) {
295: request.setState(CacheFilter.STATE_NOCACHE,
296: Boolean.TRUE);
297: }
298: request.setProxy(proxy);
299: }
300: }
301: return null;
302: }
303:
304: /**
305: * Our outgoingFilter does nothing (at all).
306: * @param request The request that has been processed.
307: * @param reply The original reply (from origin server)
308: * @return Always <strong>null</strong>.
309: */
310:
311: public Reply outgoingFilter(Request request, Reply reply) {
312: return null;
313: }
314:
315: /**
316: * This filter doesn't maintain dynamic state.
317: */
318:
319: public void sync() {
320: return;
321: }
322:
323: /**
324: * Initialize the ICP filter.
325: * This is where we parse the configuration file in order to know
326: * about our neighbors. We then register ourself to the HTTP manager.
327: * @param manager The HTTP manager.
328: * @exception PropRequestFilterException If the filter cannot
329: * launch its server part (listening for incomming ICP requests)
330: */
331:
332: public void initialize(HttpManager manager)
333: throws PropRequestFilterException {
334: // Setup our properties:
335: props = manager.getProperties();
336: props.registerObserver(this );
337: port = props.getInteger(PORT_P, 2005);
338: // Get property values:
339: this .friends = new Hashtable(10);
340: if (debug = props.getBoolean(DEBUG_P, false))
341: System.out.println("[" + getClass().getName()
342: + "]: debugging on");
343: parseConfiguration();
344: timeoutValue = props.getInteger(TIMEOUT_P, (int) timeoutValue);
345: disableCache = props.getBoolean(DISABLE_CACHE_P, disableCache);
346: // Initialize our ICPReceiver:
347: try {
348: icp = new ICPReceiver(manager, this , port);
349: } catch (SocketException ex) {
350: ex.printStackTrace();
351: throw new PropRequestFilterException(ex.getMessage());
352: }
353: if (debug)
354: System.out.println("icp: listening on port " + port);
355: manager.setFilter(this);
356: }
357:
358: }
|