Spring Web Reactive | 1. Spring WebFlux | 1.10. HTTP 캐싱
HTTP 캐싱은 Web 어플리케이션의 성능을 크게 개선시켜 준다. HTTP 캐싱은 Cache-Control
응답 헤더와 Last-Modified
와 ETag
와 같이 이어지는 조건부 요청 헤더를 중심으로 동작한다. Cache-Control
는 프라이빗 (브라우저 등) 및 퍼블릭(프록시 등)의 캐시가 응답을 캐시하여 재사용하는 방법을 권고한다. 컨텐츠가 변경되지 않은 경우, ETag
헤더를 사용하여 본문(body)이 없는 304 (NOT_MODIFIED)가 될 조건부 요청을 만든다. ETag
는 Last-Modified
헤더보다 정교한 것으로 대체 버전으로 볼 수 있다.
이번 섹션에서는 Spring WebFlux에서 사용 가능한 HTTP 캐싱 관련 옵션에 대해 설명한다.
1.10.1. CacheControl
CacheControl
는 Cache-Control
헤더에 관련 설정 구성을 지원하고, 많은 곳에서 인수로 허용된다.
RFC 7234는 Cache-Control
응답 헤더의 가능한 모든 지시어(directive)를 기술한다. CacheControl
유형은 다음의 예와 같이 일반적인 시나리오에 초점을 맞춘 사례 중심의 접근 방식을 취한다.
Java
// Cache for an hour - "Cache-Control: max-age=3600"
CacheControl ccCacheOneHour = CacheControl.maxAge(1, TimeUnit.HOURS);
// Prevent caching - "Cache-Control: no-store"
CacheControl ccNoStore = CacheControl.noStore();
// Cache for ten days in public and private caches,
// public caches should not transform the response
// "Cache-Control: max-age=864000, public, no-transform"
CacheControl ccCustom = CacheControl.maxAge(10, TimeUnit.DAYS).noTransform().cachePublic();
Kotlin
// Cache for an hour - "Cache-Control: max-age=3600"
val ccCacheOneHour = CacheControl.maxAge(1, TimeUnit.HOURS)
// Prevent caching - "Cache-Control: no-store"
val ccNoStore = CacheControl.noStore()
// Cache for ten days in public and private caches,
// public caches should not transform the response
// "Cache-Control: max-age=864000, public, no-transform"
val ccCustom = CacheControl.maxAge(10, TimeUnit.DAYS).noTransform().cachePublic()
1.10.2. 컨트롤러
컨트롤러는 HTTP 캐싱의 명시적인 지원을 추가 할 수 있다. 리소스 lastModified
또는 ETag
값은 조건부 요청 헤더와 비교하기 전에 계산해야 하므로 이런 방식을 추천한다. 다음의 예와 같이, 컨트롤러에 ETag
, Cache-Control
설정을 ResponseEntity
에 추가 할 수 있다.
Java
@GetMapping("/book/{id}")
public ResponseEntity<Book> showBook(@PathVariable Long id) {
Book book = findBook(id);
String version = book.getVersion();
return ResponseEntity
.ok()
.cacheControl(CacheControl.maxAge(30, TimeUnit.DAYS))
.eTag(version) // lastModified is also available
.body(book);
}
Kotlin
@GetMapping("/book/{id}")
fun showBook(@PathVariable id: Long): ResponseEntity<Book> {
val book = findBook(id)
val version = book.getVersion()
return ResponseEntity
.ok()
.cacheControl(CacheControl.maxAge(30, TimeUnit.DAYS))
.eTag(version) // lastModified is also available
.body(book)
}
위의 예제에서는 조건부 요청 헤더에 비해 콘텐츠가 변경되지 않았다면, 빈 본문를 포함한 304 (NOT_MODIFIED) 응답이 전송된다. 그렇지 않으면, ETag
및 Cache-Control
헤더가 응답에 추가된다.
다음의 예와 같이 컨트롤러의 조건부 요청 헤더에 체크를 할 수도 있다.
Java
@RequestMapping
public String myHandleMethod(ServerWebExchange exchange, Model model) {
long eTag = ... // (1)
if (exchange.checkNotModified(eTag)) {
return null; // (2)
}
model.addAttribute(...); // (3)
return "myViewName";
}
Kotlin
@RequestMapping
fun myHandleMethod(exchange: ServerWebExchange, model: Model): String? {
val eTag: Long = ... // (1)
if (exchange.checkNotModified(eTag)) {
return null // (2)
}
model.addAttribute(...) // (3)
return "myViewName"
}
- (1) 응용 프로그램의 고유 방식으로 계산한다.
- (2) 응답은 304 (NOT_MODIFIED)로 설정한다. 더 이상 다른 처리는 하지 않는다.
- (3) 요청 처리를 계속한다.
조건부 요청을 eTag
값, lastModified
값 또는 둘 모두에 대해 확인하기 위해 3개의 변형이 있다. 조건부 GET
과 HEAD
요청인 경우, 응답을 304 (NOT_MODIFIED)에 설정할 수 있다. 조건부 POST
, PUT
, DELETE
의 경우 대신 응답을 412 (PRECONDITION_FAILED)로 설정하여 동시 변경을 방지 할 수 있다.
1.10.3 정적 리소스
최적의 성능을 얻으려면, Cache-Control
와 조건부 응답 헤더에서 정적 리소스를 제공해야 한다. 정적 리소스 구성 섹션을 참조해라.