Skip to content

파일 업로드/다운로드 문제 해결 #74

@Tae4an

Description

@Tae4an

1. 파일 다운로드 시 CORS 및 파일 깨짐 문제

문제 상황

  • CORS 에러 발생: GCS의 signed URL을 통한 직접 다운로드 시도 시 CORS 에러 발생
  • 파일 깨짐 현상: 다운로드된 파일이 열리지 않거나 데이터가 손상됨

기존 코드

const handleDownload = async (fileName) => {
    try {
        const response = await downloadFile(fileName);
        const blob = await fetch(response.data.downloadUrl).then(r => r.blob());
        // ... 파일 다운로드 로직
    } catch (error) {
        console.error('Download failed:', error);
    }
};

시도한 해결 방법

  1. GCS CORS 설정 추가

    [
        {
            "origin": ["*"],
            "method": ["GET"],
            "responseHeader": ["Content-Type"],
            "maxAgeSeconds": 3600
        }
    ]
    • 결과: CORS 문제는 해결되었으나 파일 깨짐 현상 지속
  2. 프론트엔드에서 mode: 'no-cors' 설정

    await fetch(response.data.downloadUrl, { mode: 'no-cors' });
    • 결과: CORS 문제는 해결되었으나 파일 접근 불가

최종 해결 방법

1. 서버를 프록시로 사용하는 방식

백엔드 컨트롤러 수정:

@GetMapping("/download/{fileName}")
public ResponseEntity<Resource> downloadObject(
        @PathVariable String fileName,
        @RequestHeader("X-User-Id") String employeeId) throws GCSException {
    try {
        FileInfo fileInfo = fileInfoRepository.findByStoredFileNameAndUploader_EmployeeId(
            fileName, employeeId)
            .orElseThrow(() -> new GCSException("File not found or no permission"));

        Blob blob = storage.get(bucketName, fileName);
        ByteArrayResource resource = new ByteArrayResource(blob.getContent());

        return ResponseEntity.ok()
                .contentType(MediaType.parseMediaType(fileInfo.getContentType()))
                .contentLength(blob.getSize())
                .header(HttpHeaders.CONTENT_DISPOSITION,
                    "attachment; filename=\"" + fileInfo.getOriginalFileName() + "\"")
                .body(resource);
    } catch (Exception e) {
        throw new GCSException("Failed to download file", e);
    }
}

프론트엔드 다운로드 로직 수정:

export const downloadFile = async (fileName) => {
    try {
        const response = await gcsApi.get(`/download/${fileName}`, {
            responseType: 'blob'
        });

        const blob = new Blob([response.data], {
            type: response.headers['content-type']
        });

        const url = window.URL.createObjectURL(blob);
        const link = document.createElement('a');
        link.href = url;
        link.setAttribute('download', originalFileName);

        document.body.appendChild(link);
        link.click();

        window.URL.revokeObjectURL(url);
        link.remove();

    } catch (error) {
        console.error('Download failed:', error);
        throw error;
    }
};

2. 파일 업로드 크기 제한 문제

문제 상황

  • MaxUploadSizeExceededException 발생: 파일 크기가 Spring Boot의 기본 업로드 크기 제한을 초과함

에러 메시지

org.springframework.web.multipart.MaxUploadSizeExceededException: Maximum upload size exceeded

시도한 해결 방법

  1. FileValidator에서 maxFileSize 설정

    @Value("${file.max-size:10485760}")
    private long maxFileSize;
    • 결과: Spring의 Multipart 처리 전에 에러 발생
  2. application.yml에 파일 크기 제한 설정

    file:
      max-size: 52428800  # 50MB
    • 결과: 애플리케이션 레벨 검증은 통과했지만 여전히 Multipart 에러 발생

최종 해결 방법

Spring Boot의 Multipart 설정 추가:

spring:
  servlet:
    multipart:
      max-file-size: 50MB
      max-request-size: 50MB

file:
  max-size: 52428800  # 50MB

설정 항목 설명

  1. max-file-size: 단일 파일의 최대 크기
  2. max-request-size: 전체 multipart 요청의 최대 크기
  3. file.max-size: 애플리케이션 레벨에서의 파일 크기 제한

결론

파일 다운로드 문제

  • 해결 방법: 서버 프록시 방식을 사용하여 GCS와 클라이언트 간의 통신 문제 해결
  • 효과:
    • CORS 문제 해결
    • 파일 무결성 보장
    • 보안성 향상

파일 업로드 크기 제한 문제

  • 해결 방법: Spring Boot의 multipart 설정 추가
  • 효과:
    • 멀티파트 요청 처리 단계에서 파일 크기 제한 적용
    • 애플리케이션 레벨의 파일 크기 검증
    • 일관된 제한 설정으로 안정성 확보

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions