반응형

서버에 올리면 onLoad, onError event 둘다 안탈때가 있음 왜그런지는?

반응형
반응형

frontend

image upload시 서버 전송 하기 전에 frontend 로직진행하는 부분

<input
        accept="image/*"
        id={`input_${type}`}
        type="file"
        className="logo_preview_input"
        onChange={e => {
          if (e.target.files && e.target.files.length >= 1) {
            const reader = new FileReader();
            const img = e.target.files[0];
            reader.readAsDataURL(img);
            reader.onload = e2 => {
              setImg(e2.target.result);
              setImgName(img.name);
            };
          }
        }}
      />
      <label htmlFor={`input_${type}`} className="btn_logo_preview">
        <Button component="span" className="btn_color4">
          Select File
        </Button>
      </label>
      <p className="file_name">{imgName}</p>

 

업로드된 파일을 서버전송할 때 new FormData()를 써서 이안에 image file이랑 다른 string이랑 같이넣어줌

        const bgImg = document.getElementById('input_bg').files[0];//undefined if not uploaded
        //if (!bgImg) alert('Please select a background image file to update !');
        const previewImg = document.getElementById('input_preview').files[0];//undefined if not uploaded
        //if (!previewImg) alert('Please select a preview image file to update !');
        const buttonImg = document.getElementById('input_button').files[0];
        requestBody = new FormData();
        requestBody.append('tabName', content.tabName);
        requestBody.append('tabOrder', content.tabOrder);
        requestBody.append('themeApply', content.themeApply ? 'Y' : 'N');
        if (bgImg) requestBody.append('bgImg', bgImg);
        if (previewImg) requestBody.append('previewImg', previewImg);
        if (buttonImg) requestBody.append('buttonImg', buttonImg);

전송시 header는 보통 multipart/form-data를 사용하는 것 같은데 왜인지 이렇게 보내면 에러가 났고 아예 설정안하고 보내니 정상동작하였음

//const headers = new Headers({ 'Content-Type': 'application/json' });
//const headers = new Headers({ 'Content-Type': 'multipart/form-data' });

backend

  • formData로 보낸 것들도 url?q=형식으로 get으로 보낸것을 받는 것처럼 @RequestParam으로 받음
  • 그 중 이미지는 org.springframework.web.multipart.MultipartFile로 받음
  • 이미지외에 string으로 보낸 것들은 Map<String, Object>로 몽땅 받음
  • 만약 이름은 bgImg, previewImg이렇게 맞춰서 보냈는데 실제로는 파일이 아니라 string을 보내면 null로 받아짐
@PatchMapping("tab_withImg/{tabId}")
  public Tab updateTabWithImg(@CurrentUser UserPrincipal currentUser, @PathVariable String tabId,
      @RequestParam Map<String, Object> data, @RequestParam(required = false) MultipartFile bgImg,
      @RequestParam(required = false) MultipartFile previewImg, @RequestParam(required = false) MultipartFile buttonImg)
      throws Exception {
    logger.info("#updateTab_WithImg|currentUser=" + currentUser.getProvider());
    return homeAppAdminService.updateTab(tabId, data, bgImg, previewImg, buttonImg);
  }

 

  • multipart file을 받아서 로컬내 저장하고 처리하는 부분
  • 일반적으로 File쓰는 방법으로 저장하면 에러가 나서 java.nio.file.Path/Paths 써서 처리했었던듯
import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.springframework.web.multipart.MultipartFile;


  private String uploadFile(String type, MultipartFile multipartFile) throws Exception {
    String uploadDirStr = UPLOAD_DIR == null ? "/engn001/tomcat/8.5/servers/portal_8180/uploads" : UPLOAD_DIR;
    File uploadDir = new File(uploadDirStr);
    if (!uploadDir.exists() && !uploadDir.mkdirs())
      throw new Exception("uploadDir mkdir Fail!");
    File uploadFile = new File(uploadDirStr + "/" + multipartFile.getOriginalFilename());
    if (!uploadFile.getAbsolutePath().equals(uploadFile.getCanonicalPath()))
      throw new Exception("파일경로 및 파일명을 확인하십시오.");

    Path path = Paths.get(uploadDirStr + "/" + multipartFile.getOriginalFilename()).toAbsolutePath();
    multipartFile.transferTo(path.toFile());
    // multipartFile.transferTo(uploadFile);//file not found error
    String resultGftsUrl = uploadGfts(type, "KIC", uploadFile);
    uploadGfts(type, "AIC", uploadFile);
    uploadGfts(type, "EIC", uploadFile);
    uploadFile.delete();
    return resultGftsUrl.replaceFirst("http://kic-", "");
  }

 

반응형
반응형

[a tag]

<a href={questionModalSrc} download="88a2f203a144c233726b8ffcdccc9ed8.png">

href 주소의 Type이 이미지나 파일이어야만 다운로드가능,

  • download='img.png'에서 img.png는 변경가능
  • 확장자 안쓰면 알아서 다운로드시 Type에 해당하는 확장자 붙여줌,
  • img.htm이런식으로 확장자 잘못 붙여도 다운로드 가능

 

 <a href={`/board/viewimage?file=${encodeURIComponent(questionModalSrc)}`}>

Type이 document (html문서)라면 다운로드 에러남
href가 file Type이 아니면 이런식으로 다운로드 에러남, 이 때 img.htm의 확장자 htm은 저절로 알아서 붙은것

 

 

 

 

 

[모든 element에 적용가능한 방법] 클릭시 임시로 a tag생성하고 클릭되도록

onClick={() => { const link = document.createElement('a');  
                link.href = \`/common/files/01. CDP Admin Operation Policy.docx\`;  
                link.target = '\_blank';//링크클릭시 새창에서 열도록, 링크여는목적 아니므로 없어도 될듯  
                link.download = '다운로드될 파일명'; 이 부분이 없으면 자동다운로드 안됨
                document.body.appendChild(link);  
                link.click();  
                document.body.removeChild(link);  
              }}

 

 

 

 

 

외부에 있는 자원을 다운로드

API 구현부분

  • 테스트해보니 요청하는 헤더내의 content_type: application/json이지만 응답헤더의 Content-Type: application/octet-stream
    Content-Disposition: attachement; filename="RBSPOT.json"로 응답함
  • 참고로 다른 요청->응답은 아래와 같았음
    • 일반적인 json Fetch/XHR request -> response
      • content-type: application/json -> Content-Type: application/json;charset=UTF-8
    • 파일을 자동으로 다운로드되도록 하는 request -> response
      • content-type: application/json -> Content-Type: application/octet-stream
        Content-Disposition: attachement; filename="RBSPOT.json"
    • 파일을 서버로 업로드하는 request -> response
      • Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryCr6S7zpBWkWbkGAB ->
@Description("MetaDataFile Download from Admin.")
@GetMapping
@PreAuthorize("hasRole('ADMIN')")
@RequestMapping("/api/admin/apiCallLog/{apiCallLogId}/file")
public ResponseEntity<?> getMetaDataFile(@CurrentUser UserPrincipal currentUser,
    @PathVariable Long apiCallLogId) {

  Map<String, Object> resultMap = new HashMap<String, Object>();
  URL url = new URL(...어찌어찌해서 얻어옴);
  InputStreamResource resource = new InputStreamResource(url.openStream());
  resultMap.put("fileName", apiCallInfo.getFileName());
  resultMap.put("resource", resource);

  return ResponseEntity.ok().contentType(MediaType.parseMediaType("application/octet-stream"))
      .header(HttpHeaders.CONTENT_DISPOSITION,
          "attachement; filename=\"" + resultMap.get("fileName") + "\"")
      .body(resultMap.get("resource"));
}

구현된 API call하는 부분

export async function getFileDownload(requestPath, requestMethod, rowData) {
  await fetch(API_BASE_URL + requestPath, {
    method: 'GET',
    headers: getHeaders(),
  })
    .then(response => {
      return response.blob();
    })
    .then(blob => {
      const url = window.URL.createObjectURL(
        new Blob([blob]),
      );
      const link = document.createElement('a');
      link.href = url;
      link.setAttribute(
        'download',
        rowData.fileName,
      );//filename to be downloaded, it can be used as link.download=fileName
      //link.style='display:none'; //OK it is not given
      document.body.appendChild(link);
      link.click();
      link.parentNode.removeChild(link);
    })
    .catch(err => {
      alert('error')
    });
}

서버 내부 자원(public공간내 file) 다운로드

<button
                  type="button"
                  className="quick_link link1"
                  onClick={() => {
                    const link = document.createElement('a');
                    link.href = `/common/files/01. CDP Admin Operation Policy_(ENG)20210428.docx`;
                    link.target = '_blank';
                    link.download = '01. CDP Admin Operation Policy_(ENG)20210428.docx';
                    document.body.appendChild(link);
                    link.click();
                    document.body.removeChild(link);
                  }}>

서버응답을 받아 파일을 내려주거나 내부소스 파일을 내려주거나 다운로드 하는 부분의 공통점

const link = document.createElement('a');
link.href = `/common/files/01. CDP Admin Operation Policy_(ENG)20210428.docx`;//여기에서 차이남
//link.target = '_blank'; //없어도됨
link.download = '01. CDP Admin Operation Policy_(ENG)20210428.docx'; //link.setAttribute('download', 파일명)과 동일
document.body.appendChild(link);
link.click();
document.body.removeChild(link);//link.remove(); link.parentNode.removeChild(link);와 동일

서버응답을 받아 파일생성하는 부분

new Blob([arrayContetns], options) options는 생략가능

new Blob([response], {type" res.headers[content-type]})

Blob을 URL로 변환

window.URL.createObjectURL(blob)

URL.revokeObjectURL( window.URL.createObjectURL(blob) ) 여기서 window.URL=URL인듯

반응형
반응형

get width, height: 이거는 상대적으로 간단

<img src='http://...jpg id='bgImg'>
document.getElementById('previewImg').width -> 화면에서 보이는 width
document.getElementById('previewImg').height
document.getElementById('previewImg').naturalWidth -> 이미지 원본크기
document.getElementById('previewImg').naturalHeight
document.getElementById('previewImg').clientWidth -> =width, width와 동일
document.getElementById('previewImg').clientHeigtht

아래처럼 Image 객체를 직접 생성해서 처리할 수도 있음 (<img> element만드는 것과 동일함)

const img = new Image();
img.onload = ()=>{
      alert("Current width=" + img.width + ", " + "Original height=" + img.height);
};
img.src='http://kic-qt2-ngfts.lge.com/fts/gftsDownload.lge?biz_code=CDP&func_code=PROGRAM_IMAGE&file_path=/cdp/program_image/test/skin/ncaa_back_fhd.jpg';

이미지가 load된 후에야 size정보를 파악할 수 있으므로 onLoad 이벤트 리스너를 붙여줘야함, 안그러면 async한 처리를 하다가 뭔가 틀어질 수 있음

<img onLoad = {(e)=>{e.target.xxxx}}>

 

 

size in byte: 문제는 이거,,,

response헤더에 정보가 담겨오면 헤더를 통해 알수 있음

'Content-Length'

http://kic-qt2-ngfts.lge.com/fts/gftsDownload.lge?biz_code=CDP&func_code=PROGRAM_IMAGE&file_path=/cdp/program_image/test/skin/ncaa_back_fhd.jpg
요청하면 Content-Length:110,019로 응답이 오며 실제 응답받은 파일사이즈도 110,019 (Byte)

request를 보내고 blob해서 알아낼수도 있음

let img = new Image()
img.crossOrigin = "Anonymous"; // Helps with some cross-origin issues

fetch('foo.png')
.then(response => response.blob())
.then(blob => { img.src = URL.createObjectURL(blob); })

위 방법써도 아래와 같은 CORS 에러발생
Access to fetch at 'http:/..jpg' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
const fileImg = await fetch(URL_TO_IMG).then(r => r.blob());
fileImg.size
 const size=performance.getEntriesByName('http://kic-qt2-ngfts.lge.com/fts/gftsDownload.lge?biz_code=CDP&func_code=PROGRAM_IMAGE&file_path=/cdp/program_image/test/skin/ncaa_back_fhd.jpg');
 잘은 모르겠으나 이것도 해보니 안됨

 

그런데 문제는.............

header를 통해 알아내거나 blob하려면 request를 직접 보내야 하는데 만약 CORS 이슈가 있다면 직접 응답을 받아내지 못함 -> 이거저거 다 시도해봤으나 안됨, 되게하려면

* 이미지 파일 response해주는 서버의 CORS정책을 바꾸거나

* client가 아니라 서버에서 요청을 대리하도록 만들어야 함

반응형

+ Recent posts