001: // ForwardFrame.java
002: // $Id: ForwardFrame.java,v 1.29 2003/04/16 13:40:12 ylafon Exp $
003: // (c) COPYRIGHT MIT and INRIA, 1998.
004: // Please first read the full copyright statement in file COPYRIGHT.html
005:
006: package org.w3c.jigsaw.proxy;
007:
008: import java.io.IOException;
009: import java.util.Enumeration;
010: import org.w3c.tools.resources.Attribute;
011: import org.w3c.tools.resources.AttributeRegistry;
012: import org.w3c.tools.resources.BooleanAttribute;
013: import org.w3c.tools.resources.FramedResource;
014: import org.w3c.tools.resources.ReplyInterface;
015: import org.w3c.tools.resources.RequestInterface;
016: import org.w3c.tools.resources.ResourceReference;
017: import org.w3c.tools.resources.StringAttribute;
018: import org.w3c.jigsaw.http.HTTPException;
019: import org.w3c.jigsaw.http.Reply;
020: import org.w3c.jigsaw.http.Request;
021: import org.w3c.jigsaw.http.httpd;
022: import org.w3c.jigsaw.frames.HTTPFrame;
023: import org.w3c.util.ObservableProperties;
024: import org.w3c.www.http.HTTP;
025: import org.w3c.www.http.HeaderDescription;
026: import org.w3c.www.http.HeaderValue;
027: import org.w3c.www.http.HttpFactory;
028: import org.w3c.www.http.HttpWarning;
029: import org.w3c.www.http.HttpInvalidValueException;
030: import org.w3c.www.protocol.http.HttpException;
031:
032: public class ForwardFrame extends HTTPFrame {
033: private static final boolean debug = false;
034: private static boolean inited = false;
035: private static final String nocache[] = {};
036:
037: // private static final
038: // String micp = org.w3c.www.protocol.http.micp.MICPFilter.STATE_HOW;
039:
040: /**
041: * Attribute index - The local resource, if server-wide request.
042: */
043: protected static int ATTR_LOCAL_ROOT = -1;
044: /**
045: * Attribute index - The received by attribute of that proxy.
046: */
047: protected static int ATTR_RECEIVED_BY = -1;
048: /**
049: * Attribute index - Try to trace how the request has been processed.
050: */
051: protected static int ATTR_TRACEREQ = -1;
052: /**
053: * The HTTP warning used to indicate a heuristic expiration time.
054: */
055: protected static HttpWarning WARN_HEURISTIC = null;
056:
057: static {
058: HttpWarning w = null;
059: Attribute a = null;
060: Class c = null;
061: try {
062: c = Class.forName("org.w3c.jigsaw.proxy.ForwardFrame");
063: } catch (Exception ex) {
064: ex.printStackTrace();
065: System.exit(1);
066: }
067: // Declare the local root attribute:
068: a = new StringAttribute("local-root", null, Attribute.EDITABLE);
069: ATTR_LOCAL_ROOT = AttributeRegistry.registerAttribute(c, a);
070: // Declare the received by attribute:
071: a = new StringAttribute("received-by", null, Attribute.EDITABLE);
072: ATTR_RECEIVED_BY = AttributeRegistry.registerAttribute(c, a);
073: // Declare the received by attribute:
074: a = new BooleanAttribute("trace-request", null,
075: Attribute.EDITABLE);
076: ATTR_TRACEREQ = AttributeRegistry.registerAttribute(c, a);
077: // Build the heuristic expiration warning:
078: w = HttpFactory.makeWarning(HttpWarning.HEURISTIC_EXPIRATION);
079: w.setAgent("Jigsaw");
080: w
081: .setText("Heuristic expiration time used on this entry but not set"
082: + " by the upstream agent.");
083: WARN_HEURISTIC = w;
084: }
085:
086: /**
087: * The set of properties we inited from.
088: */
089: ObservableProperties props = null;
090: /**
091: * The HttpManager we use.
092: */
093: protected org.w3c.www.protocol.http.HttpManager manager = null;
094: /**
095: * Statistics - Number of hits.
096: */
097: public int cache_hits = 0;
098: /**
099: * Statistics - Number of misses.
100: */
101: public int cache_misses = 0;
102: /**
103: * Statistics - Number of successfull revalidations.
104: */
105: public int cache_revalidations = 0;
106: /**
107: * Statistics - Number of successfull revalidations.
108: */
109: public int cache_retrievals = 0;
110: /**
111: * Statistics - Number of requests that didn't use cache.
112: */
113: public int cache_nocache = 0;
114: /**
115: * Statistics - Number of requests handled.
116: */
117: public int reqcount = 0;
118: /**
119: * Statistics - Number of ICP redirects.
120: */
121: public int cache_icps = 0;
122: /**
123: * Statistics - Errors.
124: */
125: public int reqerred = 0;
126:
127: /**
128: * Get the local root resource name.
129: */
130:
131: public String getLocalRoot() {
132: return getString(ATTR_LOCAL_ROOT, null);
133: }
134:
135: /**
136: * Get the received by attribute value.
137: * <p>If this attribute is not defined, it will default to the name of the
138: * host running the proxy.
139: * @return A String.
140: */
141:
142: public String getReceivedBy() {
143: String value = getString(ATTR_RECEIVED_BY, null);
144: if (value == null)
145: if (getServer().getPort() == 80)
146: value = getServer().getHost();
147: else
148: value = getServer().getHost() + ":"
149: + getServer().getPort();
150: return value;
151: }
152:
153: /**
154: * Should we try to trace request path ?
155: * @return A boolean.
156: */
157:
158: public boolean getTraceRequest() {
159: return getBoolean(ATTR_TRACEREQ, false);
160: }
161:
162: /**
163: * Get the value of the <code>via</code> header to be added.
164: * @return A String encoded value for the header.
165: */
166:
167: private String via = null;
168:
169: public synchronized String getVia() {
170: if (via == null)
171: via = "1.1 " + getReceivedBy() + " ("
172: + getServer().getSoftware() + ")";
173: return via;
174: }
175:
176: /**
177: * Get the local root resource to use for internal requests.
178: */
179:
180: protected ResourceReference lroot = null;
181:
182: public synchronized ResourceReference getLocalRootResource() {
183: if (lroot == null) {
184: String lname = getLocalRoot();
185: if (lname != null)
186: lroot = getServer().loadResource(lname);
187: }
188: return lroot;
189: }
190:
191: /**
192: * Update relevant statistics (kind of a hack).
193: */
194:
195: protected void updateStatistics(org.w3c.www.protocol.http.Request r) {
196: reqcount++;
197: // Integer how = (Integer) r.getState(CacheFilter.STATE_HOW);
198: // if ( how != null ) {
199: // synchronized(this) {
200: // switch(how.intValue()) {
201: // case 1:
202: // cache_hits++;
203: // break;
204: // case 2:
205: // cache_misses++;
206: // break;
207: // case 3:
208: // cache_revalidations++;
209: // break;
210: // case 4:
211: // cache_retrievals++;
212: // break;
213: // }
214: // }
215: // } else if ( ! r.hasState(micp) ) {
216: // synchronized(this) {
217: // cache_nocache++;
218: // }
219: // } else {
220: // synchronized(this) {
221: // cache_icps++;
222: // }
223: // }
224: }
225:
226: /**
227: * Duplicate a server side request into a client side request.
228: * @param request The server side request.
229: * @return A Client side request.
230: * @exception HTTPException if processing the request failed.
231: * @exception IOException if an IO error occurs.
232: */
233:
234: protected org.w3c.www.protocol.http.Request dupRequest(
235: Request request) throws HTTPException, IOException {
236: org.w3c.www.protocol.http.Request req = null;
237: String mth = request.getMethod();
238: // Create a client request, and initialize its target & method:
239: req = manager.createRequest();
240: // reset user agentm as it will be set (or not set) by
241: // the upstream request
242: req.setUserAgent(null);
243: req.setAccept(null);
244: req.setURL(request.getURL());
245: req.setMethod(mth);
246: // If we can have 1xx headers to send back, setup an observer:
247: if ((request.getMajorVersion() >= 1)
248: && (request.getMinorVersion() >= 1)) {
249: req.setObserver(new ProxyRequestObserver(request, this ));
250: }
251: // Update the client request fields:
252: Enumeration e = request.enumerateHeaderDescriptions();
253: while (e.hasMoreElements()) {
254: HeaderDescription d = (HeaderDescription) e.nextElement();
255: HeaderValue v = request.getHeaderValue(d);
256: if (v != null)
257: req.setHeaderValue(d, v);
258: }
259: // Get rid of all hop-by-hop headers:
260: req.setHeaderValue(Reply.H_CONNECTION, null);
261: req.setHeaderValue(Reply.H_PROXY_CONNECTION, null);
262: req.setHeaderValue(Reply.H_PUBLIC, null);
263: req.setHeaderValue(Request.H_PROXY_AUTHORIZATION, null);
264: req.setHeaderValue(Reply.H_TRANSFER_ENCODING, null);
265: req.setHeaderValue(Request.H_TE, null);
266: req.setHeaderValue(Request.H_TRAILER, null);
267: req.setHeaderValue(Reply.H_UPGRADE, null);
268: req.removeHeader("keep-alive");
269: // By removing that HOST, we make sure the real Host header gets
270: // computed by the client side API
271: req.setHeaderValue(Request.H_HOST, null);
272: // Get rid of more hop by hop headers:
273: String conn[] = request.getConnection();
274: if (conn != null) {
275: for (int i = 0; i < conn.length; i++)
276: req.removeHeader(conn[i]);
277: }
278: // check if nasty people are mixing Content-Length and chunking
279: if (req.getContentLength() >= 0) {
280: String te[] = request.getTransferEncoding();
281: if (te != null) {
282: for (int i = 0; i < te.length; i++) {
283: if (te[i].equals("chunked")) {
284: req.setContentLength(-1);
285: }
286: }
287: }
288: }
289: // 14.31 decrement the value of MaxForward for TRACE and OPTIONS
290: if ((request.getMaxForwards() != -1)
291: && (request.getMethod().equals("TRACE") || request
292: .getMethod().equals("OPTIONS"))) {
293: req.setMaxForwards(request.getMaxForwards() - 1);
294: }
295: // Fix versions mismatches:
296: if (request.hasPragma("no-cache"))
297: req.setNoCache(nocache);
298: // Add the via clause:
299: req.addVia(getVia());
300: // Update the request output stream:
301: req.setOutputStream(request.getInputStream());
302: return req;
303: }
304:
305: /**
306: * Duplicate the given client side reply into a server side one.
307: * Perform any actions requested by HTTP/1.1.
308: * @param request The request ebing processed.
309: * @param reply The reply to clone.
310: * @return A server-side Reply instance.
311: * @exception HTTPException If some HTTP errors occured in the process.
312: * @exception IOException If setting the streams failed.
313: */
314:
315: protected Reply dupReply(Request request,
316: org.w3c.www.protocol.http.Reply rep) throws HTTPException,
317: IOException {
318: Reply reply = request.makeReply(rep.getStatus());
319: // get rid of "by default" headers wchich SHOULD NOT be modified
320: reply.setHeaderValue(Reply.H_SERVER, null);
321: // Duplicate reply header values:
322: Enumeration e = rep.enumerateHeaderDescriptions();
323: while (e.hasMoreElements()) {
324: HeaderDescription d = (HeaderDescription) e.nextElement();
325: HeaderValue v = rep.getHeaderValue(d);
326: if (v != null)
327: reply.setHeaderValue(d, v);
328: }
329: // Get rid of hop by hop headers:
330: reply.setHeaderValue(Reply.H_CONNECTION, null);
331: reply.setHeaderValue(Reply.H_PROXY_CONNECTION, null);
332: reply.setHeaderValue(Reply.H_PROXY_AUTHENTICATE, null);
333: reply.setHeaderValue(Reply.H_PUBLIC, null);
334: reply.setHeaderValue(Reply.H_TRANSFER_ENCODING, null);
335: reply.setHeaderValue(Reply.H_UPGRADE, null);
336: reply.setHeaderValue(Reply.H_TRAILER, null);
337: reply.removeHeader("keep-alive");
338: // Get rid of the fields enumerated in the connection header:
339: String conn[] = rep.getConnection();
340: if (conn != null) {
341: for (int i = 0; i < conn.length; i++)
342: reply.removeHeader(conn[i]);
343: }
344: // check if nasty people are mixing Content-Length and chunking
345: if (reply.getContentLength() >= 0) {
346: String te[] = rep.getTransferEncoding();
347: if (te != null) {
348: for (int i = 0; i < te.length; i++) {
349: if (te[i].equals("chunked")) {
350: reply.setContentLength(-1);
351: }
352: }
353: }
354: }
355: // Update the via route:
356: reply.addVia(getVia());
357: // Update the reply output stream:
358: try {
359: reply.setStream(rep.getInputStream());
360: } catch (Exception ex) {
361: }
362: ;
363: // if reply is using bad HTTP version we should close it
364: if (rep.getMajorVersion() == 0) {
365: reply.setKeepConnection(false);
366: }
367: // if HTTP/1.0 and no Content-Length also...
368: if ((rep.getMajorVersion() == 1)
369: && (rep.getMajorVersion() == 0)
370: && (reply.getContentLength() == -1)) {
371: reply.setKeepConnection(false);
372: }
373: // check the age
374: int age = rep.getAge();
375: if (age >= 0) {
376: // check if it is an heuristic expiration without a warning
377: if (age > 86400) {
378: if ((rep.getExpires() == -1) && (rep.getMaxAge() == -1)
379: && (rep.getSMaxAge() == -1)) {
380: // rfc2616: 13.2.4 on behalf of a bad upstream server
381: HttpWarning w[] = rep.getWarning();
382: boolean doit = true;
383: if (w != null) {
384: for (int i = 0; doit && (i < w.length); i++) {
385: doit = (w[i].getStatus() != 113);
386: }
387: }
388: if (doit) {
389: reply.addWarning(WARN_HEURISTIC);
390: }
391: }
392: }
393: }
394: reply.setProxy(true);
395: return reply;
396: }
397:
398: /**
399: * Perform the given proxied request.
400: * @param request The request to perform.
401: * @return A Reply instance.
402: * @exception org.w3c.tools.resources.ProtocolException if processing
403: * the request failed.
404: * @exception org.w3c.tools.resources.ResourceException if the resource
405: * got a fatal error.
406: */
407:
408: public ReplyInterface perform(RequestInterface ri)
409: throws org.w3c.tools.resources.ProtocolException,
410: org.w3c.tools.resources.ResourceException {
411: Request request = (Request) ri;
412: Reply reply = null;
413: boolean stated = false;
414: // check the expectations
415: if (!checkExpect(request)) {
416: reply = createDefaultReply(request, HTTP.EXPECTATION_FAILED);
417: reply.setContent("The requested expectation could not be"
418: + " met by the resource");
419: return reply;
420: }
421: // Perform the request:
422: try {
423: if (request.getMaxForwards() != -1) { // 14.31 decrement the value
424: if (request.getMaxForwards() == 0) {
425: if (request.getMethod().equals("TRACE")
426: || request.getMethod().equals("OPTIONS"))
427: return super .perform(request);
428: }
429: }
430:
431: org.w3c.www.protocol.http.Request req = dupRequest(request);
432: org.w3c.www.protocol.http.Reply rep = null;
433: // Perform the request
434: rep = manager.runRequest(req);
435: // Dump back the client reply into a server reply:
436: reply = dupReply(request, rep);
437: // Update statistics:
438: updateStatistics(req);
439: stated = true;
440: // Trace the request (debug):
441: if (getTraceRequest()) {
442: // if ( req.hasState(CacheFilter.STATE_HOW) ) {
443: // System.out.println(request.getURL()+": "+
444: // CacheFilter.getHow(req));
445: // }
446: // if ( req.hasState(micp) )
447: // System.out.println(req.getURL()+": (icp) "+
448: // req.getState(micp));
449: }
450: } catch (HTTPException shex) {
451: // An server-side HTTP-related error happened
452: if (debug) {
453: System.out
454: .println("HTTP Server Exception while running"
455: + " request:");
456: shex.printStackTrace();
457: }
458: // Make sure the request is accounted:
459: if (!stated) {
460: synchronized (this ) {
461: reqcount++;
462: reqerred++;
463: }
464: stated = true;
465: }
466: Reply r = (Reply) shex.getReply();
467: if (reply == null) {
468: // Send an appropriate error message back:
469: reply = request.makeReply(HTTP.BAD_GATEWAY);
470: boolean showerr = props.getBoolean(
471: httpd.DISPLAY_URL_ON_ERROR_P, false);
472:
473: if (showerr) {
474: reply
475: .setContent("An HTTP error occured while getting: "
476: + "<p><strong>"
477: + request.getURL()
478: + "</strong>"
479: + "<p>Details \""
480: + shex.getMessage()
481: + "\"."
482: + "<hr>Generated by <i>"
483: + getServer().getURL());
484: } else {
485: reply
486: .setContent("An HTTP error occured while getting the "
487: + "requested URI.</p>"
488: + "<hr>Generated by <i>"
489: + getServer().getURL());
490: }
491: reply
492: .setContentType(org.w3c.www.mime.MimeType.TEXT_HTML);
493: }
494: } catch (HttpInvalidValueException iex) {
495: // An server-side HTTP-related error happened
496: if (debug) {
497: System.out.println("Parsing Exception while running"
498: + " request:");
499: iex.printStackTrace();
500: }
501: // Make sure the request is accounted:
502: if (!stated) {
503: synchronized (this ) {
504: reqcount++;
505: reqerred++;
506: }
507: stated = true;
508: }
509: reply = request.makeReply(HTTP.BAD_GATEWAY);
510: boolean showerr = props.getBoolean(
511: httpd.DISPLAY_URL_ON_ERROR_P, false);
512:
513: if (showerr) {
514: reply
515: .setContent("An HTTP error occured while getting: "
516: + "<p><strong>"
517: + request.getURL()
518: + "</strong>"
519: + "<p>Details \""
520: + iex.getMessage()
521: + "\"."
522: + "<hr>Generated by <i>"
523: + getServer().getURL());
524: } else {
525: reply
526: .setContent("An HTTP error occured while getting the "
527: + "requested URI.</p>"
528: + "<hr>Generated by <i>"
529: + getServer().getURL());
530: }
531: reply.setContentType(org.w3c.www.mime.MimeType.TEXT_HTML);
532: } catch (HttpException hex) {
533: // An HTTP-related error happened
534: if (debug) {
535: System.out
536: .println("HTTP Exception while running request:");
537: hex.printStackTrace();
538: }
539: // Make sure the request is accounted:
540: if (!stated) {
541: synchronized (this ) {
542: reqcount++;
543: reqerred++;
544: }
545: stated = true;
546: }
547: org.w3c.www.protocol.http.Reply rep = hex.getReply();
548: if (rep != null) {
549: try {
550: reply = dupReply(request, rep);
551: } catch (IOException ioex) {
552: reply = null;
553: }
554: }
555: if (reply == null) {
556: // Send an appropriate error message back:
557: String msg = hex.getMessage();
558: if (msg.startsWith("Unable to contact target server ")) {
559: reply = request.makeReply(HTTP.GATEWAY_TIMEOUT);
560: } else {
561: reply = request.makeReply(HTTP.BAD_GATEWAY);
562: }
563: boolean showerr = props.getBoolean(
564: httpd.DISPLAY_URL_ON_ERROR_P, false);
565:
566: if (showerr) {
567: reply
568: .setContent("An HTTP error occured while getting: "
569: + "<p><strong>"
570: + request.getURL()
571: + "</strong>"
572: + "<p>Details \""
573: + hex.getMessage()
574: + "\"."
575: + "<hr>Generated by <i>"
576: + getServer().getURL());
577: } else {
578: reply
579: .setContent("An HTTP error occured while getting the "
580: + "requested URI.</p>"
581: + "<hr>Generated by <i>"
582: + getServer().getURL());
583: }
584: reply
585: .setContentType(org.w3c.www.mime.MimeType.TEXT_HTML);
586: }
587: } catch (Exception ex) {
588: // Debug trace (when debuggging):
589: if (debug) {
590: System.out.println("Exception while running request:");
591: ex.printStackTrace();
592: }
593: // Make sure the request is accounted:
594: if (!stated) {
595: synchronized (this ) {
596: reqcount++;
597: reqerred++;
598: }
599: stated = true;
600: }
601: // Send an appropriate error message back:
602: reply = request.makeReply(HTTP.GATEWAY_TIMEOUT);
603: boolean showerr = props.getBoolean(
604: httpd.DISPLAY_URL_ON_ERROR_P, false);
605:
606: if (showerr) {
607: reply
608: .setContent("An HTTP error occured while getting: "
609: + "<p><strong>"
610: + request.getURL()
611: + "</strong>"
612: + "<p>Details \""
613: + ex.getMessage()
614: + "\"."
615: + "<hr>Generated by <i>"
616: + getServer().getURL());
617: } else {
618: reply
619: .setContent("An HTTP error occured while getting the "
620: + "requested URI.</p>"
621: + "<hr>Generated by <i>"
622: + getServer().getURL());
623: }
624: reply.setContentType(org.w3c.www.mime.MimeType.TEXT_HTML);
625: }
626: return reply;
627: }
628:
629: /**
630: * This resource is being unloaded.
631: * Tell the HttpManager to save any pending data to stable storage.
632: */
633:
634: public synchronized void notifyUnload() {
635: if (manager != null) {
636: manager.sync();
637: }
638: super .notifyUnload();
639: }
640:
641: /**
642: * companion to initialize, called after the register
643: */
644:
645: public void registerResource(FramedResource resource) {
646: super .registerOtherResource(resource);
647: this .props = getResource().getServer().getProperties();
648: synchronized (this .getClass()) {
649: if (!inited) {
650: // Register the client side properties (if still needed):
651: httpd server = (httpd) getServer();
652: server.registerPropertySet(new ProxyProp("proxy",
653: server));
654: inited = true;
655: }
656: }
657: manager = org.w3c.www.protocol.http.HttpManager
658: .getManager(props);
659: }
660:
661: public void initialize(Object values[]) {
662: super.initialize(values);
663: }
664: }
|