# 필터가 무엇을 붙이고, 그것이 되돌려질 수 있다고 무엇이 말하는가
/**
 * 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<WorkLogResponse> 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.
   *
   * <p>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",
