sitelink1 https://developer.mozilla.org/en-US/docs/Web/API/FileReader 
sitelink2 https://developer.mozilla.org/en-US/docs/Web/API/FormData 
sitelink3 https://velog.io/@wish/%EC%84%9C%EB%B2%8...4%EA%B8%B0 
sitelink4 https://velog.io/@hye_rin/React-%EC%84%9...4%EA%B8%B0 
extra_vars4 https://velog.io/@hye_rin/React-%EC%84%9C%EB%B2%84%EC%97%90-form-%EB%B3%B4%EB%82%B4%EA%B8%B0 
extra_vars5 https://velog.io/@hye_rin/React-%EC%84%9C%EB%B2%84%EC%97%90-form-%EB%B3%B4%EB%82%B4%EA%B8%B0 
extra_vars6 https://velog.io/@hye_rin/React-%EC%84%9C%EB%B2%84%EC%97%90-form-%EB%B3%B4%EB%82%B4%EA%B8%B0 

 

<input id="file-input" type="file" multiple="multiple" class="form-control" placeholder="이미지" accept="image/*">

 

위와 같이 여러개의 파일을 선택하는 html 을 작성하고 다음과 같이 ajax 로 파일을 전송하면 된다

 

const xhr = new XMLHttpRequest();

xhr.open("POST", "/upload");

xhr.setRequestHeader("Content-Type", "multipart/form-data");

const reader = new FileReader();

reader.readAsDataURL(document.querySelector("#file-input").files[0]);

reader.onload = function () {

    const formData = new FormData();

    formData.append("file", reader.result);

    formData.append("name", "John Doe");

    formData.append("age", 30);

    xhr.send(formData);

};

xhr.onreadystatechange = function () {

    if (xhr.readyState === XMLHttpRequest.DONE) {

        if (xhr.status === 200) {

            console.log("파일 업로드 성공");

        } else {

            console.log("파일 업로드 실패");

        }

    }

};

 

위 코드는 다음과 같이 작동한다.

  1. XMLHttpRequest 객체를 생성합니다.
  2. open() 메서드를 사용하여 요청을 열고, 요청 방법, 요청 URL, 요청 헤더를 지정합니다.
  3. setRequestHeader() 메서드를 사용하여 요청 헤더를 추가합니다.
  4. reader 객체를 사용하여 파일 객체를 생성합니다.
  5. readAsDataURL() 메서드를 사용하여 파일 객체를 읽습니다.
  6. onload 이벤트 핸들러를 사용하여 파일 객체가 읽히면, formData 객체를 생성합니다.
  7. append() 메서드를 사용하여 formData 객체에 파일 객체와 다른 파라미터를 추가합니다.
  8. send() 메서드를 사용하여 formData 객체를 전송합니다.
  9. onreadystatechange 이벤트 핸들러를 사용하여 응답 상태를 수신합니다.
  10. responseText 속성을 사용하여 응답 데이터를 수신합니다.

출처 :: Bard 

 

 

 

그런데 위와 같이 script 를 작성해서 데이터를 보냈는데

commons fileupload 라이브러리를 사용하는 서버에서 파일을 받으려 하니 다음과 같은 오류가 발생했다.

"the request was rejected because no multipart boundary was found"

오류 발생 지점은 ServletFileUpload.parseRequest() 함수 수행시이다.

 

그래서 Bing 에게 물어보니 새로운 예제 코드를 알려주는데...

script 에서 formData.append("file", reader.result); 라인을 

formData.append('file', document.querySelector("#file-input").files[0]); 으로 고치면 오류가 사라진다.

 

그렇게 수정 적용하여 테스트 성공한 java 코드와 javascript 코드는 다음과 같다.

 

[java]

    private static final long serialVersionUID = 1L;

    private static final long SIZE_LIMIT = 100000 * 1024 * 1024L; // 업로드 사이즈 제한. 10000M

    private static final int TEMP_SIZE_LIMIT = 100 * 1024; // 업로드시 사용할 임시 메모리 제한. 100K

    private DiskFileItemFactory factory;

    private ServletFileUpload upload; // 팩토리로 부터 서블릿 파일업로드 객체 얻기

 

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

 

        if (ServletFileUpload.isMultipartContent(request)) {

            FileItemFactory factory = new DiskFileItemFactory();

            ServletFileUpload upload = new ServletFileUpload(factory);

            try {

                List<FileItem> items = upload.parseRequest(request);

                for (FileItem item : items) {

                    if (item.isFormField()) {

                        // Process form field

                        String fieldName = item.getFieldName();

                        String fieldValue = item.getString();

                        System.out.println("@ " + fieldName + " / " + fieldValue);

                    } else {

                        // Process uploaded file

                        String fieldName = item.getFieldName();

                        String fileName = item.getName();

                        InputStream fileContent = item.getInputStream();

                        System.out.println("# " + fieldName + " / " + fileName);

                    }

                }

            } catch (Exception e) {

                e.printStackTrace();

            }

        }

    }

 

    private void procSample1(HttpServletRequest request, HttpServletResponse response) {

 

        System.out.println("업로드 프로세스");

        System.out.println("* 업로드 시작");

 

        try {

 

            /** Initialize **/

            String contextRealPath = request.getSession().getServletContext().getRealPath("/");

            String savePath = contextRealPath + "upfolder";

            factory = new DiskFileItemFactory();

            System.out.println("* Factory 취득");

 

            // 바로 디스크에 저장되는 것이 아니라 메모리에 먼저 저장을 해둔다.

            factory.setSizeThreshold(TEMP_SIZE_LIMIT); // 임시 업로드할 사이즈를 제한한다.

            makeDirs(savePath + "/temp");

            factory.setRepository(new File(savePath + "/temp")); // 임시 디렉토리를 지정한다.

            upload = new ServletFileUpload(factory); // 업로드 객체를 얻는다.

            upload.setSizeMax(SIZE_LIMIT); // 최대 업로드 사이즈를 지정한다.

            upload.setHeaderEncoding("UTF-8"); // 파일명을 인코딩해준다.

 

            List items = upload.parseRequest(request); // 아이템을 얻는다.

            System.out.println("* 아이템을 가져온다");

 

            Iterator iter = items.iterator(); // iterator로 변경한다.

            List savedFiles = new ArrayList();

            System.out.println("* 아이템에서 파일 추출 및 저장 시작");

 

            try {

                while (iter.hasNext()) {

 

                    FileItem item = (FileItem) iter.next(); // 아이템 얻기

 

                    if (item.isFormField()) { // 파라미터면

 

                        String fieldName = item.getFieldName(); // 필드명을 얻는다.

                        String value = item.getString("UTF-8");

                        System.out.println("* fieldName=" + fieldName + ", value=" + value);

 

                    } else { // 파일이면

 

                        if (item.getSize() > 0) {

                            String name = item.getName(); // 파일명 얻기

                            String fileName = name.substring(name.lastIndexOf("\\") + 1);// 파일명을 얻는다.

                            long fileSize = item.getSize(); // 파일 사이즈를 얻는다.

 

                            System.out.println("fileName - " + fileName);

                            System.out.println("fileSize - " + fileSize);

 

                            File file = new File(savePath + "/" + fileName); // 기본경로+파일명으로 생성한다.

                            item.write(file); // 파일 저장.

                        }

 

                    }

 

                } // while

 

                System.out.println("* 아이템에서 파일 추출 및 저장 끝");

 

            } catch (Exception e) {

                System.out.println(e);

            }

        } catch (Exception e) {

            System.out.println("에러발생:" + e);

        }

    }// doPost

 

    private boolean makeDirs(String filepath) {

        boolean rtnVal = false;

        File f = new File(filepath);

        rtnVal = f.mkdirs();

        f = null;

        return rtnVal;

    }

 

[javascript]

window.document.querySelector("#send_file").addEventListener('click', () => {

    var _ajax = new XMLHttpRequest();

    sendMultipart(_ajax, 

        "/TestFileupload", 

        window.document.querySelector("#file-input").files, 

        params);

}, false);

 

const sendMultipart = function (_ajax, path, filesObj, params) {

    _ajax.open("POST", path, true);

    //_ajax.setRequestHeader("Content-Type", "multipart/form-data; boundary=boundary_value");

    try {

        const promises = [];

        const formData = new FormData();

 

        for (const fObj of filesObj) {

            promises.push(new Promise((resolve, reject) => {

                const fileReader = new FileReader();

        

                fileReader.readAsDataURL(fObj);

        

                fileReader.onload = function () {

                    formData.append("file", fObj);

                    resolve(fileReader.result);

                };

        

                fileReader.onerror = function () {

                    reject(fileReader.error);

                };

            }));

        }

        

        Promise.all(promises).then((results) => {

            console.log(results);

            for (let p of params) {

                console.log(p.name + "-" + p.value);

                formData.append(p.name, p.value);

            }

            _ajax.send(formData);

        });

    } catch (e) {

        console.error(e.message);

        if (e.message && e.message.indexOf("0x80004005") > -1) {

            return;

        }

    }

};

 

수정된 javascript 의 코드중 주석으로 된 부분에 대해 부연 설명하자면

 

_ajax.setRequestHeader("Content-Type", "multipart/form-data"); 코드를 수행하면 'no multipart boundary was found' 오류 메세지가 출력된다.

"Content-Type" 을 "multipart/form-data; boundary=boundary_value" 식으로 지정하여 전송하면 오류는 없어지지만 java 에서 파일 취득이 되지 않는다.

해당 라인을 아예 삭제해야만 파일을 취할 수 있다. (다른 브라우저에서도 정상 동작하는지는 확인 안함, 오류는 없어서 분석을 중단하고 그냥 그대로 진행했다) 

 

그리고 File 을 FormData 에 append 할때 formData.append("file", freader.result); 식으로 전달하면

java 에서 해당 값을 파일 바이너리가 아닌 파라미터로 인식 처리한다.

스펙상 FileReader.readAsDataURL() 함수는 FileReader가 지정한 파일을 base64 인코딩된 데이터 URL로 읽는다. (sitelink1 참고)

그렇다고 FileReader.readAsBinaryString(); 함수로 대체하여도 큰 차이는 없다.

다만 파일 전송시 브라우저 디버그에서 file 의 base64 인코딩 값이 출력되는데 이는 관계가 없어보이고

중요한건 FormData.append("file", fObj); 시 file 객체 자체를 넘겨야만 한다.

FileReader.result 값으로 append 하여 전송하면 서버의 FileUpload.parseRequest() 가 올바르게 수행되지 않는다.

 

마지막으로 sitelink3, sitelink4 는 이번 삽질에서 꽤나 정리가 잘된 블로그들을 발견하였기에 링크를 저장하였다.

FormData 와 FileReader 를 활용하는데 참고할만한 내용들이 나온다.

 

 

번호 제목 글쓴이 날짜 조회 수
237 json 데이터 내의 변수명에 prefix 로 type 표현하기 황제낙엽 2024.04.15 0
236 fetch() 함수 사용 예제 file 황제낙엽 2023.11.23 1
235 현재 document 의 host 와 port 를 얻는 방법 황제낙엽 2023.10.03 1
234 (Bard) FileReader 로 여러개의 파일을 read 하는 법 file 황제낙엽 2023.08.23 0
233 How to build a file upload service with vanilla JavaScript file 황제낙엽 2023.08.22 0
232 (Bard) JavaScript로 JSON 배열을 작성하는 방법 황제낙엽 2023.08.21 0
» 모바일 브라우저에서 file input element 를 이용하여 여러장의 이미지를 서버에 전송하려 할때 황제낙엽 2023.08.21 0
230 navigator.mediaDevices 황제낙엽 2023.08.21 1
229 Barcode Detection API 황제낙엽 2023.08.06 6
228 정규식을 이용한 이메일 검증 스크립트 file 황제낙엽 2023.06.25 0
227 체크박스에 체크된 항목 개수 구하기 황제낙엽 2023.06.10 1
226 JSON 클래스가 지원하는 function 황제낙엽 2023.03.31 1
225 (Copilot) JSON 객체의 내부 데이터 리스트 길이를 구하는 방법 황제낙엽 2023.03.30 2
224 배열에 대한 루프문 조회 (loop iterator) 황제낙엽 2023.03.01 3
223 (Copilot) 바닐라 스크립트가 뭐지? 황제낙엽 2023.02.24 7
222 CryptoJS 를 이용한 암호화 황제낙엽 2023.02.15 1
221 [URLSearchParams] URL 파라미터(매개변수) 값 가져오기 file 황제낙엽 2023.02.02 0
220 Fetch API (CORS 극복을 위한 노력) 황제낙엽 2021.12.05 26
219 두 서버의 자원을 접근하는 클라이언트 프레임웍(Next.js)에서의 CORS오류 file 황제낙엽 2021.12.05 228
218 CORS 의 내용과 이에 대한 우회 방안들 file 황제낙엽 2021.12.05 139