Understanding Browser Cache Control with Spring MVC and Nginx
This article explains how browsers cache resources using HTTP headers such as Last-Modified, Expires, Cache-Control and ETag, demonstrates Java Spring MVC code to set these headers, and shows how to configure Nginx for both static and proxy caching to reduce backend load and improve performance.
The article explains how browsers cache resources using HTTP response headers such as Last-Modified , Expires , Cache-Control and ETag , and how a Java Spring MVC application can control these headers to enable efficient client‑side caching.
It provides a complete Spring MVC controller example that sets Last-Modified , Date , Expires and Cache-Control headers based on a supplied modification timestamp, returning 304 Not Modified when the client’s If-Modified-Since matches.
@RequestMapping("/cache")
public ResponseEntity
cache(HttpServletRequest request,
@RequestParam("millis") long lastModifiedMillis,
@RequestHeader(value = "If-Modified-Since", required = false) Date ifModifiedSince) {
long now = System.currentTimeMillis();
long maxAge = 20;
if (ifModifiedSince != null && ifModifiedSince.getTime() == lastModifiedMillis) {
return new ResponseEntity<>(HttpStatus.NOT_MODIFIED);
}
DateFormat gmtDateFormat = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss 'GMT'", Locale.US);
String body = "
点击访问当前链接
";
MultiValueMap
headers = new HttpHeaders();
headers.add("Last-Modified", gmtDateFormat.format(new Date(lastModifiedMillis)));
headers.add("Date", gmtDateFormat.format(new Date(now)));
headers.add("Expires", gmtDateFormat.format(new Date(now + maxAge)));
headers.add("Cache-Control", "max-age=" + maxAge);
return new ResponseEntity<>(body, headers, HttpStatus.OK);
}Another example shows how to generate a weak ETag and return 304 Not Modified when the client’s If-None-Match matches, illustrating the difference between weak and strong validators.
@RequestMapping("/cache/etag")
public ResponseEntity
cache(HttpServletRequest request,
HttpServletResponse response,
@RequestHeader(value = "If-None-Match", required = false) String ifNoneMatch) {
long now = System.currentTimeMillis();
long maxAge = 10;
String body = "
点击访问当前链接
";
String etag = "W/\"" + md5(body) + "\"";
if (StringUtils.equals(ifNoneMatch, etag)) {
return new ResponseEntity<>(HttpStatus.NOT_MODIFIED);
}
DateFormat gmtDateFormat = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss 'GMT'", Locale.US);
MultiValueMap
headers = new HttpHeaders();
headers.add("ETag", etag);
headers.add("Date", gmtDateFormat.format(new Date(now)));
headers.add("Cache-Control", "max-age=" + maxAge);
return new ResponseEntity<>(body, headers, HttpStatus.OK);
}The article then discusses Nginx cache configuration, including the expires , etag and if-modified-since directives for static resources, and demonstrates how to set up a reverse‑proxy cache with proxy_cache , proxy_cache_valid and related directives to reduce backend load.
location /img {
alias /export/img/;
expires 1d;
}It also covers advanced Nginx proxy cache options such as proxy_cache_path , proxy_cache_key , proxy_cache_use_stale , proxy_cache_revalidate , and cache‑status headers (HIT, MISS, EXPIRED, UPDATING, STALE, REVALIDATED, BYPASS), providing practical tips for tuning cache size, inactivity, lock mechanisms and eviction policies.
proxy_cache_path /export/cache/proxy_cache levels=1:2 keys_zone=cache:512m inactive=5m max_size=8g use_temp_path=off;
proxy_cache_key $scheme$proxy_host$request_uri;
proxy_cache_valid 200 5s;
add_header cache-status $upstream_cache_status;Finally, the article shares operational best practices for caching HTTP 200 responses, setting appropriate cache durations for static assets and dynamic data, handling cache invalidation in large‑scale e‑commerce scenarios, and using tools such as ngx_cache_purge for manual cache clearing.
Qunar Tech Salon
Qunar Tech Salon is a learning and exchange platform for Qunar engineers and industry peers. We share cutting-edge technology trends and topics, providing a free platform for mid-to-senior technical professionals to exchange and learn.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.