Translate

[Axios] Multipart 파일 업로드 (with Vue.js)





프로젝트 시 Multipart로 파일과 JSON데이터를 업로드 해야하는 요구사항이 있어 제작한 모듈을 정리한다.


API 모듈 파일
export function requestMultipartJsonObject (requestObj) {
  // _.merge - Lodash 라이브러리
  let reqHeaders = store.state.api.common // 공통적으로 사용하는 헤더정보
  _.merge(reqHeaders, requestObj.headers) // 요청 시 설정한 헤더 override
  _.merge(reqHeaders, { 'enctype': 'multipart/form-data' }) // 헤더에 multipart 추가

  const formData = new FormData()

  // data JsonObject 처리
  formData.append('jsonData', JSON.stringify(requestObj.data))

  // files 처리
  // requestObj.files - (file type)HTMLInputElement 배열
  if (requestObj.files) {
    requestObj.files
      .filter((file) => file.name && file.file) // 파일선택되지 않은 Input Element제외
      .forEach((file) => formData.append(file.name, file.file))
  }

  return axios({
    url: requestObj.url,
    headers: reqHeaders,
    method: 'post',
    data: formData
  }).then(res => {
    requestObj.callback(res.data)
  }).catch(res => {
    // status 200이 아닌경우에도 응답값을 전달 (각 프로젝트에 맞게 수정필요)
    requestObj.callback(res.response.data)
  })
}



유틸파일
/**
 * input(File) Element배열을 requestApi 에서 사용하는 형식으로 반환
 * @param {Array of Input File Element} fileArray
 */
function getRequestFileArray (fileArray) {
  let reqFileArray = []

  verifyFileArray(fileArray)
    .map((fileObj) => {
      return fileObj.files
    })
    .filter((fileList) => {
      return fileList.length > 0
    })
    .forEach((fileList) => {
      Array.from(fileList).forEach((file) => {
        reqFileArray.push({
          name: file.name,
          file: file
        })
      })
    })

  return reqFileArray
}

/**
 * verify Array of Input File Element
 * @param {Array of Input File Element} fileArray
 */
function verifyFileArray (fileArray) {
  if (fileArray instanceof Array) {
    fileArray.filter((fileObj) => {
      if (fileObj.files instanceof FileList === false) {
        console.error('fileObj is not FileList', fileObj)
      }
      return fileObj.files instanceof FileList
    })
    return fileArray
  } else {
    console.error('fileArray object is not Array', fileArray)
    return []
  }
}


1004lucifer
사용파일 (vue)
requestMultipartJsonObject({
  url: this.fileUploadUrl, // required
  headers: { // required
    'CUSTOM-HEADER': 'custom-value' // required
  },
  callback: (data) => { // required
    console.log('data: ', data)
    this.$refs.resData3.innerHTML = JSON.stringify(data)
  },
  data: { // optional (전달할 JSON 데이터)
    'dummyKey': 'dummyValue'
  },
  // optional
  // 해당 input(file) Element 에 파일이 선택되지 않으면
  // 알아서 거르고 선택된 파일만 올라간다.
  files: getRequestFileArray([
    this.$refs.file1, // <input ref="file1" type="file" />
    this.$refs.file2
  ])
})






Fiddler를 이용해 위의 모듈을 사용해 JSON데이터와 파일2개를 업로드하면 아래와 같이 패킷이 서버로 전송된다.
1004lucifer

POST http://localhost:8080/UploadFile HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Content-Length: 3761
Origin: http://localhost:8080
enctype: multipart/form-data
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarydM2WhcM1tdaZ1o1n
Accept: application/json, text/plain, */*
Referer: http://localhost:8080/
Accept-Encoding: gzip, deflate, br
Accept-Language: ko,ja;q=0.9,ko-KR;q=0.8,en-US;q=0.7,en;q=0.6

------WebKitFormBoundarydM2WhcM1tdaZ1o1n
Content-Disposition: form-data; name="jsonData"

{"dummyKey":"dummyValue"}
------WebKitFormBoundarydM2WhcM1tdaZ1o1n
Content-Disposition: form-data; name="form-check-sign.png"; filename="form-check-sign.png"
Content-Type: image/png

 PNG
 
   
IHDR       
       o     pHYs  
   
          iTXtXML:com.adobe.xmp     <?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?> <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 5.6-c145 79.163499, 2018/08/13-16:40:22        "> <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> <rdf:Description rdf:about="" xmlns:xmp="http://ns.adobe.com/xap/1.0/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/" xmlns:stRef="http://ns.adobe.com/xap/1.0/sType/ResourceRef#" xmlns:stEvt="http://ns.adobe.com/xap/1.0/sType/ResourceEvent#" xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/" xmp:CreatorTool="Adobe Photoshop CS6 (Windows)" xmp:CreateDate="2019-04-02T00:24:40+09:00" xmp:ModifyDate="2019-04-08T22:38:07+09:00" xmp:MetadataDate="2019-04-08T22:38:07+09:00" dc:format="image/png" xmpMM:InstanceID="xmp.iid:4c6454d0-90a7-0243-96fc-139a0d4e5ee2" xmpMM:DocumentID="xmp.did:53D94762549211E99E16AF5DA6F340C4" xmpMM:OriginalDocumentID="xmp.did:53D94762549211E99E16AF5DA6F340C4" photoshop:ColorMode="3" photoshop:ICCProfile="sRGB IEC61966-2.1"> <xmpMM:DerivedFrom stRef:instanceID="xmp.iid:53D9475F549211E99E16AF5DA6F340C4" stRef:documentID="xmp.did:53D94760549211E99E16AF5DA6F340C4"/> <xmpMM:History> <rdf:Seq> <rdf:li stEvt:action="saved" stEvt:instanceID="xmp.iid:4c6454d0-90a7-0243-96fc-139a0d4e5ee2" stEvt:when="2019-04-08T22:38:07+09:00" stEvt:softwareAgent="Adobe Photoshop CC 2019 (Windows)" stEvt:changed="/"/> </rdf:Seq> </xmpMM:History> </rdf:Description> </rdf:RDF> </x:xmpmeta> <?xpacket end="r"?> H     IDAT(   1j a E ρ  E 
 rB T  I  n - Ig;i  aa     )   a   <8  { $ g  % ԴIvI6I     o8 K ?       Vx ' ) H    ԃN]      
 x 7 x 5  %MϜ L% E  Bc`鼼    k  Y     IEND B` 
------WebKitFormBoundarydM2WhcM1tdaZ1o1n
Content-Disposition: form-data; name="ico_cal.png"; filename="ico_cal.png"
Content-Type: image/png

 PNG
 
   
IHDR              | 0    tEXtSoftware Adobe ImageReadyq e<    iTXtXML:com.adobe.xmp     <?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?> <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 5.3-c011 66.145661, 2012/02/06-14:56:27        "> <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> <rdf:Description rdf:about="" xmlns:xmp="http://ns.adobe.com/xap/1.0/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/" xmlns:stRef="http://ns.adobe.com/xap/1.0/sType/ResourceRef#" xmp:CreatorTool="Adobe Photoshop CS6 (Windows)" xmp:CreateDate="2019-03-26T00:03:45+09:00" xmp:ModifyDate="2019-03-26T00:04:09-15:00" xmp:MetadataDate="2019-03-26T00:04:09-15:00" dc:format="image/png" xmpMM:InstanceID="xmp.iid:3DFC6C7A4F0F11E9B6248EFAA5EFA407" xmpMM:DocumentID="xmp.did:3DFC6C7B4F0F11E9B6248EFAA5EFA407"> <xmpMM:DerivedFrom stRef:instanceID="xmp.iid:3DFC6C784F0F11E9B6248EFAA5EFA407" stRef:documentID="xmp.did:3DFC6C794F0F11E9B6248EFAA5EFA407"/> </rdf:Description> </rdf:RDF> </x:xmpmeta> <?xpacket end="r"?>  L     IDATxڴ =H Q   ) AT 4 D a m
YԐ[   D-- LA  RcCCA    {  PM
AI         s   0?)= p {    s<    2 [{    nV L
#w w )rg XK    aܸ  n     ]xs  k  /  %\    Q(5 $      b   [ [  ~cEs 9 e| (   n <n v {uټ}\  Xߗ+=vt W
 @  WPV    Q 3r Y )de    Ԓq i   lH+n ^ؐ4z q? 5e
     q    n7OnR    T      I 7 W    < >   %vm     5k  X  y 2 - Nh_㚒 ~ ['&  D E Vx!b j  Պ jo==.  P  K    UN@T  \
   -]•  ;V         C'  [O W G    \: e>   }   <       IEND B` 
------WebKitFormBoundarydM2WhcM1tdaZ1o1n--



댓글