I need to cache a web page and then for future requests, check the cache (using url as key) and if found, return the web page from the cache instead of making a request.
I'm using Smiley's ProxyServlet and the method where the servlet writes to the OutputStream seems perfect for caching. I've added just two lines of code:
/**
* Copy response body data (the entity) from the proxy to the servlet client.
* TODO: CACHE entity here for retrieval in filter
*/
protected void copyResponseEntity( HttpResponse proxyResponse, HttpServletResponse servletResponse,
HttpRequest proxyRequest, HttpServletRequest servletRequest ) throws IOException
{
HttpEntity entity = proxyResponse.getEntity();
if ( entity != null )
{
String key = getCurrentUrlFromRequest( servletRequest ); // 1
basicCache.getCache().put( key, proxyResponse.getEntity() ); // 2
OutputStream servletOutputStream = servletResponse.getOutputStream();
entity.writeTo( servletOutputStream );
}
}
and it kinda works, it does store the HttpEntity in the cache. But when I go back to a browser and request the same url once again, when the code gets back in my Filter, I obtain the HttpEntity using the url as key, and I write it to the response, but I get a "Stream closed" error:
java.io.IOException: Stream closed
at java.base/java.util.zip.GZIPInputStream.ensureOpen(GZIPInputStream.java:63) ~[na:na]
at java.base/java.util.zip.GZIPInputStream.read(GZIPInputStream.java:114) ~[na:na]
at java.base/java.io.FilterInputStream.read(FilterInputStream.java:107) ~[na:na]
at org.apache.http.client.entity.LazyDecompressingInputStream.read(LazyDecompressingInputStream.java:64) ~[httpclient-4.5.9.jar:4.5.9]
at org.apache.http.client.entity.DecompressingEntity.writeTo(DecompressingEntity.java:93) ~[httpclient-4.5.9.jar:4.5.9]
at com.myapp.test.foo.filters.TestFilter.doFilter(TestFilter.java:37) ~[classes/:na]
Here's the filter:
@Component
@WebFilter( urlPatterns = "/proxytest", description = "a filter for test servlet", initParams = {
@WebInitParam( name = "msg", value = "==> " ) }, filterName = "test filter" )
public class TestFilter implements Filter
{
private FilterConfig filterConfig;
@Autowired BasicCache basicCache;
@Override
public void doFilter( ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain )
throws IOException, ServletException
{
String url = getCurrentUrlFromRequest( servletRequest ); // 1
HttpEntity page = (HttpEntity) basicCache.getCache().get( url ); //2
if ( null != page ) // 3
{
OutputStream servletOutputStream = servletResponse.getOutputStream(); // 4
page.writeTo( servletOutputStream ); // 5 stream closed :(
}
else
{
filterChain.doFilter( servletRequest, servletResponse );
}
}
public String getCurrentUrlFromRequest( ServletRequest request )
{
if ( !( request instanceof HttpServletRequest ) ) return null;
return getCurrentUrlFromRequest( (HttpServletRequest) request );
}
public String getCurrentUrlFromRequest( HttpServletRequest request )
{
StringBuffer requestURL = request.getRequestURL();
String queryString = request.getQueryString();
if ( queryString == null ) return requestURL.toString();
return requestURL.append( '?' ).append( queryString ).toString();
}
@Override
public void destroy()
{
}
@Override
public void init( FilterConfig filterConfig ) throws ServletException
{
this.filterConfig = filterConfig;
}
}
oh, and the BasicCache class just in case:
@Component
public class BasicCache
{
private UserManagedCache<String, HttpEntity> userManagedCache;
public BasicCache()
{
userManagedCache = UserManagedCacheBuilder.newUserManagedCacheBuilder( String.class, HttpEntity.class )
.build( true );
}
public UserManagedCache getCache()
{
return userManagedCache;
}
public void destroy()
{
if ( null != userManagedCache )
{
userManagedCache.close();
}
}
}
I am stuck with this very localized / manual / whatever you want to call it kind of caching -- I can't use the obvious "just hook up ehcache / redis / whatever and let it do it's thing". So while I know those fine caches can cache entire webpages, I don't know if they allow me to work in this admittedly unusual way.
So I'm hoping SO can show me how to get this done. I first tried just wiring in a ConcurrentHashMap for my basic cache but that didn't work either, so I thought I'd see if I could tap into whatever magic the big caching guns have, but so far I can't.
Thanks for any help!