-
[Spring] WebFlux에서 이미지 로드 구현하기Spring/Study 2023. 7. 12. 17:27
WebFlux에서 이미지 로드 구현하기
목적
WebFlux로 구현하는 프로젝트 목적에 따라 이미지를 프론트 쪽에서 사용하게 되었는데, 이 이미지를 서버에서 전송해주기로 했다.
문제점
여기서 icon과 line 이라는 2가지 폴더로 나뉘게 되는데, icon 폴더에는 svg 형태의 이미지 파일이 존재하고 line 폴더에는 png 형태의 이미지 파일이 존재한다.
만약 존재하지 않는 파일이라면 서버측에서 500코드로 오류를 응답으로 전송하게 되는데 이를 404 코드로 응답을 해줘야했다.
해결 방법
이들을 따로따로 처리해주기 위해서는 이미지를 전송해줄 때, HttpHeader를 각각 다르게 처리해서 보내줘야 했다.
FileNotFoundException으로 인한 500 에러가 발생하므로 DataBuffer로 변환하기 전에 Flux.empty()를 반환해줬다.
전체 코드
컨트롤러
@GetMapping("/images/{imageType}/{imageName}") public Mono<ResponseEntity<DataBuffer>> getImage( @PathVariable String imageType, @PathVariable String imageName) { MediaType mediaType; if (imageName.toLowerCase().endsWith(".png")) { mediaType = MediaType.IMAGE_PNG; } else if (imageName.toLowerCase().endsWith(".jpeg") || imageName.toLowerCase().endsWith(".jpg")) { mediaType = MediaType.IMAGE_JPEG; } else if (imageName.toLowerCase().endsWith(".svg")) { mediaType = MediaType.valueOf("image/svg+xml"); } else { mediaType = MediaType.IMAGE_PNG; } HttpHeaders headers = new HttpHeaders(); headers.setContentType(mediaType); Mono<DataBuffer> dataBufferMono = imageService.loadImage(imageType, imageName).reduce(DataBuffer::write); return dataBufferMono.map(dataBuffer -> ResponseEntity.ok().headers(headers).body(dataBuffer)) .defaultIfEmpty(ResponseEntity.status(HttpStatus.NOT_FOUND).build()); }
서비스
public Flux<DataBuffer> loadImage(String imageType, String imageName) { log.info("{}", imageName); ClassPathResource imageResource = new ClassPathResource("static/images/" + imageType + "/" + imageName); if (!imageResource.exists()) { log.warn("파일을 찾을 수 없음"); return Flux.empty(); } return DataBufferUtils.read( imageResource, new DefaultDataBufferFactory(), 4096); }
코드 설명
가장 먼저 서비스 계층에 대한 설명이다.
public Flux<DataBuffer> loadImage(String imageType, String imageName) { log.info("{}", imageName); ClassPathResource imageResource = new ClassPathResource("static/images/" + imageType + "/" + imageName); if (!imageResource.exists()) { log.warn("파일을 찾을 수 없음"); return Flux.empty(); } return DataBufferUtils.read( imageResource, new DefaultDataBufferFactory(), 4096); }
우선 ClassPathResource 클래스를 통해 경로상의 이미지를 불러온다.
이후 조건문을 통해 존재하지 않는 파일이라면 Flux.empty()를 반환해준다.
예외처리를 해주지 않으면 FileNotFoundException이 발생하게 된다.
이상이 없다면 DataBuffer를 통해 4096 바이트 만큼 여러개로 나누어서 전달하게된다.
다음은 컨트롤러 계층이다.
@GetMapping("/images/{imageType}/{imageName}") public Mono<ResponseEntity<DataBuffer>> getImage( @PathVariable String imageType, @PathVariable String imageName) { MediaType mediaType; if (imageName.toLowerCase().endsWith(".png")) { mediaType = MediaType.IMAGE_PNG; } else if (imageName.toLowerCase().endsWith(".jpeg") || imageName.toLowerCase().endsWith(".jpg")) { mediaType = MediaType.IMAGE_JPEG; } else if (imageName.toLowerCase().endsWith(".svg")) { mediaType = MediaType.valueOf("image/svg+xml"); } else { mediaType = MediaType.IMAGE_PNG; } HttpHeaders headers = new HttpHeaders(); headers.setContentType(mediaType); Mono<DataBuffer> dataBufferMono = imageService.loadImage(imageType, imageName).reduce(DataBuffer::write); return dataBufferMono.map(dataBuffer -> ResponseEntity.ok().headers(headers).body(dataBuffer)) .defaultIfEmpty(ResponseEntity.status(HttpStatus.NOT_FOUND).build()); }
반환값으로 Mono 타입으로 ResponseEntity에 DataBuffer의 Flux를 정리해서 응답 해줄것이다.
가장먼저 이미지는 svg와 png 두 확장자가 있으므로, 입력받는 파일의 마지막 문자열을 검사해서 확장자를 확인하고
헤더에 담을 MediaType을 따로 지정해준다.
서비스 계층의 loadImage를 불러오면 Flux 형으로 되어있을 텐데 이를 reduce 스트림 함수를 통해 Datavbuffer::write를 통해 Mono 타입으로 변환해준다.
중요
.reduce(DataBuffer::write) 스트림 함수가 여기서 중요한 역활을 하는데
Flux를 반환하는 서비스 계층의 함수를 reduce를 통해 하나의 DataBuffer로 결합할 때 까지 동작한다.
reduce는 두 개의 값을 하나로 합치는 동작을 하는데 DataBuffer 클래스의 write 메소드를 통해((1번 째 + 2 번 째) + 3번 째) + ....
위와 같은 동작을 반복하게되고, 하나의 DataBuffer 객체로 반환하게된다.
즉 Flux를 Mono 로 변환하는 동작을 한다. 그리고 만약 서비스 계층에서 올바르지 않은 파일을 요청하게 된다면 Flux.empty()를 반환했을 텐데, 이를 처리하기 위해
.defaultIfEmpty 를 통해 404 처리를 진행해줬다.