개발/Spring

Spring 파일 이미지 조회하기

뽀글뽀글 개발자 2024. 5. 21. 11:29

이미지 파일을 상대 경로로 저장했을 경우 문제점

 

사진 게시판을 구현하면서 게시판 조회 시 이미지를 보여주는 방법을 찾아보게 되었다.

처음에는 서버의 resource 폴더에 이미지를 저장해두고 img src에 이미지 파일의 상대 경로를 넣어줬다.

해당 프로젝트는 Spring SecurityJSP를 사용하고 있었고 Tool은 이클립스를 사용했다.

 

문제

해당 환경에서 상대 경로로 이미지를 불러올 때 2가지 문제가 발생했다.

첫 번째로 Spring security에서 권한이 있어야 접근할 수 있는 URI가 /admin이라고 할 때  img src="/123.jpg"를 불러오면 이미지가 나오지 않는 문제가 있었다.

그 이유는 상대 경로이기 때문에 localhost:8080/123.jpg이 불러와질 것이라 생각했지만 브라우저에서 요청을 확인해보니 해당 admin 주소의 경로가 추가되어 localhost:8080/admin/123.jpg으로 접근이 되는 문제였다.

해당 문제는 JSP 기준으로 ${pageContext.request.contextPath}를 사용하면 해결할 수 있는 문제였다.

 

두 번째 문제는 이클립스에서 업로드된 파일이 조회되기 위해서는 서버에 반영되는 약간의 시간이 필요하다는 점이다.

방금 업로드 된 파일을 조회할 경우 이미지가 나오지 않다가 10초 후에는 해당 이미지가 잘 나오는 문제가 있었다.

이 문제는 이클립스 설정을 하면 해결된다는 글을 보았지만, 나의 경우에는 설정을 해도 해결이 되지않았다.

 

이 외에도 resource에 저장할 경우 누구나 접근이 가능하다는 보안 문제와 동적인 데이터 전송을 담당하는 WAS에서 파일을 주는 것 보다 정적 파일을 전송하는 Web Server에서 파일을 전송하는 것이 더 효율적이라는 점도 있었다.

 

이미지 파일을 Byte로 조회하는 컨트롤러 

위에서 언급한 문제를 해결하기 위해서 이미지 파일만 반환해주는 컨트롤러를 따로 만들고, img src에 해당 컨트롤러의 경로를 넣어주는 방법을 찾게 되었다.

 

로컬(ex. C:\Temp)에 파일을 저장하고, 컨트롤러에 요청을 보내면 서버가 로컬 파일을 읽어서 byte[]로 변환 후 리턴한다.

그렇다면 사진을 불러오기 위해 매번 요청을 보내야하는가?  img src는 렌더링 될 때 해당 경로에서 파일을 읽어오는 요청을 보내게된다. 

이때 컨트롤러 주소를 담아주게 되면 페이지를 불러올 때 자동으로 img 태그가 컨트롤러에 요청을 보내고 반환 받은 바이트 배열을 이용해서 이미지를 렌더링하게 된다.

 

코드

@GetMapping(value = "/imgPreview.do",produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
public ResponseEntity<?> getImage(@RequestParam Long id) throws IOException{
    byte[] fileStream = surveyService.getFileImg(id);
    return ResponseEntity.ok(fileStream);
}
	
    
@Override
public byte[] getFileImg(Long id) throws IOException {
    FileVO fileVO = surveyMapper.selectFile(id);
    String path = fileVO.getSavedPath();
    return Files.readAllBytes(Paths.get(path));
}


<img class="w-50 h-50 mb-4" src="/imgPreview.do?id=1" alt="이미지"/>

 

검색 결과 일반적으로 아래 방식으로 파일의 바이트를 읽는 경우가 많았지만, 나의 경우에는 결과가 null로 나오는 문제가 있었고, 문제를 해결하기 위해 고민해보다가 결과적으로 파일을 바이트 배열로 변환해서 주기만 하면된다는 생각에 위와 같은 방법을 사용하였다.

InputStream in = servletContext.getResourceAsStream("/WEB-INF/images/image-example.jpg");
byte[] media = IOUtils.toByteArray(in);

 

MediaType은 검색을 통해 반환하는 파일에 맞춰 작성하면 된다.

 

 

+ 파일 다운로드

이미지 조회의 내용과는 무관하지만 파일 다운로드의 내용도 유사하기 때문에 적어본다.

다운로드 역시 FileInputStream을 통해 파일 정보를 읽어서 해당 스트림 정보를 ServletResponse의 outputStream에 담아서 전송하는 것이다. 

이미지 조회든 파일 다운이든 여러 방법이 있겠지만 결국은 파일을 바이트로(FileInputStream은 InputStream을 상속받아 파일을 바이트로 읽어들인다) 읽어서 응답에 담아준다는 점은 동일하다. 

public void fileDownload(@RequestParam Long id, HttpServletResponse response) 
        throws SQLException, IOException{
    /* 다운로드 받을 파일을 받아옴 */
    Entry<String, File> fileEntry = postService.download(id);
    File file = fileEntry.getValue();
    /* 파일 이름 전송 시 ASCII 문자 집합만을 허용하므로 UTF-8로 변환 */
    String originalFileName = URLEncoder.encode(fileEntry.getKey(), "UTF-8").replaceAll("\\+", "%20");

    /* response의 contentType을 다운로드 방식으로 설정 */
    response.setContentType("application/download");
    response.setContentLength((int)file.length());
    response.setHeader("Content-disposition", "attachment;filename=\"" + originalFileName + "\"");

    /* response 객체를 통해서 파일 전송을 위한 스트림 생성*/
    OutputStream os = response.getOutputStream();

    /* fileInputStream으로 파일을 읽은 후 그 값을 outputStream에 값을 넣어 전송 */
    FileInputStream fis = new FileInputStream(file);
    FileCopyUtils.copy(fis, os);

    /* 사용 후 리소스 누수 방지를 위한 close */
    fis.close();
    os.close();
}

 

 

개발하면서 궁금했던 점

파일을 로컬에 저장할 경우 서버가 여러 대라면 이미지 조회가 랜덤하게 되지않을까?라는 의문이 생겼다.

해결 방법은 파일을 로컬에 저장하고 일반적으로 linux 기반의 환경에서 배포를 하는데 linux 환경에서는 서버 간의 공유 폴더를 생성할 수 있다.

해당 경로에 파일을 저장해서 문제를 해결할 수 있고, WAS의 앞단인 Web Server에서 정적 파일을 반환하도록 할 수 있다.