기능

javascript와 img태그를 사용하여 사용자로부터 버튼 클릭을 입력 받을 시 a 태그를 생성, blob을 불러와 이미지를 다운로드 하려고 한다. 

 

 

발생문제

1. 단순 blob이 아닌 url을 이용하여 파일을 다운로드 시 새창으로 열리는 현상이 있다.

(해결)아래와 같이 fetch, axios등을 이용하여 blob으로 내려받아 오픈한다.

     const response = await axios.get(IMG_URL + url, {
        responseType: 'blob',
      });

      const blob = new Blob([response.data]);
      const imageUrl = URL.createObjectURL(blob);

      const link = document.createElement('a');
      link.href = imageUrl;
      link.download = url;
      document.body.appendChild(link);
      link.click();
      document.body.removeChild(link);

 

2. 간헐적 cors 에러가 발생

 

(1)의 코드를 이용하여 다운로드를 시도 시 간헐적으로 cors가 발생한다.

크롬 개발자 도구의 disable cache를 체크 시 정상적으로 다운로드 수행됨을 확인하여, 캐시 문제라고 판단하였다. 이 캐시 문제를 해결하기 위하여 아래와 같은 헤더 설정을 해보았지만 브라우저는 여전히 캐싱을 하였다.

      const response = await axios.get(IMG_URL + url, {
        responseType: 'blob',
        withCredentials: false,
        headers: {
          'Cache-Control': 'no-store',
          'Access-Control-Allow-Origin': '*',
          'Access-Control-Allow-Credentials': 'true',
        },
      });

 

이 원인에 대해 자세히 설명해준 " https://blog.hwahae.co.kr/all/tech/5412 " 글을 요약하면

 

html 태그 상 img src태그에서 aaa.co.kr/img1.png를 불러들일때 헤더를 포함하여 브라우저가 캐싱, 추 후 사용자의 버튼 클릭을 입력받아 다운로드 받는 함수 (1)를 수행하지만, 이미 img태그에서 aaa.co.kr/img1.png를 캐싱해버렸고, axios함수에서 또 한번 같은 주소인 aaa.co.kr/img1.png를 호출할때 img태그에서 사용한 헤더 정보를 가지고 요청(특정 정보가 없는 헤더정보)하여 cors에러가 발생하게 된것.

 

그 외 https를 서버에 올렸을 때, 서버상에서는 정상동작하고 로컬(http)에서는 cors가 발생하기도 하였다.

'Javascript > DOM 관련' 카테고리의 다른 글

[DOM API] DOM 그리고 HTMLElement  (0) 2019.12.17
[JAVASCRIPT] 브라우저 로드 순서  (0) 2019.12.01
[JAVASCRIPT] 스크롤 거리 구하기  (2) 2019.12.01
[JAVASCRIPT] event processing  (0) 2019.12.01

 

 

 

문제의 404 화면(수정메뉴 왼쪽 이미지 깨짐)

 

 

 

 

- 현상 

위와 같이, cloud storage에 이미지를 업로드 직후 이미지  not found 현상.

그러나 일정시간 후 혹은 일정 횟수 강제로 refresh하면 이미지가 보이는 현상.

 

- 원인

처음에는 업로드 링크(getDownloadURL())가 반환이 제대로 되지 않았나 추측하여 확인해보니,링크는 문제가 없었다.

 

자세히 살펴보니, 이미지의 크기가 큰 경우 업로드가 완료되었더라도, 심지어 해당 이미지 링크가 정확히 firestore에 의하여 반환(getDownloadURL())이 되었더라도, 해당 링크가 유효하기까지는 시간이 걸린다는 것을 확인하였다.

 

- 추측 

추측 근거1 : 무식한 방법으로 확인을 해보았는데, 큰 크기의 이미지 파일(400MB)를 업로드 후 반환된 이미지 링크를 그대로 복사하여 브라우저에 직접 접근해보니, 이 역시 404가 발생되다가, 일정 시간 후 정상 표출되는 점.

추측 근거2 : 에러가 발생한 시점의 링크 주소와 정상적으로 표출될때의 링크주소는 같으므로, 링크는 문제가 없다는 점.

추측 근거3 : 정상적으로 표출될때까지의 시간이 이미지 크기에 비례하여 소요된다는 점. 

 

 

- 처리

이미지를 업로드 후 내부적으로 해당 이미지링크가 정상처리될때까지 progress bar를 표출하는 방법이 있겠고,

해당 이미지가 정상적으로 보여질때까지 재시도를 하는 방법이 있겠다.

 

여기서 필자는, 재시도하는 방법을 택했으며, 브라우저를 refresh(F5)하는 방법보다는,

해당 이미지에 error를 캐치하여 재시도하는 로직을 넣었다.

 

 

ember.js 프로젝트의 경우

 

firebase로부터 반환된 이미지 주소를 get

 

template

 

 

 

react 프로젝트의 경우

 

 

 

와 같이 error핸들을 catch하여 retry로직을 타도록 처리하였다.

 

 

 

 

해당 이미지는 허블딥필드 (400mb짜리 이미지), 재시도 로직에 의하여 3회 후 정상 표출됨을 확인

(재시도 3번 후 정상 표출됨을 확인)

 

 

그외 방안

1. 애초에 이미지 등록 시 크기 제한하는 방안.

2. 위의 재시도 방법

3. progress bar 처리

 

그러나 수정/등록 시 불필요하게 progress bar를 통하여 사용성을 저해하기 보다는

정상처리 될때까지 재시도 처리함(2번).

 

 

 

 

 

---------------------------------------------------------------------------------------------------------------

 

번외,

 

2022.01.14 이와 관련하여 chatGPT에 문의해보니 답변이 바로 나왔다. 어떻게... 가능한거지...

 

 

When you upload an image to Firebase Storage, it is first stored in a temporary cache before it is fully uploaded to the server. This is done to ensure that the upload process is as fast and efficient as possible, even if the user's internet connection is not very stable.

Once the image is fully uploaded to the server, it is then made available for retrieval. However, depending on the network conditions, there may be a delay between the time the image is uploaded and the time it is fully available for retrieval.

This delay can be caused by several factors such as network congestion, high traffic to the server, or slow upload speeds. Additionally, Firebase Storage is built on top of Google Cloud Storage, which is a highly-scalable object storage service that enables users to store and access data in near real-time.

If you want to ensure that the image is immediately available for retrieval after upload, you can use the put method provided by the Firebase Storage SDK, along with the .on method that allows you to register a listener that triggers once the image is uploaded.

You can use the put method to upload the image and the .on method to register a listener that will listen for the state_changed event, which is triggered when the upload state changes. Once the state_changed event is triggered, you can check if the upload is complete and if so, you can retrieve the URL of the uploaded image and use it to display the image on the page.

In summary, the delay in the image appearing on the page is likely due to the image being temporarily stored in a cache before it is fully uploaded to the server, and it can be caused by network conditions, high traffic, or slow upload speeds. However, you can use the put method with the .on method to ensure that the image is immediately available for retrieval after upload.

 

 

개인적인 생각으로는 지구 상 어디에서든지 위 현상은 겪었을테지만, 문제는 그 현상에 대한 글을 어떻게 효율적으로 검색하냐.. 가 문제다.

위 답변에 대한 글, 지식, 질/답 글을 검색할 수가 없어서 삽질(?)아닌 삽질을 하게 되었는데. chatGPT는 도대체 어떻게 ...

 

사실 개발이라는 것은 원하는 정보를 찾아서 응용하는것이 대부분이라고 생각된다. 그리고 트러블슈팅. 트러블슈팅의 연속,

하나의 작은 문제를 해결하고 다음 문제를 해결하다보면 기능이 완성되어지고 그 기능들이 모여져서 프로그램이 완성된다.

그러나 모든 것을 다 해본적도 없고 사람의 암기력은 한계가 있으므로 아는 것이라도 다시 문서를 뒤적거려야 하고 본문글처럼 자기가 가진 수준의 지식으로 여러가지 실험과 시도를 통해 해결하면서 프로그램을 만드는데, "정확한 질문"만으로 확실하진 않지만 저런 수준의 답변은 개발자들에게 꽤나 생산성을 높여줄것같다.

GPT4는 더 높은 성능을 보여줄것이라고 하고, 유료라더라도 충분히 지불할 의향이 있다!!

 

 

 "opencv를 이용하여 간단한 자동차 번호 인식 코드를 달라"라고 하니 쌩뚱 맞은 코드를 내놓는것을 보면 완벽하진 않아보인다. 그러나 함수단위로 input과 output을 명시한다면 원하는 코드를 짜주는 것 같다. 조금 더 적극적으로 사용해봐야겠음.

마이그레이션 순서

 

  1. Add TypeScript
  2. Add tsconfig.json
  3. Start simple
  4. Convert all files
  5. Increase strictness
  6. Clean it up
  7. Celebrate

 

1. typescript 설치

 

npm install --save typescript @types/node @types/react @types/react-dom @types/jest

yarn add typescript @types/node @types/react @types/react-dom @types/jest

를 통하여 typescript 및 기타 모듈을 설치한다.

 

기존 js 기반 react의 react-router-dom의 경우

npm install react-router-dom로 설치한다.

 

하지만 ts에서는 npm install @types/react-router-dom 로 설치를 새로 해야한다.

이 글의 마지막에 이 프로젝트에서 사용되어지는 @types/*의 package.json을 넣어두겠다.

 

위 문제의 에러 메시지는 보통 "  Could not find a declaration file for module 'react-redux'.  "와 같이

모듈을 찾을 수 없다고 나온다.

 

 

2. tsconfig.json 추가

 

tsconfig.json는 프로젝트를 컴파일하는 데 필요한 루트 파일과 컴파일러 옵션을 지정하는 파일입니다.

 

[참고사이트]

www.typescriptlang.org/docs/handbook/tsconfig-json.html

typescript-kr.github.io/pages/tsconfig.json.html

 

 

npx tsc --init

를 수행하여 생성하거나 직접 tsconfig.json 생성

 

아래는 npx tsc --init를 이용하여 기본적으로 생성되는 tsconfig.json 포맷.

더보기

{

  "compilerOptions": {

    /* Visit https://aka.ms/tsconfig.json to read more about this file */

 

    /* Basic Options */

    // "incremental": true,                   /* Enable incremental compilation */

    "target""es5",                          /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */

    "module""commonjs",                     /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */

    // "lib": [],                             /* Specify library files to be included in the compilation. */

    // "allowJs": true,                       /* Allow javascript files to be compiled. */

    // "checkJs": true,                       /* Report errors in .js files. */

    // "jsx": "preserve",                     /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */

    // "declaration": true,                   /* Generates corresponding '.d.ts' file. */

    // "declarationMap": true,                /* Generates a sourcemap for each corresponding '.d.ts' file. */

    // "sourceMap": true,                     /* Generates corresponding '.map' file. */

    // "outFile": "./",                       /* Concatenate and emit output to single file. */

    // "outDir": "./",                        /* Redirect output structure to the directory. */

    // "rootDir": "./",                       /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */

    // "composite": true,                     /* Enable project compilation */

    // "tsBuildInfoFile": "./",               /* Specify file to store incremental compilation information */

    // "removeComments": true,                /* Do not emit comments to output. */

    // "noEmit": true,                        /* Do not emit outputs. */

    // "importHelpers": true,                 /* Import emit helpers from 'tslib'. */

    // "downlevelIteration": true,            /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */

    // "isolatedModules": true,               /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */

 

    /* Strict Type-Checking Options */

    "strict"true,                           /* Enable all strict type-checking options. */

    // "noImplicitAny": true,                 /* Raise error on expressions and declarations with an implied 'any' type. */

    // "strictNullChecks": true,              /* Enable strict null checks. */

    // "strictFunctionTypes": true,           /* Enable strict checking of function types. */

    // "strictBindCallApply": true,           /* Enable strict 'bind', 'call', and 'apply' methods on functions. */

    // "strictPropertyInitialization": true,  /* Enable strict checking of property initialization in classes. */

    // "noImplicitThis": true,                /* Raise error on 'this' expressions with an implied 'any' type. */

    // "alwaysStrict": true,                  /* Parse in strict mode and emit "use strict" for each source file. */

 

    /* Additional Checks */

    // "noUnusedLocals": true,                /* Report errors on unused locals. */

    // "noUnusedParameters": true,            /* Report errors on unused parameters. */

    // "noImplicitReturns": true,             /* Report error when not all code paths in function return a value. */

    // "noFallthroughCasesInSwitch": true,    /* Report errors for fallthrough cases in switch statement. */

 

    /* Module Resolution Options */

    // "moduleResolution": "node",            /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */

    // "baseUrl": "./",                       /* Base directory to resolve non-absolute module names. */

    // "paths": {},                           /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */

    // "rootDirs": [],                        /* List of root folders whose combined content represents the structure of the project at runtime. */

    // "typeRoots": [],                       /* List of folders to include type definitions from. */

    // "types": [],                           /* Type declaration files to be included in compilation. */

    // "allowSyntheticDefaultImports": true,  /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */

    "esModuleInterop"true,                  /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */

    // "preserveSymlinks": true,              /* Do not resolve the real path of symlinks. */

    // "allowUmdGlobalAccess": true,          /* Allow accessing UMD globals from modules. */

 

    /* Source Map Options */

    // "sourceRoot": "",                      /* Specify the location where debugger should locate TypeScript files instead of source locations. */

    // "mapRoot": "",                         /* Specify the location where debugger should locate map files instead of generated locations. */

    // "inlineSourceMap": true,               /* Emit a single file with source maps instead of having a separate file. */

    // "inlineSources": true,                 /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */

 

    /* Experimental Options */

    // "experimentalDecorators": true,        /* Enables experimental support for ES7 decorators. */

    // "emitDecoratorMetadata": true,         /* Enables experimental support for emitting type metadata for decorators. */

 

    /* Advanced Options */

    "skipLibCheck"true,                     /* Skip type checking of declaration files. */

    "forceConsistentCasingInFileNames"true  /* Disallow inconsistently-cased references to the same file. */

  }

}

 

 

 

 

3. convert all files

 

사실 TS를 모르는 상태에서 TS 변환 후 배워나가겠다..라는 방법은 맞지 않다.

ts로 전환하는 과정에서는 기존 js에 ts의 type annotation을 일일히 수작업으로 맞추어주어야 하기 때문에

ts에 대한 문법적 지식이 전무한 상태에서는 진행하기가 힘들다고 생각이 된다.

 

tsconfig.json rule을 매우 느슨하게 설정(strict:false)하여 진행해볼수도 있다.

(본 작성자는 TS를 모르는 상태에서 작업을 진행하였다)

 

 

모든 js react프로젝트에 해당하는것은 아니지만, 적어도 내가 진행하는 프로젝트에서 나오는 문제점들을 하나하나 나열해가면서 진행한다.

 

1. 위에 말했던 react-router-dom이 ts버전인 package를 설치해야하는 문제.

 

2. 

상대경로 관련 문제

 

 

import App from "app"; 부분을 보자.

정확히는 tsconfig.json의 "moduleResolution""node", << 이 옵션에 관한 문제.

 

chiabi.github.io/2018/08/30/typescript/

www.typescriptlang.org/docs/handbook/module-resolution.html .

 

세부적인 moduleResolution은 위의 링크에서 확인하길 바랍니다.

필자는 import App from "./App"; << 로 상대경로를 지정했습니다.

해당 옵션을 보고, node로 설정 후 require로 끌어오는 것을 추천드립니다.

 

 

+ Recent posts