📌0. 서블릿 컨테이너 기본 동작
자원이 프로젝트 내부 /resources 또는 /static 밑에 있을 때
✅ 0-1. Spring MVC (레거시 설정, xml 기반)
- DispatcherServlet을 /에 매핑하면 모든 요청이 DispatcherServlet으로 감.
- 정적 리소스도 DispatcherServlet을 거치게 됨 → 그래서 Spring이 ResourceHttpRequestHandler를 통해 다시 정적 리소스 처리를 해줘야 함.
✅ 0-2. Spring Boot 동작 원리
스프링부트는 자동으로 ResourceHandlerRegistry를 등록해서, 별도 mvc:resources를 안 써도 정적 리소스 핸들러가 붙음.
- 기본 매핑 경로:
- classpath:/static/
- classpath:/public/
- classpath:/resources/
- classpath:/META-INF/resources/
- 그래서 static/images/test.png 같은 파일을 두면 http://localhost:8080/images/test.png 로 바로 접근 가능.
📌 1. 자원이 프로젝트 외부에 있을 때
- 톰캣 같은 컨테이너는 기본적으로 / 요청을 DefaultServlet이 처리 → 정적 파일(css, js, 이미지 등)을 찾아서 반환.
- 그런데 내 프로젝트에서는 썸네일(/thumb-images/**)이 웹앱 내부의 정적 리소스가 아니라, 외부 경로(file:/appl/upfiles/thumbnails/) 에 있음.
- DefaultServlet은 webapp/ 경로밖의 파일은 몰라서 못 서빙함 → 그대로 404.
📌 1-1. Spring MVC 설정
정적 리소스를 사용하려면 반드시 설정해야 할 파일
✅ 1-1-1. web.xml 설정 (DispatcherServlet 매핑)
이 설정으로 인해 /thumb-images/** 같은 정적 요청도 DispatcherServlet을 거치게 됨
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>appServlet</servlet-name>
<url-pattern>/</url-pattern> <!-- 모든 요청을 Spring으로 전달 -->
</servlet-mapping>
✅ 1-1-2. servlet-context.xml (또는 dispatcher-servlet.xml) 설정
DispatcherServlet이 들어온 요청을 HandlerMapping → mvc:resources 설정에 따라 ResourceHttpRequestHandler로 연결.
DispatcherServlet이 요청을 받고, ResourceHttpRequestHandler가 외부 경로를 찾아 반환
<!-- (1) DispatcherServlet이 못 찾는 정적 리소스를 서블릿 컨테이너에게 위임 -->
<mvc:default-servlet-handler />
<!-- (2) 정적 리소스 매핑 -->
<mvc:resources mapping="/thumb-images/**" location="file:/appl/upfiles/thumbnails/" />
<!-- 로컬 개발 시 -->
<!-- <mvc:resources mapping="/thumb-images/**" location="file:/D:/appl/upfiles/thumbnails/" /> -->
<!-- (3) 기타 static 리소스 -->
<mvc:resources mapping="/resources/**" location="/resources/" />
- file: 접두사는 절대경로 기반을 의미
- classpath:는 WAR 내부 리소스를 가리킴 (자주 쓰는 /static/, /public/ 등 포함)
📌 1-2. Spring Boot 설정
/resource 경로에 있는 건 설정 x, 외부 경로는 설정 필요
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/thumb-images/**")
.addResourceLocations("file:/appl/upfiles/thumbnails/");
}
}
📌 2. 리소스 경로 설정 (Window / Linux)
| 환경 | 설정 예시 (servlet-context.xml) |
| 로컬(윈도우) | file:/D:/appl/upfiles/thumbnails/ |
| 운영(리눅스) | file:/appl/upfiles/thumbnails/ |
- D:/ 드라이브는 리눅스에서는 인식되지 않음
- 반드시 슬래시 / 로 끝나는 경로로 지정해야 정상 동작
📌 3. 테스트 방법 및 확인 포인트
1️⃣ 브라우저에서 정적 파일 요청
GET /thumb-images/thumb_20250714131822380.jpg
2️⃣ 로그 확인: DispatcherServlet → ResourceHttpRequestHandler
Mapped to ResourceHttpRequestHandler [URL [file:/appl/upfiles/thumbnails/]]
3️⃣ 정상 응답이면 200 OK
❌ 404 에러면 아래 항목들 확인
📌 4. 404에러 (에러 로그 → 원인 추적 → 해결)
사실 이게 글 쓴 이유다.
🔍 현상: 이미지 404, 정적 리소스 응답 안됨
🔹 로그 분석
Mapped to ResourceHttpRequestHandler [URL [file:/D:/appl/upfiles/thumbnails/]]
Resource not found
-- 전체 로그
15:56:50.455 [DEBUG] --- [https-jsse-nio-443-exec-1] o.s.w.s.h.SimpleUrlHandlerMapping.getHandler:523: Mapped to ResourceHttpRequestHandler [URL [file:/D:/appl/upfiles/thumbnails/]] 15:56:50.455 [DEBUG] --- [https-jsse-nio-443-exec-1] o.s.w.s.r.ResourceHttpRequestHandler.handleRequest:560: Resource not found
🔹 원인 확인 명령어 (리눅스 서버)
# 경로 존재 여부 확인
ls -l /appl/upfiles/thumbnails/thumb_20250626145206541.079.jpg
# 경로 권한 확인
ls -ld /appl/upfiles/thumbnails
ls -l /appl/upfiles/thumbnails
# SELinux context 확인
ls -Z /appl/upfiles/thumbnails
🔹 확인된 문제
나의 경우에는 파일 업로드 시 사용하는 경로 변수가 아래와 같게 설정되어 있어서 D디렉토리 경로를 포함시킨게 문제였다. (로컬은 윈도우, 서버는 로키서버 였음)
<entry key="file.thumbnail.path">D:/appl/upfiles/thumbnails</entry>
[root@ebiz logs]# ls -l D:/appl/upfiles/thumbnails/thumb_20250626145206541.079.jpg
ls: cannot access 'D:/appl/upfiles/thumbnails/thumb_20250626145206541.079.jpg': No such file or directory
[root@ebiz logs]# ls -l /appl/upfiles/thumbnails/thumb_20250626145206541.079.jpg
-rw-r--r--. 1 root root 151195 6월 26 14:52 /appl/upfiles/thumbnails/thumb_20250626145206541.079.jpg
| 항목 | 결과 |
| 요청 URL | /thumb-images/thumb_20250626145206541.079.jpg |
| 로그상 매핑 | file:/D:/appl/upfiles/thumbnails/ (리눅스에서 X) |
| 리눅스 실제 경로 | /appl/upfiles/thumbnails |
| 파일 권한 | -rw-r--r-- (정상) |
| 디렉토리 권한 | drwxr-xr-x (정상) |
| SELinux context | default_t (비정상) |
🔧 해결 방법
나는 1번만으로 해결 됐는데 404에러 발생시 아래 2, 3 방법도 체크 할 것!!
1️⃣ 경로 수정
<!-- 잘못된 경로 -->
<mvc:resources mapping="/thumb-images/**" location="file:/D:/appl/upfiles/thumbnails/" />
<!-- 리눅스에서의 정상 설정 -->
<mvc:resources mapping="/thumb-images/**" location="file:/appl/upfiles/thumbnails/" />
2️⃣ SELinux context 수정
sudo semanage fcontext -a -t httpd_sys_content_t "/appl/upfiles/thumbnails(/.*)?"
sudo restorecon -Rv /appl/upfiles/thumbnails
3️⃣ 접근권한 설정
chmod 644 /appl/upfiles/thumbnails/*
chmod 755 /appl/upfiles/thumbnails
🚀 마무리 체크리스트
| 항목 | 확인 |
| DispatcherServlet이 모든 요청을 수신하는지 | ✅ web.xml 확인 |
| <mvc:resources> 설정이 올바른지 | ✅ file:/경로/ |
| 리눅스의 경로 사용이 맞는지 | ✅ D:/ ❌ |
| 디렉토리와 파일 권한 설정 | ✅ chmod, ls -l |
| SELinux가 차단하고 있지 않은지 | ✅ ls -Z, semanage |
'Backend > spring (Boot)' 카테고리의 다른 글
| Connection Pool과 Size 선정 기준 (with HikariCP) (0) | 2025.09.01 |
|---|---|
| [Spring, JSP] session 생성 차단 (0) | 2025.07.26 |
| Spring boot 예외 처리: @ControllerAdvice, @ExceptionHandler, ResponseStatusException (0) | 2025.07.26 |
| [error] java.lang.IllegalArgumentException: Name for argument of type [int] not specified (0) | 2025.02.23 |
| [error] getOutputStream() has already been called for this response (0) | 2025.02.23 |