Greg Wilkins wrote:
>
> 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:
>
>     /** Get a PushBuilder associated with this request initialized as
> follows:<ul>
>       * <li>The method is initialized to "GET"</li>
>       * <li>The headers from this request are copied to the Builder,
> except for:<ul>
>       * <li>Conditional headers (eg. If-Modified-Since)
>       * <li>Range headers
>       * <li>Expect headers
>       * <li>Authentication headers
>       * <li>Referrer headers
>       * </ul></li>
>       * <li>If the request was Authenticated, an Authentication header will
>       * be set with a container generated token that will result in
> equivalent
>       * authentication</li>
>       * <li>The query string from {_at_link #getQueryString()}
What is the reasoning behind copying the query string? Shouldn't it be 
set from the push URL?
<snip>
>
> 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);
 From a users point of view it would be good if all these methods 
returned the PushBuilder, so you can chain the calls together (i.e. make 
it a fluent API). On the flip side none of the rest of the API works 
like this, so I guess it is a trade off between consistency and ease of use.
>
>      public Enumeration<String> getHeaderNames();
What about Set<String> instead? Enumeration is a pretty yuck API, 
although once again I guess it is a trade off between being consistent 
with the existing HttpServletRequest API.
>      public String getHeader(String name);
>      public void setHeader(String name,String value);
I this we should include all the header manipulation methods that are 
present on HttpServletRequest as well. At the very least I think we 
should support multiple header values (getHeaders() and addHeader()), 
and for completeness we should probably also have getDateHeader etc.
>
>      public String getQueryString();
>      public void setQueryString(String query);
I don't know if we need this, I would expect that it would just be 
parsed from the push URI. The way this API is currently described I 
think it is not very intuitive, e.g. if I am at /story?id=3 and I push 
the URL /images?id=6 I will end up with /images?id=3, which is not 
really what I would expect.
>
>      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 {_at_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.
I think we should also just have a isPushSupported() method on the 
request, so code can check if push is supported without creating a builder.
>
>   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?
For the most part I like the idea.
Stuart
>
>
>
>
>
> On 5 December 2014 at 19:56, Greg Wilkins <gregw_at_intalio.com
> <mailto:gregw_at_intalio.com>> wrote:
>
>
>     All,
>
>     another thing that I think would be helpful for implementing push is
>     an event for when a response is committed, but before the content
>     has been sent.
>
>     This is the perfect time for a push algorithm to extract information
>     about associated resources (size, etag, modified, session,
>     set-cookies etc.) and also a great time to start doing pushed.
>
>     If we try to implement push before commit, then we may not see a
>     newly valid session or some authority token set in a cookie that
>     needs to be used when creating the associated requests.    If we
>     delay later, then the client will have already received content and
>     may have already issued requests for the associated resources.
>
>     Would it be possible to add something like:
>
>     interface ServletResponseListener
>     {
>          void responseCommitted(ServletResponseEvent event);
>     }
>
>     class ServletResponseEvent
>     {
>          ServletRequest getServletRequest() {...}
>          ServletResponse getServletResponse() {...}
>     }
>
>     thoughts?
>
>
>
>     On 28 November 2014 at 16:27, Greg Wilkins <gregw_at_intalio.com
>     <mailto:gregw_at_intalio.com>> wrote:
>
>
>         Hi all,
>
>         I've had a few more thoughts on a HTTP Push API that benefit
>         from a little more experience and experimentation.
>
>         One of the proposals discussed was for an API that looked
>         something like:
>
>         response.push(uri)
>
>         The issue with this are:
>
>          1. Cannot mutate headers and container will have to work out
>             all the headers needed
>          2. Does not work for cross context pushing (not a huge loss,
>             but breaks orthogonality of container)
>
>         The alternative API that I proposed was
>
>         servletContext.getRequestDispatcher(uri).push(request)
>
>         which solves both the problems identified above as cross context
>         can be used and a wrapped request to be pushed in if the headers
>         need to be mutated.
>
>         So this led me to start thinking what mutations are needed when
>         generating the psuedo request that will be in the push promise
>         and which is used to generate the pushed resource.    There are
>         actually quiet a few that I can think of:
>
>           * If the base request contains a session ID in it's URI,  then
>             the pushed URIs need to be updated with a session ID.
>           * If the requested session ID is for an invalid session, but
>             getSession() returns a new valid session, then any session
>             ID in the pushed request (either as cookie or uri param)
>             should be the new session ID and not the old invalid one.
>           * Conditional headers need to be removed/mutated as any etags
>             or modified dates will relate to the base request and not
>             the pushed resource.
>           * Referer header should be set as the base resource, not the
>             base resources referrer
>           * Any digest authentication headers need to be removed as they
>             apply only to the base request.   We then need to decide if
>             pushed requests need to be reauthenticated and/or reauthorised?
>           * Perhaps the method needs to be mutated? Can we push GETs
>             from POSTS?
>           * Expect 100 headers removed
>           * Range headers removed
>           * Upgrade headers removed
>
>
>         So this is a lot of mutating and filtering required to make a
>         good pushed request and I'm sure there are complexities and
>         interactions yet to be discovered that need to be handled.  Thus
>         I'm starting to think that passing in the request as the
>         template is not really the best way to go:
>
>           * as it will be a bit clumsy to have to wrap the request and
>             then do all this mutating.
>           * if left to the application, it is likely that many of these
>             things will not be correctly handled
>
>         Thus I'm leaning back to a simpler API where just the uri is
>         passed and the container is responsible for doing all these
>         kinds of mutations.  Perhaps some kind of optional additional
>         headers could be passed in?
>
>         Anyway, in short, I think this mechanism is going to be a lot
>         more complex and tricky to get right than I originally thought.
>
>         cheers
>
>
>
>
>
>
>
>
>         --
>         Greg Wilkins <gregw_at_intalio.com <mailto:gregw_at_intalio.com>>  @
>         Webtide - /an Intalio subsidiary/
>         http://eclipse.org/jetty HTTP, SPDY, Websocket server and client
>         that scales
>         http://www.webtide.com  advice and support for jetty and cometd.
>
>
>
>
>     --
>     Greg Wilkins <gregw_at_intalio.com <mailto:gregw_at_intalio.com>>  @
>     Webtide - /an Intalio subsidiary/
>     http://eclipse.org/jetty HTTP, SPDY, Websocket server and client
>     that scales
>     http://www.webtide.com  advice and support for jetty and cometd.
>
>
>
>
> --
> Greg Wilkins <gregw_at_intalio.com <mailto:gregw_at_intalio.com>>  @  Webtide
> - /an Intalio subsidiary/
> http://eclipse.org/jetty HTTP, SPDY, Websocket server and client that scales
> http://www.webtide.com  advice and support for jetty and cometd.