mcardle 2005/11/18 17:26:11 CET
Modified files:
src/net/sf/j2ep/responsehandlers ResponseHandlerBase.java
Log:
* supports HTTP redirection
* deals properly with static requesthandler
* rewrites Referer and Location HTTP headers
* supports HTTP redirection
* deals with chunked HTTP transfers
* more debug info
Revision Changes Path
1.11 +120 -41
esi_server/src/net/sf/j2ep/responsehandlers/ResponseHandlerBase.java
http://jahia.mine.nu:8080/cgi-bin/cvsweb.cgi/esi_server/src/net/sf/j2ep/responsehandlers/ResponseHandlerBase.java.diff?r1=1.10&r2=1.11&f=h
Index: ResponseHandlerBase.java
===================================================================
RCS file:
/home/cvs/repository/esi_server/src/net/sf/j2ep/responsehandlers/ResponseHandlerBase.java,v
retrieving revision 1.10
retrieving revision 1.11
diff -u -r1.10 -r1.11
--- ResponseHandlerBase.java 8 Nov 2005 10:58:49 -0000 1.10
+++ ResponseHandlerBase.java 18 Nov 2005 16:26:10 -0000 1.11
@@ -22,6 +22,8 @@
import java.io.ByteArrayOutputStream;
import java.net.InetAddress;
import java.net.UnknownHostException;
+import java.net.URL;
+import java.net.MalformedURLException;
import java.util.Vector;
import java.util.Enumeration;
import java.util.Date;
@@ -37,17 +39,19 @@
import net.sf.j2ep.factories.MethodNotAllowedException;
import net.sf.j2ep.ProxyFilter;
-import org.apache.commons.httpclient.*;
import org.apache.commons.httpclient.util.DateUtil;
import org.apache.commons.httpclient.util.DateParseException;
import org.apache.commons.httpclient.methods.GetMethod;
+import org.apache.commons.httpclient.*;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.logging.Log;
import org.jahia.esi.*;
+import org.jahia.esi.exceptions.RedirectException;
import org.jahia.esi.cache.CacheAdminstrator;
import org.jahia.esi.cache.CacheObject;
import org.jahia.esi.cache.FragmentCache;
import org.jahia.esi.cache.UrlCacheObject;
+import org.jahia.urls.URI;
/**
@@ -55,11 +59,33 @@
* can write the headers and process the output stream.
*
* @author Anders Nyman
+ * @author MC
*/
public abstract class ResponseHandlerBase implements ResponseHandler {
private static Log log = LogFactory.getLog(ResponseHandlerBase.class);
+
+ protected URL origRequestUrl;
+ protected HttpServletRequest clientRequest;
+
+ public void initOrigRequestUrl(HttpServletRequest request) {
+ this.clientRequest = request;
+ try {
+ this.origRequestUrl = ProxyFilter.getURL(request);
+ } catch (MalformedURLException e) {
+ log.error("Bad request URL: " + e);
+ }
+ }
+
+ public URL getClientRequestURL() {
+ return origRequestUrl;
+ }
+
+ public HttpServletRequest getClientRequest() {
+ return clientRequest;
+ }
+
/**
* Method we are using for this request.
*/
@@ -70,13 +96,6 @@
*/
protected boolean cacheMethod;
-
- protected RequestHandler requestHandler;
-
- public void setRequestHandler(RequestHandler requestHandler) {
- this.requestHandler = requestHandler;
- }
-
/**
* Basic constructor only setting the method.
*
@@ -105,7 +124,7 @@
/**
* Sets the headers, writes the stream and sets the status code.
*
- * @see
net.sf.j2ep.model.ResponseHandler#process(javax.servlet.http.HttpServletResponse)
+ * @see
net.sf.j2ep.model.ResponseHandler#process(javax.servlet.http.HttpServletResponse,
javax.servlet.http.HttpServletRequest)
*/
@@ -163,7 +182,7 @@
ByteArrayOutputStream cacheStream = new ByteArrayOutputStream(1000);
- if (streamFromServer != null) {
+ if (streamFromServer != null) {
byte[] buffer = new byte[1024];
int read = streamFromServer.read(buffer);
while (read > 0) {
@@ -186,7 +205,7 @@
//fragmentCache.add(cacheKey, this.method, cacheByteContent);
fragmentCache.add(cacheKey, this.methodToServer, cacheByteContent,
- requestHandler.getClientRequestURL().toString(),
+ this.getClientRequestURL().toString(),
null, null, null ,null); //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
@@ -216,30 +235,58 @@
* @param clientResponse The response that will have headers written to
it
*/
protected void
copyHeadersFromServerResponseToClientResponse(HttpServletResponse
clientResponse) {
- copyHeadersFromServerResponseToClientResponse( clientResponse,
false);
+ copyHeadersFromServerResponseToClientResponse( clientResponse,
false, false);
}
protected void
copyHeadersFromServerResponseToClientResponse(HttpServletResponse
clientResponse,
- boolean
ignoreCookies) {
+ boolean
ignoreCookies,
+ boolean
isRedirect) {
Header[] headers = methodToServer.getResponseHeaders();
for (int i=0; i < headers.length; i++) {
Header header = headers[i];
String name = header.getName();
- boolean contentLength = name.equalsIgnoreCase("content-length");
+ String val = header.getValue(); //TODO: need to consider the
case when more than one value is possible for one header i.e. using
HeaderElement[] getValues()
+ boolean contentLength =
false;//name.equalsIgnoreCase("content-length"); //Why does it ignore
contentLength?
boolean connection = name.equalsIgnoreCase("connection");
boolean setcookie = name.equalsIgnoreCase("set-cookie");
+ boolean location = name.equalsIgnoreCase("location");
+ boolean referer = name.equalsIgnoreCase("referer");
+ boolean transferEncodingChunked =
name.equalsIgnoreCase("Transfer-Encoding")
+ &&
val.toLowerCase().indexOf("chunked")!=-1;
+
+ log.debug("Detected Server Response header : "+name+":"+val);
+
+ //rewrite redirection URL so that it points to the ESI server
and not
+ //the remote Jahia server,
+ //also rewrite the referer header
+ //TODO: this should probably be precalculated in during
addFragment stage rather than doing it everytime for each request sent back
(but it also depends on if we have multiple servers ...)
+ if ((location && isRedirect) || referer) {
+ URI uri = new URI(val.trim());
+ uri.setHostName(this.getClientRequestURL().getHost());
+ uri.setPort(this.getClientRequestURL().getPort());
+ if (!referer) {
+ log.debug("replacing Host from "+uri.getHostName()+" to
"+this.getClientRequestURL().getHost() + " due to HTTP redirect");
+ log.debug("replacing Port from "+uri.getPort()+" to
"+this.getClientRequestURL().getPort() + " due to HTTP redirect");
+ log.debug("Redirecting to "+uri.toString(""));
+ }
+ else {
+ log.debug("Rewrote Referer Header from "+val+"to
"+uri.toString(""));
+ }
+ val = uri.toString(""); //"" makes sure it doesn't reencode
the URL
+ }
- if (!contentLength && !connection ) {
+ if (!contentLength && !connection && !transferEncodingChunked) {
//optionally ignore Set-Cookie header
if (setcookie && ignoreCookies) {
- log.debug("Ignored Set-Cookie header because this is a
response returned from Cache. Header:"+header);
+ log.info("Ignored Set-Cookie header because this is a
response returned from Cache. Header:"+header);
continue;
}
else if (setcookie) {
- log.debug("Keeping Set-Cookie header because this is a
response returned directly from Server. Header:"+header);
+ log.info("Keeping Set-Cookie header because this is a
response returned directly from Server. Header:"+header);
}
- clientResponse.addHeader(name, header.getValue());
+ clientResponse.addHeader(name, val);
+ log.debug("Added Client Response header : "+name+":"+val);
}
}
@@ -284,8 +331,8 @@
ProxyFilter.getHttpClient().executeMethod(methodToServer);
- printRequestHeaders(methodToServer);
- printResponseHeaders(methodToServer);
+ printRequestHeaders(methodToServer, " for POST");
+ printResponseHeaders(methodToServer, " for POST");
if (methodToServer.getStatusCode() == 405) {
Header allow = methodToServer.getResponseHeader("allow");
@@ -303,16 +350,18 @@
public void process(HttpServletResponse clientResponse,
HttpServletRequest clientRequest) throws IOException, MethodNotAllowedException
{
- String cacheKey =
CacheAdminstrator.getInstance().generateEntryKey(this.requestHandler);
+ initOrigRequestUrl(clientRequest);
+
+ String cacheKey =
CacheAdminstrator.getInstance().generateEntryKey(clientRequest,
this.getClientRequestURL());
- String clientRequestUrl =
this.requestHandler.getClientRequestURL().toString();
+ String clientRequestUrl = this.getClientRequestURL().toString();
FragmentCache fragmentCache = FragmentCache.getInstance();
//Case 1: Passthrough (post etc...)
if (!this.cacheMethod) {
- log.debug(" * Case 1: Passthrough (post etc...) *
url:"+cacheKey);
+ log.debug(" * BEGIN ---- > Case 1: Passthrough (post etc...) *
url:"+cacheKey);
beginServerRequest();
copyHeadersFromServerResponseToClientResponse(clientResponse);
//TODO: set cookies here
@@ -320,16 +369,23 @@
if (!methodToServer.getName().equals("HEAD")) //since there is
no content for HEAD requests
sendStreamToClientPassthrough(clientResponse);
+ log.debug(" * END ---- > Case 1: Passthrough (post etc...) *
url:"+cacheKey);
}
else {
CacheObject cacheObject = null;
boolean freshCopy = false;
+ boolean redirect = false;
//Case 2: Not in cache so fetch any necessary bits
if (!fragmentCache.contains(cacheKey)) {
log.debug(" * Case 2: Not in cache so fetch any necessary
bits *");
- fetch ( clientResponse, clientRequest, cacheKey ,
clientRequestUrl);
+ try {
+ fetch ( clientResponse, clientRequest, cacheKey ,
clientRequestUrl);
+ }
+ catch (RedirectException e) {
+ redirect = true;
+ }
freshCopy = true;
}
else {
@@ -347,8 +403,8 @@
//the Set-Cookie header is only forwarded back to the client if
//this is a fresh copy of the object we are returning.
//So we can guarantee that the returned Set-Cookie is the one
targetted for the current client
- copyHeadersFromServerResponseToClientResponse(clientResponse,
!freshCopy); //doesn't copy over saved cookie for responses returned from the
cache
- //TODO: might be inconsistancies
+ copyHeadersFromServerResponseToClientResponse(clientResponse,
!freshCopy, redirect); //doesn't copy over saved cookie for responses returned
from the cache
+ //TODO: might be inconsistencies
//TODO: 1/ when simply a fragment of a skeleton is fetched
//TODO: and we return the Set-Cookie of the cached skeleton and
not of the newly
//TODO: fetched fragment. Not sure what behaviour to adopt here.
@@ -356,16 +412,17 @@
//TODO: but isn't because the object is already cached. Might
not be important since
//TODO: original GET URL contains an ESI identifier which has a
similar role to cookies. Need to check
- boolean return304NotModified =
checkIfConditionalHtppHeaders(clientRequest, cacheObject);
+ boolean return304NotModified =
checkIfConditionalHttpHeaders(clientRequest, cacheObject);
if (return304NotModified) {
- log.debug("Returning a HTPP 304 for
"+cacheObject.getUrlCacheObject().getUrl());
+ log.debug("Returning a HTTP 304 for
"+cacheObject.getUrlCacheObject().getUrl());
clientResponse.setStatus(HttpServletResponse.SC_NOT_MODIFIED); //HTTP 304: the
document was not modified
//no data sent since request object hasn't changed
}
else {
clientResponse.setStatus(getStatusCode());
- if (!methodToServer.getName().equals("HEAD")) //since there
is no content for HEAD requests
+ if (!methodToServer.getName().equals("HEAD") //since there
is no content for HEAD requests
+ && !redirect ) //since there is no content for
redirection requests
sendStreamToClientFromCache(clientResponse, cacheKey,
cachedByteContent);
}
}
@@ -375,20 +432,21 @@
public void fetch (HttpServletResponse clientResponse,
HttpServletRequest clientRequest
- , String urlKey, String clientRequestUrl) {
+ , String urlKey, String clientRequestUrl)
+ throws RedirectException {
//can be called because object is not in cache, or some sub-object
is not in cache
FragmentCache fragmentCache = FragmentCache.getInstance();
-
UrlCacheObject urlObj = (UrlCacheObject)
fragmentCache.getHashCache().get(urlKey);
Vector threads = new Vector();
+ boolean retrievingSkeleton = false;
if (urlObj==null) {
//TODO:Need to support all other methods such as POST
!!!!!!!!!!!!!!!!!!!!!!
GetMethod getMethod = Utils.copyMethod(methodToServer);
- getMethod.setFollowRedirects( true );
+ getMethod.setFollowRedirects( false );
//TODO: statusCode ??
//TODO: cookies??
//TODO: esi headers??
@@ -403,9 +461,10 @@
clientRequestUrl
);
+ retrievingSkeleton = true;
threads.add(thread);
+
thread.setName("from:RespHdl,["+urlKey.substring(urlKey.length()-15)+"]id:"+thread.getId());
thread.start();
-
}
else {
String[] refObjs = urlObj.getReferencedObjs();
@@ -420,14 +479,15 @@
//Need to support all other methods POST
!!!!!!!!!!!!!!!!!!!!!!
GetMethod getMethod = Utils.copyMethod(methodToServer);
try {
- URI uri = new
URI(urlObj.getReferencedObjsUrls()[i]); //TODO: what is this esacpe business?
+ org.apache.commons.httpclient.URI uri =
+ new
org.apache.commons.httpclient.URI(urlObj.getReferencedObjsUrls()[i], true);
//we don't want to fetch the original URL from
methodToServer
// but the one referenced here
getMethod.setURI(uri);
} catch (URIException e) {
e.printStackTrace();
}
- getMethod.setFollowRedirects(true);
+ getMethod.setFollowRedirects(false);
//TODO: statusCode ??
//TODO: cookies??
//TODO: esi headers??
@@ -443,6 +503,7 @@
);
threads.add(thread);
+
thread.setName("from:RespHdl,ref:["+refUrlKey.substring(refUrlKey.length()-15)+"]id:"+thread.getId());
thread.start();
}
else log.debug("Cache HIT on refUrlKey ["+ refUrlKey +"]
referenced by ["+urlKey+"]");
@@ -454,6 +515,14 @@
GetThread thread = (GetThread) threadEnum.nextElement();
try {
thread.join();
+ //need to redirect the client browser if we got a redirect
on the skeleton retrieval
+ if (retrievingSkeleton) {
+ if (thread.isRedirectDetected()) {
+ log.debug("throwing RedirectException");
+ throw new RedirectException("server redirect
detected on skeleton request");
+ }
+ }
+
} catch (InterruptedException ex) {
log.error("THREAD WAS INTERRUPTED !!!!!!!!!!!!!!!!!!!!");
//TODO: deal with error
@@ -461,10 +530,10 @@
}
}
- private boolean checkIfConditionalHtppHeaders(HttpServletRequest
clientRequest, CacheObject cacheObject) {
+ private boolean checkIfConditionalHttpHeaders(HttpServletRequest
clientRequest, CacheObject cacheObject) {
boolean return304NotModified = false;
- printRequestHeaders(clientRequest);
+ //printRequestHeaders(clientRequest);
//check Entity Tag Revalidation
String IfNoneMatchHeader = clientRequest.getHeader("If-None-Match");
@@ -529,6 +598,9 @@
}
public static void printRequestHeaders(HttpServletRequest clientRequest)
{
+ printRequestHeaders( clientRequest, null);
+ }
+ public static void printRequestHeaders(HttpServletRequest clientRequest,
String msg) {
StringBuffer buffer = new StringBuffer();
Enumeration headerNamesEnum = clientRequest.getHeaderNames();
while (headerNamesEnum.hasMoreElements()) {
@@ -539,26 +611,33 @@
buffer.append(hName +":"+ hVal+"\n");
}
}
- log.debug("HTTP client REQUEST Headers : \n" + buffer.toString());
+ log.debug("HTTP client REQUEST Headers : "+((msg==null)?"":msg)+"\n"
+ buffer.toString());
}
public static void printRequestHeaders(HttpMethod method) {
+ printRequestHeaders(method, null);
+ }
+ public static void printRequestHeaders(HttpMethod method, String msg) {
Header[] headers = method.getRequestHeaders();
StringBuffer buffer = new StringBuffer();
for (int i=0; i<headers.length; i++){
buffer.append(headers[i]);
}
- log.debug("HTTP Server REQUEST Headers : \n" + buffer.toString());
+ log.debug("HTTP Server REQUEST Headers : "+((msg==null)?"":msg)+"\n"
+ buffer.toString());
}
-
public static void printResponseHeaders(HttpMethod method) {
+ printResponseHeaders( method, null);
+ }
+
+ public static void printResponseHeaders(HttpMethod method, String msg) {
Header[] headers = method.getResponseHeaders();
StringBuffer buffer = new StringBuffer();
for (int i=0; i<headers.length; i++){
buffer.append(headers[i]);
}
- log.debug("HTTP Server RESPONSE Headers : \n" + buffer.toString());
+ log.debug("HTTP Server ["+method.getStatusCode()+"] RESPONSE
Headers "+((msg==null)?"":msg)+" : \n"
+ + buffer.toString());
}