본문 바로가기
Backend/Spring(활용)

HDFS WebHDFS API를 활용한 대용량 파일 다운로드 구현 (Spring Boot + Vue.js 예제)

by 재성스 2025. 2. 13.
반응형

 

 

1. HDFS WebHDFS란?

WebHDFS는 HTTP 프로토콜을 사용하여 HDFS에 접근할 수 있도록 하는 API다.
이를 활용하면 Java의 Hadoop 라이브러리를 직접 사용하지 않고도 RESTful API를 통해 데이터를 다룰 수 있다.

WebHDFS API 주요 특징

  • HTTP 요청을 통해 HDFS 파일 읽기 및 쓰기 가능
  • 보안 설정을 통해 사용자 인증 및 권한 관리 지원
  • 데이터 노드(Datanode)로 리디렉션(307 Redirect)하여 효율적으로 파일 전송

 

2. HDFS WebHDFS API를 활용한 파일 다운로드 개요

HDFS에서 파일을 다운로드하는 기본적인 과정은 다음과 같다:

  1. GET 요청을 WebHDFS에 보낸다.
  2. HDFS는 307 Temporary Redirect 응답을 반환하며, 데이터 노드(Datanode) URL을 제공한다.
  3. 프론트엔드 또는 백엔드는 리디렉션된 URL을 따라 파일을 다운로드한다.

Tip: noredirect=true 옵션을 추가하면 307 리디렉션 없이 직접 다운로드 URL을 반환할 수 있다.


 

3. Spring Boot에서 HDFS 파일 다운로드 구현

Spring Boot 백엔드에서 HDFS 파일 다운로드를 처리하기 위해 FeignClient를 활용.

HDFS FeignClient 설정

@FeignClient(name = "hdfsClient", url = "${hdfs.base-url}")
public interface HdfsFeignClient {

    // 파일 존재 여부 확인
    @GetMapping(value = "/webhdfs/v1/{filePath}", produces = MediaType.APPLICATION_JSON_VALUE)
    ResponseEntity<Map<String, Object>> checkFileExists(
            @PathVariable("filePath") String filePath,
            @RequestParam("op") String operation,
            @RequestParam("user.name") String userName
    );

    // 파일 다운로드 (리디렉션 없이 JSON으로 URL 받기)
    @GetMapping(value = "/webhdfs/v1/{filePath}", produces = MediaType.APPLICATION_JSON_VALUE)
    ResponseEntity<Map<String, String>> getDownloadUrl(
            @PathVariable("filePath") String filePath,
            @RequestParam("op") String operation,
            @RequestParam("user.name") String userName,
            @RequestParam("noredirect") boolean noRedirect
    );
}
 

파일 다운로드 서비스 구현

@Service
public class HdfsFileService {

    @Autowired
    private HdfsFeignClient hdfsFeignClient;

    private static final String HDFS_USER = "hdfs";

    public ResponseEntity<Resource> downloadFile(String filePath) {
        // 1️⃣ 파일 존재 여부 확인
        ResponseEntity<Map<String, Object>> checkResponse = hdfsFeignClient.checkFileExists(filePath, "GETFILESTATUS", HDFS_USER);
        if (checkResponse.getStatusCode() == HttpStatus.NOT_FOUND) {
            throw new RuntimeException("파일이 존재하지 않습니다: " + filePath);
        }

        // 2️⃣ WebHDFS에서 직접 다운로드 URL 가져오기
        ResponseEntity<Map<String, String>> response = hdfsFeignClient.getDownloadUrl(filePath, "OPEN", HDFS_USER, true);
        if (response.getStatusCode() != HttpStatus.OK || !response.getBody().containsKey("Location")) {
            throw new RuntimeException("HDFS 다운로드 URL을 가져오지 못했습니다.");
        }

        String downloadUrl = response.getBody().get("Location");

        // 3️⃣ URL을 통해 파일 다운로드 및 스트림 반환
        try {
            HttpURLConnection connection = (HttpURLConnection) new URL(downloadUrl).openConnection();
            connection.setRequestMethod("GET");

            InputStream inputStream = connection.getInputStream();
            InputStreamResource resource = new InputStreamResource(inputStream);

            return ResponseEntity.ok()
                    .contentType(MediaType.APPLICATION_OCTET_STREAM)
                    .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + filePath.substring(filePath.lastIndexOf("/") + 1))
                    .body(resource);
        } catch (IOException e) {
            throw new RuntimeException("HDFS 파일 다운로드 실패", e);
        }
    }
}
 

컨트롤러

@RestController
@RequestMapping("/hdfs")
public class HdfsFileController {

    @Autowired
    private HdfsFileService hdfsFileService;

    @GetMapping("/download/{filePath}")
    public ResponseEntity<Resource> downloadHdfsFile(@PathVariable String filePath) {
        return hdfsFileService.downloadFile(filePath);
    }
}

 

4. Vue.js 프론트엔드에서 파일 다운로드 처리

Vue.js 프론트엔드는 axios를 활용하여 백엔드에서 파일을 가져오고, Blob 객체를 사용해 사용자에게 다운로드 링크를 제공한다.

파일 다운로드 함수

import axios from "axios";

const downloadFile = async (filePath) => {
    try {
        const response = await axios.get(`/hdfs/download/${filePath}`, {
            responseType: "blob"
        });

        // 파일 다운로드 처리
        const blob = new Blob([response.data]);
        const url = window.URL.createObjectURL(blob);
        const link = document.createElement("a");

        // Content-Disposition 헤더에서 파일명 가져오기
        let fileName = "downloaded_file";
        const contentDisposition = response.headers["content-disposition"];
        if (contentDisposition) {
            const matches = contentDisposition.match(/filename\*?=(UTF-8'')?(.+)/);
            if (matches) {
                fileName = decodeURIComponent(matches[2].replace(/["']/g, ""));
            }
        }

        link.href = url;
        link.setAttribute("download", fileName);
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
        window.URL.revokeObjectURL(url);
    } catch (error) {
        console.error("파일 다운로드 실패", error);
    }
};

 

5. 정리 및 최적화 고려 사항

 

✅ noredirect=true를 사용하여 WebHDFS에서 직접 다운로드 URL을 받을 수 있음
✅ 대용량 파일 다운로드 시 InputStreamResource를 활용하여 메모리 효율을 높임
✅ Vue.js에서 Content-Disposition 헤더를 파싱하여 파일명을 정상적으로 표시
✅ 보안 및 성능 최적화를 위해 캐싱 전략, 파일 크기 제한, 인증 방식을 고려해야 함

반응형