org.openid4java.util.HttpCache Maven / Gradle / Ivy
The newest version!
/*
* Copyright 2006-2008 Sxip Identity Corporation
*/
package org.openid4java.util;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpHead;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.params.AllClientPNames;
import org.apache.http.conn.ssl.X509HostnameVerifier;
import org.apache.http.message.BasicNameValuePair;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Date;
import javax.net.ssl.SSLContext;
/**
* Wrapper cache around HttpClient providing caching for HTTP requests.
* Intended to be used to optimize the number of HTTP requests performed
* during OpenID discovery.
*
* @author Marius Scurtescu, Johnny Bufu
*/
public class HttpCache extends AbstractHttpFetcher
{
private static Log _log = LogFactory.getLog(HttpCache.class);
private static final boolean DEBUG = _log.isDebugEnabled();
/**
* HttpClient used to place the HTTP requests.
*/
private HttpClient _client;
/**
* Cache for GET requests. Map of URL -> HttpResponse.
*/
private Map _getCache = new HashMap();
// todo: cache management
/**
* Cache for HEAD requests. Map of URL -> HttpResponse.
*/
private Map _headCache = new HashMap();
public HttpCache()
{
this(null);
}
public HttpCache(SSLContext sslContext)
{
this(sslContext, null);
}
/**
* Constructs a new HttpCache object, that will be initialized with the
* default set of HttpRequestOptions.
*
* @see HttpRequestOptions
*/
public HttpCache(SSLContext sslContext, X509HostnameVerifier hostnameVerifier)
{
super();
_client = HttpClientFactory.getInstance(
getDefaultRequestOptions().getMaxRedirects(),
getDefaultRequestOptions().getAllowCircularRedirects(),
getDefaultRequestOptions().getSocketTimeout(),
getDefaultRequestOptions().getConnTimeout(),
null, sslContext, hostnameVerifier);
}
/**
* Removes a cached GET response.
*
* @param url The URL for which to remove the cached response.
*/
private void removeGet(String url)
{
if (_getCache.keySet().contains(url))
{
_log.info("Removing cached GET response for " + url);
_getCache.remove(url);
}
else
_log.info("NOT removing cached GET for " + url + " NOT FOUND.");
}
/* (non-Javadoc)
* @see org.openid4java.util.HttpFetcher#get(java.lang.String, org.openid4java.util.HttpRequestOptions)
*/
public HttpResponse get(String url, HttpRequestOptions requestOptions)
throws IOException
{
DefaultHttpResponse resp = (DefaultHttpResponse) _getCache.get(url);
if (resp != null)
{
if (match(resp, requestOptions))
{
_log.info("Returning cached GET response for " + url);
return resp;
} else
{
_log.info("Removing cached GET for " + url);
removeGet(url);
}
}
HttpGet get = new HttpGet(url);
org.apache.http.HttpResponse httpResponse = null;
HttpEntity responseEntity = null;
try
{
get.getParams().setParameter(AllClientPNames.HANDLE_REDIRECTS, Boolean.TRUE);
HttpUtils.setRequestOptions(get, requestOptions);
httpResponse = _client.execute(get);
responseEntity = httpResponse.getEntity();
int statusCode = httpResponse.getStatusLine().getStatusCode();
String statusLine = httpResponse.getStatusLine().getReasonPhrase();
ResponseBody body = getResponseBody(responseEntity,
requestOptions.getMaxBodySize());
resp = new DefaultHttpResponse(statusCode, statusLine,
requestOptions.getMaxRedirects(), get.getURI().toString(),
httpResponse.getAllHeaders(), body.getBody());
resp.setBodySizeExceeded(body.isBodyTruncated());
// save result in cache
_getCache.put(url, resp);
}
finally
{
HttpUtils.dispose(responseEntity);
}
return resp;
}
private List toList(Map parameters) {
List list = new ArrayList(parameters.size());
for (Entry entry : parameters.entrySet()) {
list.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
}
return list;
}
@Override
public HttpResponse post(String url, Map parameters,
HttpRequestOptions requestOptions) throws IOException {
// we don't actually cache posts, since they are used for
// association requests and signature verification
// build the post message with the parameters from the request
HttpPost post = new HttpPost(url);
DefaultHttpResponse resp;
org.apache.http.HttpResponse httpResponse = null;
try
{
// can't follow redirects on a POST (w/o user intervention)
post.getParams().setBooleanParameter(AllClientPNames.HANDLE_REDIRECTS, false);
HttpUtils.setRequestOptions(post, requestOptions);
post.setEntity(new UrlEncodedFormEntity(toList(parameters), "UTF-8"));
// place the http call to the OP
if (DEBUG) _log.debug("Performing HTTP POST on " + url);
httpResponse = _client.execute(post);
int statusCode = httpResponse.getStatusLine().getStatusCode();
String statusLine = httpResponse.getStatusLine().getReasonPhrase();
ResponseBody body = getResponseBody(httpResponse.getEntity(),
requestOptions.getMaxBodySize());
resp = new DefaultHttpResponse(statusCode, statusLine,
requestOptions.getMaxRedirects(), post.getURI().toString(),
httpResponse.getAllHeaders(), body.getBody());
resp.setBodySizeExceeded(body.isBodyTruncated());
}
finally
{
HttpUtils.dispose(httpResponse);
}
return resp;
}
/**
* Returns content of an HTTP response entitity, but no more than maxBytes.
* @throws IOException
*/
private ResponseBody getResponseBody(HttpEntity response, int maxBodySize) throws IOException {
InputStream httpBodyInput = response.getContent();
if (httpBodyInput == null) {
return new ResponseBody(null, false);
}
// trim down maxBodySize if we know the content is smaller than
// maxBodySize
if ((response.getContentLength() > 0)
&& (response.getContentLength() < maxBodySize)) {
maxBodySize = (int) response.getContentLength();
}
byte data[] = new byte[maxBodySize];
int totalRead = 0;
int currentRead;
while (totalRead < maxBodySize)
{
currentRead = httpBodyInput.read(
data, totalRead, maxBodySize - totalRead);
if (currentRead == -1) break;
totalRead += currentRead;
}
boolean bodySizeExceeded = (httpBodyInput.read() > 0);
httpBodyInput.close();
if (DEBUG) _log.debug("Read " + totalRead + " bytes.");
return new ResponseBody(new String(data, 0, totalRead), bodySizeExceeded);
}
private boolean match(DefaultHttpResponse resp, HttpRequestOptions requestOptions)
{
// use cache?
if ( resp != null && ! requestOptions.isUseCache())
{
_log.info("Explicit fresh GET requested; removing cached copy");
return false;
}
//is cache fresh?
if ( resp != null && (requestOptions.getCacheTTLSeconds() >= 0))
{
long cacheTTL = requestOptions.getCacheTTLSeconds() * 1000;
Date now = new Date();
long currentTime = now.getTime();
long cacheExpTime = resp.getTimestamp() + cacheTTL;
if (cacheExpTime < currentTime)
{
String cacheExpTimeStr = (new Date(cacheExpTime)).toString();
_log.info("Cache Expired at " + cacheExpTimeStr + "; removing cached copy");
return false;
}
}
// content type rules
String requiredContentType = requestOptions.getContentType();
if (resp != null && requiredContentType != null)
{
Header responseContentType = resp.getResponseHeader("content-type");
if ( responseContentType != null &&
responseContentType.getValue() != null &&
!responseContentType.getValue().split(";")[0]
.equalsIgnoreCase(requiredContentType) )
{
_log.info("Cached GET response does not match " +
"the required content type, removing.");
return false;
}
}
if (resp != null &&
resp.getMaxRedirectsFollowed() > requestOptions.getMaxRedirects())
{
_log.info("Cached GET response used " +
resp.getMaxRedirectsFollowed() +
" max redirects; current requirement is: " +
requestOptions.getMaxRedirects());
return false;
}
return true;
}
/* (non-Javadoc)
* @see org.openid4java.util.HttpFetcher#head(java.lang.String, org.openid4java.util.HttpRequestOptions)
*/
public HttpResponse head(String url, HttpRequestOptions requestOptions)
throws IOException
{
DefaultHttpResponse resp = (DefaultHttpResponse) _headCache.get(url);
if (resp != null)
{
if (match(resp, requestOptions))
{
_log.info("Returning cached HEAD response for " + url);
return resp;
} else
{
_log.info("Removing cached HEAD for " + url);
removeGet(url);
}
}
HttpHead head = new HttpHead(url);
org.apache.http.HttpResponse httpResponse = null;
HttpEntity responseEntity = null;
try
{
head.getParams().setParameter(AllClientPNames.HANDLE_REDIRECTS, Boolean.TRUE);
HttpUtils.setRequestOptions(head, requestOptions);
httpResponse = _client.execute(head);
responseEntity = httpResponse.getEntity();
int statusCode = httpResponse.getStatusLine().getStatusCode();
String statusLine = httpResponse.getStatusLine().getReasonPhrase();
resp = new DefaultHttpResponse(statusCode, statusLine,
requestOptions.getMaxRedirects(), head.getURI().toString(),
httpResponse.getAllHeaders(), null);
// save result in cache
_headCache.put(url, resp);
}
finally
{
HttpUtils.dispose(responseEntity);
}
return resp;
}
private static class DefaultHttpResponse implements HttpResponse
{
/**
* The status code of the HTTP response.
*/
private int _statusCode;
/**
* The status line of the HTTP response.
*/
private String _statusLine;
/**
* The maximum HTTP redirects limit that was configured
* when this HTTP response was obtained.
*/
private int _maxRedirectsFollowed;
/**
* The final URI from where the document was obtained,
* after following redirects.
*/
private String _finalUri;
/**
* Map of header names List of Header objects of the HTTP response.
*/
private Map _responseHeaders;
/**
* The HTTP response body.
*/
private String _body;
/**
* Flag to indicate if the HTTP response size exceeded the maximum
* allowed by the (default) HttpRequestOptions.
*/
private boolean _bodySizeExceeded = false;
/**
* timestamp of creation
*
*(number of milliseconds since January 1, 1970, 00:00:00 GMT)
*/
private long _timestamp;
/**
* Constructs a new HttpResponse with the provided parameters.
*/
public DefaultHttpResponse(int statusCode, String statusLine,
int redirectsFollowed, String finalUri,
Header[] responseHeaders, String body)
{
_statusCode = statusCode;
_statusLine = statusLine;
_maxRedirectsFollowed = redirectsFollowed;
_finalUri = finalUri;
_responseHeaders = new HashMap();
if (responseHeaders != null)
{
String headerName;
Header header;
for (int i=0; i < responseHeaders.length; i++)
{
// HTTP header names are case-insensitive
headerName = responseHeaders[i].getName().toLowerCase();
header = responseHeaders[i];
List headerList = (List) _responseHeaders.get(headerName);
if (headerList != null)
headerList.add(responseHeaders[i]);
else
_responseHeaders.put(headerName,
new ArrayList(Arrays.asList(new Header[] {header})));
}
}
_body = body;
Date now = new Date();
_timestamp = now.getTime();
}
/**
* Gets the status code of the HttpResponse.
*/
public int getStatusCode()
{
return _statusCode;
}
/**
* Gets the status line of the HttpResponse.
*/
public String getStatusLine()
{
return _statusLine;
}
/**
* Gets the maximum HTTP redirects limit that was configured
* when this HTTP response was obtained.
*/
public int getMaxRedirectsFollowed()
{
return _maxRedirectsFollowed;
}
/**
* Gets the final URI from where the document was obtained,
* after following redirects.
*/
public String getFinalUri()
{
return _finalUri;
}
/**
* Gets the first header matching the provided headerName parameter,
* or null if no header with that name exists.
*/
public Header getResponseHeader(String headerName)
{
List headerList = (List) _responseHeaders.get(headerName.toLowerCase());
if (headerList != null && headerList.size() > 0)
return (Header) headerList.get(0);
else
return null;
}
/**
* Gets an array of Header objects for the provided headerName parameter.
*/
public Header[] getResponseHeaders(String headerName)
{
List headerList = (List) _responseHeaders.get(headerName.toLowerCase());
if (headerList != null)
return (Header[]) headerList.toArray(new Header[headerList.size()]);
else
return new Header[]{}; // empty array, same as HttpClient's method
}
/**
* Gets the HttpResponse body.
*/
public String getBody()
{
return _body;
}
/**
* Returns true if the HTTP response size exceeded the maximum
* allowed by the (default) HttpRequestOptions.
* @return
*/
public boolean isBodySizeExceeded()
{
return _bodySizeExceeded;
}
/**
* Sets the flag to indicate whether the HTTP response size exceeded
* the maximum allowed by the (default) HttpRequestOptions.
*/
public void setBodySizeExceeded(boolean bodySizeExceeded)
{
this._bodySizeExceeded = bodySizeExceeded;
}
public long getTimestamp()
{
return _timestamp;
}
}
private static class ResponseBody {
private final String body;
private final boolean bodyIsTruncated;
public ResponseBody(String body, boolean truncated)
{
this.body = body;
this.bodyIsTruncated = truncated;
}
public String getBody()
{
return body;
}
public boolean isBodyTruncated()
{
return bodyIsTruncated;
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy