Skip to content

Improve Spring Resource Handling support#3123

Closed
bclozel wants to merge 1 commit into
spring-projects:masterfrom
bclozel:gh-1604
Closed

Improve Spring Resource Handling support#3123
bclozel wants to merge 1 commit into
spring-projects:masterfrom
bclozel:gh-1604

Conversation

@bclozel
Copy link
Copy Markdown
Member

@bclozel bclozel commented Jun 4, 2015

This commit improves support of the Resource Handling features
introduced in Spring Framework 4.1. Those features add new ways to
resolve and transform static resources in applications.
See this blog post
for more details.

The ResourceUrlEncodinFilter is added for compatible template engines:
Velocity and Thymeleaf. It assists them with rewriting the URLs of
static resources when rendering templates.

New keys are added in the ResourceProperties in order to configure
the Resource Handling chain. ResourceResolvers and
ResourceTransformers are registered accordingly in
WebMvcAutoConfiguration.

If enabled with "spring.resources.chain.enabled:true", the default
configuration will add a ContentVersionStrategy on all static
resources, meaning their names will be changed for cache busting
purposes by adding a content hash at the end of the file name.

Fixes: #1604

@bclozel
Copy link
Copy Markdown
Member Author

bclozel commented Jun 4, 2015

Don't merge this right now! This must be discussed with @snicoll (and other Boot members).
This PR is now open for discussion, including the following themes:

  • do the configuration keys look OK to you?
  • what do you think of the defaults?

Also, to enable URL rewriting in templates, you need your template engine to either:

  • register a ResourceUrlEncodingFilter and use HttpServletResponse.encodeURL() (like JSP, Thymeleaf or Velocity)
  • use a custom "template helper" in your favorite template engine (Freemarker, JMustache...) to rewrite URLs using the ResourceUrlProvider bean

This PR is configuring the filter for Thymeleaf and Velocity - but I don't think we can register it for JSP users without impacting everyone.
This PR could also come up with additional template helpers, but it would require some more documentation on templates - which may not be in Boot's scope.

Also, since SPR-12323, Spring Framework can now serve "version-agnostic" dependencies and/or rewrite "version agnostic" URLs in templates. This should make this part of webjars documentation for Spring much easier. But this requires to add the webjars-locator dependency on classpath. I don't think this is something to consider for the web starter, but maybe something worth mentioning in the reference documentation.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd put that at @Bean level.

@snicoll
Copy link
Copy Markdown
Member

snicoll commented Jun 8, 2015

Found a few glitches (no need to fix that, we can manage that when merging). This looks good to me even though I think it may be good to explain a bit more what contentVersioningPatterns and fixedVersioningPatterns are in practice.

Are these mutually exclusive or can you use them at the same time?

@bclozel
Copy link
Copy Markdown
Member Author

bclozel commented Jun 10, 2015

Thanks for the initial review - I've updated my PR.

Now I'm (too) familiar with the Resource Handling features in Spring Framework and I'd like to make them really easy to understand and accessible in Boot apps.

Version strategies explained

Basically, a VersionStrategy help you resolve resources from a request URL, or create a public URL from a given resource - they include some "version string" within the URL to improve HTTP caching strategies (if the resource changes, its URL changes).

The "content versioning" one calculates a hash of the content and include that hash in the file name, such as /js/jquery.js -> /js/jquery-872ca6a9fdda9e2c1516a84cff5c3bc6.js. This strategy works well for images, css files, scripts... as long as you're linking to them from templates.

The "fixed versioning" adds a static version string that represents the application version; something like a git hash "d34db33f". This strategy is less optimal, since resources change URL even if they've not been modified. But this is essential as soon as those resources are loaded from a dynamic loader such as the ES6 module loader, or require.js.

Concrete examples

Basically applications that don't use dynamic resource loaders (for example, angularJS 1.x apps) will use a content strategy for "/**"; see this example.

Applications that use resource loaders (such as the new ES6 module loader) will use a content strategy for css, images - but a fixed strategy for resources loaded by this module loader; see this example.

So both can be used at the same time and IMO both make sense for modern applications.

TODO

I guess that we need to find proper props keys+descriptions that 1) refer to the real spring mvc resource handling features 2) still make sense in this context. Any idea?

@snicoll snicoll self-assigned this Jun 17, 2015
@snicoll snicoll added type: enhancement A general enhancement and removed in progress labels Jun 17, 2015
@snicoll
Copy link
Copy Markdown
Member

snicoll commented Jun 22, 2015

for the record, we've discussed how we could rework the configuration part to offer the following configuration:

spring.resources.chain.enabled=true
spring.resources.chain.cache=true
spring.resources.chain.content.enabled=true
spring.resources.chain.content.paths=/static/**
spring.resources.chain.fixed.enabled=true
spring.resources.chain.fixed.version=1.0
spring.resources.chain.fixed.paths=/js/**,/foo/**

This provides a better flexibility to offer additional items in the future, if need to be. Note that we need to add spring.resources.chain.cache to the list of config keys that are tuned by devtools.

@bclozel bclozel force-pushed the gh-1604 branch 2 times, most recently from e9628ca to bcda586 Compare June 22, 2015 17:00
@bclozel
Copy link
Copy Markdown
Member Author

bclozel commented Jun 22, 2015

I've updated this PR with those changes.
Should we use paths or patterns for configuring those mappings? It seems other property keys in Boot use path-patterns.

To summarize my feelings about this PR:

  • the main goal is to make more popular, easier to use, the resource handling feature
  • it's hard to be really opinionated about that one, since there's no "standard" or strong tendency for resource handling in front-end applications

What do you think @sdeleuze @rstoyanchev ?

@rstoyanchev
Copy link
Copy Markdown
Contributor

Overall seems to strike the right balance. A few comments and questions.

I don't think we can register it for JSP users without impacting everyone.

What's the exact consideration for this? I don't quite see the issue.

But this requires to add the webjars-locator dependency on classpath.

Could WebJarsResourceResolver be registered if webjars-locator is on the classpath?

spring.resources.chain.content.enabled=true
spring.resources.chain.fixed.enabled=true

Looking at this there is no hint of what resolver this is about, nor that it's for the same resolver. How about inserting .version after .chain? So it would be spring.resources.chain.version.content.enabled.

@bclozel
Copy link
Copy Markdown
Member Author

bclozel commented Jun 23, 2015

Overall seems to strike the right balance. A few comments and questions.

Great, thanks!

What's the exact consideration for this? I don't quite see the issue.

Well the Boot team is asking developers to seriously reconsider the use of JSPs for good reasons. So registering the ResourceUrlEncodingFilter for all Boot applications is a bit too much, especially when there are so many other template engines supported that don't require this.

Could WebJarsResourceResolver be registered if webjars-locator is on the classpath?

Yes, it's already the case with Spring Framework itself; the question here is to add that dependency to a boot starter. Registering asset locations is free, but adding such a JAR to all Boot web applications is probably not the best solution.

Looking at this there is no hint of what resolver this is about, nor that it's for the same resolver. How about inserting .version after .chain? So it would be spring.resources.chain.version.content.enabled.

I like it, it's more readable.
Yaml format would look like this:

spring:
  resources:
    chain:
      enabled: true
      cache: true
      version:
        content:
          enabled: true
          paths: /static/**
        fixed:
          enabled: true
          version: 1.0
          paths: /js/**,/foo/**

Or this:

spring:
  resources:
    chain:
      enabled: true
      cache: true
      version.content:
        enabled: true
        paths: /static/**
      version.fixed:
        enabled: true
        version: 1.0
        paths: /js/**,/foo/**

@snicoll Is this something usual, i.e. having "empty" keys for better readability?

@rstoyanchev
Copy link
Copy Markdown
Contributor

Well the Boot team is asking developers to seriously reconsider the use of JSPs for good reasons. So registering the ResourceUrlEncodingFilter for all Boot applications is a bit too much, especially when there are so many other template engines supported that don't require this.

Okay that makes sense. Maybe the presence of an InternalResourceViewResolver bean could be a giveaway that JSP is in use? But it's not always straight forward to detect. For example it could be nested inside ContentNegotiatingViewResolver#getViewResovlers, or it could also be in ViewResolverComposite#getViewResolvers, or both.

@bclozel
Copy link
Copy Markdown
Member Author

bclozel commented Jun 23, 2015

Good catch.
But it looks like InternalResourceViewResolver is always configured - see this autoconfig class. I'll discuss this with Stéphane...

@bclozel bclozel force-pushed the gh-1604 branch 2 times, most recently from 42e5798 to 13f63a6 Compare June 25, 2015 13:07
This commit improves support of the Resource Handling features
introduced in Spring Framework 4.1. Those features add new ways to
resolve and transform static resources in applications.
See [this blog
post](https://spring.io/blog/2014/07/24/spring-framework-4-1-handling-static-web-resources)
for more details.

The `ResourceUrlEncodinFilter` is added for compatible template engines:
Velocity and Thymeleaf. It assists them with rewriting the URLs of
static resources when rendering templates.

New keys are added in the `ResourceProperties` in order to configure
the Resource Handling chain. `ResourceResolvers` and
`ResourceTransformers` are registered accordingly in
`WebMvcAutoConfiguration`.

Here is an example of enabling a `ContentVersionStrategy` on all
static resources, meaning their names will be changed for cache
busting purposes by adding a content hash at the end of the file name.
Like "/js/jquery.js -> /js/jquery-872ca6a9fdda9e2c1516a84cff5c3bc6.js".

```
spring.resources.chain.enabled:true
spring.resources.chain.strategy.content.enabled:true
spring.resources.chain.strategy.content.paths:/**
```

Fixes spring-projects#1604
@snicoll
Copy link
Copy Markdown
Member

snicoll commented Jun 25, 2015

The PR looks good. I think that enabling one of the strategies should enable the chain if necessary. We can fix that with a @PostConstruct on WebMvcProperties that adds some checks. There is still an inconsistency if the chain is set to false explicitly and one of the strategy is set to true. I'd advocate that we consider the chain to be disabled in such a case.

I'll merge it early next week. Thanks!

@bclozel bclozel closed this in dd561d1 Jun 28, 2015
snicoll added a commit that referenced this pull request Jun 28, 2015
* pr/3123:
  Polish
  Improve Spring Resource Handling support
@snicoll snicoll added this to the 1.3.0.M2 milestone Jun 28, 2015
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type: enhancement A general enhancement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Investigate Spring 4.1 resource handling support

4 participants