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 사용자 모듈 만들기 황제낙엽 2019.07.09 41735
236 User Agent 정보 모음 file 황제낙엽 2011.02.22 7768
235 페이지 스크롤 끝 확인 황제낙엽 2011.10.24 6230
234 숫자 여부와 자리수를 체크 하는 예제 황제낙엽 2009.01.12 5265
233 User Agent Parser들 황제낙엽 2017.11.20 4132
232 ActiveX 설치 여부를 검사하는 스크립트 황제낙엽 2011.02.13 4053
231 [JavaScript Tutorials] Handling runtime errors in JavaScript using try/catch/finally (해석중) 황제낙엽 2009.04.08 2784
230 브라우저의 새로고침과 종료에 대한 이벤트 황제낙엽 2017.08.11 2725
229 연속해서 스트림 받기 (flush data from servlet to jsp with ajax) 황제낙엽 2013.01.04 2427
228 오류:호출자(서버 응용 프로그램이 아닌 서버)가 사용될 수 없어서 사라졌습니다. file 황제낙엽 2012.03.14 1949
227 외부 라이브러리 (.js) 의 바람직하지 않은 동적 로딩 (eval함수 이용) 황제낙엽 2012.01.18 1851
226 window.postMessage 이해하기 file 황제낙엽 2017.10.16 1612
225 부동소수점 (floating-point) file 황제낙엽 2018.03.26 1122
224 javascirpt IME-Mode 설정하기 황제낙엽 2010.08.17 1112
223 경과 시간 구하기 황제낙엽 2019.10.04 1071
222 CORS(Cross-Origin Resource Sharing) - 4 file 황제낙엽 2017.03.07 873
221 각 브라우저 별 User Agent 정보 황제낙엽 2011.02.22 823
220 중첩 함수, 함수 클로저 황제낙엽 2008.08.12 820
219 자바스크립트의 쉬프트 연산자 (Shift Operator) 와 음수 (Negative) 이야기 황제낙엽 2012.05.31 726
218 Memory leak 및 성능 측정 도구 file 황제낙엽 2011.11.23 666