파일 업로드 시 content type application/octet-stream not supported 에러 (415 Error)

김동건·2025년 1월 9일
0

기존에 있던 등록창에서 파일 업로드 기능을 새로 추가해야해 코드를 리팩토링 하였다.

기존에 있던 데이터들은 json 타입으로 넘어간다.

var data = Object.assign($("#formAdd").serializeObject(), {
    gatewayCluster: $("#gatewayCluster").val(),
    status: "ENABLED",
    methods: makeMethods(),
    apiPolicy: $("#apiPolicy").val(),
    billPolicy: $("#billPolicy").val(),
    service: $("#service").val(),
    engineApi: $("#engineApi").val(),
    customRouteUri: $("#customRouteUri").val(),
    extras: makeSpringProperties(),
    scopes: $scopes,
    keywords: $("#keywords").val()
});

formData.append("request", JSON.stringify(data));

여기에 파일 업로드를 추가해야하는데, 파일은 multipart/form-data로 넘겨야 하기 때문에, 따로 formData를 추가했다.

function completeRegistration() {
        // 1) 중복 체크 및 데이터 검증
        if (globalVars.dupCheckFlag === "N") {
            alert("오픈 API URI 중복 체크를 진행 해주세요");
            return false;
        }
        if (!dataValidation()) return false;

        // 2) JSON 데이터 구성
        var $scopes = [];
        if ($("#scopeList").find(":checked").val() !== "") {
            $scopes = $("#scopeList").find(":checked").map(function (i, d) {
                return { id: d.value };
            }).toArray();
        }

        var data = Object.assign($("#formAdd").serializeObject(), {
            gatewayCluster: $("#gatewayCluster").val(),
            status: "ENABLED",
            methods: makeMethods(),
            apiPolicy: $("#apiPolicy").val(),
            billPolicy: $("#billPolicy").val(),
            service: $("#service").val(),
            engineApi: $("#engineApi").val(),
            customRouteUri: $("#customRouteUri").val(),
            extras: makeSpringProperties(),
            scopes: $scopes,
            keywords: $("#keywords").val()
        });

        if (!confirm("편집 내용을 저장하시겠습니까?")) return;

        // 3) 파일 가져오기
        const file = document.getElementById("fileInput").files[0];
        const formData = new FormData();

        // 4) 파일 존재 시 추가
        if (file) {
            formData.append("uploadedFile", file);
        } else if (globalVars.currentFileId) {
            // 기존 파일 유지
            data.fileId = globalVars.currentFileId;
        }
		
  		// 5) 기존 json 추가
        formData.append("request", JSON.stringify(data));

        // 6) Ajax multipart/form-data 요청
        $.ajax({
            url: "/my-url",  // 정확한 백엔드 경로 확인
            type: "POST",
            data: formData,
            processData: false,    // jQuery가 데이터를 처리하지 않도록 설정
            contentType: false,    // jQuery가 Content-Type 헤더를 설정하지 않도록 설정
            success: function (d) {
                if (d.error) {
                    alert("오류가 발생했습니다.\n" + d.message);
                    return false;
                }
                alert("성공적으로 처리되었습니다.");
                $(".m_modal").removeClass("on");
                $("body").css("overflow", "auto");
                searchPart();  // 예: 목록 갱신 함수
            },
            error: function (xhr, status, err) {
                alert("업로드 중 오류 발생: " + err);
            }
        });
    }

하지만....

2025-01-09 14:11:21.496 WARN 1 --- [io-8080-exec-10] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'application/octet-stream' not supported]

백엔드 Controller에서 내가 생각한 multipart/form-data로 넘어가지 않는 듯 했다.

이슈

스프링에서 이미지 업로드를 할때 Json 데이터를 Multipart로 같이 보내는 경우 Content-Type을 application/json으로 명시해줘주지 않으면 application/octet-stream not supported 예외가 발생

해결법

JSON.stringify를 통해 javascript object를 생성하고 blob 객체의 타입을 application/json으로 만들어서 formdata에 append를 한다.

아래의 코드를

formData.append("request", JSON.stringify(data));

이렇게 수정

formData.append("request", new Blob([JSON.stringify(data)], { type: "application/json" }), "request.json");

Spring Controller에도

    @PostMapping(
            value = "/my-url",
            consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
            produces = MediaType.APPLICATION_JSON_VALUE
    )

명시적으로 consumesproduces를 삽입한다.

Content-Type 헤더와 Consumes 설정

Consumes

클라이언트가 서버에게 보내는 데이터 타입을 명시한다.

@RequestMappingconsumes 설정과 Content-Type request

헤더가 일치할 경우에 URL이 호출된다.

Content-Type

HTTP 메시지(요청과 응답 모두)에 담겨 보내는 데이터 형식을 알려주는 헤더이다. 즉, 현재 리퀘스트 또는 리스폰스의 바디에 들어 있는 데이터가 어떤 타입인지를 나타낸다.

HTTP 요청이 GET방식인 경우

무조건 URL 끝에 쿼리스트링(key=value) 형식이기 때문에

Content-Type 헤더가 굳이 필요없으므로 Content-TypePOST방식이나 PUT방식처럼 BODY에 데이터를 싣어 보낼 때 중요하다.

이제 매핑이 된다....!!!!

profile
백엔드를 공부하고 있는 초보 개발자입니다.

0개의 댓글

관련 채용 정보