001/* 002 * Copyright 2002-2018 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.util.concurrent.TimeUnit; 020 021import org.springframework.util.StringUtils; 022 023/** 024 * A builder for creating "Cache-Control" HTTP response headers. 025 * 026 * <p>Adding Cache-Control directives to HTTP responses can significantly improve the client 027 * experience when interacting with a web application. This builder creates opinionated 028 * "Cache-Control" headers with response directives only, with several use cases in mind. 029 * 030 * <ul> 031 * <li>Caching HTTP responses with {@code CacheControl cc = CacheControl.maxAge(1, TimeUnit.HOURS)} 032 * will result in {@code Cache-Control: "max-age=3600"}</li> 033 * <li>Preventing cache with {@code CacheControl cc = CacheControl.noStore()} 034 * will result in {@code Cache-Control: "no-store"}</li> 035 * <li>Advanced cases like {@code CacheControl cc = CacheControl.maxAge(1, TimeUnit.HOURS).noTransform().cachePublic()} 036 * will result in {@code Cache-Control: "max-age=3600, no-transform, public"}</li> 037 * </ul> 038 * 039 * <p>Note that to be efficient, Cache-Control headers should be written along HTTP validators 040 * such as "Last-Modified" or "ETag" headers. 041 * 042 * @author Brian Clozel 043 * @author Juergen Hoeller 044 * @since 4.2 045 * @see <a href="https://tools.ietf.org/html/rfc7234#section-5.2.2">rfc7234 section 5.2.2</a> 046 * @see <a href="https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching"> 047 * HTTP caching - Google developers reference</a> 048 * @see <a href="https://www.mnot.net/cache_docs/">Mark Nottingham's cache documentation</a> 049 */ 050public class CacheControl { 051 052 private long maxAge = -1; 053 054 private boolean noCache = false; 055 056 private boolean noStore = false; 057 058 private boolean mustRevalidate = false; 059 060 private boolean noTransform = false; 061 062 private boolean cachePublic = false; 063 064 private boolean cachePrivate = false; 065 066 private boolean proxyRevalidate = false; 067 068 private long staleWhileRevalidate = -1; 069 070 private long staleIfError = -1; 071 072 private long sMaxAge = -1; 073 074 075 /** 076 * Create an empty CacheControl instance. 077 * @see #empty() 078 */ 079 protected CacheControl() { 080 } 081 082 083 /** 084 * Return an empty directive. 085 * <p>This is well suited for using other optional directives without "max-age", 086 * "no-cache" or "no-store". 087 * @return {@code this}, to facilitate method chaining 088 */ 089 public static CacheControl empty() { 090 return new CacheControl(); 091 } 092 093 /** 094 * Add a "max-age=" directive. 095 * <p>This directive is well suited for publicly caching resources, knowing that 096 * they won't change within the configured amount of time. Additional directives 097 * can be also used, in case resources shouldn't be cached ({@link #cachePrivate()}) 098 * or transformed ({@link #noTransform()}) by shared caches. 099 * <p>In order to prevent caches to reuse the cached response even when it has 100 * become stale (i.e. the "max-age" delay is passed), the "must-revalidate" 101 * directive should be set ({@link #mustRevalidate()} 102 * @param maxAge the maximum time the response should be cached 103 * @param unit the time unit of the {@code maxAge} argument 104 * @return {@code this}, to facilitate method chaining 105 * @see <a href="https://tools.ietf.org/html/rfc7234#section-5.2.2.8">rfc7234 section 5.2.2.8</a> 106 */ 107 public static CacheControl maxAge(long maxAge, TimeUnit unit) { 108 CacheControl cc = new CacheControl(); 109 cc.maxAge = unit.toSeconds(maxAge); 110 return cc; 111 } 112 113 /** 114 * Add a "no-cache" directive. 115 * <p>This directive is well suited for telling caches that the response 116 * can be reused only if the client revalidates it with the server. 117 * This directive won't disable cache altogether and may result with clients 118 * sending conditional requests (with "ETag", "If-Modified-Since" headers) 119 * and the server responding with "304 - Not Modified" status. 120 * <p>In order to disable caching and minimize requests/responses exchanges, 121 * the {@link #noStore()} directive should be used instead of {@code #noCache()}. 122 * @return {@code this}, to facilitate method chaining 123 * @see <a href="https://tools.ietf.org/html/rfc7234#section-5.2.2.2">rfc7234 section 5.2.2.2</a> 124 */ 125 public static CacheControl noCache() { 126 CacheControl cc = new CacheControl(); 127 cc.noCache = true; 128 return cc; 129 } 130 131 /** 132 * Add a "no-store" directive. 133 * <p>This directive is well suited for preventing caches (browsers and proxies) 134 * to cache the content of responses. 135 * @return {@code this}, to facilitate method chaining 136 * @see <a href="https://tools.ietf.org/html/rfc7234#section-5.2.2.3">rfc7234 section 5.2.2.3</a> 137 */ 138 public static CacheControl noStore() { 139 CacheControl cc = new CacheControl(); 140 cc.noStore = true; 141 return cc; 142 } 143 144 145 /** 146 * Add a "must-revalidate" directive. 147 * <p>This directive indicates that once it has become stale, a cache MUST NOT 148 * use the response to satisfy subsequent requests without successful validation 149 * on the origin server. 150 * @return {@code this}, to facilitate method chaining 151 * @see <a href="https://tools.ietf.org/html/rfc7234#section-5.2.2.1">rfc7234 section 5.2.2.1</a> 152 */ 153 public CacheControl mustRevalidate() { 154 this.mustRevalidate = true; 155 return this; 156 } 157 158 /** 159 * Add a "no-transform" directive. 160 * <p>This directive indicates that intermediaries (caches and others) should 161 * not transform the response content. This can be useful to force caches and 162 * CDNs not to automatically gzip or optimize the response content. 163 * @return {@code this}, to facilitate method chaining 164 * @see <a href="https://tools.ietf.org/html/rfc7234#section-5.2.2.4">rfc7234 section 5.2.2.4</a> 165 */ 166 public CacheControl noTransform() { 167 this.noTransform = true; 168 return this; 169 } 170 171 /** 172 * Add a "public" directive. 173 * <p>This directive indicates that any cache MAY store the response, 174 * even if the response would normally be non-cacheable or cacheable 175 * only within a private cache. 176 * @return {@code this}, to facilitate method chaining 177 * @see <a href="https://tools.ietf.org/html/rfc7234#section-5.2.2.5">rfc7234 section 5.2.2.5</a> 178 */ 179 public CacheControl cachePublic() { 180 this.cachePublic = true; 181 return this; 182 } 183 184 /** 185 * Add a "private" directive. 186 * <p>This directive indicates that the response message is intended 187 * for a single user and MUST NOT be stored by a shared cache. 188 * @return {@code this}, to facilitate method chaining 189 * @see <a href="https://tools.ietf.org/html/rfc7234#section-5.2.2.6">rfc7234 section 5.2.2.6</a> 190 */ 191 public CacheControl cachePrivate() { 192 this.cachePrivate = true; 193 return this; 194 } 195 196 /** 197 * Add a "proxy-revalidate" directive. 198 * <p>This directive has the same meaning as the "must-revalidate" directive, 199 * except that it does not apply to private caches (i.e. browsers, HTTP clients). 200 * @return {@code this}, to facilitate method chaining 201 * @see <a href="https://tools.ietf.org/html/rfc7234#section-5.2.2.7">rfc7234 section 5.2.2.7</a> 202 */ 203 public CacheControl proxyRevalidate() { 204 this.proxyRevalidate = true; 205 return this; 206 } 207 208 /** 209 * Add an "s-maxage" directive. 210 * <p>This directive indicates that, in shared caches, the maximum age specified 211 * by this directive overrides the maximum age specified by other directives. 212 * @param sMaxAge the maximum time the response should be cached 213 * @param unit the time unit of the {@code sMaxAge} argument 214 * @return {@code this}, to facilitate method chaining 215 * @see <a href="https://tools.ietf.org/html/rfc7234#section-5.2.2.9">rfc7234 section 5.2.2.9</a> 216 */ 217 public CacheControl sMaxAge(long sMaxAge, TimeUnit unit) { 218 this.sMaxAge = unit.toSeconds(sMaxAge); 219 return this; 220 } 221 222 /** 223 * Add a "stale-while-revalidate" directive. 224 * <p>This directive indicates that caches MAY serve the response in which it 225 * appears after it becomes stale, up to the indicated number of seconds. 226 * If a cached response is served stale due to the presence of this extension, 227 * the cache SHOULD attempt to revalidate it while still serving stale responses 228 * (i.e. without blocking). 229 * @param staleWhileRevalidate the maximum time the response should be used while being revalidated 230 * @param unit the time unit of the {@code staleWhileRevalidate} argument 231 * @return {@code this}, to facilitate method chaining 232 * @see <a href="https://tools.ietf.org/html/rfc5861#section-3">rfc5861 section 3</a> 233 */ 234 public CacheControl staleWhileRevalidate(long staleWhileRevalidate, TimeUnit unit) { 235 this.staleWhileRevalidate = unit.toSeconds(staleWhileRevalidate); 236 return this; 237 } 238 239 /** 240 * Add a "stale-if-error" directive. 241 * <p>This directive indicates that when an error is encountered, a cached stale response 242 * MAY be used to satisfy the request, regardless of other freshness information. 243 * @param staleIfError the maximum time the response should be used when errors are encountered 244 * @param unit the time unit of the {@code staleIfError} argument 245 * @return {@code this}, to facilitate method chaining 246 * @see <a href="https://tools.ietf.org/html/rfc5861#section-4">rfc5861 section 4</a> 247 */ 248 public CacheControl staleIfError(long staleIfError, TimeUnit unit) { 249 this.staleIfError = unit.toSeconds(staleIfError); 250 return this; 251 } 252 253 254 /** 255 * Return the "Cache-Control" header value, if any. 256 * @return the header value, or {@code null} if no directive was added 257 */ 258 public String getHeaderValue() { 259 StringBuilder headerValue = new StringBuilder(); 260 if (this.maxAge != -1) { 261 appendDirective(headerValue, "max-age=" + this.maxAge); 262 } 263 if (this.noCache) { 264 appendDirective(headerValue, "no-cache"); 265 } 266 if (this.noStore) { 267 appendDirective(headerValue, "no-store"); 268 } 269 if (this.mustRevalidate) { 270 appendDirective(headerValue, "must-revalidate"); 271 } 272 if (this.noTransform) { 273 appendDirective(headerValue, "no-transform"); 274 } 275 if (this.cachePublic) { 276 appendDirective(headerValue, "public"); 277 } 278 if (this.cachePrivate) { 279 appendDirective(headerValue, "private"); 280 } 281 if (this.proxyRevalidate) { 282 appendDirective(headerValue, "proxy-revalidate"); 283 } 284 if (this.sMaxAge != -1) { 285 appendDirective(headerValue, "s-maxage=" + this.sMaxAge); 286 } 287 if (this.staleIfError != -1) { 288 appendDirective(headerValue, "stale-if-error=" + this.staleIfError); 289 } 290 if (this.staleWhileRevalidate != -1) { 291 appendDirective(headerValue, "stale-while-revalidate=" + this.staleWhileRevalidate); 292 } 293 294 String valueString = headerValue.toString(); 295 return (StringUtils.hasText(valueString) ? valueString : null); 296 } 297 298 private void appendDirective(StringBuilder builder, String value) { 299 if (builder.length() > 0) { 300 builder.append(", "); 301 } 302 builder.append(value); 303 } 304 305}