웹 프레임워크마다 사용하는 언어도 다르고, 내부 동작 방식도 다르지만, 정적 리소스 로딩 전략은 상당히 유사하다는 것을 알게 됐다. Nextcloud 같은 PHP 기반 시스템과 스프링 같은 Java 기반 프레임워크는 전혀 다른 기술 스택을 사용하지만, 정적 리소스를 서빙하는 방식에서 비슷한 접근법을 사용한다.

 왜 그럴까? 단순히 성능 때문일까? 아니면 정적 리소스를 다루는 데 있어 자연스럽게 공통된 패턴이 형성되는 걸까? 스프링의 정적 리소스 관리 방식이 어떻게 설계되어 있는지 간단히 살펴보고, Nextcloud 같은 사례를 통해 이 전략이 어떻게 일반화될 수 있는지 확인해 보려한다.

 

 

 

 스프링에서의 정적 리소스 관리 

 

 웹 애플리케이션을 개발할 때 정적 리소스를 어떻게 제공할 것인가는 중요한 고려사항이다. 스프링에서는 기본적으로 spring.web.resources.static-locations 속성을 통해 정적 리소스의 위치를 지정할 수 있다.

일반적인 설정 예시는 다음과 같다:

spring.web.resources.static-locations=classpath:/static/,classpath:/public/

이렇게 설정하면 /static/ 또는 /public/ 디렉터리에 있는 정적 리소스(예: CSS, JS, 이미지 파일 등)가 요청될 때, 별도의 컨트롤러를 거치지 않고 직접 제공된다.

 

정적 리소스 매핑 방식

 

스프링의 정적 리소스 매핑 방식은 크게 세 가지로 나뉜다:

1. 클래스패스 리소스 제공: classpath:/static/ 등의 경로에서 파일을 로딩하여 제공.
2. 외부 디렉터리 매핑: file:/opt/static/ 같은 설정을 통해 외부 경로에서 로딩.
3. 리소스 핸들러 설정: WebMvcConfigurer를 통해 직접 매핑.

 

 

예를 들어, WebMvcConfigurer를 활용해 정적 리소스 매핑을 추가하면 다음과 같다.

 

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/resources/**")
                .addResourceLocations("classpath:/static/", "file:/opt/static/");
    }
}

 

 

 

 

 

 스프링에서 이러한 정적 리소스 전략을 채택한 이유 

 

스프링이 이런 방식으로 정적 리소스를 제공하는 데에는 몇 가지 이유가 있다.

 

성능과 확장성

스프링은 정적 리소스를 서블릿 컨테이너(Tomcat 등)에서 직접 제공하도록 설정할 수 있다. 이런 방식은 별도의 비즈니스 로직을 처리하는 컨트롤러를 거치지 않기 때문에 성능에 유리하다.


또한, CDN과 연계하여 정적 리소스를 외부에 위임할 수도 있다. 이를 위해 WebMvcConfiguerer 설정을 활용하면 캐시 기간을 지정할 수도 있다. 이 때는 앱에서 알맞은 캐시 정책을 지정해야한다.

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/resources/**")
                .addResourceLocations("/public", "classpath:/static/")
                .setCacheControl(CacheControl.maxAge(1, TimeUnit.HOURS));
    }
}

 

 

참고) HTTP Caching

https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching

 

보안 및 접근 제어

정적 리소스는 대부분 공개되어야 하지만, 특정 리소스는 인증된 사용자에게만 제공되어야 할 수도 있다. 스프링 시큐리티와 연계하면 이러한 접근 제어가 가능하다.

예를 들어, /admin/ 경로의 리소스는 관리자인 경우에만 접근할 수 있도록 설정할 수 있다.

http.authorizeRequests()
    .antMatchers("/admin/**").hasRole("ADMIN")
    .anyRequest().permitAll();

 

 

 

 다른 프레임워크 사례를 통한 정적 리소스 로딩 전략 (Nextcloud) 

Nextcloud는 PHP 기반의 오픈소스 스토리지 솔루션이지만, 정적 리소스 로딩 방식에서 스프링과 유사한 전략을 채택하고 있다.

 

Nextcloud의 정적 리소스 관리 방식

Nextcloud는 정적 파일을 core, apps, themes 등의 디렉터리에 분산하여 관리한다. 예를 들어, CSS 및 JavaScript 파일은 /core/js/, /core/css/ 등에 위치하며, 애플리케이션별 리소스는 /apps/{app_name}/ 아래에 저장된다.

이를 통해 정적 리소스를 서빙할 때 PHP를 거치지 않고 직접 웹서버(Apache, Nginx)에서 제공할 수 있도록 설정한다.

예를 들어, Nextcloud의 Nginx 설정을 보면 다음과 같이 정적 리소스를 직접 제공하는 방식이 사용된다.

location ~* \.(?:css|js|woff2?|svg|gif|png|jpg|jpeg|ico)$ {
    expires 6M;
    access_log off;
}

 

이는 스프링의 ResourceHandlerRegistry 설정과 유사한 개념으로, 정적 리소스를 별도의 서버에서 캐싱하여 성능을 최적화하는 전략이다.

 

 

Nextcloud의 캐싱 전략

 

Nextcloud는 정적 리소스의 캐싱을 활용하여 로딩 속도를 최적화한다.
- etag를 이용한 변경 감지
- Last-Modified 헤더를 활용한 브라우저 캐싱
- Content-Encoding: gzip 압축을 통한 최적화

 

이러한 전략은 스프링에서도 동일하게 적용할 수 있다. 예를 들어, spring.web.resources.chain.strategy.content.enabled=true 설정을 적용하면 정적 리소스의 해시 기반 캐싱을 활성화할 수 있다.

spring.web.resources.chain.strategy.content.enabled=true
spring.web.resources.chain.strategy.content.paths=/**

 

 

 

단순한 정적 파일도 결국은 아키텍처의 일부

 

 처음 스프링에서 정적 리소스를 다룰 때는 단순히 /static/에 넣으면 알아서 제공되겠지 싶었다. 그런데 프로젝트가 커지고, 특정 리소스는 인증이 필요하거나, 특정 경로에 따라 다르게 서빙해야 하는 요구사항이 생기면서 "어떤 방식으로 정적 리소스를 관리할 것인가?"라는 질문이 중요해졌다.

 Nextcloud 같은 사례를 보면, 이런 고민은 단순히 스프링만의 문제가 아니라 웹 애플리케이션 전반에서 공통적으로 나타나는 흐름이라는 걸 알 수 있다. Nextcloud도 정적 리소스를 별도의 경로에서 관리하고, PHP를 거치지 않고 직접 제공하는 방식으로 전략을 세웠다. 결국, 중요한 건 정적 리소스를 코드와 분리하고, 필요한 경우 접근 제어 및 정책을 유연하게 조정할 수 있도록 구조를 잡는 것이다.

스프링에서 기본 제공하는 ResourceHandlerRegistry 같은 기능이 단순해 보이지만, 실제 운영 환경에서는 이를 기반으로 "우리 서비스에서 정적 리소스가 어떻게 관리되어야 하는가?"를 고민하는 게 핵심이다. Nextcloud처럼 특정 리소스를 개별 앱 단위로 나누는 것도 방법이고, 특정 요청 패턴에 따라 다른 전략을 적용할 수도 있다. 중요한 건 "정적 리소스를 다룰 때 한 가지 정답은 없으며, 우리가 만든 서비스의 특성에 맞게 전략을 세워야 한다"는 점이다.

반응형
  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 카카오스토리 공유하기