* Consider conditional GET headers in RequestTrans.equals(). This fixes a bug

where Mozilla/Firefox displayed an empty page when fetching the same page
  with different headers within a short timeframe.
* Fix Last-Modified handler heading which was broken in more than one way.
* Don't generate ETag headers for error pages.
* Rename ResponseTrans.writeErrorReport() to reportError().
* Set response status to 500 (internal server error) in ResponseTrans.reportError().
This commit is contained in:
hns 2006-05-18 20:54:08 +00:00
parent c8a3c3d702
commit 1121dcbfdc
5 changed files with 49 additions and 45 deletions

View file

@ -56,7 +56,7 @@ public class RequestTrans implements Serializable {
private String session; private String session;
// the map of form and cookie data // the map of form and cookie data
private final Map values; private final Map values = new SystemMap();
// the HTTP request method // the HTTP request method
private String method; private String method;
@ -65,7 +65,7 @@ public class RequestTrans implements Serializable {
private long ifModifiedSince = -1; private long ifModifiedSince = -1;
// set of ETags the client sent with If-None-Match header // set of ETags the client sent with If-None-Match header
private Set etags; private final Set etags = new HashSet();
// when was execution started on this request? // when was execution started on this request?
private final long startTime; private final long startTime;
@ -83,7 +83,6 @@ public class RequestTrans implements Serializable {
this.path = path; this.path = path;
this.request = null; this.request = null;
this.response = null; this.response = null;
values = new SystemMap();
startTime = System.currentTimeMillis(); startTime = System.currentTimeMillis();
} }
@ -96,7 +95,6 @@ public class RequestTrans implements Serializable {
this.request = request; this.request = request;
this.response = response; this.response = response;
this.path = path; this.path = path;
values = new SystemMap();
startTime = System.currentTimeMillis(); startTime = System.currentTimeMillis();
} }
@ -166,25 +164,33 @@ public class RequestTrans implements Serializable {
* detect multiple identic requests. * detect multiple identic requests.
*/ */
public int hashCode() { public int hashCode() {
if (session == null || path == null) if (session == null || path == null) {
return super.hashCode(); return super.hashCode();
return 17 + (37 * session.hashCode()) + } else {
(37 * path.hashCode()); return 17 + (37 * session.hashCode()) +
(37 * path.hashCode());
}
} }
/** /**
* A request is considered equal to another one if it has the same user, path, * A request is considered equal to another one if it has the same method,
* and request data. This is used to evaluate multiple simultanous requests only once * path, session, request data, and conditional get data. This is used to
* evaluate multiple simultanous identical requests only once.
*/ */
public boolean equals(Object what) { public boolean equals(Object what) {
try { if (what instanceof RequestTrans) {
RequestTrans other = (RequestTrans) what; if (session == null || path == null) {
return super.equals(what);
return (session.equals(other.session) && path.equalsIgnoreCase(other.path) && } else {
values.equals(other.getRequestData())); RequestTrans other = (RequestTrans) what;
} catch (Exception x) { return (session.equals(other.session)
return false; && path.equalsIgnoreCase(other.path)
&& values.equals(other.values)
&& ifModifiedSince == other.ifModifiedSince
&& etags.equals(other.etags));
}
} }
return false;
} }
/** /**
@ -284,11 +290,8 @@ public class RequestTrans implements Serializable {
* @param etagHeader ... * @param etagHeader ...
*/ */
public void setETags(String etagHeader) { public void setETags(String etagHeader) {
etags = new HashSet();
if (etagHeader.indexOf(",") > -1) { if (etagHeader.indexOf(",") > -1) {
StringTokenizer st = new StringTokenizer(etagHeader, ", \r\n"); StringTokenizer st = new StringTokenizer(etagHeader, ", \r\n");
while (st.hasMoreTokens()) while (st.hasMoreTokens())
etags.add(st.nextToken()); etags.add(st.nextToken());
} else { } else {

View file

@ -477,10 +477,11 @@ public final class ResponseTrans extends Writer implements Serializable {
* @param appName the application name * @param appName the application name
* @param message the error message * @param message the error message
*/ */
public void writeErrorReport(String appName, String message) { public void reportError(String appName, String message) {
if (reqtrans.isXmlRpc()) { if (reqtrans.isXmlRpc()) {
writeXmlRpcError(new RuntimeException(message)); writeXmlRpcError(new RuntimeException(message));
} else { } else {
status = 500;
write("<html><body><h3>"); write("<html><body><h3>");
write("Error in application "); write("Error in application ");
write(appName); write(appName);
@ -576,19 +577,22 @@ public final class ResponseTrans extends Writer implements Serializable {
} }
} }
// if etag is not set, calc MD5 digest and check it, but only if not a redirect boolean autoETags = "true".equals(app.getProperty("autoETags", "true"));
if (etag == null && lastModified == -1 && redir == null) { // if etag is not set, calc MD5 digest and check it, but only if
// not a redirect or error
if (autoETags &&
etag == null &&
lastModified == -1 &&
status == 200 &&
redir == null) {
try { try {
digest = MessageDigest.getInstance("MD5"); digest = MessageDigest.getInstance("MD5");
// if (contentType != null) // if (contentType != null)
// digest.update (contentType.getBytes()); // digest.update (contentType.getBytes());
byte[] b = digest.digest(response); byte[] b = digest.digest(response);
etag = "\"" + new String(Base64.encode(b)) + "\""; etag = "\"" + new String(Base64.encode(b)) + "\"";
// only set response to 304 not modified if no cookies were set // only set response to 304 not modified if no cookies were set
if (reqtrans != null && reqtrans.hasETag(etag) && countCookies() == 0) { if (reqtrans.hasETag(etag) && countCookies() == 0) {
response = new byte[0]; response = new byte[0];
notModified = true; notModified = true;
} }
@ -671,13 +675,12 @@ public final class ResponseTrans extends Writer implements Serializable {
* @param modified the Last-Modified header in milliseconds * @param modified the Last-Modified header in milliseconds
*/ */
public void setLastModified(long modified) { public void setLastModified(long modified) {
if ((modified > -1) && (reqtrans != null) && // date headers don't do milliseconds, round to seconds
(reqtrans.getIfModifiedSince() >= modified)) { lastModified = (modified / 1000) * 1000;
if (reqtrans.getIfModifiedSince() == lastModified) {
notModified = true; notModified = true;
throw new RedirectException(null); throw new RedirectException(null);
} }
lastModified = modified;
} }
/** /**
@ -696,8 +699,7 @@ public final class ResponseTrans extends Writer implements Serializable {
*/ */
public void setETag(String value) { public void setETag(String value) {
etag = (value == null) ? null : ("\"" + value + "\""); etag = (value == null) ? null : ("\"" + value + "\"");
if (etag != null && reqtrans.hasETag(etag)) {
if ((etag != null) && (reqtrans != null) && reqtrans.hasETag(etag)) {
notModified = true; notModified = true;
throw new RedirectException(null); throw new RedirectException(null);
} }

View file

@ -674,7 +674,7 @@ public final class Application implements IPathElement, Runnable {
} catch (Exception x) { } catch (Exception x) {
errorCount += 1; errorCount += 1;
res = new ResponseTrans(this, req); res = new ResponseTrans(this, req);
res.writeErrorReport(name, x.getMessage()); res.reportError(name, x.getMessage());
} finally { } finally {
if (primaryRequest) { if (primaryRequest) {
activeRequests.remove(req); activeRequests.remove(req);

View file

@ -248,7 +248,6 @@ public final class RequestEvaluator implements Runnable {
currentElement = root; currentElement = root;
requestPath.add(null, currentElement); requestPath.add(null, currentElement);
for (int i = 0; i < ntokens; i++) { for (int i = 0; i < ntokens; i++) {
if (currentElement == null) { if (currentElement == null) {
throw new FrameworkException("Object not found."); throw new FrameworkException("Object not found.");
@ -496,7 +495,7 @@ public final class RequestEvaluator implements Runnable {
Thread.sleep((long) (base + (Math.random() * base * 2))); Thread.sleep((long) (base + (Math.random() * base * 2)));
} catch (InterruptedException interrupt) { } catch (InterruptedException interrupt) {
// we got interrrupted, create minimal error message // we got interrrupted, create minimal error message
res.writeErrorReport(app.getName(), error); res.reportError(app.getName(), error);
done = true; done = true;
// and release resources and thread // and release resources and thread
rtx = null; rtx = null;
@ -508,7 +507,7 @@ public final class RequestEvaluator implements Runnable {
error = "Application too busy, please try again later"; error = "Application too busy, please try again later";
// error in error action. use traditional minimal error message // error in error action. use traditional minimal error message
res.writeErrorReport(app.getName(), error); res.reportError(app.getName(), error);
done = true; done = true;
} }
} catch (Throwable x) { } catch (Throwable x) {
@ -557,7 +556,7 @@ public final class RequestEvaluator implements Runnable {
} }
} else { } else {
// error in error action. use traditional minimal error message // error in error action. use traditional minimal error message
res.writeErrorReport(app.getName(), error); res.reportError(app.getName(), error);
done = true; done = true;
} }
} }
@ -695,7 +694,7 @@ public final class RequestEvaluator implements Runnable {
app.logEvent("Stopping Thread for Request " + app.getName() + "/" + req.getPath()); app.logEvent("Stopping Thread for Request " + app.getName() + "/" + req.getPath());
stopTransactor(); stopTransactor();
res.reset(); res.reset();
res.writeErrorReport(app.getName(), "Request timed out"); res.reportError(app.getName(), "Request timed out");
} }
session.commit(this); session.commit(this);
@ -894,7 +893,7 @@ public final class RequestEvaluator implements Runnable {
this.reqtype = reqtype; this.reqtype = reqtype;
req = new RequestTrans(reqtypeName, functionName); req = new RequestTrans(reqtypeName, functionName);
session = new Session(functionName, app); session = new Session(functionName, app);
res = new ResponseTrans(app, getRequest()); res = new ResponseTrans(app, req);
result = null; result = null;
exception = null; exception = null;
} }

View file

@ -332,7 +332,7 @@ public abstract class AbstractServletClient extends HttpServlet {
} }
// write response // write response
writeResponse(request, response, restrans); writeResponse(request, response, reqtrans, restrans);
} catch (Exception x) { } catch (Exception x) {
try { try {
if (debug) { if (debug) {
@ -352,8 +352,9 @@ public abstract class AbstractServletClient extends HttpServlet {
} }
} }
void writeResponse(HttpServletRequest req, HttpServletResponse res, protected void writeResponse(HttpServletRequest req, HttpServletResponse res,
ResponseTrans hopres) throws IOException { RequestTrans hopreq, ResponseTrans hopres)
throws IOException {
if (hopres.getForward() != null) { if (hopres.getForward() != null) {
sendForward(res, req, hopres); sendForward(res, req, hopres);
return; return;
@ -389,9 +390,8 @@ public abstract class AbstractServletClient extends HttpServlet {
// set last-modified header to now // set last-modified header to now
long modified = hopres.getLastModified(); long modified = hopres.getLastModified();
if (modified > -1) { if (modified > -1) {
res.setDateHeader("Last-Modified", System.currentTimeMillis()); res.setDateHeader("Last-Modified", modified);
} }
res.setContentLength(hopres.getContentLength()); res.setContentLength(hopres.getContentLength());
@ -498,7 +498,7 @@ public abstract class AbstractServletClient extends HttpServlet {
long lastModified = (file.lastModified() / 1000) * 1000; long lastModified = (file.lastModified() / 1000) * 1000;
long ifModifiedSince = req.getDateHeader("If-Modified-Since"); long ifModifiedSince = req.getDateHeader("If-Modified-Since");
if (lastModified == ifModifiedSince) { if (lastModified == ifModifiedSince) {
res.setStatus(304); res.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
return; return;
} }
int length = (int) file.length(); int length = (int) file.length();