본문 바로가기

Spring

WebMvcConfigurer addResourceHandlers 경로 설정

프로젝트의 성능 개선을 위해 정적 자원을 캐싱하려고 했다. css와 js에 다른 cache-control을 주고 싶었는데 이상하게 자꾸 404가 떴다. WebMvcConfigurer 구현체에서 addResourceHandlers 메서드 재정의를 간단히만 했어서 이 부분이 무지했었나보다 조금만 틀었는데도 헤맸다.

나와 같이 헤매는 사람이 혹시 있을까봐 글을 작성한다. (나만 모르는 것 같긴 하지만..)

기존에는 모든 정적 자원에 1년의 cache period를 줬었다.

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
    registry.addResourceHandler("resources/**")
        .addResourceLocations("classpath:/static/")
        .setCachePeriod(60 * 60 * 24 * 365);
}

GET /resources/js/index.js HTTP/1.1 과 같은request line이 오면 resources/static/js/index.js에 있는 파일을 잘 찾았었다.

그 다음 css 파일에는 cache max-age를 1년, js 파일에는 다른 no-cache, private으로 다른 cache-control을 하기 위해 작성한 코드는 다음과 같다.

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
    registry.addResourceHandler("resources/js/**")
        .addResourceLocations("classpath:/static/")
        .setCacheControl(CacheControl.noCache().cachePrivate());
    registry.addResourceHandler("resources/css/**")
        .addResourceLocations("classpath:/static/")
        .setCachePeriod(60 * 60 * 24 * 365);
}

원래 있던 코드 addResourceHandler 에 js/, css/만 붙이면 될 줄 알았다. addResourceHandler에서 지정한 경로와 addResourceLocations에서 지정한 경로는 영향을 안받는다고 생각했다. 기존에 있던 코드가 잘 됐으니 addResourceHandler 만 수정해주면 될 줄 알았다. 그런데 테스트가 다 실패했다.

왜 실패했냐면 GET /resources/js/index.js HTTP/1.1 와 같은 request line이 오면 registry.addResourceHandler("resources/js/**") 여기서는 걸렸을 것이다.

그다음 addResourceLocations("classpath:/static/") 메서드를 처리할 때 파일 경로가 resources/static/index.js 로 변환됐을 것이다. 원래 index.js가 있는 파일 경로는 resources/static/js/index.js 이다. 파일 경로에서 js/가 날라간 것이다.

결론은 addResourceLocations("classpath:/static/") 메서드에서 /static/ 뒤에 붙여지는 경로는 addResourceHandler("resources/js/") 메서드에서 정의한 / 에 해당되는 경로 부터 라는 것이다.

그러니까 /resources/js/index.js 요청에서 "resources/js/**" 로 걸러져서 index.js만 남고 index.js가 .addResourceLocations("classpath:/static/") 메서드를 통해 resources/static/ 뒤에 붙여진 것이다. -> resources/static/index.js

.addResourceLocations("classpath:/static/") 메서드에도 "js/"를 추가해주니 해결되었다. .addResourceLocations("classpath:/static/js")

결국엔 아래와 같은 코드로 테스트를 통과할 수 있었다.

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
    registry.addResourceHandler("resources/**")
        .addResourceLocations("classpath:/static/js")
        .setCacheControl(CacheControl.noCache().cachePrivate());
    registry.addResourceHandler(PREFIX_STATIC_RESOURCES + "/" + version.getVersion() + "/css/**")
        .addResourceLocations("classpath:/static/css/")
        .setCachePeriod(60 * 60 * 24 * 365);
}

위와 같은 상황을 경험하면서 추가로 공부한 내용을 정리해보자.

Spring PathPattern

/*, /** 등 Spring의 PathPattern에 대해서 자세히 몰랐는데 Spring 공식문서를 보면 예제가 자세히 나와있다.

example 부분만 정리해보자.

  • /pages/t?st.html — /pages/test.html 및 /pages/tXst.html 에 일치한다 그러나 /pages/toast.html 에는 일치하지 않는다.
  • /resources/*.png — resources 디렉토리의 모든 png 파일과 일치한다.
  • /resources/** — /resources/image.png 및 /resources/css/spring.css를 포함하여 / resources / 경로 아래의 모든 파일과 일치한다.
  • /resources/{*path} — / resources / 경로 아래의 모든 파일과 일치하고 "path"라는 변수에서 상대 경로를 캡처한다. /resources/image.png는 "path"→ "/image.png"와 일치하고 /resources/css/spring.css는 "path"→ "/css/spring.css"와 일치한다."
  • /resources/{filename:\w+}.dat - /resources/spring.dat와 일치하고 파일 이름 변수에 "spring"값을 한다.

SpringBoot Static Resource

Springboot에서 /index.html 과 같이 오는 요청을 어떻게 resources/static/index.html 파일과 매핑시켜서 응답할까?

// org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration 
private static final String\[] CLASSPATH_RESOURCE_LOCATIONS = {
            "classpath:/META-INF/resources/", "classpath:/resources/",
            "classpath:/static/", "classpath:/public/" };

위에서 보는 것과 같이 classpath 상에서 /META-INF/resources/, /resources, /static, /public 경로를 기본탐색한다.

/WEB-INF/resources 의 경우, jar 파일로 배포할 경우에는 인식하지 않기때문에 사용하지 않도록 주의한다.

정적 자원들은 /static 디렉토리 사용을 권장한다.
별도의 루트('/') 경로에 대한 설정이 되어 있지 않은 경우에는 Spring MVC auto-configuration 와 관련된 자동설정에 따라서 static index.html을 지원하게 된다.

Spring에서 모르는 부분이 산더미이다.😉 더 열심히 해야지.

'Spring' 카테고리의 다른 글

HttpMessageNotReadableException, LocalDateTime  (3) 2020.11.18
DDD에서는 왜 간접 참조를 더 권장할까?  (0) 2020.10.10
@Mock vs @MockBean  (0) 2020.08.14
MockMvc VS RestAssured  (0) 2020.08.14
Tacademy JPA 강의 정리  (1) 2020.07.01