I've cleaned up the PushBuilder API as currently being used Jetty
9.3.0-SNAPSHOT (see attached interface and javadoc paste below)
I'm slowly liking the builder API more as a way of creating push requests,
but it still does not answer the questions of when to do the pushes and how
to resolve duplicates.
My current thinking on duplicates is that PushBuilder#push should throw an
exception (or maybe return false?) if the path+query has been pushed before
for the associated request.
As for when, I think that is an open question, my own push filter does it
before the associated resource is generated, but I can see reasons to do it
during generation as well. Not so sure about after generation, as that may
be too late and requests may already have been sent by the client. A
callback for when the response is committed could be a good option that we
have discussed before. Ultimately I don't think we can decide when in
the API.
cheers
org <eclipse-javadoc:%E2%98%82=jetty-server/src%5C/main%5C/java%3Corg>.
eclipse
<eclipse-javadoc:%E2%98%82=jetty-server/src%5C/main%5C/java%3Corg.eclipse>.
jetty
<eclipse-javadoc:%E2%98%82=jetty-server/src%5C/main%5C/java%3Corg.eclipse.jetty>
.server
<eclipse-javadoc:%E2%98%82=jetty-server/src%5C/main%5C/java%3Corg.eclipse.jetty.server>
.PushBuilder
Build a request to be pushed. A PushBuilder is obtained by calling
Request.getPushBuilder()
<eclipse-javadoc:%E2%98%82=jetty-server/src%5C/main%5C/java%3Corg.eclipse.jetty.server%7BPushBuilder.java%E2%98%83PushBuilder%E2%98%82Request%E2%98%82getPushBuilder%E2%98%82>
which creates an initialises the builder as follows:
- Each call to getPushBuilder() will return a new instance of a
PushBuilder based off the Request. Any mutations to the returned
PushBuilder are not reflected on future returns.
- The method is initialised to "GET"
- The requests headers are added to the Builder, except for:
- Conditional headers (eg. If-Modified-Since)
- Range headers
- Expect headers
- Authorisation headers
- Referrer headers
- If the request was Authenticated, an Authorisation header will be set
with a container generated token that will result in equivalent
Authorisation for the pushed request
- The query string from ServletRequest.getQueryString()
<eclipse-javadoc:%E2%98%82=jetty-server/src%5C/main%5C/java%3Corg.eclipse.jetty.server%7BPushBuilder.java%E2%98%83PushBuilder%E2%98%82ServletRequest%E2%98%82getQueryString%E2%98%82>
- The ServletRequest.getRequestedSessionId()
<eclipse-javadoc:%E2%98%82=jetty-server/src%5C/main%5C/java%3Corg.eclipse.jetty.server%7BPushBuilder.java%E2%98%83PushBuilder%E2%98%82ServletRequest%E2%98%82getRequestedSessionId%E2%98%82>
value, unless at the time of the call ServletRequest.getSession(boolean)
<eclipse-javadoc:%E2%98%82=jetty-server/src%5C/main%5C/java%3Corg.eclipse.jetty.server%7BPushBuilder.java%E2%98%83PushBuilder%E2%98%82ServletRequest%E2%98%82getSession%E2%98%82boolean>
has previously been called to create a new HttpSession
<eclipse-javadoc:%E2%98%82=jetty-server/src%5C/main%5C/java%3Corg.eclipse.jetty.server%7BPushBuilder.java%E2%98%83PushBuilder%E2%98%82HttpSession>,
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
the request
- The Referer header will be set to ServletRequest.getRequestURL()
<eclipse-javadoc:%E2%98%82=jetty-server/src%5C/main%5C/java%3Corg.eclipse.jetty.server%7BPushBuilder.java%E2%98%83PushBuilder%E2%98%82ServletRequest%E2%98%82getRequestURL%E2%98%82>
plus any ServletRequest.getQueryString()
<eclipse-javadoc:%E2%98%82=jetty-server/src%5C/main%5C/java%3Corg.eclipse.jetty.server%7BPushBuilder.java%E2%98%83PushBuilder%E2%98%82ServletRequest%E2%98%82getQueryString%E2%98%82>
- If HttpServletResponse.addCookie(Cookie)
<eclipse-javadoc:%E2%98%82=jetty-server/src%5C/main%5C/java%3Corg.eclipse.jetty.server%7BPushBuilder.java%E2%98%83PushBuilder%E2%98%82HttpServletResponse%E2%98%82addCookie%E2%98%82Cookie>
has been called on the associated response, then a corresponding Cookie
header will be added to the PushBuilder, unless the Cookie.getMaxAge()
<eclipse-javadoc:%E2%98%82=jetty-server/src%5C/main%5C/java%3Corg.eclipse.jetty.server%7BPushBuilder.java%E2%98%83PushBuilder%E2%98%82Cookie%E2%98%82getMaxAge%E2%98%82>
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 isConditional()
<eclipse-javadoc:%E2%98%82=jetty-server/src%5C/main%5C/java%3Corg.eclipse.jetty.server%7BPushBuilder.java%E2%98%83PushBuilder%E2%98%82%E2%98%82isConditional%E2%98%82>
header is set to true.
A PushBuilder can be customised by chained calls to mutator methods before
the push()
<eclipse-javadoc:%E2%98%82=jetty-server/src%5C/main%5C/java%3Corg.eclipse.jetty.server%7BPushBuilder.java%E2%98%83PushBuilder%E2%98%82%E2%98%82push%E2%98%82>
method is called to initiate a push request with the current state of the
builder. After the call to push()
<eclipse-javadoc:%E2%98%82=jetty-server/src%5C/main%5C/java%3Corg.eclipse.jetty.server%7BPushBuilder.java%E2%98%83PushBuilder%E2%98%82%E2%98%82push%E2%98%82>,
the builder may be reused for another push, however the path(String)
<eclipse-javadoc:%E2%98%82=jetty-server/src%5C/main%5C/java%3Corg.eclipse.jetty.server%7BPushBuilder.java%E2%98%83PushBuilder%E2%98%82%E2%98%82path%E2%98%82String>
, etag(String)
<eclipse-javadoc:%E2%98%82=jetty-server/src%5C/main%5C/java%3Corg.eclipse.jetty.server%7BPushBuilder.java%E2%98%83PushBuilder%E2%98%82%E2%98%82etag%E2%98%82String>
and lastModified(String)
<eclipse-javadoc:%E2%98%82=jetty-server/src%5C/main%5C/java%3Corg.eclipse.jetty.server%7BPushBuilder.java%E2%98%83PushBuilder%E2%98%82%E2%98%82lastModified%E2%98%82String>
values will have been nulled. All other values are retained over calls to
push()
<eclipse-javadoc:%E2%98%82=jetty-server/src%5C/main%5C/java%3Corg.eclipse.jetty.server%7BPushBuilder.java%E2%98%83PushBuilder%E2%98%82%E2%98%82push%E2%98%82>
.
On 31 January 2015 at 08:20, Edward Burns <edward.burns_at_oracle.com> wrote:
> >>>>> On Fri, 28 Nov 2014 16:27:11 +1100, Greg Wilkins <gregw_at_intalio.com>
> said:
>
> GW> I've had a few more thoughts on a HTTP Push API that benefit from a
> little
> GW> more experience and experimentation.
>
> GW> One of the proposals discussed was for an API that looked something
> like:
>
> GW> response.push(uri)
>
> [...]
>
> GW> The alternative API that I proposed was
>
> GW> servletContext.getRequestDispatcher(uri).push(request)
>
> [...]
>
> GW> So this is a lot of mutating and filtering required to make a good
> pushed
> GW> request and I'm sure there are complexities and interactions yet to be
> GW> discovered that need to be handled.
>
> [...]
>
> GW> Thus I'm leaning back to a simpler API where just the uri is passed
> and the
> GW> container is responsible for doing all these kinds of mutations.
> Perhaps
> GW> some kind of optional additional headers could be passed in?
>
> GW> Anyway, in short, I think this mechanism is going to be a lot more
> complex
> GW> and tricky to get right than I originally thought.
>
> This sort of back and forth is normal at this stage of the design
> process. I think we need a more formal push proposal to pull this all
> together. Perhaps a conference call as well.
>
> >>>>> 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.
>
> >>>>> On Mon, 8 Dec 2014 12:42:07 -0800, Edward Burns <
> edward.burns_at_oracle.com> said:
>
> EB> Well, I'm sorry to see the simplicity of the existing API have to give
> EB> way to something that seems to be starting to get quite complex.
>
> 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>
>
> EB> If we obtain the PushBuilder from the current request, then why would
> EB> the method not be taken on from the existing request?
>
> GW> Because we cannot push POSTs and PUTs. We can push GETs and HEAD,
> maybe
> GW> even OPTION and these can be pushed in association with a POST (can't
> think
> GW> why a PUT would have push associates).
>
> GW> So just trying to come up with a default that will work with minimal
> calls
> GW> for most situations. Maybe it is initialised to the same method as the
> GW> original request, unless that method is not supported by push, in which
> GW> case it is initialised to GET.
>
> >>>>> On Tue, 9 Dec 2014 19:13:25 -0500 (EST), Stuart Douglas <
> sdouglas_at_redhat.com> said:
>
> SD> According to the spec I think it is just GET and HEAD, as the
> SD> request must be both safe and cacheable, and OPTIONS is not
> SD> cacheable. In practice GET is likely to be the only one that
> SD> actually gets pushed.
>
> SD> I think that in this case if the user attempts to set the method to
> SD> one that we know is explicitly disallowed by the HTTP2 spec we
> SD> should throw an IllegalArgumentException.
>
> GW> The other option is to just support GET and HEAD and control which
> GW> one with a setHeadRequest(boolean) method?
>
> GW> That removes the issue of throwing IAE for the arbitrary methods
> GW> that might get set on the push builder.
>
> >>>>> On Wed, 10 Dec 2014 08:44:42 +0100, Greg Wilkins <gregw_at_intalio.com>
> said:
>
> GW> There is a use-case for pushing HEAD requests. If the server is not
> sure
> GW> of the state of the client cache, but is also not in possession of the
> etag
> GW> or last modified date for a resource, then it can push a HEAD response
> to
> GW> inform the cache of the current state of the resource.
>
> >>>>> On Wed, 10 Dec 2014 19:16:27 +1100, Stuart Douglas <
> stuart.w.douglas_at_gmail.com> said:
>
> GW> I think there is a good chance that over time people will extend the
> GW> HTTP2 protocol in ways we can't really predict now, and limiting the
> GW> push API to just two methods when the spec allows more seems short
> GW> sighted.
>
> GW> We have already been talking about way that we can take advantage of
> GW> HTTP2 to replace some of our propietary protocols. For example you
> GW> could use server push to push load information from a backend server
> GW> to a load balancer, or you could use HTTP2 for EJB invocations, and
> GW> use a custom verb to push the result of async invocations.
>
> GW> I think we should throw an IAE basically to let users know they are
> GW> doing it wrong and a compliant browser will reject the push, however
> GW> we should not add any further restrictions.
>
> There seems to be consensus that we should not bake assumptions about
> the allowable methods that can be pushed into the API.
>
> EB> [...]
>
> 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()
>
> EB> I prefer the isPushSupported() method. I think calling
> EB> getPushBuilder() should throw IllegalStateException if push is not
> EB> supported.
>
> Using null to convey semantics is bad practice.
>
> GW> Sounds like a converging consensus on that.
>
> I'll take it where I can get it.
>
> GW> void push(String uriInContext,String etag,long lastModified);
>
> MT> s/long/Long/ else lastModified can't be null
>
> MT> How about void push(String uriInContext) as a convenience method for
> MT> when etag and lastmodified are both null?
>
> [...]
>
> MT> I like the general idea. A few comments in line based on a quick scan
> of
> MT> the API. I expect I'll have more detailed feedback when I get to the
> MT> point where I am implementing this.
>
> >>>>> On Mon, 8 Dec 2014 08:26:49 +0100, Greg Wilkins <gregw_at_intalio.com>
> said:
>
> GW> Yep, good idea... plus I am making a few other similar trims. The
> API is
> GW> being updated as I play with it, so consider what I posted mostly as
> GW> conceptual. I'll post an updated version in the next few days.
>
> >>>>> On Tue, 9 Dec 2014 12:34:20 +0100, Greg Wilkins <gregw_at_intalio.com>
> said:
>
> GW> I have incorporated some of the feedback received into Jetty's
> experimental
> GW> implementation. You can see the PushBuilder (as a class rather than
> GW> interface) here:
>
> GW>
> http://git.eclipse.org/c/jetty/org.eclipse.jetty.project.git/tree/jetty-server/src/main/java/org/eclipse/jetty/server/PushBuilder.java
>
> GW> and the push methods on request are seen at
> GW>
> http://git.eclipse.org/c/jetty/org.eclipse.jetty.project.git/tree/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java#n218
>
> GW> and a filter that utilizes the API is
> GW>
> http://git.eclipse.org/c/jetty/org.eclipse.jetty.project.git/tree/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/PushSessionCacheFilter.java
>
> GW> I'm going to now gather some experience using this on our live site.
> I
> GW> think further progress on making this the start of a proposed standard
> API
> GW> will probably need to wait until other implementations catch up with
> GW> using/implementing push.
>
> Well, this was mid December. Can you share any developments since then?
>
> >>>>> On Wed, 10 Dec 2014 05:42:20 -0500 (EST), Stuart Douglas <
> sdouglas_at_redhat.com> said:
>
> SD> I updated https://http2.undertow.io to enable push today (with a
> SD> nasty hack to work around IE's push support issues), using a similar
> SD> kind of handler that learns over time.
>
> That was fast.
>
> SD> Something that did occur to me that there is a real possibility of
> SD> different layers generating the same push request, e.g. if you have
> SD> something like Greg's filter that learn's over time, and you have
> SD> user code that pushes based on what it knows is going to be
> SD> requested there is a good chance they will both generate the same
> SD> push (this happened to me today, as my directory listing rendering
> SD> code was already pushing the css).
>
> SD> I'm not sure how to best deal with this, the obvious way is to just
> SD> ignore a push for the same resource after the first has been sent,
> SD> although in this case the later response will be from the
> SD> application layer which should have more information. Maybe we
> SD> should make implementations queue the pushes until headers are sent
> SD> and then send the last push for each path?
>
> It seems like we need a system where each desired push is given a hash
> so that pushes for the same resource from different layers can be
> subject to a "last one wins" or "first one wins" strategy.
>
> >>>>> On Wed, 10 Dec 2014 11:53:24 +0100, Greg Wilkins <gregw_at_intalio.com>
> said:
>
> GW> Stuart,
> GW> I agree that having multiple push strategies operating at once is
> going to
> GW> be a problem and I definitely agree that it is not obvious how to best
> deal
> GW> with it.
>
> GW> Queuing the push promise until the headers are sent may help, but then
> I
> GW> can also imagine frameworks that will be generating content
> dynamically and
> GW> will want to push resources as the html is generated, so the headers
> will
> GW> be long gone. If we say the container is responsible for duplicate
> GW> elimination, then such frameworks could be very lazy. I'm going to
> ponder
> GW> on this one some more.
>
> >>>>> On Wed, 10 Dec 2014 22:01:59 +1100, Stuart Douglas <
> stuart.w.douglas_at_gmail.com> said:
>
> SD> My initial thoughts are to queue until header generation, preserving
> the
> SD> most recent, send all queued at commit time, and after that send
> SD> immediately and discard duplicates. There may well be some important
> use
> SD> cases this is missing though, I have not really thought it through.
>
> This is something we need to list as an open question. Let's call it
> the "multiple duplicate push requset problem".
>
> Ed
>
> --
> | edward.burns_at_oracle.com | office: +1 407 458 0017
> | 26 days til DevNexus 2015
> | 36 days til JavaLand 2015
> | 46 days til CONFESS 2015
>
--
Greg Wilkins <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.