# 필터가 무엇을 붙이고, 그것이 되돌려질 수 있다고 무엇이 말하는가 /** * Applies the skeleton's default HTTP cache policy: every response gets {@code Cache-Control: * no-store} and a {@code Vary: Accept, Accept-Encoding, Authorization} header. Defaults are set * before the chain so a cacheable endpoint can opt in by returning a {@code ResponseEntity} whose * {@code Cache-Control} header overwrites the default. See README for the design rationale. */ @Component @Order(Ordered.HIGHEST_PRECEDENCE + 20) public class CacheControlFilter extends OncePerRequestFilter { static final String DEFAULT_CACHE_CONTROL = "no-store"; static final String DEFAULT_VARY = "Accept, Accept-Encoding, Authorization"; @Override protected void doFilterInternal( HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { response.setHeader(ApiHeaders.CACHE_CONTROL, DEFAULT_CACHE_CONTROL); response.setHeader(ApiHeaders.VARY, DEFAULT_VARY); chain.doFilter(request, response); } # 그 정책을 이 필터가 혼자 갖도록 보안 설정이 프레임워크 기본값을 끈다 http.cors(c -> c.configurationSource(corsConfigurationSource())) // Disable Spring Security's default Cache-Control writer; CacheControlFilter // owns the cache header policy. See README for the design rationale. .headers(headers -> headers.cacheControl(cache -> cache.disable())) 다만 픽스처 모듈은 그 설정을 스캔에서 뺀다 : 38: SecurityConfig.class, 39: MethodSecurityConfig.class 그 모듈이 적어 둔 제외 이유 : // The standalone sample is a public demo app: URL access should not require a // configured IdP or method-security principal. Production modules keep these guards. 그 모듈이 대신 두는 체인. 캐시 기록기를 끄지 않는다 : http.securityMatcher("/**") .csrf(csrf -> csrf.disable()) .sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth -> auth.anyRequest().permitAll()); return http.build(); } # 조건부 읽기를 구현한 자리 전부와 그것을 켜는 조건 main 에서 응답에 개체 태그를 쓰는 파일 (아웃바운드 S3 클라이언트는 뺐다) : WorkLogController.java WebSuccessResponse.java WebResponseContract.java MvcDownloadResponseWriter.java ReactiveDownloadResponseWriter.java 이 파일들과 304 판정 클래스를, 시험을 빼고 부르는 자리가 있는지 : WebSuccessResponse 를 부르는 자리 : WebResponseContract.java (끝) WebResponseContract 를 부르는 자리 : WebSuccessResponse.java (끝) ConditionalReadEvaluator 를 부르는 자리 : (끝) 같은 패키지의 ETags 를 실제로 호출하는 자리 : WorkLogController.java (끝) 레지스트리가 선언한 런타임 컴포지션과 이 리프의 소속 : "runtime_compositions": [ "app-bootstrap", "sample-portfolio" ], "modules": [ "id": "adapter-inbound-web", "runtime_memberships": [ "app-bootstrap", "sample-portfolio" ] 304 판정을 구현해 둔 클래스와 그 자바독 첫 줄 : /** * Decides whether a {@code GET} or {@code HEAD} may be answered with a 304. 내려받기 컨트롤러를 켜는 조건과 그 스위치의 출하값 : 24:import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 41:@ConditionalOnProperty(prefix = "app.fileserver-platform", name = "enabled", havingValue = "true") 854: enabled: ${APP_FILESERVER_PLATFORM_ENABLED:false} .env:161:APP_FILESERVER_PLATFORM_ENABLED=false .env.example:240:APP_FILESERVER_PLATFORM_ENABLED=false # 내려받기 경로가 스스로 쓰는 태그와 지시자 37: response.setHeader(HttpHeaders.ETAG, descriptor.representation().strongEtag()); 43: response.setHeader(HttpHeaders.CACHE_CONTROL, descriptor.cacheControl()); 같은 문자열을 자바 밖에서 다시 쓰는 자리 : .env:181:APP_FILESERVER_PLATFORM_DOWNLOAD_CACHE_CONTROL=private, no-store app-bootstrap/src/main/resources/application.yml:880: cache-control: ${APP_FILESERVER_PLATFORM_DOWNLOAD_CACHE_CONTROL:private, no-store} .env.example:255:APP_FILESERVER_PLATFORM_DOWNLOAD_CACHE_CONTROL=private, no-store DownloadPolicy.java:23: /** Design default: single Range, {@code private, no-store}, attachment only. */ DownloadPolicy.java:25: return new DownloadPolicy(RangeBudget.unbounded(), "private, no-store", false); FileserverPlatformSettings.java:127: @DefaultValue("private, no-store") String cacheControl, # 픽스처 모듈 쪽 조건부 읽기 경로 /** Emits an {@code ETag}; honours {@code If-None-Match} with a 304 (no body). */ @GetMapping("/worklogs/{id}") public ResponseEntity getOne( @PathVariable String id, @RequestHeader(value = ApiHeaders.IF_NONE_MATCH, required = false) String ifNoneMatch) { WorkLog workLog = get.handle(new GetWorkLogQuery(toId(id))); String etag = etagOf(workLog); if (ETags.matches(ifNoneMatch, etag)) { return ResponseEntity.status(HttpStatus.NOT_MODIFIED).eTag(etag).build(); } return ResponseEntity.ok().eTag(etag).body(WorkLogWebMapper.toResponse(workLog)); } 이 모듈 main 에서 Cache-Control 을 스스로 정하는 자리 : (0) # 둘을 조정하라고 만들어 둔 패키지 28줄 WebCacheHeaders.java 72줄 WebCachePolicy.java 120줄 WebCachePolicyCatalog.java 90줄 WebVaryPolicy.java 그 타입들을 이름으로 부르는 파일과 횟수 : 2 main · WebCacheHeaders.java 5 main · WebCachePolicy.java 23 main · WebCachePolicyCatalog.java 6 main · WebVaryPolicy.java 22 test · WebCachePolicyTest.java cache 패키지 밖에서 부르는 파일 : (0) # 그 패키지를 그렇게 둔 근거 /** * The published cache profiles, their directives and the {@code Vary} rule. * *

CORE: a caching decision is a set of directives, not a framework call. Keeping it free of * Spring is what lets the same profile be applied by the servlet writer, the reactive writer and * the OpenAPI document without three renderings of it. */ CACHE("cache", "cache", WebModulePurity.CORE), # 그 근거가 말한 적용자들이 실제로 무엇을 쓰는가 main 에서 Cache-Control 값을 정하는 자리 (헤더 이름 상수 두 파일은 뺐다) : MvcDownloadResponseWriter.java:43 response.setHeader(HttpHeaders.CACHE_CONTROL, descriptor.cacheControl()); ReactiveDownloadResponseWriter.java:36 headers.set(HttpHeaders.CACHE_CONTROL, descriptor.cacheControl()); TusController.java:138 .header(HttpHeaders.CACHE_CONTROL, "no-store"); CacheControlFilter.java:24 static final String DEFAULT_CACHE_CONTROL = "no-store"; CacheControlFilter.java:31 response.setHeader(ApiHeaders.CACHE_CONTROL, DEFAULT_CACHE_CONTROL); 뺀 두 파일 : ApiHeaders.java WebHeaderName.java 연산 프로파일이 이름으로 들고 있는 캐시 정책과 그것을 읽는 자리 : 36: CachePolicyName cachePolicy, 95: CachePolicyName.standard(), 110: CachePolicyName.standard(), cachePolicy() 를 읽는 자리 : (0) 지시자 문자열을 직접 들고 있는 main 자리 : DownloadPolicy.java:25 return new DownloadPolicy(RangeBudget.unbounded(), "private, no-store", false); FileserverPlatformSettings.java:127 @DefaultValue("private, no-store") String cacheControl, TusController.java:138 .header(HttpHeaders.CACHE_CONTROL, "no-store"); CacheControlFilter.java:24 static final String DEFAULT_CACHE_CONTROL = "no-store"; WebCachePolicy.java:41 if (cacheControl.contains("no-store") && freshness.isPresent()) { WebCachePolicy.java:43 "no-store and a freshness lifetime contradict each other: one says never keep this, the" WebCachePolicyCatalog.java:36 "sensitive", "private, no-store", WebVaryPolicy.none(), false, Optional.empty()); WebCachePolicyCatalog.java:49 "public, max-age=31536000, immutable", WebCachePolicyCatalog.java:65 "public, max-age=0, must-revalidate", WebCachePolicyCatalog.java:80 "private, max-age=30", CachePolicyName.java:28 return new CachePolicyName("no-store"); GraphQlHttpGetCachePolicy.java:13 NO_STORE("no-store"), GraphQlHttpGetCachePolicy.java:16 PRIVATE("private, max-age=0"), GraphQlHttpGetCachePolicy.java:21 SHARED("public, max-age=60"); 목록이 발행하는 프로파일 이름과 그 지시자 : 36: "sensitive", "private, no-store", WebVaryPolicy.none(), false, Optional.empty()); 48: "immutable-asset", 64: "revalidated", 79: "browser-private",