π€ HTTP Cache
μΊμ±μ ν λ² λ°μμ¨ λ°μ΄ν°λ₯Ό μ μ₯νκ³ μλ€κ° μ΄ν ν΄λΉ λ°μ΄ν°κ° νμν κ²½μ° μ¬νμ©νλ λ°©λ²μ μλ―Έν©λλ€.
μΉ μ¬μ΄νΈμ κ²½μ° ν΄λΌμ΄μΈνΈ(λΈλΌμ°μ λ±)κ° μΉ μλ²μ μμ²μ 보λ΄λ©΄, μΉ μλ²μμ HTML, CSS, JS νμΌ λ±μ λ°ννλ μμΌλ‘ λμν©λλ€.
μΉ μ¬μ΄νΈμ μ λ°μ΄νΈλ λμ²΄λ‘ μμ£Ό λ°μνμ§ μμΌλ―λ‘ νλ² λ°μμ¨ HTML, CSS, JSκ°μ μ μ νμΌλ€μ μΊμ±ν΄λ λ€ μ¬μ¬μ©νλ€λ©΄, μ΄ν μ€λ«λμ λ€νΈμν¬λ₯Ό ν΅ν λ°μ΄ν° μ μ‘μ΄ μ¬λΌμ§μΌλ‘μ¨ λΉ λ₯Έ μλ΅μ μ¬μ©μμκ² μ 곡ν μ μμ΅λλ€.
κ·Έλ¬λ κ²°κ΅ μΈμ κ°λ μ΄λ¬ν 리μμ€κ° λ³κ²½λ μ μμΌλ―λ‘, 리μμ€κ° λ³νκΈ° μ κΉμ§λ§ μΊμ±νκ³ λ³ν μ΄νμλ λμ΄μ μΊμ±νμ§ μλ κ²μ΄ μ€μν©λλ€.
μ΄λ² κΈμμλ μ΄λ¬ν μΊμ± λ°©λ²κ³Ό, 리μμ€μ μ λ°μ΄νΈλ₯Ό μ²λ¦¬νλ λ°©λ²μ λν΄ μμλ³΄κ² μ΅λλ€.
π€ HTTP Cacheμ μ’ λ₯
RFC9111μ λ°λ₯΄λ©΄, HTTP Cacheλ ν¬κ² Shared Cacheμ Private Cacheλ‘ κ΅¬λΆν μ μμ΅λλ€.
π³ Private Cache
Private Cacheλ νΉμ ν ν΄λΌμ΄μΈνΈ(μΌλ°μ μΌλ‘ λΈλΌμ°μ )μ μ μ₯λλ μΊμμ λλ€.
μ΄κ³³μ μ μ₯λ 리μμ€λ€μ λ€λ₯Έ ν΄λΌμ΄μΈνΈ(λΈλΌμ°μ )μ 곡μ λμ§ μμ΅λλ€.
Private Cacheλ₯Ό μ¬μ©νκΈ° μν΄μλ μλ΅ ν΄λμ λ€μμ μ§μ ν΄μΌ ν©λλ€.
Cache-Control: private
π³ Shared Cache
Shared Cacheλ ν΄λΌμ΄μΈνΈμ μλ² μ¬μ΄μ μμΉνλ©°, μ¬λ¬ μ¬μ©μ κ°μ 곡μ ν μ μλ μλ΅μ μ μ₯ν μ μμ΅λλ€.
μ΄λ λ€μ Proxy Cacheμ Managed Cacheλ‘ λ μΈλΆνν μ μμ΅λλ€.
π³ Proxy Cache
(ν¬μλ) νλ‘μμ μ μ₯λλ μΊμλ₯Ό μλ―Έν©λλ€.
μ΄λ μΌλ°μ μΌλ‘ μλΉμ€ κ°λ°μκ° κ΄λ¦¬νμ§ μμΌλ―λ‘ μ μ ν HTTP ν€λ λ±μ ν΅ν΄ μ μ΄ν΄μΌ ν©λλ€.
π³ Managed Cache
리λ²μ€ νλ‘μ, CDNλ±μ μ μ₯λλ μΊμμ λλ€.
μ΄λ μλΉμ€ κ°λ°μκ° μΊμλ₯Ό μ§μ κ΄λ¦¬ν μ μλ€λ κ²μ΄ νΉμ§μ λλ€.
π€ Heuristic Caching
HTTPλ μΊμνλ κ²μ κΆμ₯νκ³ , λ κ·Έλ΄ μ μλλ‘ μ€κ³λμκΈ° λλ¬Έμ μΊμ μ μ΄λ₯Ό μ§μ νμ§ μλλΌλ νΉμ μ‘°κ±΄μ΄ μΆ©μ‘±λλ©΄ μλμΌλ‘ μΊμλ₯Ό μ¬μ©ν©λλ€.
μ΄λ₯Ό Heuristic Cachingμ΄λΌκ³ ν©λλ€.
κ·Έλ¬λ μ΄λ μΊμ λμμ μλνμ§ μμ κ²½μ°μλ μΊμνκΈ° λλ¬Έμ λ¬Έμ κ° λ°μν μ μμ΅λλ€.
μ΄λ¬ν λ¬Έμ λ₯Ό ν΄κ²°νκΈ° μν΄ Cache-Control ν€λμ no-cacheλ₯Ό λͺ μνμ¬ Heuristic Cachingμ μ κ±°ν μ μμ΅λλ€.
Cache-Control: no-cache
π€ HTTP μΊμλ₯Ό λ§λ£μν€λ λ°©λ²
HTTP μΊμλ₯Ό λ§λ£μν€κΈ° μν λ°©λ²μλ λ€μκ³Ό κ°μ κ²λ€μ΄ μ‘΄μ¬ν©λλ€.
- μ ν¨ κΈ°κ° μ€μ
- μ‘°κ±΄λΆ μμ²μ ν΅ν κ²μ¦
- κ°μ μ¬κ²μ¬
μ΄λ€μ λν΄ νλνλ μμ보λλ‘ νκ² μ΅λλ€.
π€ μ ν¨ κΈ°κ° μ€μ (Cache-Control: max-ageμ Expires)
HTTP/1.0μμλ Expires ν€λλ₯Ό ν΅ν΄ μΊμμ μ ν¨ κΈ°κ°μ μ€μ νμ΅λλ€.
Expires ν€λλ λͺ
μμ μΈ μκ°μ μ¬μ©νμ¬ μΊμμ μλͺ
μ μ§μ νλλ°μ, μμλ λ€μκ³Ό κ°μ΅λλ€.
Expires: Tue, 28 Feb 2022 22:22:22 GMT
κ·Έλ¬λ μμ κ°μ΄ μκ°μ λͺ μνλ νμμ ꡬ문 λΆμμ΄ μ΄λ ΅κ³ λ²κ·Έκ° λ§μ΄ λ°κ²¬λλ λ± μ΄λ°μ λ° λ¬Έμ κ° λ§μλ€κ³ ν©λλ€.
μ΄λ₯Ό ν΄κ²°νκΈ° μν΄ HTTP/1.1μμ Cache-Controlμ max-ageλΌλ directiveκ° μκ²Όμ΅λλ€.
Cache-Control: max-age=3600
max-ageλ μμ κ°μ΄ μ¬μ©λ μ μμΌλ©°, μ΄ λ¨μλ‘ λ§λ£ κΈ°κ°μ μ€μ ν μ μμ΅λλ€.
μ μμμ κ²½μ° μΊμλ 3600μ΄, μ¦ 1μκ° λ€μ λ§λ£λλ μΊμμ λλ€.
π€ μ‘°κ±΄λΆ μμ²μ ν΅ν κ²μ¦ (If-Modified-Since, ETag/If-None-Match)
μμ λ°©λ²μΌλ‘ μ€μ ν μΊμκ° λ§λ£λμμ λ, μ€μ λ‘ μλ²μ λ°μ΄ν°κ° λ³νμ μλ μκ³ κ·Έλ μ§ μμ μλ μμ΅λλ€.
νμμ κ²½μ°, λ³κ²½λμ§ μμ λ°μ΄ν°λ₯Ό λ€μ λ΄λ €λ°λ κ²μ λΆνμν λ€νΈμν¬ νΈλν½μ λ°μμν€κ² λ©λλ€.
μ΄λ¬ν λ¬Έμ λ₯Ό λ°©μ§νλ €λ©΄ μΊμκ° λ§λ£λμλλΌλ μ€μ λ°μ΄ν°κ° λ³κ²½λμμ κ²½μ°μλ§ λ¦¬μμ€λ₯Ό λ΄λ €λ°λ λ°©λ²μ΄ νμν©λλ€.
μ΄λ₯Ό μν λ°©λ²μ μ ν¨μ± κ²μ¦(validation) νΉμ μ¬κ²μ¦(revalidation)μ΄λΌ λΆλ¦ λλ€.
μ ν¨μ± κ²μ¦μ If-Modifired-Since νΉμ If-None-Match ν€λκ° ν¬ν¨λ μ‘°κ±΄λΆ μμ²μ ν΅ν΄ μνλ©λλ€.
π³ If-Modified-Since
νλμ μμλ₯Ό μ΄ν΄λ³΄λ©° μμ보λλ‘ νκ² μ΅λλ€.
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 1024
Date: Tue, 22 Feb 2022 22:22:22 GMT
Last-Modified: Tue, 22 Feb 2022 22:00:00 GMT
Cache-Control: max-age=3600
<!doctype html>
…
μ μλ΅μ 보면 λ€μκ³Ό κ°μ μ 보λ₯Ό μ μ μμ΅λλ€.
- μλ΅μ 22:22:22μ μμ±λμλ€.
- μλ΅μΌλ‘ μ 곡ν λ°μ΄ν°μ λ§μ§λ§ μμ μΌμ 22:00:00 μ΄λ€.
- ν΄λΉ μλ΅μ 1μκ°λμ(23:22:22κΉμ§) μ ν¨νλ€.
μ΄μ μΊμκ° λ§λ£λλ μκ°μΈ 23:22:22κ° λμλ€κ³ κ°μ ν΄λ³΄κ² μ΅λλ€.
23:22:22μ΄ λλ©΄ μΊμλ λ§λ£λμ΄ μ¬μ¬μ©ν μ μμ΅λλ€.
κ·Έλ¬λ μΊμκ° λ§λ£λμλ€κ³ ν΄μ λ°μ΄ν°κ° λ°λ―μ΄ λ³κ²½λλ κ²μ μλκΈ° λλ¬Έμ, μ§μ λ μκ° μ΄νμ λ°μ΄ν°κ° λ³κ²½λ μ΄λ ₯μ΄ μλμ§ νμΈνκ³ , λ°μ΄ν°κ° λ³κ²½λ κ²½μ°μλ§ μλ‘μ΄ λ°μ΄ν°λ₯Ό λ°μμ€λ κ³Όμ μ΄ νμν©λλ€.
μλ μμ²μ ν΄λΌμ΄μΈνΈκ° If-Modified-Since ν€λλ₯Ό μ¬μ©νμ¬ μ§μ λ μκ° μ΄νμ λ³κ²½λ μ¬νμ΄ μλμ§ μλ²μ 묻λ μμ²μ λλ€.
GET /index.html HTTP/1.1
Host: example.com
Accept: text/html
If-Modified-Since: Tue, 22 Feb 2022 22:00:00 GMT
μ μμ²μ λ°μ μλ²λ 2κ°μ§ νλμ μ·¨ν μ μμ΅λλ€.
- λ°μ΄ν°κ° If-Modified-Since μ΄ν λ³κ²½λ κ²½μ°
- λ°μ΄ν°κ° If-Modified-Since μ΄ν λ³κ²½λμ§ μμ κ²½μ°
λ°μ΄ν°κ° λ³κ²½λ κ²½μ°λΌλ©΄ μλ‘μ΄ λ¦¬μμ€λ₯Ό ν¬ν¨νμ¬ μΌλ°μ μΈ μλ΅μ λ°νν©λλ€.
κ·Έλ¬λ λ°μ΄ν°κ° λ³κ²½λμ§ μμ κ²½μ°, μλ²μ μλ΅μ λ€μκ³Ό κ°μ΅λλ€.
HTTP/1.1 304 Not Modified
Content-Type: text/html
Date: Tue, 22 Feb 2022 23:22:22 GMT
Last-Modified: Tue, 22 Feb 2022 22:00:00 GMT
Cache-Control: max-age=3600
ν΄λΌμ΄μΈνΈλ μ μλ΅μ λ°μ μ΄ν, λ§λ£λ μΊμλ₯Ό λ€μ μλ‘μ΄ λ°μ΄ν°λΌ μ¬κΈ°κ³ 1μκ°λμ μ¬μ¬μ©ν μ μμ΅λλ€.
μ΄λ₯Ό ν΅ν΄ λΆνμν λ€νΈμν¬ νΈλν½μ΄ λ°μνλ λ¬Έμ λ ν΄κ²°λμμ΅λλ€.
κ·Έλ¬λ μλ²κ° λ°μ΄ν°μ μμ μκ°μ νλ¨νλ λΆλΆμμ λ¬Έμ κ° λ°μνκ² λ©λλ€.
μλ₯Ό λ€μ΄ νμΌμ μκ° νμμ΄ λ³΅μ‘νκ³ κ΅¬λ¬Έ λΆμμ΄ μ΄λ ΅κ±°λ, μλ² μμ²΄κ° λΆμ°λμ΄ νμΌ μ λ°μ΄νΈ μκ°μ λκΈ°ννλ λ° μ΄λ €μμ κ²ͺμ μ μμ΅λλ€.
μ΄λ¬ν λ¬Έμ λ₯Ό ν΄κ²°νκΈ° μν΄ νμΌμ μ λ°μ΄νΈ μκ°μ ν΅ν΄ νλ¨νλ κ²μ΄ μλλΌ μμμ λ¬Έμμ΄μ ν΅ν΄ νμΌμ μμ μ¬λΆλ₯Ό νλ¨νλ λ°©μμ΄ λ±μ₯νμμ΅λλ€.
μ΄κ²μ΄ λ°λ‘ ETagμ λλ€.
π³ ETag/If-None-Match
ETagμ κ°μ μλ²μμ μμλ‘ μμ±νλ κ°μ λλ€.
ETagμ κ°μ μμ±νλ λ°©λ²μλ μ νμ΄ μμΌλ―λ‘ μλ²λ μνλ μλ¨μ λ°λΌ μμ λ‘κ² κ°μ μ€μ ν μ μμ΅λλ€.
μλ₯Ό λ€μ΄ ETag ν€λμ ν΄μκ°μ μ¬μ©νκ³ index.html 리μμ€μ ν΄μκ°μ΄ 33a64df5μΈ κ²½μ° μλ΅μ λ€μκ³Ό κ°μ΅λλ€:
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 1024
Date: Tue, 22 Feb 2022 22:22:22 GMT
ETag: "33a64df5"
Cache-Control: max-age=3600
<!doctype html>
…
μ΄μ μΊμκ° λ§λ£λλ 1μκ° μ΄νμ ν΄λΌμ΄μΈνΈκ° μλ²μ λ€μ μμ²μ 보λ΄μΌ ν©λλ€.
μ΄λ ν΄λΌμ΄μΈνΈλ ETagμ κ°μ κ°μ Έμμ If-None-Match μμ² ν€λμ λ£μ΄ μλ²μ μμ²μ 보λ λλ€.
GET /index.html HTTP/1.1
Host: example.com
Accept: text/html
If-None-Match: "33a64df5"
μλ²λ μμ²λ 리μμ€μ λν΄ ETag ν€λμ κ°μ΄ μμ²μ If-None-Match κ°κ³Ό λμΌν κ²½μ° 304 Not Modifiedλ₯Ό λ°νν©λλ€.
κ·Έλ¬λ ETagμ κ°μ΄ μΌμΉνμ§ μλ κ²½μ° μλ²λ μ΅μ λ²μ μ 리μμ€λ₯Ό λ°νν©λλ€.
π€ κ°μ μ¬κ²μ¬(Force Revalidation)
μλ΅μ μΊμ±νλ€κ³ νλλΌλ, νμ μ¬μ©μμκ² μ΅μ μ μ 보λ₯Ό μ 곡νκΈ° μν΄ λ§€λ² λ°μ΄ν°μ λ³κ²½μ΄ μ‘΄μ¬νλμ§ νμΈνλλ‘ λ§λ€κ³ μΆμ μ μμ΅λλ€.
νΉμ μλ΅ μ체λ₯Ό μΊμ±νμ§ λͺ»νλλ‘ νμ¬, λ§€λ² μλ²λ‘λΆν° μ΅μ μ λ°μ΄ν°λ₯Ό λ°μμ€κ² λ§λ€ μλ μμ΅λλ€.
π³ no-cache
μ μμ κ²½μ° Cache-Control ν€λμ no-cache directiveλ₯Ό μ¬μ©νμ¬ μ ν¨μ± κ²μ¬λ₯Ό κ°μ ν μ μμ΅λλ€.
Cache-Control: no-cache
π‘ no-cacheλ μΊμνμ§ μμμ μλ―Ένλ κ²μ΄ μλ, μΊμλ μ§ννμ§λ§ λ§€λ² λ°μ΄ν°μ λ³κ²½μ κ²μ¬ν¨μ μλ―Έν©λλ€.
μλ²κ° no-cacheλ₯Ό μ¬μ©ν κ²½μ°, μλ΅μ λ€μκ³Ό κ°μ΅λλ€.
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 1024
Date: Tue, 22 Feb 2022 22:22:22 GMT
ETag: deadbeef
Cache-Control: no-cache
<!doctype html>
…
μ μλ΅μ λ°μΌλ©΄ ν΄λΌμ΄μΈνΈλ μΊμλ μννμ§λ§, λ§€λ² μλ²μ μμ²μ 보λ΄κ² λ©λλ€.
μλ²λ 리μμ€κ° μ λ°μ΄νΈλ κ²½μ° 200 OK μλ΅μ μ 곡νκ³ , κ·Έλ μ§ μμΌλ©΄ 304 Not Modified μλ΅μ μ 곡ν©λλ€.
π³ no-store
no-cacheλ μΊμλ₯Ό μννμ§λ§ λ§€λ² μμ²μ ν΅ν΄ λ°μ΄ν°μ μ λ°μ΄νΈλ₯Ό μ¬κ²μ¬νλλ‘ κ°μ νμμ΅λλ€.
μ΄μ λ¬λ¦¬ μΊμ μ체λ₯Ό μννμ§ λͺ»νλλ‘ κ°μ νκ³ μΆμ κ²½μ°κ° μμ κ²μ λλ€.
μ΄λ¬ν κ²½μ° no-store directiveλ₯Ό μ¬μ©ν μ μμ΅λλ€.
Cache-Control: no-store
π€ μ€νλ§μμ HTTP μΊμ μ¬μ©νκΈ°
π³ CacheControl
CacheControl ν΄λμ€λ Cache-Control ν€λμ κ΄λ ¨λ μ€μ ꡬμ±μ μ 곡νλ©°, λ€μκ³Ό κ°μ μμΉμμ μ¬μ©λ μ μμ΅λλ€.
- WebContentInterceptor
- ResponseEntity
- HttpServletResponse
- μ μ 리μμ€μ λν μΊμ±
- ShallowEtagHeaderFilter
π³ WebContentInterceptor
μ€νλ§μμλ μΌκ΄μ μΌλ‘ HTTP μΊμλ₯Ό μ μ©νκΈ° μν΄ WebContentInterceptor ν΄λμ€λ₯Ό λ§λ€μ΄ μ 곡ν©λλ€.
μλμ κ°μ΄ μ¬μ©ν μ μμ΅λλ€.
@Configuration
public class CacheWebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(final InterceptorRegistry registry) {
WebContentInterceptor webContentInterceptor = new WebContentInterceptor();
webContentInterceptor.addCacheMapping(
CacheControl.noCache().cachePrivate(),
"/**"
);
registry.addInterceptor(webContentInterceptor);
}
}
π³ ResponseEntity
@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)
.body(book);
}
μμ κ°μ΄ μ¬μ©ν μ μμ΅λλ€.
λν ETagμ μΌμΉ μ¬λΆμ λ°λΌ μλ΅μ λ€λ₯΄κ² νλ κ²λ κ°λ₯ν©λλ€.
@RequestMapping
public String myHandleMethod(WebRequest request, Model model) {
long eTag = ...
if (request.checkNotModified(eTag)) {
return null;
}
model.addAttribute(...);
return "myViewName";
}
π³ μ μ 리μμ€μ λν μΊμ±
WebMvcConfigurerμ addResourceHandlers() λ©μλλ₯Ό μ¬μ©νμ¬ μ μ 리μμ€μ λν μΊμ±μ νΈνκ² μ²λ¦¬ν μ μμ΅λλ€.
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**")
.addResourceLocations("/public", "classpath:/static/")
.setCacheControl(CacheControl.maxAge(Duration.ofDays(365)));
}
}
π³ ShallowEtagHeaderFilter
ETagλ₯Ό μμ±νκ³ , λΉκ΅ν΄μ£Όλ μν μ μννλ νν°μ λλ€.
μ΄λ μμ±νλ ETagλ μλ΅ contentμ λνμ¬ MD5 ν΄μλ₯Ό μ μ©νμ¬ μμ±λ©λλ€.
λ€μκ³Ό κ°μ΄ μ¬μ©νμ€ μ μμ΅λλ€.
@Configuration
public class EtagFilterConfiguration {
@Bean
public FilterRegistrationBean<ShallowEtagHeaderFilter> shallowEtagHeaderFilter() {
FilterRegistrationBean<ShallowEtagHeaderFilter> filterRegistrationBean
= new FilterRegistrationBean<>(new ShallowEtagHeaderFilter());
filterRegistrationBean.addUrlPatterns("/etag", PREFIX_STATIC_RESOURCES + "/*");
return filterRegistrationBean;
}
}
π Reference
https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching
https://httpwg.org/specs/rfc9111.html
https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-caching.html
https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-config/static-resources.html
https://www.baeldung.com/spring-mvc-cache-headers