Inlining nocache.js

79 views
Skip to first unread message

George Georgovassilis

unread,
Aug 7, 2009, 3:51:39 AM8/7/09
to Google Web Toolkit Contributors
I'd like to save first time visitors that roundtrip to fetch
nocache.js. Instead I've declared the module HTML page as non-
cacheable (works nice thanks to E-Tag) and moved images and GWT-
compiler output to a fully cacheable directory.

After inlining nocache.js into the module HTML I had to change the
paths to the XYZ.cache.html permutations, but couldn't get RPC to work
reliably across all browsers.

Is there a way to do this cleanly?

John Tamplin

unread,
Aug 7, 2009, 7:05:20 AM8/7/09
to Google-Web-Tool...@googlegroups.com
There is a Google-internal linker that does this, and will be cleaned up and moved to GWT itself in the near future.  I don't know an exact timeframe for this however.

--
John A. Tamplin
Software Engineer (GWT), Google

Cameron Braid

unread,
Aug 7, 2009, 8:41:29 AM8/7/09
to Google-Web-Tool...@googlegroups.com
I'd be keen to see this land in trunk !

Cam

2009/8/7 John Tamplin <j...@google.com>

Arthur Kalmenson

unread,
Aug 7, 2009, 9:43:21 AM8/7/09
to Google-Web-Tool...@googlegroups.com
I'd love to see this in the trunk too. We have only 2 round trips on
start up now, thanks to ClientBundle. Getting it down to one will be
very slick!

--
Arthur Kalmenson

John Tamplin

unread,
Aug 7, 2009, 9:57:18 AM8/7/09
to Google-Web-Tool...@googlegroups.com
On Fri, Aug 7, 2009 at 9:43 AM, Arthur Kalmenson <arthu...@gmail.com> wrote:
I'd love to see this in the trunk too. We have only 2 round trips on
start up now, thanks to ClientBundle. Getting it down to one will be
very slick!

Are you counting fetching the host HTML page?  With this approach, the selection script is done away with but you still have a fetch for the compiled script so that it can remain permanently cacheable.  You could theoretically inline it into the host page, but since none of that is cacheable that would only make sense for very tiny compiled outputs.

Note that there is a cost, as your host HTML page now can't be cached (since the selection script essentially runs in the server).  If your host page is complex, this may be a net loss.  You could try leaving it cacheable but setting Vary headers for the pieces that you use to make the decision, but my understanding is that many caches do not handle this properly.

Also, you cannot use any deferred binding properties that can't be determined by the HTTP fetch of your host page.

Bruce Johnson

unread,
Aug 7, 2009, 11:09:50 AM8/7/09
to Google-Web-Tool...@googlegroups.com
2 requests is very impressive, Arthur! This is the sort of conscientiousness (i.e. for optimizing user experience) I hope all GWT developers would strive for. Nice work.

And yes, we'd like to help you get that down to 1, too.

On Fri, Aug 7, 2009 at 9:43 AM, Arthur Kalmenson <arthu...@gmail.com> wrote:

tfreitas

unread,
Aug 7, 2009, 11:20:38 AM8/7/09
to Google Web Toolkit Contributors
+1

On Aug 8, 8:43 am, Arthur Kalmenson <arthur.k...@gmail.com> wrote:
> I'd love to see this in the trunk too. We have only 2 round trips on
> start up now, thanks to ClientBundle. Getting it down to one will be
> very slick!
>
> --
> Arthur Kalmenson
>

Arthur Kalmenson

unread,
Aug 7, 2009, 11:39:52 AM8/7/09
to Google-Web-Tool...@googlegroups.com
> Are you counting fetching the host HTML page? With this approach, the
> selection script is done away with but you still have a fetch for the
> compiled script so that it can remain permanently cacheable. You could
> theoretically inline it into the host page, but since none of that is
> cacheable that would only make sense for very tiny compiled outputs.

I don't think firebug counts the initial request to fetch the host
page, so two requests. One for the nocache.js and another for the
cachable HTML. With the inlining of the nocache.js file, you could get
it down to 0 requests if the retrieved page is cached forever.

Are you saying to inline the generated JS in the host page too? How
could you do that? Don't you need the selector script to pick the
correct compiled version? Maybe I'm just not understanding what you
mean.

> 2 requests is very impressive, Arthur! This is the sort of conscientiousness
> (i.e. for optimizing user experience) I hope all GWT developers would strive
> for. Nice work.
> And yes, we'd like to help you get that down to 1, too.

Thanks Bruce! But it's really all thanks to Bob's ClientBundle :)

--
Arthur Kalmenson

Bruce Johnson

unread,
Aug 7, 2009, 11:55:39 AM8/7/09
to Google-Web-Tool...@googlegroups.com
The best you can do for a cold cache is 2 round-trips (including the host page) if you inline the selection script (.nocache.js) in the host page. You definitely do want the compiled script (.cache.html) *not* embedded in the host page, so that it can have "cache forever" semantics. Then, when users have a warm cache, it's literally exactly 1 round-trip for the module to start.

BTW, once you do inline the selection script, it will be more important to get the HTTP semantics just right. Although it says "nocache", the proper semantics are "must-revalidate", so typically, the host page won't have changed on the server, and thus it becomes an If-Modified-Since (IMS) request with a Not-Modified (NM) response. 

In other words, the best case for a warm cache is this 1 round-trip: IMS => NM, which is very cheap because there's no response payload. Your code can start essentially instantly on a decent network.

John Tamplin

unread,
Aug 7, 2009, 12:03:06 PM8/7/09
to Google-Web-Tool...@googlegroups.com
On Fri, Aug 7, 2009 at 11:39 AM, Arthur Kalmenson <arthu...@gmail.com> wrote:
I don't think firebug counts the initial request to fetch the host
page, so two requests. One for the nocache.js and another for the
cachable HTML. With the inlining of the nocache.js file, you could get
it down to 0 requests if the retrieved page is cached forever.

Well, since it can't be strongly named (if it was then the page with the link would have to be weakly cached for when it changed), the host page can't be cached forever but it can be cached based on the frequency you might need to update it.  Note that you need to keep the old compiled script around for as long as the host page might be cached (and any server resources it might need, which makes server RPC changes difficult), since caches with the old host page will be referring to the old compiled output.
 
Are you saying to inline the generated JS in the host page too? How
could you do that? Don't you need the selector script to pick the
correct compiled version? Maybe I'm just not understanding what you
mean.

See the example below for how it currently works and the change usign server-side selection.

The problem is that the browser will fetch foo.html and get the proper compiled output for their browser/locale/etc.  An intervening cache, say at an ISP, will then return that foo.html response to a different user with different parameters.  As I mentioned in the other message, you have to either make the host page uncacheable (which is only feasible with small compiled scripts) or rely on caches honoring the Vary headers, which is problematic on the Internet in general though might be feasible if you control the network your users use to access it.

Currently you have a host page, say Showcase.html, that is cached based on the frequency you expect to update it and contains a reference to the selection script.
...
<script language='javascript' src='showcase/showcase.nocache.js'></script>

That then fetches the selection script, which is cached based on the frequency you expect to update the app but with must-validate set and executes in the browser, and chooses a strong-named JS file for the compiled output, say XYZ.cache.html.  That file is then cached for 1 year ("forever" according to the HTTP spec).

In the new scheme, you have a host page that is generated by the server from a template.  That is mark cacheable for the shorter of the time you expect to update the host page template or recompile your app, with must-revalidate set.  It directly includes a script tag referencing XYZ.cache.html, where the server made the deferred binding decision based on the information available in the request.  In the typical case, it will use the User-Agent header and perhaps the Accept-Language header and the locale query parameter in the URL.

Since the results of the request for Showcase.html now depend on those parameters of the HTTP request, you have to make sure that a cache doesn't return a previous copy served to someone with Firefox to the next guy to ask for it who might be using Safari.  One way to do that is to make sure it isn't cached at all (or at least with must-validate), and the other is to rely on the caches to correctly interpret Vary headers so they know that the response you returned may be different if those headers are different.  As a side note, it doesn't know that you don't care about the difference between IE 7.0 and IE 7.0.1 (making these up so you get the point), or even the screen resolution that some user agents send in the request, so even caches that accept the Vary header will be less effective.  There are other ways around it with newer standards, but even fewer of the caches out there will make use of those features so it will be a while before they are actually useful.

George Georgovassilis

unread,
Aug 7, 2009, 2:50:08 PM8/7/09
to Google Web Toolkit Contributors
This occurred to me also when I started cutting down the requests: you
cannot do the initial selection on the server as HTTP proxies will
then see only one permutation: the first one that is ever retrieved by
them.

I've reduced the total number of HTTP requests required to load to
just 2 (1 for the initial module html and 1 for the .cache.html), but
as stated earlier, inlining does not work as smoothly as Bruce (and
for that matter, me also) wants it to. First you have to (manually/ant
task) change the strings in nocache.js to prepend the actual path to
XYZ.cache.html, and then RPC will get back to you because
the .cache.html scripts disagree with the serialization policy about
the service entry points.

So, is there a clean way?

On Aug 7, 6:03 pm, John Tamplin <j...@google.com> wrote:

George Georgovassilis

unread,
Aug 7, 2009, 3:07:56 PM8/7/09
to Google Web Toolkit Contributors
Nevermind, solved my RPC-woes by copying the policy file to the web-
app root. Ugly, but down to 2 requests.

On Aug 7, 8:50 pm, George Georgovassilis <g.georgovassi...@gmail.com>
wrote:

John Tamplin

unread,
Aug 7, 2009, 3:36:27 PM8/7/09
to Google-Web-Tool...@googlegroups.com
On Fri, Aug 7, 2009 at 2:50 PM, George Georgovassilis <g.georgo...@gmail.com> wrote:

This occurred to me also when I started cutting down the requests: you
cannot do the initial selection on the server as HTTP proxies will
then see only one permutation: the first one that is ever retrieved by
them.

I've reduced the total number of HTTP requests required to load to
just 2 (1 for the initial module html and 1 for the .cache.html), but
as stated earlier, inlining does not work as smoothly as Bruce (and
for that matter, me also) wants it to. First you have to (manually/ant
task) change the strings in nocache.js to prepend the actual path to
XYZ.cache.html, and then RPC will get back to you because
the .cache.html scripts disagree with the serialization policy about
the service entry points.

So, is there a clean way?

With server-side selection, it just works.  All you have to do is create the host page template so it can fill in the compile output name, write server equivalents for any custom property providers you have, configure your servlet container properly so the server components can intercept the request for the host page (if you aren't using Java on the server you will have to recreate that functionality) and inherit the right .gwt.xml file to pull in the server-side selection linker.
Reply all
Reply to author
Forward
0 new messages