jsr369-experts@servlet-spec.java.net

[jsr369-experts] Call for latest Push proposal

From: Edward Burns <edward.burns_at_oracle.com>
Date: Mon, 10 Aug 2015 14:40:36 -0700

Hello Volunteers,

Can I ask Greg to share again the latest incarnation of his HTTP/2
Server Push proposal? What I presented at GeekOut was a two-pronged
approach [1]:

1. A builder API as Greg proposed

2. A simple requestDispatcher call that does the "synthetic request"
thing, making it trivially easier for frameworks such as JSF to
leverage.

I would really like to have 2 be available as a degenerate case of 1,
but I'd like to see Greg's latest builder proposal. I've included below
the most recent one I could find but it's very old already and I'm
pretty sure Greg has evolved Jetty beyond that.

If there's no response, I'll just try to carry this one forward.

Thanks,

Ed

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