I've been working with Push a little bit more and have an alternative proposal for the API.
I've started work on this, but thought I'd seek some feedback before I commit too much effort. The idea is that often there will be many resources to push and that a lot of the work needed to work out headers, sessions and cookies is common to all the pushes. So we need a new PushBuilder instance that does all this work once. We would obtain a PushBuilder from a new method on the request:
The javadoc for that method expands as
Get a PushBuilder associated with this request initialized as follows:
- The method is initialized to "GET"
- The headers from this request are copied to the Builder, except for:
- Conditional headers (eg. If-Modified-Since)
- Range headers
- Expect headers
- Authentication headers
- Referrer headers
- If the request was Authenticated, an Authentication header will be set with a container generated token that will result in equivalent authentication
- The query string from
getQueryString()
- The
getRequestedSessionId()
value, unless at the time of the call getSession(boolean)
has previously been called to create a new HttpSession
, in which case the new session ID will be used as the PushBuilders requested session ID. - The source of the requested session id will be the same as for this request
- The builders Referer header will be set to
getRequestURL()
plus any getQueryString()
- If
HttpServletResponse.addCookie(Cookie)
has been called on the associated response, then a corresponding Cookie header will be added to the PushBuilder, unless the Cookie.getMaxAge()
is <=0, in which case the Cookie will be removed from the builder. - If this request has has the conditional headers If-Modified-Since or If-None-Match then the
PushBuilder.isConditional()
header is set to true.
Each call to getPushBuilder() will return a new instance of a PushBuilder based off this Request. Any mutations to the returned PushBuilder are not reflected on future returns.
The PushBuilder itself allows most of the initialisation to be overridden if need be, but hopefully it wont:
public interface PushBuilder
{
public String getMethod();
public void setMethod(String method);
public Enumeration<String> getHeaderNames();
public String getHeader(String name);
public void setHeader(String name,String value);
public String getQueryString();
public void setQueryString(String query);
public boolean isRequestedSessionIdValid();
public boolean isRequestedSessionIdFromCookie();
public boolean isRequestedSessionIdFromURL();
public String getRequestedSessionId();
public void setRequestedSessionId(String id);
public Cookie[] getCookies();
public void removeCookie(String name);
public void addCookie(Cookie cookie);
public boolean isConditional();
public void setConditional(boolean conditional);
/* ------------------------------------------------------------ */
/** Push a resource.
* Push a resource based on the current state of the PushBuilder. If {@link #isConditional()}
* is true and an etag or lastModified value is provided, then an appropriate conditional header
* will be generated. If an etag and lastModified value are provided only an If-None-Match header
* will be generated. If the builder has a session ID, then the pushed request
* will include the session ID either as a Cookie or as a URI parameter as appropriate. The builders
* query string is merged with any passed query string.
* @param uriInContext The URI within the current context of the resource to push.
* @param etag The etag for the resource or null if not available
* @param lastModified The last modified date of the resource or null if not available
* @throws IllegalArgumentException if the method set expects a request
* body (eg POST)
*/
void push(String uriInContext,String etag,long lastModified);
}
So normal usage of the push builder should be pretty simple, but it is flexible if need be. The key aspect of the API is that it separates
out the two phases of customising push request: firstly the customisation for session, authentication etc that are common to all pushed
requests; secondly each pushed request can be customised with conditional headers, session URI parameters and query string.
Here is an example usage in Jetty's PushCacheFilter:
// Are there associated resources to push?
if (!primaryResource.getAssociated().isEmpty())
{
// Let's make sure we push them to a valid session
request.getSession(true);
// Is push actually supported?
PushBuilder builder = baseRequest.getPushBuilder();
if (builder!=null)
{
// Yes, lets set a header as a demo
builder.setHeader("Via","Push Example");
// If query strings are not relevant, then clear
if (!isPreservingQueries())
builder.setQueryString(null);
// Push each resource
for (AssociatedResource resource : primaryResource.getAssociated())
{
builder.push(resource.getURI(),resource.getETag(),resource.getLastModified());
}
}
}
Thoughts?