diff --git a/src/main/java/com/kamco/cd/kamcoback/common/download/RangeDownloadResponder.java b/src/main/java/com/kamco/cd/kamcoback/common/download/RangeDownloadResponder.java
index a5a6cb5a..01cd0d24 100644
--- a/src/main/java/com/kamco/cd/kamcoback/common/download/RangeDownloadResponder.java
+++ b/src/main/java/com/kamco/cd/kamcoback/common/download/RangeDownloadResponder.java
@@ -5,7 +5,6 @@ import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
-import java.util.Map;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.ResourceRegion;
@@ -15,43 +14,11 @@ import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
-/**
- * Range(이어받기) 지원 파일 다운로드 응답 생성기.
- *
- *
- Range 헤더 없으면 200 + Resource - Range 헤더 있으면 206 + ResourceRegion - 잘못된 Range면 416 +
- * Content-Range: bytes
- *
- *
주의: - 다중 Range는 첫 번째 Range만 처리(안정성 목적). - 파일이 이미 생성되어 있는 대용량(zip 등) 다운로드에 적합.
- */
@Component
public class RangeDownloadResponder {
- /** 기본(zip) 다운로드 응답 생성. */
public ResponseEntity> buildZipResponse(
Path filePath, String downloadFileName, HttpServletRequest request) throws IOException {
- return buildResponse(
- filePath, downloadFileName, MediaType.APPLICATION_OCTET_STREAM, request, Map.of());
- }
-
- /** 추가 헤더가 필요한 경우(예: X-Accel-Buffering) 사용. */
- public ResponseEntity> buildZipResponse(
- Path filePath,
- String downloadFileName,
- HttpServletRequest request,
- Map extraHeaders)
- throws IOException {
- return buildResponse(
- filePath, downloadFileName, MediaType.APPLICATION_OCTET_STREAM, request, extraHeaders);
- }
-
- /** 콘텐츠 타입까지 지정하고 싶을 때 사용. */
- public ResponseEntity> buildResponse(
- Path filePath,
- String downloadFileName,
- MediaType contentType,
- HttpServletRequest request,
- Map extraHeaders)
- throws IOException {
if (!Files.isRegularFile(filePath)) {
return ResponseEntity.notFound().build();
@@ -60,73 +27,53 @@ public class RangeDownloadResponder {
long totalSize = Files.size(filePath);
Resource resource = new FileSystemResource(filePath);
- // 공통 헤더(200/206 공통)
String disposition = "attachment; filename=\"" + downloadFileName + "\"";
String rangeHeader = request.getHeader(HttpHeaders.RANGE);
- if (rangeHeader == null || rangeHeader.isBlank()) {
- // 200 OK (전체 다운로드)
- ResponseEntity.BodyBuilder ok =
- ResponseEntity.ok()
- .contentType(contentType)
- .header(HttpHeaders.CONTENT_DISPOSITION, disposition)
- .header(HttpHeaders.ACCEPT_RANGES, "bytes")
- .contentLength(totalSize);
+ // 🔥 공통 헤더 (여기 고정)
+ ResponseEntity.BodyBuilder base =
+ ResponseEntity.ok()
+ .contentType(MediaType.APPLICATION_OCTET_STREAM)
+ .header(HttpHeaders.CONTENT_DISPOSITION, disposition)
+ .header(HttpHeaders.ACCEPT_RANGES, "bytes")
+ .header("X-Accel-Buffering", "no");
- applyExtraHeaders(ok, extraHeaders);
- return ok.body(resource);
+ if (rangeHeader == null || rangeHeader.isBlank()) {
+ return base.contentLength(totalSize).body(resource);
}
- // Range 파싱
List ranges;
try {
ranges = HttpRange.parseRanges(rangeHeader);
} catch (IllegalArgumentException ex) {
- // 416 Range Not Satisfiable
- ResponseEntity.BodyBuilder badRange =
- ResponseEntity.status(416).header(HttpHeaders.CONTENT_RANGE, "bytes */" + totalSize);
- applyExtraHeaders(badRange, extraHeaders);
- return badRange.build();
+ return ResponseEntity.status(416)
+ .header(HttpHeaders.CONTENT_RANGE, "bytes */" + totalSize)
+ .header("X-Accel-Buffering", "no")
+ .build();
}
- // 실무에선 단일 Range만 처리하는 게 안전합니다.
HttpRange range = ranges.get(0);
long start = range.getRangeStart(totalSize);
long end = range.getRangeEnd(totalSize);
if (start >= totalSize) {
- ResponseEntity.BodyBuilder badRange =
- ResponseEntity.status(416).header(HttpHeaders.CONTENT_RANGE, "bytes */" + totalSize);
- applyExtraHeaders(badRange, extraHeaders);
- return badRange.build();
+ return ResponseEntity.status(416)
+ .header(HttpHeaders.CONTENT_RANGE, "bytes */" + totalSize)
+ .header("X-Accel-Buffering", "no")
+ .build();
}
long regionLength = end - start + 1;
ResourceRegion region = new ResourceRegion(resource, start, regionLength);
- // 206 Partial Content
- ResponseEntity.BodyBuilder partial =
- ResponseEntity.status(206)
- .contentType(contentType)
- .header(HttpHeaders.CONTENT_DISPOSITION, disposition)
- .header(HttpHeaders.ACCEPT_RANGES, "bytes")
- .header(HttpHeaders.CONTENT_RANGE, "bytes " + start + "-" + end + "/" + totalSize)
- .contentLength(regionLength);
-
- applyExtraHeaders(partial, extraHeaders);
- return partial.body(region);
- }
-
- private void applyExtraHeaders(
- ResponseEntity.HeadersBuilder> builder, Map extraHeaders) {
- if (extraHeaders == null || extraHeaders.isEmpty()) return;
-
- extraHeaders.forEach(
- (k, v) -> {
- if (k != null && !k.isBlank() && v != null) {
- builder.header(k, v);
- }
- });
+ return ResponseEntity.status(206)
+ .contentType(MediaType.APPLICATION_OCTET_STREAM)
+ .header(HttpHeaders.CONTENT_DISPOSITION, disposition)
+ .header(HttpHeaders.ACCEPT_RANGES, "bytes")
+ .header("X-Accel-Buffering", "no")
+ .header(HttpHeaders.CONTENT_RANGE, "bytes " + start + "-" + end + "/" + totalSize)
+ .contentLength(regionLength)
+ .body(region);
}
}
diff --git a/src/main/java/com/kamco/cd/kamcoback/inference/InferenceResultApiController.java b/src/main/java/com/kamco/cd/kamcoback/inference/InferenceResultApiController.java
index 3fc6a081..91ab9525 100644
--- a/src/main/java/com/kamco/cd/kamcoback/inference/InferenceResultApiController.java
+++ b/src/main/java/com/kamco/cd/kamcoback/inference/InferenceResultApiController.java
@@ -374,8 +374,7 @@ public class InferenceResultApiController {
Path zipPath = Path.of(path);
// Range + 200/206/416 공통 처리 (추가 헤더 포함)
- return rangeDownloadResponder.buildZipResponse(
- zipPath, uid + ".zip", request, Map.of("X-Accel-Buffering", "no"));
+ return rangeDownloadResponder.buildZipResponse(zipPath, uid + ".zip", request);
}
@Operation(summary = "shp 파일 다운로드 이력 조회", description = "추론관리 분석결과 shp 파일 다운로드 이력 조회")