๐Ÿ“‚ Spring static resources custom โ€“ uri๋กœ ํŒŒ์ผ ์‹œ์Šคํ…œ์— ์ ‘๊ทผํ•˜๊ธฐ

๊น€๊ณต์˜ยท2024๋…„ 8์›” 11์ผ

how-to

๋ชฉ๋ก ๋ณด๊ธฐ
9/12
post-thumbnail

์ด ๊ธ€์—์„œ๋Š” Spring์—์„œ ์›ํ•˜๋Š” ํด๋”์— http://ip:port/resources/{file_name} ๋“ฑ์˜ uri๋กœ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋„๋ก ์„ค์ •ํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด ๋‹ค๋ฃน๋‹ˆ๋‹ค.
๊ตฌํ˜„ ๋ฐฉ๋ฒ•์€ ๐Ÿงญ ํ™˜๊ฒฝ ์„ค์ • ๋ถ€ํ„ฐ ๋‚˜์™€์žˆ์Šต๋‹ˆ๋‹ค.

์™œ ์ •์  ๋ฆฌ์†Œ์Šค ์ ‘๊ทผ์ด ํ•„์š”ํ•œ๊ฐ€

์ €๋„ ๊ทธ๋Ÿด ์ƒ๊ฐ์€ ์—†์—ˆ๋Š”๋ฐ์š”......

ํ˜„์žฌ ์ง„ํ–‰ ์ค‘์ธ ํ”„๋กœ์ ํŠธ์—์„œ๋Š” FE, BE, AI๊ฐ€ ํ•˜๋‚˜์˜ ์›Œํฌ์Šคํ…Œ์ด์…˜์—์„œ ๋Œ์•„๊ฐ€๋„๋ก ํ•ด์•ผํ•œ๋‹ค๋Š” ์š”๊ตฌ์‚ฌํ•ญ์ด ์žˆ์—ˆ๋‹ค. ์ด๋Ÿฌํ•œ ํŠน์„ฑ์„ ๋ฐ˜์˜ํ•ด ํŒŒ์ผ์„ ์ฃผ๊ณ ๋ฐ›์„ ๋•Œ resource path๋ฅผ ๊ธฐ์ค€์œผ๋กœ ํ•˜์œ„ relative path๋ฅผ ์ฃผ๊ณ  ๋ฐ›๋„๋ก API๋ฅผ ์„ค๊ณ„ํ–ˆ๋‹ค. ๊ทธ๋ž˜์„œ ํŒŒ์ผ์„ ์ง์ ‘์ ์œผ๋กœ ์ „์†กํ•˜์ง€ ์•Š๊ณ , ํ•„์š”ํ•œ ํŒŒ์ผ์˜ path๋งŒ ์ „๋‹ฌํ•ด ์‚ฌ์šฉํ•œ๋‹ค.

์™œ ๊ทธ๋ ‡๊ฒŒ ์„ค๊ณ„ํ•œ๊ฑด๋ฐ?

ํ•ด๋‹น ํ”„๋กœ์ ํŠธ์—์„œ๋Š” DICOM ํŒŒ์ผ์„ ์ฃผ๊ณ ๋ฐ›์•„์•ผ ํ•œ๋‹ค. ์ฒ˜์Œ์—๋Š” ํŒŒ์ผ์„ ์ง์ ‘์ ์œผ๋กœ ์ „์†กํ•˜๋Š” ๋ฐฉํ–ฅ์œผ๋กœ ์„ค๊ณ„๋ฅผ ํ–ˆ์—ˆ์ง€๋งŒ, DICOM ํŒŒ์ผ์˜ ํฌ๊ธฐ๋ฅผ ๋ณด๊ณ  ์„ค๊ณ„๋ฅผ ์ˆ˜์ •ํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค. DICOM ํŒŒ์ผ์€ ๊ฐœ๋‹น 20~30MB์˜ ํฐ ์šฉ๋Ÿ‰์„ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค. ๊ฐ study์˜ ์„ธ๋ถ€ ์กฐํšŒ ๋“ฑ์—์„œ ์•ฝ 20๊ฐœ ์ด์ƒ์˜ DICOM ํŒŒ์ผ์„ ์ „์†กํ•ด์•ผํ–ˆ๊ธฐ์— ์ง์ ‘ ์ „์†กํ•˜๋Š” ๋ฐฉ์‹์ด ์˜ค๋ฒ„ํ—ค๋“œ๊ฐ€ ํด ๊ฒƒ์ด๋ผ ํŒ๋‹จํ–ˆ๋‹ค. ํŠนํžˆ DICOM ํŒŒ์ผ์„ BE โ†’ FE๋กœ ํ•ด๋‹น DICOM ํŒŒ์ผ์ด ์žˆ๋Š” resource path๋งŒ ์ „๋‹ฌํ•ด์ฃผ๋ฉด ํ”„๋ก ํŠธ์—์„œ ํ•„์š”ํ•  ๋•Œ ํŒŒ์ผ์„ ๋กœ๋”ฉํ•  ์ˆ˜ ์žˆ์–ด ํšจ์œจ์ ์ด์—ˆ๋‹ค. ๊ฒฐ๋ก ์ ์œผ๋กœ๋Š” ๊ทธ๊ฒŒ ํšจ์œจ์ ์ด๋ผ์„œ ๊ทธ๋ ‡๊ฒŒ ์„ค๊ณ„ํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค๋Š” ์–˜๊ธฐ๋‹ค.

Spring์—์„œ ์ •์  ๋ฆฌ์†Œ์Šค ์ ‘๊ทผ์„ ์ง€์›ํ•˜๋Š”๊ฐ€

๋„ค? ๊ธฐ๋ณธ์ ์œผ๋กœ ์ง€์›ํ•ด์ค€๋‹ค๊ณ ์š”?

์ผ๋ฐ˜์ ์œผ๋กœ ์šฐ๋ฆฌ๊ฐ€ ํ”„๋กœ์ ํŠธ๋ฅผ ์ƒ์„ฑํ•˜๋ฉด ์žˆ๋Š” {project root path}/src/main/resources/static ๋‚ด๋ถ€์— ์žˆ๋Š” ํŒŒ์ผ์— ๊ธฐ๋ณธ์ ์œผ๋กœ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•˜๋‹ค. ๋ณ„๋„๋กœ ์„ค์ •ํ•ด์ฃผ์ง€ ์•Š์•„๋„ ์œ„์˜ ๊ฒฝ๋กœ์— ์žˆ๋Š” ๋ชจ๋“  ์ •์  ๋ฆฌ์†Œ์Šค๋“ค์— ๋Œ€ํ•ด http://ip:port/{static_resource_file}๋กœ ์ ‘๊ทผํ•˜๋ฉด ๋œ๋‹ค.

ํ”„๋กœ์ ํŠธ ๋‚ด์˜ ์ •์  ๋ฆฌ์†Œ์Šค์— ๋Œ€ํ•œ url ์„ค์ •

์ ‘๊ทผ ๊ฒฝ๋กœ(url)๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ /**(root)๋ถ€ํ„ฐ ๋งคํ•‘๋œ๋‹ค. ์ด๋ฅผ ์ˆ˜์ •ํ•˜๊ณ ์‹ถ๋‹ค๋ฉด, application.yml์—์„œ spring.mvc.static-path-pattern์„ ์•„๋ž˜์™€ ๊ฐ™์ด ์„ค์ •ํ•ด์ฃผ๋ฉด ๋œ๋‹ค.

spring:
	mvc:
    	static-path-pattern: {์›ํ•˜๋Š” url ์„ค์ •} (e.g. /static/**)

ํ”„๋กœ์ ํŠธ ๋‚ด์˜ ์ •์  ๋ฆฌ์†Œ์Šค ์œ„์น˜ ์„ค์ •

{project root path}/src/main/resources/ ๋‚ด๋ถ€์˜ ์›ํ•˜๋Š” ๋””๋ ‰ํ† ๋ฆฌ์— ์ ‘๊ทผํ•˜๋„๋ก ์„ค์ •ํ•  ์ˆ˜๋„ ์žˆ๋‹ค. ์ด๋ฅผ ์„ค์ •ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” application.yml์—์„œ spring.resources.static-locations๋ฅผ ์„ค์ •ํ•ด์ฃผ๋ฉด ๋œ๋‹ค. ๊ธฐ๋ณธ ๊ฐ’์€ /static/๋กœ ์„ค์ •๋˜์–ด์žˆ๋‹ค.

spring:
	resources:
    	static-locations: {์›ํ•˜๋Š” ๊ฒฝ๋กœ ์„ค์ •} (e.g. /images/)

์ €๋Š” ํ”„๋กœ์ ํŠธ ์™ธ๋ถ€์˜ ๋””๋ ‰ํ† ๋ฆฌ์— ์ ‘๊ทผํ•˜๊ณ ์‹ถ์€๋ฐ........

๊ธฐ๋ณธ์ ์œผ๋กœ ์ง€์›ํ•˜๋Š” ์ •์  ๋ฆฌ์†Œ์Šค ์ ‘๊ทผ์€ ์›ํ•˜๋Š” ๋ฆฌ์†Œ์Šค๋ฅผ ๋ชจ๋‘ ์œ„์˜ ๊ฒฝ๋กœ์— ๋„ฃ์–ด์ค˜์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ํ•ด๋‹น ํ”„๋กœ์ ํŠธ์—์„œ๋Š” ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋Š” ๋ฐฉ๋ฒ•์ด์—ˆ๋‹ค. ๋”ฐ๋ผ์„œ BE ์„œ๋ฒ„ ๋‚ด๋ถ€ ์ €์žฅ ๊ณต๊ฐ„์— ์žˆ๋Š” resource ๋””๋ ‰ํ† ๋ฆฌ๋ฅผ ์›ํ•˜๋Š” url์— ๋งคํ•‘ํ•ด์ฃผ๊ธฐ ์œ„ํ•ด ๋ณ„๋„๋กœ ์„ค์ •ํ•ด์ฃผ์—ˆ๋‹ค.

๊ทธ๊ฑฐ ๋ˆ„๊ฐ€ ํ•ด์ฃผ๋Š”๊ฑด๋ฐ

์ •์  ๋ฆฌ์†Œ์Šค ์ ‘๊ทผ์„ ์ปค์Šคํ…€ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š”WebMvcConfigurer๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ํด๋ž˜์Šค์—์„œ addResourceHandlers๋ฅผ overrideํ•ด์ฃผ๋ฉด ๋œ๋‹ค. ResourceHandler๋ฅผ ์ •์˜ํ•˜๋ฉด์„œ uri์™€ ํŒŒ์ผ ์‹œ์Šคํ…œ ๋‚ด๋ถ€์˜ ๊ฒฝ๋กœ๋ฅผ ์„ค์ •ํ•ด์ฃผ๋ฉด url์„ ํ†ตํ•ด ์ ‘๊ทผ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

๐Ÿงญ ํ™˜๊ฒฝ ์„ค์ •

environment

  • Spring v3.3.1
  • Gradle build

application.yml

resourcePath ๋””๋ ‰ํ† ๋ฆฌ ํ•˜์œ„์˜ ๋ชจ๋“  resource์— ์ ‘๊ทผํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” /๋กœ ๋๋‚˜๋„๋ก ์•„๋ž˜์™€ ๊ฐ™์ด directory path๋ฅผ ์„ค์ •ํ•ด์•ผ ํ•œ๋‹ค. ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ช…์€ ์›ํ•˜๋Š”๋Œ€๋กœ ์„ค์ •ํ•ด์ค˜๋„ ๋œ๋‹ค. ์—ฌ๊ธฐ์„œ๋Š” DICOM ํŒŒ์ผ๋“ค์— ๋Œ€ํ•œ resourcePath๋ฅผ ์•„๋ž˜์™€ ๊ฐ™์ด ์„ค์ •ํ•ด์ฃผ์—ˆ๋‹ค.

dicom:
	resourcePath: /home/ubuntu/dicom/

๐Ÿš€ How to use

MvcConfig.java

MvcConfig.java ํด๋ž˜์Šค๋Š” WebMvcConfigurer์˜ ๊ตฌํ˜„์ฒด์ด๋‹ค.resource handler ์„ค์ • ์‹œ ์œ„์—์„œ ๋ฏธ๋ฆฌ ์ •์˜ํ•ด๋‘” ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค. ๋‚ด ๊ฒฝ์šฐ์—๋Š” http://ip:port/api/v1/resources/**๋กœ ์ ‘๊ทผํ•˜๊ธฐ ์œ„ํ•ด ์•„๋ž˜์™€ ๊ฐ™์ด ์„ค์ •ํ•ด์ฃผ์—ˆ๋‹ค.

package example.global.config;

import java.io.File;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class MvcConfig implements WebMvcConfigurer {

    @Value("${dicom.resourcePath}")
    private String localStoragePath;

    /**
     * add resource handlers to serve static resources with http://ip:port/api/v1/resources/**
     *
     * @param registry resource handler registry
     */
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler(File.separator + "api/v1/resources" + File.separator + "**")
            .addResourceLocations("file:" + localStoragePath);
    }
}

GET ์š”์ฒญ์„ ํ†ตํ•œ ์ •์  ๋ฆฌ์†Œ์Šค ์ ‘๊ทผ

ํ…Œ์ŠคํŠธ ์‹œ ์›น ๋ธŒ๋ผ์šฐ์ €์— http://ip:port/api/v1/resources/{resource file path}๋ฅผ ์ž…๋ ฅํ•ด ์ •์ƒ์ ์œผ๋กœ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•œ ๊ฒƒ์„ ํ™•์ธํ•œ๋‹ค. ๋‹น์—ฐํ•œ ๋ง์ด์ง€๋งŒ ์œ„์—์„œ ์„ค์ •ํ•œ url๋กœ ์ ‘๊ทผํ•ด์•ผํ•œ๋‹ค. ํ”„๋ก ํŠธ์—์„œ ์š”์ฒญ ์‹œ ๋™์ผํ•œ url์— GET ์š”์ฒญ์„ ์ „์†กํ•ด ์ ‘๊ทผ ๊ฐ€๋Šฅํ•˜๋‹ค.

์งœ์ž”~ ์‚ฌ์‹ค ๋‹จ์ ์ด ์žˆ๋‹ต๋‹ˆ๋‹ค~

์ƒ๊ฐ๋ณด๋‹ค ํฐ ๋‹จ์ ์ธ๋ฐ?

์ €๋ ‡๊ฒŒ๋งŒ ์„ค์ •ํ•˜๋ฉด ๋ชจ๋“  ๊ฒŒ ์ข‹์„ ๊ฒƒ ๊ฐ™์ง€๋งŒ...... ์‚ฌ์‹ค ํฐ ๋‹จ์ ์ด ์žˆ๋‹ค. ์ธ์ฆ ๋ฐ ์ธ๊ฐ€ ์—†์ด uri๋กœ ์ •์  ์ž์›์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋งŒ๋“œ๋Š” ๊ฒƒ์€ ๋ณด์•ˆ ์ธก๋ฉด์—์„œ ๋ฌธ์ œ์˜ ์†Œ์ง€๊ฐ€ ์žˆ๋‹ค. ๋ณ„๋กœ ํฐ ๋ฌธ์ œ๊ฐ€ ์•„๋‹ ์ˆ˜ ์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, ๊ฒฝ๋กœ๋งŒ ์•ˆ๋‹ค๋ฉด ๊ฐœ์ธ์ •๋ณด ๋“ฑ์ด ๋‹ด๊ธด ํŒŒ์ผ์— ์ ‘๊ทผํ•˜๋Š” ๊ฒƒ๋„ ๊ฐ€๋Šฅํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ด๋ฅผ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ์•„๋ž˜์˜ ์กฐ์น˜๋ฅผ ๋ฏธ๋ฆฌ ์ทจํ•ด๋‘๋Š” ๊ฒƒ์„ ์ถ”์ฒœํ•œ๋‹ค. ๋ฌผ๋ก  ์‹์•ฝ์ฒ˜ ๋“ฑ์˜ ๊ณต๊ณต๊ธฐ๊ด€์—์„œ ์š”๊ตฌํ•˜๋Š” ์‚ฌ์ด๋ฒ„๋ณด์•ˆ ์š”๊ตฌ์‚ฌํ•ญ์—๋Š” ํฌํ•จ๋˜์–ด ์žˆ์ง€๋Š” ์•Š๋‹ค.

์–ด๋–ป๊ฒŒ ๋ณด์™„ํ•˜๋Š”๊ฐ€

ํ•„์š”ํ•œ ์ž์›์ด ํฌํ•จ๋œ ์ตœ์†Œ ๋‹จ์œ„์˜ ๋””๋ ‰ํ† ๋ฆฌ ์—ฐ๊ฒฐํ•˜๊ธฐ

์›ํ•˜๋Š” ์ž์›์ด ํฌํ•จ๋œ ๋””๋ ‰ํ† ๋ฆฌ๋ฅผ resourcePath๋กœ ์„ค์ •ํ•ด๋‘๋ฉด ํ•˜์œ„ ๋””๋ ‰ํ† ๋ฆฌ ๋ฐ ํŒŒ์ผ์— ๋ชจ๋“  ์ ‘๊ทผ์ด ๊ฐ€๋Šฅํ•˜๋‹ค. ๋”ฐ๋ผ์„œ ๋ถˆํ•„์š”ํ•˜๊ฒŒ ์ƒ์œ„ ๋””๋ ‰ํ† ๋ฆฌ๊ฐ€ resourcePath๋กœ ์„ค์ •๋˜์–ด์žˆ๋‹ค๋ฉด ์ด๋ฅผ ํ•„์š”ํ•œ ์ž์›๋งŒ ๋ชจ์•„๋‘” ํ•˜์œ„ ๋””๋ ‰ํ† ๋ฆฌ๋กœ ๋ณ€๊ฒฝํ•˜๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•œ๋‹ค. ๋ฌผ๋ก  ๊ทธ๋ž˜๋„ ์ ‘๊ทผ ์ž์ฒด๋ฅผ ๋ง‰์ง€๋Š” ๋ชปํ•œ๋‹ค.

์ •์  ์ž์› ์ ‘๊ทผ uri์— authenticated() ์„ค์ •ํ•˜๊ธฐ

Spring security๋ฅผ ํ™œ์šฉํ•ด ์ธ์ฆ์„ ๊ตฌํ˜„ํ•˜๊ณ  ์žˆ๋‹ค๋ฉด, ์„ค์ •ํ•ด๋‘” ์ •์  ์ž์›์— ์ ‘๊ทผํ•œ uri์— ๊ถŒํ•œ ์„ค์ •์„ ๋ถ€์—ฌํ•  ์ˆ˜ ์žˆ๋‹ค. ๋‚ด ๊ฒฝ์šฐ์—๋Š” /api/v1/resources/**์— ์ ์–ด๋„ ๋กœ๊ทธ์ธํ•ด์•ผ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋„๋ก SecurityConfig.java์—์„œ ์•„๋ž˜์™€ ๊ฐ™์ด ์„ค์ •ํ•ด์ฃผ์—ˆ๋‹ค.

package example.global.config;

// package ์ƒ๋žต

/**
 * Security Configuration
 */
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            ... // ๋‚˜๋จธ์ง€ ์„ค์ •์€ ์ƒ๋žต
            .authorizeHttpRequests(
                (authorizeRequests) ->
                    authorizeRequests
						// ์ •์  ์ž์›์— ์ ‘๊ทผํ•˜๋Š” uri์— ๋Œ€ํ•ด ๊ถŒํ•œ ์„ค์ •
                        .requestMatchers("/api/v1/resources/**").authenticated()
                        .anyRequest().permitAll()
            )
        return http.build();
    }
}

์ด์ „์— /resources/**์— ๋Œ€ํ•ด์„œ authenticated()๋ฅผ ์„ค์ •ํ•ด ํ…Œ์ŠคํŠธ ํ•ด๋ณธ ์ ์ด ์žˆ์—ˆ๋Š”๋ฐ, ๋™์ž‘ํ•˜์ง€ ์•Š์•˜๋‹ค. ์ปค์Šคํ…€ํ•œ ๊ฒฝ์šฐ์—๋งŒ ์„ค์ •๋˜๋Š” ์ด์œ ์— ๋Œ€ํ•ด์„œ๋Š” ๋” ๊ณต๋ถ€๊ฐ€ ํ•„์š”ํ•  ๊ฒƒ์œผ๋กœ ์ƒ๊ฐ๋œ๋‹ค.


Reference : Serve Static Resources with Spring | Baeldung

profile
๋‚˜๋Š”์•ผ ๋งํ•˜๋Š” ๊ฐœ๋ฐœ(๊ฐ)์ž

0๊ฐœ์˜ ๋Œ“๊ธ€