如何使用未修改的 http 304 让 WRO 回答?
How to have WRO answer with a http 304 not modified?
我们在我们的网络应用程序中通过 wro 提供 javascript 资源(和其他)。
在 PROD 环境中,浏览器获取(例如)app.js angular webapp 的内容 'expires' headers 一年后。
意味着对于后续请求,浏览器会从缓存中获取它,而无需向服务器发出请求。
如果我们部署新版本的 webapp,浏览器不会获取新版本,因为它是从本地缓存中获取的。
目标是配置 wro or/and spring 以便 headers 将正确设置为让浏览器每次执行请求,而服务器 return 一个 304 未修改。所以我们会让客户端自动 "updated" 支持新的部署。
有人已经做到了吗?
我们使用Spring的Java配置:
@Configuration
public class Wro4jConfiguration {
@Value("${app.webapp.web.minimize}")
private String minimize;
@Value("${app.webapp.web.disableCache}")
private String disableCache;
@Autowired
private Environment env;
@Bean(name = "wroFilter")
public WroFilter wroFilter() {
ConfigurableWroFilter filter = new ConfigurableWroFilter();
filter.setWroManagerFactory(new Wro4jManagerFactory());
filter.setWroConfigurationFactory(createProperties());
return filter;
}
private PropertyWroConfigurationFactory createProperties() {
Properties props = new Properties();
props.setProperty("jmxEnabled", "false");
props.setProperty("debug", String.valueOf(!env.acceptsProfiles(EnvConstants.PROD)));
props.setProperty("gzipResources", "false");
props.setProperty("ignoreMissingResources", "true");
props.setProperty("minimizeEnabled", minimize);
props.setProperty("resourceWatcherUpdatePeriod", "0");
props.setProperty("modelUpdatePeriod", "0");
props.setProperty("cacheGzippedContent", "false");
// let's see if server-side cache is disabled (DEV only)
if (Boolean.valueOf(disableCache)) {
props.setProperty("resourceWatcherUpdatePeriod", "1");
props.setProperty("modelUpdatePeriod", "5");
}
return new PropertyWroConfigurationFactory(props);
}
}
默认情况下,WroFilter 设置以下headers:ETag(资源的md5 校验和),Cache-Control (public, max-age=315360000), 过期(资源创建后 1 年)。
关于那些 header 的重要性有 plenty of details。简短的解释是这样的:
当服务器从客户端请求中读取 ETag 时,服务器可以确定是发送文件 (HTTP 200) 还是告诉客户端只使用其本地副本 (HTTP 304)。 ETag 基本上只是文件的校验和,当文件内容发生变化时,它会在语义上发生变化。如果只发送 ETag,则客户端将始终必须发出请求。
Expires 和 Cache-Control headers 非常相似,客户端(和 proxies/caches)使用它来确定是否需要向服务器。
所以您真正想要做的是同时使用 headers - 根据内容更改的频率将 Expires header 设置为合理的值。然后配置要发送的ETags,这样当客户端确实向服务器发送请求时,它可以更容易地确定是否将文件发回。
如果您希望客户端始终检查最新的资源版本,则不应发送过期 & cache-control headers。
或者,还有一种更激进的缓存技术:将资源的校验和编码到它的路径中。因此,每次更改资源时,该资源的路径也会更改。这种方法保证客户端总是请求最新版本。对于这种方法,理论上资源永远不会过期,因为每次更改资源时校验和都会更改。
根据 Alex 的信息和文档参考,我最终覆盖了 WroFilter.setResponseHeaders 以放置适当的过期值。
这工作正常。 Wro 已经负责设置 ETag、Date 和其他,所以我只覆盖过期延迟和日期。
@Configuration
public class Wro4jConfiguration {
@Value("${app.webapp.web.browserCache.maxAgeInHours}")
private String maxAgeInHours;
@Bean(name = "wroFilter")
public WroFilter wroFilter() {
ConfigurableWroFilter filter = createFilter();
filter.setWroManagerFactory(new Wro4jManagerFactory());
filter.setWroConfigurationFactory(createProperties());
return filter;
}
private ConfigurableWroFilter createFilter() {
return new ConfigurableWroFilter() {
private final int BROWSER_CACHE_HOURS = Integer.parseInt(maxAgeInHours);
private final int BROWSER_CACHE_SECONDS = BROWSER_CACHE_HOURS * 60 * 60;
@Override
protected void setResponseHeaders(final HttpServletResponse response){
super.setResponseHeaders(response);
if (!getConfiguration().isDebug()) {
ZonedDateTime cacheExpires = ZonedDateTime.of(LocalDateTime.now(), ZoneId.of("GMT")).plusHours(BROWSER_CACHE_HOURS);
String cacheExpiresStr = cacheExpires.format(DateTimeFormatter.RFC_1123_DATE_TIME);
response.setHeader(HttpHeader.EXPIRES.toString(), cacheExpiresStr);
response.setHeader(HttpHeader.CACHE_CONTROL.toString(), "public, max-age=" + BROWSER_CACHE_SECONDS);
}
}
};
}
// Other config methods
}
我们在我们的网络应用程序中通过 wro 提供 javascript 资源(和其他)。 在 PROD 环境中,浏览器获取(例如)app.js angular webapp 的内容 'expires' headers 一年后。
意味着对于后续请求,浏览器会从缓存中获取它,而无需向服务器发出请求。 如果我们部署新版本的 webapp,浏览器不会获取新版本,因为它是从本地缓存中获取的。
目标是配置 wro or/and spring 以便 headers 将正确设置为让浏览器每次执行请求,而服务器 return 一个 304 未修改。所以我们会让客户端自动 "updated" 支持新的部署。 有人已经做到了吗?
我们使用Spring的Java配置:
@Configuration
public class Wro4jConfiguration {
@Value("${app.webapp.web.minimize}")
private String minimize;
@Value("${app.webapp.web.disableCache}")
private String disableCache;
@Autowired
private Environment env;
@Bean(name = "wroFilter")
public WroFilter wroFilter() {
ConfigurableWroFilter filter = new ConfigurableWroFilter();
filter.setWroManagerFactory(new Wro4jManagerFactory());
filter.setWroConfigurationFactory(createProperties());
return filter;
}
private PropertyWroConfigurationFactory createProperties() {
Properties props = new Properties();
props.setProperty("jmxEnabled", "false");
props.setProperty("debug", String.valueOf(!env.acceptsProfiles(EnvConstants.PROD)));
props.setProperty("gzipResources", "false");
props.setProperty("ignoreMissingResources", "true");
props.setProperty("minimizeEnabled", minimize);
props.setProperty("resourceWatcherUpdatePeriod", "0");
props.setProperty("modelUpdatePeriod", "0");
props.setProperty("cacheGzippedContent", "false");
// let's see if server-side cache is disabled (DEV only)
if (Boolean.valueOf(disableCache)) {
props.setProperty("resourceWatcherUpdatePeriod", "1");
props.setProperty("modelUpdatePeriod", "5");
}
return new PropertyWroConfigurationFactory(props);
}
}
默认情况下,WroFilter 设置以下headers:ETag(资源的md5 校验和),Cache-Control (public, max-age=315360000), 过期(资源创建后 1 年)。
关于那些 header 的重要性有 plenty of details。简短的解释是这样的:
当服务器从客户端请求中读取 ETag 时,服务器可以确定是发送文件 (HTTP 200) 还是告诉客户端只使用其本地副本 (HTTP 304)。 ETag 基本上只是文件的校验和,当文件内容发生变化时,它会在语义上发生变化。如果只发送 ETag,则客户端将始终必须发出请求。
Expires 和 Cache-Control headers 非常相似,客户端(和 proxies/caches)使用它来确定是否需要向服务器。
所以您真正想要做的是同时使用 headers - 根据内容更改的频率将 Expires header 设置为合理的值。然后配置要发送的ETags,这样当客户端确实向服务器发送请求时,它可以更容易地确定是否将文件发回。
如果您希望客户端始终检查最新的资源版本,则不应发送过期 & cache-control headers。
或者,还有一种更激进的缓存技术:将资源的校验和编码到它的路径中。因此,每次更改资源时,该资源的路径也会更改。这种方法保证客户端总是请求最新版本。对于这种方法,理论上资源永远不会过期,因为每次更改资源时校验和都会更改。
根据 Alex 的信息和文档参考,我最终覆盖了 WroFilter.setResponseHeaders 以放置适当的过期值。 这工作正常。 Wro 已经负责设置 ETag、Date 和其他,所以我只覆盖过期延迟和日期。
@Configuration
public class Wro4jConfiguration {
@Value("${app.webapp.web.browserCache.maxAgeInHours}")
private String maxAgeInHours;
@Bean(name = "wroFilter")
public WroFilter wroFilter() {
ConfigurableWroFilter filter = createFilter();
filter.setWroManagerFactory(new Wro4jManagerFactory());
filter.setWroConfigurationFactory(createProperties());
return filter;
}
private ConfigurableWroFilter createFilter() {
return new ConfigurableWroFilter() {
private final int BROWSER_CACHE_HOURS = Integer.parseInt(maxAgeInHours);
private final int BROWSER_CACHE_SECONDS = BROWSER_CACHE_HOURS * 60 * 60;
@Override
protected void setResponseHeaders(final HttpServletResponse response){
super.setResponseHeaders(response);
if (!getConfiguration().isDebug()) {
ZonedDateTime cacheExpires = ZonedDateTime.of(LocalDateTime.now(), ZoneId.of("GMT")).plusHours(BROWSER_CACHE_HOURS);
String cacheExpiresStr = cacheExpires.format(DateTimeFormatter.RFC_1123_DATE_TIME);
response.setHeader(HttpHeader.EXPIRES.toString(), cacheExpiresStr);
response.setHeader(HttpHeader.CACHE_CONTROL.toString(), "public, max-age=" + BROWSER_CACHE_SECONDS);
}
}
};
}
// Other config methods
}