<title>Server Caching</title>

<summarylist/>

<p/>Server caching can speed dynamic pages to
near-static speeds.   Many pages created by database
queries only change every 15 minutes or so, e.g. CNN or Slashdot.
Resin can cache the results and serve them like static pages.
Resin's caching will work for any servlet, including JSP
and XTP pages.  It depends only on the headers the servlet returns in
the response.

<p/>By default, pages are not cached.  To cache, a page must set a
HTTP caching header and be cacheable.  Examples of non-cacheable pages
include any page using sessions.

<p/>Resin's caching operates like a proxy cache.  It's controlled by the
same HTTP headers as any proxy cache.  Every user shares the same
cached page.

<section title='Expires'>

<p/>Setting the <var>Expires</var> header will cache the results
until the time expires.  For heavily loaded pages, even setting short
expires times can significantly improve performance.  Sessions should
be disabled for caching.

<p/>The following example sets expiration for 15 seconds.  So the
counter should update slowly.

<example title='Expires'>
&lt;%@ page session=false %>
&lt;%! int counter; %>
&lt;%
long now = System.currentTimeMillis();
response.setDateHeader("Expires", now + 15000);
%>
Count: &lt;%= counter++ %>
</example>

<p/><var/Expires/> is useful for database generated pages which are
continuously, but slowly updated.  To cache based on something with a known
modified date, like a file, you can use <var/If-Modified/>.

</section>

<section title='If-Modified'>

The <var>If-Modified</var> headers let you cache based on an
underlying change date.  For example, the page may only change when an
underlying source page changes.  Resin lets you easily use 
<var>If-Modified</var> by overriding methods in HttpServlet or
in a JSP page.

<p/>The following page only changes when the underlying 'test.xml'
page changes.

<example>
&lt;%@ page session=false %>
&lt;%!
int counter;

public long getLastModified(HttpServletRequest req)
{
  String path = req.getRealPath("test.xml");
  return new File(path).lastModified();
}
%>
Count: &lt;%= counter++ %>
</example>

<p/><var/If-Modified/> pages are useful in combination with the
<var/cache-mapping/> configuration.

</section>

<section title='Servlets'>

Caching servlets is exactly like caching JSP pages (or XTP or
static files.)  Resin's caching mechanism works like a proxy cache: it
don't care how the page is generated; as long as the proper caching
headers are set, the page will be cached.

<example>
package test;

import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;

public class TestServlet extends HttpServlet {
  int counter;

  public long getLastModified(HttpServletRequest req)
  {
    String path = req.getRealPath("test.xml");
    return new File(path).lastModified();
  }

  public void doGet(HttpServletRequest req,
                    HttpServletResponse res)
    throws IOException, ServletException
  {
    PrintWriter out = res.getWriter();

    out.println("Count: " + counter++);
  }
}
</example>

</section>

<section title='Included Pages'>

Resin can cache subpages even when the top page can't be cached.
Sites allowing user personalization will often design pages with
<var/jsp:include/> subpages.  Some subpages are user-specific and can't
be cached.  Others are common to everybody and can be cached.

<p/>Resin treats subpages as independent requests, so they can be
cached independent of the top-level page.  Try the following, use the
first <var/expires/> counter example as the included page.  Create a
top-level page that looks like:

<example>
&lt;%@ page session=true %>
&lt;% if (! session.isNew()) { %>
&lt;h1>Welcome back!&lt;/h1>
&lt;% } %>

&lt;jsp:include page="expires.jsp"/>
</example>

</section>

<section title='Caching Anonymous Users'>

<p/>In many cases, logged in users get specialized pages, but
anonymous users all see the same page.  In this case, you can still
take advantage of Resin's caching, but you'll need to do a little work
in your design.

<p/>First, you'll need to create an <var/include()/> subpage that contains
the common page.  The top page can't be cached because it depends on
whether a user is logged in or not.

<p/>You must use <var/include()/> because <var/forward()/> is cached just
like the top page.  The top page isn't cacheable because of the user login,
so the forwarded page isn't cacheable either.

<p/>Here's what a sample subpage might look like:

<example>
&lt;%@ page session=false %>
&lt;%! int counter; %>
&lt;%
long now = System.currentTimeMillis();
response.setDateHeader("Expires", now + 15000);

String user = request.getParameter("user");
%>
User: &lt;%= user %> &lt;%= counter++ %>
</example>

<p/>The top page slightly trickier because it needs to pass the user
to the subpage.  You need to pass a unique id.  If you pass a boolean
<var/logged-in/> parameter, all logged in users will see the same page.

<example>
&lt;%@ page session=true %>
&lt;%
String user = getSomeSortOfUniqueUserId();
if (user == null)
  user = "Anonymous";
%>

... 
&lt;jsp:include page='cachedpage.jsp'/>
  &lt;jsp:page name='user' value='&lt;%= user %>'/>
&lt;/jsp:include>
</example>

<p/>Of course, the top-level page could also be a servlet:

<example>
...

String user = getSomeSortOfUniqueUserId(request);
if (user == null)
  user = "Anonymous";

RequestDispatcher disp;
disp = request.getRequestDispatcher("/cachedpage.jsp?user=" + user);

disp.include(request, response);
</example>

</section>

<section title='Experimental Anonymous Caching'>

<p/>Resin 1.2 include an anonymous user caching feature.  If a user
is not logged in, she will get a cached page.  If she's logged in, she'll
get her own page.  This feature will not work if anonymous users are
assigned cookies for tracking purposes.

<p/>To make anonymous caching work, you must set the <var/Cache-Control:
x-anonymous/> header.  If you omit the x-anonymous header, Resin will use
the <var/Expires/> to cache the same page for every user.

<example>
&lt;%@ page session=false %>
&lt;%! int counter; %>
&lt;%
long now = System.currentTimeMillis();
response.setDateHeader("Expires", now + 15000);
response.addHeader("Cache-Control", "x-anonymous");

String user = request.getParameter("user");
%>
User: &lt;%= user %> &lt;%= counter++ %>
</example>

<p/>The top page must still set the <var/Expires/> or
<var/If-Modified/> header, but Resin will take care of deciding if the
page is cacheable or not.  If the request has any cookies, Resin will
not cache it or use the cached page.  If it has no cookies, Resin will
use the cached page.

<p/>When using x-anonymous, user tracking cookies will make the
page uncacheable even if the page is the same for all users.
Resin chooses to cache or not based on the existence of any cookies
in the request, whether they're used or not.

</section>

<section title='cache-mapping'>

<var/cache-mapping/> assigns a browser Expires to an
If-Modified cacheable page.  It does not affect Expires cached pages
and it does not affect Resin's caching.  The FileServlet takes
advantage of <var/cache-mapping/> because it's an If-Modified servlet.

<p/>Often, you want a long Expires time for a page to a browser.  For
example, any gif will not change for 24 hours.  That keeps browsers
from asking for the same gif every five seconds; that's especially
important for tiny formatting gifs.  However, as soon as that page or
gif changes, you want the change immediately available to any new
browser or to a browser using reload.

<p/>Here's how you would set the Expires to 24 hours for a gif, based
on the default FileServlet.

<example>
&lt;web-app id='/'>
  &lt;cache-mapping url-pattern='*.gif'
                  expires='24h'/>
&lt;/web-app>
</example>

<p/>The <var/cache-mapping/> automatically generates the Expires
header.  It only works for cacheable pages setting If-Modified or
ETag.  It will not affect pages explicily setting Expires or
non-cacheable pages.  So it's safe to create a cache-mapping
for <var/*.jsp/> even if only some are cacheable.

</section>

