001/* 002 * Copyright 2002-2019 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.http; 018 019import java.time.Duration; 020import java.util.concurrent.TimeUnit; 021 022import org.springframework.lang.Nullable; 023import org.springframework.util.StringUtils; 024 025/** 026 * A builder for creating "Cache-Control" HTTP response headers. 027 * 028 * <p>Adding Cache-Control directives to HTTP responses can significantly improve the client 029 * experience when interacting with a web application. This builder creates opinionated 030 * "Cache-Control" headers with response directives only, with several use cases in mind. 031 * 032 * <ul> 033 * <li>Caching HTTP responses with {@code CacheControl cc = CacheControl.maxAge(1, TimeUnit.HOURS)} 034 * will result in {@code Cache-Control: "max-age=3600"}</li> 035 * <li>Preventing cache with {@code CacheControl cc = CacheControl.noStore()} 036 * will result in {@code Cache-Control: "no-store"}</li> 037 * <li>Advanced cases like {@code CacheControl cc = CacheControl.maxAge(1, TimeUnit.HOURS).noTransform().cachePublic()} 038 * will result in {@code Cache-Control: "max-age=3600, no-transform, public"}</li> 039 * </ul> 040 * 041 * <p>Note that to be efficient, Cache-Control headers should be written along HTTP validators 042 * such as "Last-Modified" or "ETag" headers. 043 * 044 * @author Brian Clozel 045 * @author Juergen Hoeller 046 * @since 4.2 047 * @see <a href="https://tools.ietf.org/html/rfc7234#section-5.2.2">rfc7234 section 5.2.2</a> 048 * @see <a href="https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching"> 049 * HTTP caching - Google developers reference</a> 050 * @see <a href="https://www.mnot.net/cache_docs/">Mark Nottingham's cache documentation</a> 051 */ 052public class CacheControl { 053 054 @Nullable 055 private Duration maxAge; 056 057 private boolean noCache = false; 058 059 private boolean noStore = false; 060 061 private boolean mustRevalidate = false; 062 063 private boolean noTransform = false; 064 065 private boolean cachePublic = false; 066 067 private boolean cachePrivate = false; 068 069 private boolean proxyRevalidate = false; 070 071 @Nullable 072 private Duration staleWhileRevalidate; 073 074 @Nullable 075 private Duration staleIfError; 076 077 @Nullable 078 private Duration sMaxAge; 079 080 081 /** 082 * Create an empty CacheControl instance. 083 * @see #empty() 084 */ 085 protected CacheControl() { 086 } 087 088 089 /** 090 * Return an empty directive. 091 * <p>This is well suited for using other optional directives without "max-age", 092 * "no-cache" or "no-store". 093 * @return {@code this}, to facilitate method chaining 094 */ 095 public static CacheControl empty() { 096 return new CacheControl(); 097 } 098 099 /** 100 * Add a "max-age=" directive. 101 * <p>This directive is well suited for publicly caching resources, knowing that 102 * they won't change within the configured amount of time. Additional directives 103 * can be also used, in case resources shouldn't be cached ({@link #cachePrivate()}) 104 * or transformed ({@link #noTransform()}) by shared caches. 105 * <p>In order to prevent caches to reuse the cached response even when it has 106 * become stale (i.e. the "max-age" delay is passed), the "must-revalidate" 107 * directive should be set ({@link #mustRevalidate()} 108 * @param maxAge the maximum time the response should be cached 109 * @param unit the time unit of the {@code maxAge} argument 110 * @return {@code this}, to facilitate method chaining 111 * @see #maxAge(Duration) 112 * @see <a href="https://tools.ietf.org/html/rfc7234#section-5.2.2.8">rfc7234 section 5.2.2.8</a> 113 */ 114 public static CacheControl maxAge(long maxAge, TimeUnit unit) { 115 return maxAge(Duration.ofSeconds(unit.toSeconds(maxAge))); 116 } 117 118 /** 119 * Add a "max-age=" directive. 120 * <p>This directive is well suited for publicly caching resources, knowing that 121 * they won't change within the configured amount of time. Additional directives 122 * can be also used, in case resources shouldn't be cached ({@link #cachePrivate()}) 123 * or transformed ({@link #noTransform()}) by shared caches. 124 * <p>In order to prevent caches to reuse the cached response even when it has 125 * become stale (i.e. the "max-age" delay is passed), the "must-revalidate" 126 * directive should be set ({@link #mustRevalidate()} 127 * @param maxAge the maximum time the response should be cached 128 * @return {@code this}, to facilitate method chaining 129 * @since 5.2 130 * @see <a href="https://tools.ietf.org/html/rfc7234#section-5.2.2.8">rfc7234 section 5.2.2.8</a> 131 */ 132 public static CacheControl maxAge(Duration maxAge) { 133 CacheControl cc = new CacheControl(); 134 cc.maxAge = maxAge; 135 return cc; 136 } 137 138 /** 139 * Add a "no-cache" directive. 140 * <p>This directive is well suited for telling caches that the response 141 * can be reused only if the client revalidates it with the server. 142 * This directive won't disable cache altogether and may result with clients 143 * sending conditional requests (with "ETag", "If-Modified-Since" headers) 144 * and the server responding with "304 - Not Modified" status. 145 * <p>In order to disable caching and minimize requests/responses exchanges, 146 * the {@link #noStore()} directive should be used instead of {@code #noCache()}. 147 * @return {@code this}, to facilitate method chaining 148 * @see <a href="https://tools.ietf.org/html/rfc7234#section-5.2.2.2">rfc7234 section 5.2.2.2</a> 149 */ 150 public static CacheControl noCache() { 151 CacheControl cc = new CacheControl(); 152 cc.noCache = true; 153 return cc; 154 } 155 156 /** 157 * Add a "no-store" directive. 158 * <p>This directive is well suited for preventing caches (browsers and proxies) 159 * to cache the content of responses. 160 * @return {@code this}, to facilitate method chaining 161 * @see <a href="https://tools.ietf.org/html/rfc7234#section-5.2.2.3">rfc7234 section 5.2.2.3</a> 162 */ 163 public static CacheControl noStore() { 164 CacheControl cc = new CacheControl(); 165 cc.noStore = true; 166 return cc; 167 } 168 169 170 /** 171 * Add a "must-revalidate" directive. 172 * <p>This directive indicates that once it has become stale, a cache MUST NOT 173 * use the response to satisfy subsequent requests without successful validation 174 * on the origin server. 175 * @return {@code this}, to facilitate method chaining 176 * @see <a href="https://tools.ietf.org/html/rfc7234#section-5.2.2.1">rfc7234 section 5.2.2.1</a> 177 */ 178 public CacheControl mustRevalidate() { 179 this.mustRevalidate = true; 180 return this; 181 } 182 183 /** 184 * Add a "no-transform" directive. 185 * <p>This directive indicates that intermediaries (caches and others) should 186 * not transform the response content. This can be useful to force caches and 187 * CDNs not to automatically gzip or optimize the response content. 188 * @return {@code this}, to facilitate method chaining 189 * @see <a href="https://tools.ietf.org/html/rfc7234#section-5.2.2.4">rfc7234 section 5.2.2.4</a> 190 */ 191 public CacheControl noTransform() { 192 this.noTransform = true; 193 return this; 194 } 195 196 /** 197 * Add a "public" directive. 198 * <p>This directive indicates that any cache MAY store the response, 199 * even if the response would normally be non-cacheable or cacheable 200 * only within a private cache. 201 * @return {@code this}, to facilitate method chaining 202 * @see <a href="https://tools.ietf.org/html/rfc7234#section-5.2.2.5">rfc7234 section 5.2.2.5</a> 203 */ 204 public CacheControl cachePublic() { 205 this.cachePublic = true; 206 return this; 207 } 208 209 /** 210 * Add a "private" directive. 211 * <p>This directive indicates that the response message is intended 212 * for a single user and MUST NOT be stored by a shared cache. 213 * @return {@code this}, to facilitate method chaining 214 * @see <a href="https://tools.ietf.org/html/rfc7234#section-5.2.2.6">rfc7234 section 5.2.2.6</a> 215 */ 216 public CacheControl cachePrivate() { 217 this.cachePrivate = true; 218 return this; 219 } 220 221 /** 222 * Add a "proxy-revalidate" directive. 223 * <p>This directive has the same meaning as the "must-revalidate" directive, 224 * except that it does not apply to private caches (i.e. browsers, HTTP clients). 225 * @return {@code this}, to facilitate method chaining 226 * @see <a href="https://tools.ietf.org/html/rfc7234#section-5.2.2.7">rfc7234 section 5.2.2.7</a> 227 */ 228 public CacheControl proxyRevalidate() { 229 this.proxyRevalidate = true; 230 return this; 231 } 232 233 /** 234 * Add an "s-maxage" directive. 235 * <p>This directive indicates that, in shared caches, the maximum age specified 236 * by this directive overrides the maximum age specified by other directives. 237 * @param sMaxAge the maximum time the response should be cached 238 * @param unit the time unit of the {@code sMaxAge} argument 239 * @return {@code this}, to facilitate method chaining 240 * @see #sMaxAge(Duration) 241 * @see <a href="https://tools.ietf.org/html/rfc7234#section-5.2.2.9">rfc7234 section 5.2.2.9</a> 242 */ 243 public CacheControl sMaxAge(long sMaxAge, TimeUnit unit) { 244 return sMaxAge(Duration.ofSeconds(unit.toSeconds(sMaxAge))); 245 } 246 247 /** 248 * Add an "s-maxage" directive. 249 * <p>This directive indicates that, in shared caches, the maximum age specified 250 * by this directive overrides the maximum age specified by other directives. 251 * @param sMaxAge the maximum time the response should be cached 252 * @return {@code this}, to facilitate method chaining 253 * @since 5.2 254 * @see <a href="https://tools.ietf.org/html/rfc7234#section-5.2.2.9">rfc7234 section 5.2.2.9</a> 255 */ 256 public CacheControl sMaxAge(Duration sMaxAge) { 257 this.sMaxAge = sMaxAge; 258 return this; 259 } 260 261 /** 262 * Add a "stale-while-revalidate" directive. 263 * <p>This directive indicates that caches MAY serve the response in which it 264 * appears after it becomes stale, up to the indicated number of seconds. 265 * If a cached response is served stale due to the presence of this extension, 266 * the cache SHOULD attempt to revalidate it while still serving stale responses 267 * (i.e. without blocking). 268 * @param staleWhileRevalidate the maximum time the response should be used while being revalidated 269 * @param unit the time unit of the {@code staleWhileRevalidate} argument 270 * @return {@code this}, to facilitate method chaining 271 * @see #staleWhileRevalidate(Duration) 272 * @see <a href="https://tools.ietf.org/html/rfc5861#section-3">rfc5861 section 3</a> 273 */ 274 public CacheControl staleWhileRevalidate(long staleWhileRevalidate, TimeUnit unit) { 275 return staleWhileRevalidate(Duration.ofSeconds(unit.toSeconds(staleWhileRevalidate))); 276 } 277 278 /** 279 * Add a "stale-while-revalidate" directive. 280 * <p>This directive indicates that caches MAY serve the response in which it 281 * appears after it becomes stale, up to the indicated number of seconds. 282 * If a cached response is served stale due to the presence of this extension, 283 * the cache SHOULD attempt to revalidate it while still serving stale responses 284 * (i.e. without blocking). 285 * @param staleWhileRevalidate the maximum time the response should be used while being revalidated 286 * @return {@code this}, to facilitate method chaining 287 * @since 5.2 288 * @see <a href="https://tools.ietf.org/html/rfc5861#section-3">rfc5861 section 3</a> 289 */ 290 public CacheControl staleWhileRevalidate(Duration staleWhileRevalidate) { 291 this.staleWhileRevalidate = staleWhileRevalidate; 292 return this; 293 } 294 295 /** 296 * Add a "stale-if-error" directive. 297 * <p>This directive indicates that when an error is encountered, a cached stale response 298 * MAY be used to satisfy the request, regardless of other freshness information. 299 * @param staleIfError the maximum time the response should be used when errors are encountered 300 * @param unit the time unit of the {@code staleIfError} argument 301 * @return {@code this}, to facilitate method chaining 302 * @see #staleIfError(Duration) 303 * @see <a href="https://tools.ietf.org/html/rfc5861#section-4">rfc5861 section 4</a> 304 */ 305 public CacheControl staleIfError(long staleIfError, TimeUnit unit) { 306 return staleIfError(Duration.ofSeconds(unit.toSeconds(staleIfError))); 307 } 308 309 /** 310 * Add a "stale-if-error" directive. 311 * <p>This directive indicates that when an error is encountered, a cached stale response 312 * MAY be used to satisfy the request, regardless of other freshness information. 313 * @param staleIfError the maximum time the response should be used when errors are encountered 314 * @return {@code this}, to facilitate method chaining 315 * @since 5.2 316 * @see <a href="https://tools.ietf.org/html/rfc5861#section-4">rfc5861 section 4</a> 317 */ 318 public CacheControl staleIfError(Duration staleIfError) { 319 this.staleIfError = staleIfError; 320 return this; 321 } 322 323 /** 324 * Return the "Cache-Control" header value, if any. 325 * @return the header value, or {@code null} if no directive was added 326 */ 327 @Nullable 328 public String getHeaderValue() { 329 String headerValue = toHeaderValue(); 330 return (StringUtils.hasText(headerValue) ? headerValue : null); 331 } 332 333 /** 334 * Return the "Cache-Control" header value. 335 * @return the header value (potentially empty) 336 */ 337 private String toHeaderValue() { 338 StringBuilder headerValue = new StringBuilder(); 339 if (this.maxAge != null) { 340 appendDirective(headerValue, "max-age=" + this.maxAge.getSeconds()); 341 } 342 if (this.noCache) { 343 appendDirective(headerValue, "no-cache"); 344 } 345 if (this.noStore) { 346 appendDirective(headerValue, "no-store"); 347 } 348 if (this.mustRevalidate) { 349 appendDirective(headerValue, "must-revalidate"); 350 } 351 if (this.noTransform) { 352 appendDirective(headerValue, "no-transform"); 353 } 354 if (this.cachePublic) { 355 appendDirective(headerValue, "public"); 356 } 357 if (this.cachePrivate) { 358 appendDirective(headerValue, "private"); 359 } 360 if (this.proxyRevalidate) { 361 appendDirective(headerValue, "proxy-revalidate"); 362 } 363 if (this.sMaxAge != null) { 364 appendDirective(headerValue, "s-maxage=" + this.sMaxAge.getSeconds()); 365 } 366 if (this.staleIfError != null) { 367 appendDirective(headerValue, "stale-if-error=" + this.staleIfError.getSeconds()); 368 } 369 if (this.staleWhileRevalidate != null) { 370 appendDirective(headerValue, "stale-while-revalidate=" + this.staleWhileRevalidate.getSeconds()); 371 } 372 return headerValue.toString(); 373 } 374 375 private void appendDirective(StringBuilder builder, String value) { 376 if (builder.length() > 0) { 377 builder.append(", "); 378 } 379 builder.append(value); 380 } 381 382 383 @Override 384 public String toString() { 385 return "CacheControl [" + toHeaderValue() + "]"; 386 } 387 388}