[Web on Reactive Stack] 1. 스프링 웹플럭스: 1.10. HTTP 캐싱

한글로 번역한 Web on Reactive Stack, 1. Spring Webflux: 1.10. HTTP Caching

#spring #webflux #reactive


1.10. HTTP 캐싱(Caching)

HTTP 캐싱은 웹 애플리케이션의 성능을 크게 향상시킬 수 있다. HTTP 캐싱은 Cache-Control 응답 헤더와 Last-ModifiedETag와 같은 조건부 요청 헤더를 중심으로 동작한다. Cache-Control는 클라이언트 캐시(private cache, 예를 들어 브라우저)와 공유 캐시(public cache, 예를 들어 프록시)에 응답을 캐시하고 재사용할지 방법을 정의한다. ETag 헤더는 내용이 변경되지 않은 경우 body 없이 304(NOT_MODIFIED) 응답을 내보낼 때 사용된다. ETagLast-Modified 헤더의 보다 정교한 후속 버전으로 볼 수 있다.

이 섹션은 스프링 웹플럭스에서 사용 가능한 HTTP 캐싱 관련 옵션에 대해 설명한다.


1.10.1. CacheControl

CacheControlCache-Control 헤더와 관련된 설정을 지원하며, 다양한 곳에서 사용할 수 있다.

  • 컨트롤러(Controllers)
  • 정적 자원(Static Resources)

RFC 7234Cache-Control 응답 헤더의 모든 지시문(directive)을 설명하지만, CacheControl 타입은 아래 예제와 같이 일반적인 시나리오에 중점을 둔, 사용 사례 지향적인(use case-oriented) 접근 방식을 취한다.

[역주] 자주 사용되는 케이스별로 미리 코드를 정의해둘 수 있다.

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. Controllers

컨트롤러는 HTTP 캐시를 명시할 수 있다. 요청 헤더와 비교하기 전에 리소스의 lastModified 또는 ETag 값을 계산해야 하므로, 보통은 이 방법을 권장한다. 컨트롤러는 아래 예제와 같이 ETagCache-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) 응답을 전송한다. 아닌 경우는 ETagCache-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) 요청 처리를 계속한다.

요청에 대한 최신 여부를 확인하기 위해 eTaglastModified 또는 둘 다 사용할 수 있다. 조건부 GET 요청과 HEAD 요청은 응답을 304(NOT_MODIFIED)로 설정할 수 있다. 조건부 POST, PUT 그리고 DELETE의 경우 동시 수정을 막기 위해 412(RECONNDITION_FAILED)로 설정한다.


1.10.3. 정적 자원(Static Resources)

정적 자원도 Cache-Control과 조건부 응답 헤더를 사용하여 최적의 성능을 낼 수 있다. 설정 방법은 Static Resources를 참고하라.


목차 가이드