Spring Boot File Upload & Serving 서버 + React Player 1편

Sieun Sim·2020년 5월 31일
0

Spring Boot

목록 보기
2/2

1편은 업로드 없이 존재하는 파일을 서빙만 하는 버전

영상과 썸네일을 static하게 가져오려는 과정에서,
file path 설정에서 애를 먹으면서 이번 기회에 pring boot의 classpath와 absolute path 에 대해서 다시 공부했다.

classpath

classpath는 지금 실행 중인 위치에서 상대적으로 결정된다.

Locations of static resources. Defaults to classpath:[/META-INF/resources/, /resources/, /static/, /public/].

내 로컬에서 run 해가며 개발한 뒤 서버에서 무식하게 .jar파일로 빌드해 src/target 폴더에서 주워와 실행을 시켰는데, 처음에 addResourceHandler에서 classpath로 접근했더니 로컬에서 intelliJ로 run 시켰을 때와는 달리 접근이 되지 않았다. 그래서 absolute path를 로그에 찍어보니, 빌드 된 target폴더 안에서의 classpath를 가리키는 것이였다. /home/ubuntu/project/src/target/static/ 이런식으로 classpath가 target 폴더에 물려버린 것이다. target/classess가 classpath가 된다. 그래서 dev/prod 모두에서 편하게 쓰기 위해 application.properties에 영상들의 절대 경로를 변수로 설정해두고 진행하기로 했다.


scp

Secure copy. 똑같이 복사를 해주는 cp와 ftp는 둘 다 평문으로 전송한다고 한다. 반면scp는 ssh와 같이 암호화 전송이다.
scp -p <port> 계정@ip/path 형식이며 ssh가 뚫린 포트로 사용하면 된다.  

이번 내 역할에서 영상 업로드 기능 api를 만드는 것은 포함되지 않았다. 따라서 ffmpeg를 이용해 영상을 만들어 scp로 ssh가 열려있는 22 포트로 파일을 보냈다. 중간에 인터넷을 끊을 일이 생겨서, scp로 이어받기처럼 알아서 이미 받은 부분을 제외하고 받을 수 있는지 찾아봤다. 이미 받은 파일들의 write 권한을 없애 강제 read-only로 만들어 버리라는 귀여운 트릭을 찾을 수 있었다. 내 예상과는 달리 와이파이를 잠시 끊었다가 다른 와이파이로 갈아탔는데도 연결은 끊기지 않고 계속됐다.


file:// 프로토콜

/home/ubuntu/videos 같은 절대경로로 파일을 찾아오려는데 생각처럼 잘 되지 않았다. 알고보니 파일을 받아오려면 file 프로토콜을 따로 써야 했다.

file:// protocol is used in a URI that specifies the location of an operating system file.

파일에 대한 uri 접근은 기본적으로 file://<host>/<path> 형태이다. 위키만세..

file: 뒤에 붙는 슬래시는 1. /path, 2. ///path, 3. //localhost 만이 적합하다.
여기서 host란 DNS root 이자 Fully qualified domain name로, dir/dir/..형태로 접근 가능한 path 여야 한다.

기본적으로 /path의 시작 slash는 생략할 수 없다. file:/path ~ 형태로 하나의 슬래시가 가능한 이유는, host 앞의 //가 생략될 수 있기 때문이다. 생략되면 host는 자동으로 localhost가 되는데, localhost는 이 URL이 읽히는 그 머신, 즉 내 컴퓨터를 뜻하게 된다.

또는 //를 생략하지 않고 localhost만 생략할 수도 있다. 이 경우 file:/// 처럼 슬래시가 3개가 된다. 그래서 file: 뒤에 슬래시는 기본적으로는 2개는 안되고 1개 혹은 3개 이상(4개부터는 cd ///////와 같은 이치인 듯)이 되는 것이다.

File URLs are rarely used in Web pages on the public Internet, since they imply that a file exists on the designated host.

file: 스키마는 다른 외부 전송 프로토콜이나 서버 경로가 아닌 파일시스템에서 직접 가져오기 때문에 host라는게 의미가 없다고 한다. file://외부링크/path 형태를 볼 수 없는 이유다.

그리고 몰랐는데 크롬 브라우저에 이 형식에 맞게 file://localhost 등의 url을 입력했을 때 내 시스템의 파일을 보여준다..! file://host 의 host가 DNS root라길래 브라우저에서 resolv.conf에 있는 내 nameserver(127.0.0.53)를 찾아 접속했더니 접속이 됐다. 물론 localhost나 127.0.0.1로도 가능했다. 그런데 클라이언트(localhost:3000)에서 서버(localhost:8080)에의 파일에 접근할 때 정적 루트를 file://localhost/로 잡았더니 404 not found가 나온다. URL을 읽는 머신 이라면서 스프링 부트를 통해서 접근할 때는 왜 주소로 먹히지 않는지 잘 모르겠다..

그리고 file://path 형태로 슬래쉬를 2개만 쓰면, 이 path를 host로 인식해버려 오류가 났다.

file://home ->

java.net.UnknownHostException: home
    at java.base/java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:220) ~[na:na]
    at java.base/java.net.Socket.connect(Socket.java:609) ~[na:na]
    at java.base/sun.net.ftp.impl.FtpClient.doConnect(FtpClient.java:961) ~[na:na]
    at java.base/sun.net.ftp.impl.FtpClient.tryConnect(FtpClient.java:923) ~[na:na]
    at java.base/sun.net.ftp.impl.FtpClient.connect(FtpClient.java:1018) ~[na:na]
    at java.base/sun.net.ftp.impl.FtpClient.connect(FtpClient.java:1004) ~[na:na]
    at java.base/sun.net.www.protocol.ftp.FtpURLConnection.connect(FtpURLConnection.java:312) ~[na:na]

이 오류에서 한 가지 더 알 수 있는건 file:// 프로토콜로 접근하려 하면 스프링 내부적으로 자바의 ftp 모듈을 써서 파일을 가져온다는 거다. 오류 메시지에서 반가운 .getInputStream 도 볼 수 있었는데, MultipartFile에서 가져오던 InputStream 처럼 결국 내부적으로는 자바의 파일 읽는 클래스인 InputStream을 써서 가져오는 것 같다.


Config file

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Value("${video.absolute.location}")
    private String videoLocation;
    @Override
    public void addResourceHandlers(final ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/videos/**")
                .addResourceLocations(videoLocation);
    }

    //일단 CORS 다 해제
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("*");
    }

}

application.properties

video.absolute.location=file:////root/videos/

절대로 절대경로 뒤에 /를 빼먹으면 안된다.
*dir/ -> dir 폴더의 하위
dir -> 진짜 dir 라는 파일 *

보통 웹에서 접속할 때는 마지막 /를 중시한 적이 없었던 것 같다. 여기서는 경로를 설정하는거라 / 하나로도 아예 다른 뜻이 되어버린다. 주의하기

application.propertoes에서 dev환경과 prod 환경일 때 설정을 다르게 했는데, 정말 1차원적인 배포다.. 찾다보니 설정 파일도 dev 환경인지 prod 환경인지에 따라서 다르게 지정해줄 수도 있고, 더 똑똑한 배포 방법이 훨씬 많을텐데 급한불 끄면 공부좀 해야겠다.


addResourceHandler 말고 다른 방법 (+0523)

나는 addResourceHandler를 사용함으로써 스프링이 지원하는 정적 파일 지원을 활용한것이고,

이 때 스프링이 내부적으로 ftp를 사용해주는 것 같다.

이걸 보시고 멘토님이 파일을 객체를 새로 써 클라이언트에게 반환해주는 방법도 제시해주셨다.

내가 생각하는 이 방법의 흐름은

1. 클라이언트가 서버 컴퓨터에 직접 접근하는게 아닌, Controller로 요청을 보낸다.

2. 서버는 File f = new File(path); 이런식으로 새로운 파일을 만들고, 내보내고자 하는 파일을 읽어 이 file에 쓴 뒤 요청에 응답하면서 반환한다.

구글링해보니 이런 방식을 선택한 경우도 볼 수 있었다. 그리고 누군가 1번보다는 2번이 보안상 안전하다고 하는데, 로컬 파일에 직접 접근하는것은 지양하는게 좋아보이긴 한다. 서버 컴퓨터의 파일을 그대로 내보내는게 위험해보인다.

그리고 또 다른 방식을 찾아볼 수 있었는데, ftp를 그대로 사용하는 것이다. ftp 서버를 만든 뒤 ftp://host/path 이런식으로 file://프로토콜을 이용한 ftp 간접사용이 아닌 직접 연결해 사용하는 식이다.

0개의 댓글