Cached CSS (and Javascript?) issue

48 views
Skip to first unread message

Alex Black

unread,
Feb 12, 2010, 12:25:56 PM2/12/10
to Lift
I'm wondering if other people have encountered this issue, or if we're
doing something wrong, or if there is a nice solution to this.

Whenever we update our site, with new code and CSS and JS, any user
who visits it gets OLD css and js files (from their browser cache)
unless they force a refresh.

This is a jarring experience, users's see the site with old CSS and
old JS which is effectively broken.

Any thoughts? If it helps, our site is at http://snapsort.com. We're
using Jetty, behind nginx.

I notice that when the browser requests the CSS files, the server
responds 304 not modified, so I figured if the CSS was modified (when
we update) then the server would not respond 304 not modified :)

- Alex

Jeppe Nejsum Madsen

unread,
Feb 12, 2010, 12:40:34 PM2/12/10
to lif...@googlegroups.com

You should probably verify it it's nginx that's caching the file and
returning the 304 or if the request is forwarded to Jetty...

/Jeppe

Marius

unread,
Feb 12, 2010, 1:28:07 PM2/12/10
to Lift
Oh yes I did and I hate it. Ironically I was about to propose a
solution for this.

instead of

<link rel="stylesheet" type="text/css" href="mycss.css"/>

do something like:

<lift:css name="mycss.css" />

this would render:

<link rel="stylesheet" type="text/css" href="mycss.css?
i784yrfiuhferfhweir57=_"/>

the query string parameter would be generated at application start-up.
If you update you css/js and restart the application the browser will
refresh it. Potentially generating the random query string param could
be a LiftRules function that by default generates a sequence once per
application time. Thus you could potentially set your own function
that reads this for a config file?

Similarly <lift:js name="myjs.js"/> would do the same.

Br's,
Marius

On 12 feb., 19:25, Alex Black <a...@alexblack.ca> wrote:
> I'm wondering if other people have encountered this issue, or if we're
> doing something wrong, or if there is a nice solution to this.
>
> Whenever we update our site, with new code and CSS and JS, any user
> who visits it gets OLD css and js files (from their browser cache)
> unless they force a refresh.
>
> This is a jarring experience, users's see the site with old CSS and
> old JS which is effectively broken.
>

> Any thoughts? If it helps, our site is athttp://snapsort.com.  We're

Jeppe Nejsum Madsen

unread,
Feb 12, 2010, 1:53:44 PM2/12/10
to lif...@googlegroups.com
On Fri, Feb 12, 2010 at 7:28 PM, Marius <marius...@gmail.com> wrote:
> Oh yes I did and I hate it. Ironically I was about to propose a
> solution for this.
>
> instead of
>
> <link rel="stylesheet" type="text/css" href="mycss.css"/>
>
> do something like:
>
> <lift:css name="mycss.css" />
>
> this would render:
>
> <link rel="stylesheet" type="text/css" href="mycss.css?
> i784yrfiuhferfhweir57=_"/>
>
> the query string parameter would be generated at application start-up.
> If you update you css/js and restart the application the browser will
> refresh it. Potentially generating the random query string param could
> be a LiftRules function that by default generates a sequence once per
> application time. Thus you could potentially set your own function
> that reads this for a config file?
>
> Similarly <lift:js name="myjs.js"/> would do the same.

I had similar thoughts sometime ago, but haven't looked at it since then:

http://groups.google.com/group/liftweb/browse_thread/thread/1130ce4f9d5af010/a36c52fde3b2bc5b?lnk=gst&q=jeppe++cache&pli=1

For me, the real benefit would be combining many requests into one and
setting a future "Expires" date but still be sure that all artifacts
will be updated correctly.

/Jeppe

Timothy Perrett

unread,
Feb 12, 2010, 2:12:26 PM2/12/10
to lif...@googlegroups.com
This is pretty much what rails does. Straight from github:

<link href="/stylesheets/bundle_common.css?7371c81fbc6b010a32fb11b42a0fc322c3c57863" media="screen" rel="stylesheet" type="text/css" />
<link href="/stylesheets/bundle_github.css?7371c81fbc6b010a32fb11b42a0fc322c3c57863" media="screen" rel="stylesheet" type="text/css" />

It would be nice to provide some kind of generic helper snippet for this. Some hash that is configured on boot up would be great; provide some function in lift rules to alter the behaviour and away we go. Perhaps something like:

<lift:css href="/whatever.css" />

Thoughts?

Cheers, Tim

> --
> You received this message because you are subscribed to the Google Groups "Lift" group.
> To post to this group, send email to lif...@googlegroups.com.
> To unsubscribe from this group, send email to liftweb+u...@googlegroups.com.
> For more options, visit this group at http://groups.google.com/group/liftweb?hl=en.
>
>

Marius

unread,
Feb 12, 2010, 2:15:24 PM2/12/10
to Lift
Tim please see my proposal above :)

On 12 feb., 21:12, Timothy Perrett <timo...@getintheloop.eu> wrote:
> This is pretty much what rails does. Straight from github:
>

> <link href="/stylesheets/bundle_common.css?7371c81fbc6b010a32fb11b42a0fc322c3c578 63" media="screen" rel="stylesheet" type="text/css" />
> <link href="/stylesheets/bundle_github.css?7371c81fbc6b010a32fb11b42a0fc322c3c578 63" media="screen" rel="stylesheet" type="text/css" />


>
> It would be nice to provide some kind of generic helper snippet for this. Some hash that is configured on boot up would be great; provide some function in lift rules to alter the behaviour and away we go. Perhaps something like:
>
> <lift:css href="/whatever.css"  />
>
> Thoughts?
>
> Cheers, Tim
>
> On 12 Feb 2010, at 18:53, Jeppe Nejsum Madsen wrote:
>
>
>
> > On Fri, Feb 12, 2010 at 7:28 PM, Marius <marius.dan...@gmail.com> wrote:
> >> Oh yes I did and I hate it. Ironically I was about to propose a
> >> solution for this.
>
> >> instead of
>
> >> <link rel="stylesheet" type="text/css" href="mycss.css"/>
>
> >> do something like:
>
> >> <lift:css name="mycss.css" />
>
> >> this would render:
>
> >> <link rel="stylesheet" type="text/css" href="mycss.css?
> >> i784yrfiuhferfhweir57=_"/>
>
> >> the query string parameter would be generated at application start-up.
> >> If you update you css/js and restart the application the browser will
> >> refresh it. Potentially generating the random query string param could
> >> be a LiftRules function that by default generates a sequence once per
> >> application time. Thus you could potentially set your own function
> >> that reads this for a config file?
>
> >> Similarly <lift:js name="myjs.js"/> would do the same.
>
> > I had similar thoughts sometime ago, but haven't looked at it since then:
>

> >http://groups.google.com/group/liftweb/browse_thread/thread/1130ce4f9...

Marius

unread,
Feb 12, 2010, 2:20:01 PM2/12/10
to Lift
Jeppe probably we can combine the two proposals. Perhaps something
like:

<lift:css name="mycss.css, some_other.css. /classpath/baz.css" />

thus Lift could generate:

<link rel="stylesheet" type="text/css" href="compound_2434rfe34534.css?
i784yrfiuhferfhweir57=_"/>

compound_2434rfe34534.css is a synthetic name that would contain the
mycss.css, some_other.css. /classpath/baz.css concatenated. Same thing
for JS. This content could potentially be compressed.

I can open a ticket and start looking into this.

Br's,
Marius

On 12 feb., 20:53, Jeppe Nejsum Madsen <je...@ingolfs.dk> wrote:


> On Fri, Feb 12, 2010 at 7:28 PM, Marius <marius.dan...@gmail.com> wrote:
> > Oh yes I did and I hate it. Ironically I was about to propose a
> > solution for this.
>
> > instead of
>
> > <link rel="stylesheet" type="text/css" href="mycss.css"/>
>
> > do something like:
>
> > <lift:css name="mycss.css" />
>
> > this would render:
>
> > <link rel="stylesheet" type="text/css" href="mycss.css?
> > i784yrfiuhferfhweir57=_"/>
>
> > the query string parameter would be generated at application start-up.
> > If you update you css/js and restart the application the browser will
> > refresh it. Potentially generating the random query string param could
> > be a LiftRules function that by default generates a sequence once per
> > application time. Thus you could potentially set your own function
> > that reads this for a config file?
>
> > Similarly <lift:js name="myjs.js"/> would do the same.
>
> I had similar thoughts sometime ago, but haven't looked at it since then:
>

> http://groups.google.com/group/liftweb/browse_thread/thread/1130ce4f9...

David Pollak

unread,
Feb 12, 2010, 2:22:22 PM2/12/10
to lif...@googlegroups.com
On Fri, Feb 12, 2010 at 11:20 AM, Marius <marius...@gmail.com> wrote:
Jeppe probably we can combine the two proposals. Perhaps something
like:

<lift:css name="mycss.css, some_other.css. /classpath/baz.css" />

thus Lift could generate:

<link rel="stylesheet" type="text/css" href="compound_2434rfe34534.css?
i784yrfiuhferfhweir57=_"/>

compound_2434rfe34534.css is a synthetic name that would contain the
mycss.css, some_other.css. /classpath/baz.css concatenated. Same thing
for JS. This content could potentially be compressed.

I can open a ticket and start looking into this.

Kewl!
 
--
You received this message because you are subscribed to the Google Groups "Lift" group.
To post to this group, send email to lif...@googlegroups.com.
To unsubscribe from this group, send email to liftweb+u...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/liftweb?hl=en.




--
Lift, the simply functional web framework http://liftweb.net
Beginning Scala http://www.apress.com/book/view/1430219890
Follow me: http://twitter.com/dpp
Surf the harmonics

Marius

unread,
Feb 12, 2010, 2:25:34 PM2/12/10
to Lift

Jeppe Nejsum Madsen

unread,
Feb 12, 2010, 2:31:48 PM2/12/10
to lif...@googlegroups.com
On Fri, Feb 12, 2010 at 8:20 PM, Marius <marius...@gmail.com> wrote:
> Jeppe probably we can combine the two proposals.

Yes, that would be natural

> Perhaps something like:
>
> <lift:css name="mycss.css, some_other.css. /classpath/baz.css" />
>
> thus Lift could generate:
>
> <link rel="stylesheet" type="text/css" href="compound_2434rfe34534.css?
> i784yrfiuhferfhweir57=_"/>
>
> compound_2434rfe34534.css is a synthetic name that would contain the
> mycss.css, some_other.css. /classpath/baz.css concatenated. Same thing
> for JS. This content could potentially be compressed.

One thing that I think will be important (at some point :-) is to do
combining of individual tags. If a page is constructed from several
snippets/widgets, each emitting different js files (think jQuery
plugins) and css files, these need to be combined somehow. This means
that each page will get it's own unique synthetic css/js file. This
probably needs to be configurable in some way :-)

> I can open a ticket and start looking into this.

Awesome! I'll watch from the sideline!

/Jeppe

Marius

unread,
Feb 12, 2010, 2:35:03 PM2/12/10
to Lift

On 12 feb., 21:31, Jeppe Nejsum Madsen <je...@ingolfs.dk> wrote:


> On Fri, Feb 12, 2010 at 8:20 PM, Marius <marius.dan...@gmail.com> wrote:
> > Jeppe probably we can combine the two proposals.
>
> Yes, that would be natural
>
> > Perhaps something like:
>
> > <lift:css name="mycss.css, some_other.css. /classpath/baz.css" />
>
> > thus Lift could generate:
>
> > <link rel="stylesheet" type="text/css" href="compound_2434rfe34534.css?
> > i784yrfiuhferfhweir57=_"/>
>
> > compound_2434rfe34534.css is a synthetic name that would contain the
> > mycss.css, some_other.css. /classpath/baz.css concatenated. Same thing
> > for JS. This content could potentially be compressed.
>
> One thing that I think will be important (at some point :-) is to do
> combining of individual tags. If a page is constructed from several
> snippets/widgets, each emitting different js files (think jQuery
> plugins) and css files, these need to be combined somehow. This means
> that each page will get it's own unique synthetic css/js file. This
> probably needs to be configurable in some way :-)

Yeah that is a slightly different use-case that require more noodling.
But would worth considering in the future.

Alex Black

unread,
Feb 12, 2010, 3:48:00 PM2/12/10
to Lift
hey guys, I love the enthusiasm, but putting a unique value on the css
filenames seems like a hack, surely we can do better?

Whats supposed to happen is:
- browser requests resource (e.g. styles.css) with a conditonal get
(if newer than X)
- server checks to see if resource is newer than X
- if it is new than x then: return resource
- if it is not newer than x, then return 304 not modified

- Alex

Ross Mellgren

unread,
Feb 12, 2010, 3:48:55 PM2/12/10
to lif...@googlegroups.com
I believe IE6 does not follow the correct process you describe and will always cache CSS files of the same name.

-Ross

Alex Black

unread,
Feb 12, 2010, 3:51:39 PM2/12/10
to Lift
1. Luckily IE6 is dying out :) unless http://saveie6.com/ works
2. surely even IE6 obeys expires headers or some caching rules?

Ross Mellgren

unread,
Feb 12, 2010, 3:54:01 PM2/12/10
to lif...@googlegroups.com
It does normally, but not for CSS resources. I just did a quick google and I found one page that said it will send a HEAD for a cached CSS but only when the browser session is restarted. I know that at my work we've had to do the filename hack because nothing else works with IE6.

And IE6 may be dying out to some degree, but it's still popular enough (and particularly in certain industries) that it has to be supported for many companies :-/

-Ross

Alex Black

unread,
Feb 12, 2010, 3:56:17 PM2/12/10
to Lift
If this was implemented, it should be a unique id once per CSS change,
not once per application start.

E.g. we deploy to production every few weeks, and client browsers
should be able to cache files that entire time until change them,
regardless of reboots.

But obviously a value once per application start is much easier.

I think what this points out is that its the file's datetime that is
the state that needs to be used here, and the existing mechanism of
HTTP looks like the way to go. Perhaps I'll dig into it and try to
understand why this is not working as one would expect.

Alex Black

unread,
Feb 12, 2010, 3:58:54 PM2/12/10
to Lift
I'd vote for: figure out if this can be done properly (e.g. rely on
file system date/time file and HTTP), and if necessary add a hack to
support browsers like IE6.

Jeppe Nejsum Madsen

unread,
Feb 12, 2010, 3:59:50 PM2/12/10
to lif...@googlegroups.com
On Fri, Feb 12, 2010 at 9:48 PM, Alex Black <al...@alexblack.ca> wrote:
> hey guys, I love the enthusiasm, but putting a unique value on the css
> filenames seems like a hack, surely we can do better?
>
> Whats supposed to happen is:
> - browser requests resource (e.g. styles.css) with a conditonal get
> (if newer than X)
> - server checks to see if resource is newer than X
> - if it is new than x then: return resource
> - if it is not newer than x, then return 304 not modified

Yes, that's how it should work if everything was configured correctly
(which I think it wasn't for the OP)

But what we were discussing (at least I was :-) was more that Lift
should serve resources with an "Expires" date in the far future. That
way the browser will only make a single request for a resource (as
long as the file is cached). This works well for returning visitors.
But of course an updated resource should be fetched, hence the unique
filenames.

Combining individual files will improve load times for first time
visitors by reducing the number of requests.

/Jeppe

Jeppe Nejsum Madsen

unread,
Feb 12, 2010, 4:01:21 PM2/12/10
to lif...@googlegroups.com
On Fri, Feb 12, 2010 at 9:56 PM, Alex Black <al...@alexblack.ca> wrote:
> If this was implemented, it should be a unique id once per CSS change,
> not once per application start.
>
> E.g. we deploy to production every few weeks, and client browsers
> should be able to cache files that entire time until change them,
> regardless of reboots.
>
> But obviously a value once per application start is much easier.
>
> I think what this points out is that its the file's datetime that is
> the state that needs to be used here, and the existing mechanism of
> HTTP looks like the way to go.  Perhaps I'll dig into it and try to
> understand why this is not working as one would expect.

Or just the MD5 hash of the contents...

/Jeppe

Alex Black

unread,
Feb 12, 2010, 4:04:38 PM2/12/10
to Lift
> Yes, that's how it should work if everything was configured correctly
> (which I think it wasn't for the OP)

Heh, I'm the OP.

I'll have to dig into why its not working as expected I guess.

> But what we were discussing (at least I was :-) was more that Lift
> should serve resources with an "Expires" date in the far future. That
> way the browser will only make a single request for a resource (as
> long as the file is cached). This works well for returning visitors.
> But of course an updated resource should be fetched, hence the unique
> filenames.

There are some things I like about that solution, but the unique
filenames just seems wrong.

So I see that a far in the future expires works, but the reason you
need the unique filenames is because it doesn't really work. The far
in the future expires says "you can cache this for a long time cause
it won't change".

The other option is say "you can cache this for like the next hour"
but every time you fetch it, you can tell me when you last got it
(conditional GET), and I won't send it to you if it hasn't changed
(304 not modified). This results in more requests, but no need for
unique filenames or anything, instead if the file changes then the
server will serve it up to whoever needs it.

> Combining individual files will improve load times for first time
> visitors by reducing the number of requests.

That sounds like a great idea.. would like the same thing for JS.
Does the YUI compressor tool that lift uses with maven have this type
of feature? I Thought I read that it did.

>
> /Jeppe

Alex Black

unread,
Feb 12, 2010, 4:05:23 PM2/12/10
to Lift
> Or just the MD5 hash of the contents...

ah, now you're talking. That sounds like a good solution.

Jeppe Nejsum Madsen

unread,
Feb 12, 2010, 4:11:38 PM2/12/10
to lif...@googlegroups.com
On Fri, Feb 12, 2010 at 10:04 PM, Alex Black <al...@alexblack.ca> wrote:
>> Yes, that's how it should work if everything was configured correctly
>> (which I think it wasn't for the OP)
>
> Heh, I'm the OP.

Ahh sorry :-)

> The other option is say "you can cache this for like the next hour"
> but every time you fetch it, you can tell me when you last got it
> (conditional GET), and I won't send it to you if it hasn't changed
> (304 not modified).  This results in more requests, but no need for
> unique filenames or anything, instead if the file changes then the
> server will serve it up to whoever needs it.

But it's the "more requests" part I'm not interested in :-) For a
moderately complex page, there can easily be 20+ requests for
images/css/js etc. I would like to avoid that if possible.

/Jeppe

Alex Black

unread,
Feb 12, 2010, 4:14:59 PM2/12/10
to Lift
This looks a bit complex:

http://www.samaxes.com/2009/05/combine-and-minimize-javascript-and-css-files-for-faster-loading/

but its an example of how other people have handled combining css and
javascript files.

On Feb 12, 4:11 pm, Jeppe Nejsum Madsen <je...@ingolfs.dk> wrote:

aw

unread,
Feb 13, 2010, 2:07:18 AM2/13/10
to Lift
On Feb 12, 1:14 pm, Alex Black <a...@alexblack.ca> wrote:
> This looks a bit complex:
>
> http://www.samaxes.com/2009/05/combine-and-minimize-javascript-and-cs...

>
> but its an example of how other people have handled combining css and
> javascript files.

Here is another one: http://code.google.com/p/wro4j/

Marius

unread,
Feb 13, 2010, 3:35:13 AM2/13/10
to Lift

On 12 feb., 22:56, Alex Black <a...@alexblack.ca> wrote:
> If this was implemented, it should be a unique id once per CSS change,
> not once per application start.

There is no proper API to see when file names are changed unless we
poll. I prefer to have LiftRules function that by default takes a
value generated at startup. User's can override it to generate the
token more frequently depending on their use-case.

>
> E.g. we deploy to production every few weeks, and client browsers
> should be able to cache files that entire time until change them,
> regardless of reboots.
>
> But obviously a value once per application start is much easier.
>
> I think what this points out is that its the file's datetime that is
> the state that needs to be used here, and the existing mechanism of
> HTTP looks like the way to go.  Perhaps I'll dig into it and try to
> understand why this is not working as one would expect.

If you can dig deeper that would be useful.

Marius

unread,
Feb 13, 2010, 3:45:15 AM2/13/10
to Lift

On 12 feb., 23:04, Alex Black <a...@alexblack.ca> wrote:
> > Yes, that's how it should work if everything was configured correctly
> > (which I think it wasn't for the OP)
>
> Heh, I'm the OP.
>
> I'll have to dig into why its not working as expected I guess.
>
> > But what we were discussing (at least I was :-) was more that Lift
> > should serve resources with an "Expires" date in the far future. That
> > way the browser will only make a single request for a resource (as
> > long as the file is cached). This works well for returning visitors.
> > But of course an updated resource should be fetched, hence the unique
> > filenames.
>
> There are some things I like about that solution, but the unique
> filenames just seems wrong.
>
> So I see that a far in the future expires works, but the reason you
> need the unique filenames is because it doesn't really work. The far
> in the future expires says "you can cache this for a long time cause
> it won't change".
>
> The other option is say "you can cache this for like the next hour"
> but every time you fetch it, you can tell me when you last got it
> (conditional GET), and I won't send it to you if it hasn't changed
> (304 not modified).  This results in more requests, but no need for
> unique filenames or anything, instead if the file changes then the
> server will serve it up to whoever needs it.

It doesn't sound like today this solution is consistent on all "major"
browsers. Can you confirm that it does?
I used the query string solution in the past (like many others) and
this works reasonably well. It is not a perfect solution
but better then today. Besides if we want to adopt a different
solution that would be pretty easy because this knowledge will be
built
in the snippet and the user code wont really change.

Timothy Perrett

unread,
Feb 13, 2010, 11:44:32 AM2/13/10
to lif...@googlegroups.com
Seems like this conversation is diverging somewhat.

Can I suggest there are two things in play here, and they address different problems.

1. Stopping the caching of resource files for an application build; the proposed solution is an md5 of the file contents so that the value persists application restarts (or with whatever random string)

2. Consolidation of CSS files on a given page for performance firstly, and secondly for caching.

Would there be times when people would not want the behaviour of 2? Im generally not a huge fan of things that mess with user code or could provide uneasy behaviour; im thinking specifically when people build there templates where CSS values are overridden by values loaded after initial value ad unless its munged together right, it might damage the expected behaviour (think blueprint)...? Can I suggest we solve the caching problem using the known hack of random strings, then deal with this proposal of resource consolidation?

Cheers

Tim

Marius

unread,
Feb 13, 2010, 12:09:19 PM2/13/10
to Lift

On 13 feb., 18:44, Timothy Perrett <timo...@getintheloop.eu> wrote:
> Seems like this conversation is diverging somewhat.
>
> Can I suggest there are two things in play here, and they address different problems.
>
> 1. Stopping the caching of resource files for an application build; the proposed solution is an md5 of the file contents so that the value persists application restarts (or with whatever random string)

calculating an md5 of a file would induce additional computation time
and we'd need to maintain hashes for each resource file. The prototype
that I have now is a function in LiftRules that by default returns a
random value generated on startup. Applications that needs MD5 per
file could calculate that and maintain them.

>
> 2. Consolidation of CSS files on a given page for performance firstly, and secondly for caching.
>
> Would there be times when people would not want the behaviour of 2? Im generally not a huge fan of things that mess with user code or could provide uneasy behaviour; im thinking specifically when people build there templates where CSS values are overridden by values loaded after initial value ad unless its munged together right, it might damage the expected behaviour (think blueprint)...? Can I suggest we solve the caching problem using the known hack of random strings, then deal with this proposal of resource consolidation?

What I'm playing with is"


<lift:css.combine>
<res:css name="abc.css"/>
<res:css name="def.css"/>
</lift:css.combine>

under the hood this would be a function that return a Stream Response
that "concatenates" the streams of files in questions serving them
sequentially in the corresponding order. So from Lift's perspective
there is no additional computation involved comparing with current
situation except we serve desired resources in one response.

To sum up the random string is what I think we should start with. IMO
it is a fairly good solution that can evolve in time towards something
else.

Naftoli Gugenheim

unread,
Feb 13, 2010, 9:45:52 PM2/13/10
to liftweb
May I suggest that instead of naming the tag 'css,' it be given a name that's more agnostic of the content it affects and more indicative of what it does? Technically this could be used for any type of resource.
What about something like
<lift:uniqueurl path="url />
Or something else?
Thanks for contributing this!

2010/2/12 Marius <marius...@gmail.com>

Marius

unread,
Feb 14, 2010, 3:32:03 AM2/14/10
to Lift
That is an option, thanks.

On 14 feb., 04:45, Naftoli Gugenheim <naftoli...@gmail.com> wrote:
> May I suggest that instead of naming the tag 'css,' it be given a name
> that's more agnostic of the content it affects and more indicative of what
> it does? Technically this could be used for any type of resource.
> What about something like
> <lift:uniqueurl path="url />
> Or something else?
> Thanks for contributing this!
>

> 2010/2/12 Marius <marius.dan...@gmail.com>

> > liftweb+u...@googlegroups.com<liftweb%2Bunsu...@googlegroups.com >

Jeppe Nejsum Madsen

unread,
Feb 14, 2010, 4:54:14 AM2/14/10
to lif...@googlegroups.com
Naftoli Gugenheim <nafto...@gmail.com> writes:

> May I suggest that instead of naming the tag 'css,' it be given a name
> that's more agnostic of the content it affects and more indicative of what
> it does? Technically this could be used for any type of resource.
> What about something like
> <lift:uniqueurl path="url />
> Or something else?

This might be useful on it's own, but doing this for css/javascripts
prohibits some of the optimization stuff that would otherwise be
possible (ie bundling, minifying etc)

/Jeppe

Alex Black

unread,
Feb 15, 2010, 4:26:43 PM2/15/10
to Lift
> There is no proper API to see when file names are changed unless we
> poll. I prefer to have  LiftRules function that by default takes a
> value generated at startup. User's can override it to generate the
> token more frequently depending on their use-case.

I was interested in less-frequently (not more).

What did you think of Jeppe's proposal to use the MD5 hash of the file
as the token?

Alex Black

unread,
Feb 15, 2010, 4:27:32 PM2/15/10
to Lift
We don't seem to have this problem with images..

Could this have to do with the fact that the CSS files and javascript
are served out of the WAR file, whereas our images are served by a
separate Jetty context?

On Feb 13, 3:35 am, Marius <marius.dan...@gmail.com> wrote:
> On 12 feb., 22:56, Alex Black <a...@alexblack.ca> wrote:
>
> > If this was implemented, it should be a unique id once perCSSchange,
> > not once per application start.
>
> There is no proper API to see when file names are changed unless we
> poll. I prefer to have  LiftRules function that by default takes a
> value generated at startup. User's can override it to generate the
> token more frequently depending on their use-case.
>
>
>
> > E.g. we deploy to production every few weeks, and client browsers
> > should be able to cache files that entire time until change them,
> > regardless of reboots.
>
> > But obviously a value once per application start is much easier.
>
> > I think what this points out is that its the file's datetime that is
> > the state that needs to be used here, and the existing mechanism of
> > HTTP looks like the way to go.  Perhaps I'll dig into it and try to
> > understand why this is not working as one would expect.
>
> If you can dig deeper that would be useful.
>
>
>
>
>
> > On Feb 12, 1:28 pm, Marius <marius.dan...@gmail.com> wrote:
>
> > > Oh yes I did and I hate it. Ironically I was about to propose a
> > > solution for this.
>
> > > instead of
>
> > > <link rel="stylesheet" type="text/css" href="mycss.css"/>
>
> > > do something like:
>

> > > <lift:cssname="mycss.css" />


>
> > > this would render:
>
> > > <link rel="stylesheet" type="text/css" href="mycss.css?
> > > i784yrfiuhferfhweir57=_"/>
>
> > > the query string parameter would be generated at application start-up.

> > > If you update youcss/js and restart the application the browser will


> > > refresh it. Potentially generating the random query string param could
> > > be a LiftRules function that by default generates a sequence once per
> > > application time. Thus you could potentially set your own function
> > > that reads this for a config file?
>
> > > Similarly <lift:js name="myjs.js"/> would do the same.
>
> > > Br's,
> > > Marius
>
> > > On 12 feb., 19:25, Alex Black <a...@alexblack.ca> wrote:
>
> > > > I'm wondering if other people have encountered this issue, or if we're
> > > > doing something wrong, or if there is a nice solution to this.
>

> > > > Whenever we update our site, with new code andCSSand JS, any user
> > > > who visits it gets OLDcssand js files (from their browser cache)


> > > > unless they force a refresh.
>
> > > > This is a jarring experience, users's see the site with oldCSSand
> > > > old JS which is effectively broken.
>
> > > > Any thoughts? If it helps, our site is athttp://snapsort.com.  We're
> > > > using Jetty, behind nginx.
>

> > > > I notice that when the browser requests theCSSfiles, the server
> > > > responds 304 not modified, so I figured if theCSSwas modified (when

Jeppe Nejsum Madsen

unread,
Feb 15, 2010, 5:09:49 PM2/15/10
to lif...@googlegroups.com
On Mon, Feb 15, 2010 at 10:27 PM, Alex Black <al...@alexblack.ca> wrote:
> We don't seem to have this problem with images..
>
> Could this have to do with the fact that the CSS files and javascript
> are served out of the WAR file, whereas our images are served by a
> separate Jetty context?

It very well could be. I just noticed that files served from
/classpath gets an Expires header 24 hours into the future.....

/Jeppe

Naftoli Gugenheim

unread,
Feb 15, 2010, 6:49:54 PM2/15/10
to liftweb
Is Alex's problem that the browsers update 24 hours later or not at all or something else?

2010/2/15 Jeppe Nejsum Madsen <je...@ingolfs.dk>
--
You received this message because you are subscribed to the Google Groups "Lift" group.
To post to this group, send email to lif...@googlegroups.com.
To unsubscribe from this group, send email to liftweb+u...@googlegroups.com.

Alex Black

unread,
Feb 16, 2010, 8:55:30 AM2/16/10
to Lift
> calculating an md5 of a file would induce additional computation time
> and we'd need to maintain hashes for each resource file. The prototype
> that I have now is a function in LiftRules that by default returns a
> random value generated on startup. Applications that needs MD5 per
> file could calculate that and maintain them.

Hi Marius, what does the proposed token represent? It looks to me like
it represents a given resource (css file) per running instance of
Jetty.

By using MD5 it instead represents the file itself.

Problems with using a token that represents a given resource per
running instance of jetty:
- if the server restarts you use a new token, so all clients are
forced to re-get the 'new' resource
- if you run more than one server, then each server has different
tokens, so clients think there are different resources

I also like the suggestion that a solution to the consolidation
problem be kept separate from the problem of generating unique urls
for cachable resources (such as CSS, javascript, etc).

Marius

unread,
Feb 16, 2010, 10:18:14 AM2/16/10
to Lift

On Feb 16, 3:55 pm, Alex Black <a...@alexblack.ca> wrote:
> > calculating an md5 of a file would induce additional computation time
> > and we'd need to maintain hashes for each resource file. The prototype
> > that I have now is a function in LiftRules that by default returns a
> > random value generated on startup. Applications that needs MD5 per
> > file could calculate that and maintain them.
>
> Hi Marius, what does the proposed token represent? It looks to me like
> it represents a given resource (css file) per running instance of
> Jetty.

In my prototype it is a random string generated once at startup. So
this is the same for all css/js references. But this is imposed just
by the default implementation of the LiftRules.attachResourceId
function. A different implementation can generate unique MD5 sequences
based on each individual file. But I'm not convinced that this should
be on the framework side.

>
> By using MD5 it instead represents the file itself.
>
> Problems with using a token that represents a given resource per
> running instance of jetty:
> - if the server restarts you use a new token, so all clients are
> forced to re-get the 'new' resource

Correct.

> - if you run more than one server, then each server has different
> tokens, so clients think there are different resources

Correct again.


>
> I also like the suggestion that a solution to the consolidation
> problem be kept separate from the problem of generating unique urls
> for cachable resources (such as CSS, javascript, etc).

The MD5 generation if we want it to reflect the file content would
take some time to generate. It would indeed happen once if that
resource was not seen yet. But along with this there will be other
logic:

1. Detect when files changes ... for that we'd need a polling
mechanism as we wouldn't want hash calculation on each page rendering.
2. Maintain the hash cache

Personally I do not think this is an imperative thing for the
framework. I think it is more important for Lift to allow the
flexibility to apply this type of logic and this is what I'm aiming
to. I agree with you that MD5 approach is more consistent but this
random token applied per server instance is not that bad as it's main
purpose is not to optimize the resource loading but to have a minimal
mechanism to force browsers to refresh the resources if we change css/
js on server side (as the original issue was). Other people may not
prefer the MD5 approach but rely more on expiration headers and so
on.

Your two cases described would be solved by using the MD5 approach but
I don't think it is a disaster if we restart the servers clients will
fetch again the CSS as they think that the resource changed.

Furthermore if one of the committers wants to add this MD5 logic after
this support is in, he can certainly do it. To me the proper
abstraction in allowing that is more important right now.

Jeppe Nejsum Madsen

unread,
Feb 16, 2010, 11:03:03 AM2/16/10
to lif...@googlegroups.com
> In my prototype it is a random string generated once at startup. So
> this is the same for all css/js references. But this is imposed just
> by the default implementation of the LiftRules.attachResourceId
> function. A different implementation can generate unique MD5 sequences
> based on each individual file. But I'm not convinced that this should
> be on the framework side.

From a high level perspective, I think a framework should (in order)

1) Provide flexibilty to do what users want
2) Provide the best defaults possible, with the least surprises and
best performance such that all users don't have to reinvent the same
wheel.

[...]

> 1. Detect when files changes ... for that we'd need a polling
> mechanism as we wouldn't want hash calculation on each page rendering.
> 2. Maintain the hash cache

I think it should be sufficient to do this once per app restart.

> Personally I do not think this is an imperative thing for the
> framework. I think it is more important for Lift to allow the
> flexibility to apply this type of logic and this is what I'm aiming
> to.

Agreed cf. the above.

> Furthermore if one of the committers wants to add this MD5 logic after
> this support is in, he can certainly do it.

If no one beats me to it, I'll have a look at some point, but really
busy now with other stuff

>To me the proper
> abstraction in allowing that is more important right now.

Yep

/Jeppe

Marius

unread,
Feb 21, 2010, 3:49:22 AM2/21/10
to Lift
Guys,

I'm starting to have second thoughts about having css or js combine
(concatenation of multiple files into a single response) on lift side.
I'm starting to question that real benefits as in production sites in
many cases the lift app has a http reverse proxy front end that can
serve static content js/css etc. Thus combining multiple js/css with
simple tools seems more practical.

Thoughts?

Br's,
Marius

Jeppe Nejsum Madsen

unread,
Feb 22, 2010, 1:12:14 PM2/22/10
to lif...@googlegroups.com

By simple tools I assume you mean at build time? How would this handle
classpath resources?

I don't think that doing it on the lift side conflicts with the
reverse proxy. If everything is configured correctly, the proxy should
only fetch the resource from lift once, see that the resource expires
far in the future and cache it.

There are a number of (I think) conflicting scenarios that Lift should support:

1) Good defaults that deliver great performance out of the box without
too much hassle during development/build/deploy time. This is where I
think Lift combining resources would be used.
2) The absolute best performance no matter what. This probably
includes multiple hosts for static resources, CDNs etc. If you're
going this route you're willing to sacrifice ease of use for that last
ounce of speed.

If/when load time becomes an issue for us this will be one of the
first things I'll try to investigate :-)

/Jeppe

Marius

unread,
Feb 23, 2010, 6:19:02 AM2/23/10
to Lift

On Feb 22, 8:12 pm, Jeppe Nejsum Madsen <je...@ingolfs.dk> wrote:


> On Sun, Feb 21, 2010 at 9:49 AM, Marius <marius.dan...@gmail.com> wrote:
> > Guys,
>
> > I'm starting to have second thoughts about having css or js combine
> > (concatenation of multiple files into a single response) on lift side.
> > I'm starting to question that real benefits as in production sites in
> > many cases the lift app has a http reverse proxy front end that can
> > serve static content js/css etc. Thus combining multiple js/css with
> > simple tools seems more practical.
>
> > Thoughts?
>
> By simple tools I assume you mean at build time? How would this handle
> classpath resources?

Yes on build time.
/classpath/myresourcescombined.css will reside in the reverse proxy.
This file will reside in the reverse proxy document root.

>
> I don't think that doing it on the lift side conflicts with the
> reverse proxy. If everything is configured correctly, the proxy should
> only fetch the resource from lift once, see that the resource expires
> far in the future and cache it.
>
> There are a number of (I think) conflicting scenarios that Lift should support:
>
> 1) Good defaults that deliver great performance out of the box without
> too much hassle during development/build/deploy time. This is where I
> think Lift combining resources would be used.

I somehow disagree. IMO production tuning is necessary regardless of
the web framework used. How do other frameworks behave in this are. I
don't know of any that does the resources combining and claim that
this is preferable then using the reverse proxy.

> 2) The absolute best performance no matter what.

I think this is a utopia.In my experience having reverse proxies
serving static content (combined wherever possible) will give you
"best" performance. The application server should not be burden with
serving static content as long as there are cheap reverse proxies out
there.

> This probably
> includes multiple hosts for static resources, CDNs etc. If you're
> going this route you're willing to sacrifice ease of use for that last
> ounce of speed.

Application performance should be tuned in production environments. I
don't see where the "ease of use" sacrifice is. Lift apps will
function properly without a reverse proxy but the reverse proxy is
much more suitable for serving static content than the application
server which will be burden with other requests.

Reply all
Reply to author
Forward
0 new messages