개발 이야기/React & Next

[Typescript/React-Query] 이미지 api 호출 트러블슈팅

Heman 2023. 5. 14. 22:25

[Typescript/React-Query] 이미지 api 호출 트러블슈팅

프로젝트를 함께 개발하는 팀원이 api response 로 이미지가 내려오는데 에러가 난다고 하더랍니다.
제가 담당하고 있는 도메인에도 추후 동일한 환경의 api 가 제공될 예정이기에, 페어코딩으로 해결했던 내용을 재구성하였습니다.

 

이전까지 한번도 이미지 자체를 response 데이터로 받아본적이 없었다능...

 

...핑계다.. 나레기...

 

hooks / api / ... / foo.ts

처음 코드를 작성할 때는 api 로 부터 이미지 자체를 받는 것이 아닌, 이미지 url을 받을 것이라고 생각했습니다.

그래서 다른 일반적인 api 요청 hooks 와 동일한 컨벤션으로 코드를 작성했지요.

 

const FooApi = {
  getImage: (params: GetFooImageParamsType) => {
    return requester.get<GetFooImageResponseType>(
      `${BASE_URL}/get-image`,
      { params }
    );
  }
};

const useGetImage = (params: GetFooImageParamsType) => {
  const queryKey = ['foo-image', params];

  return useQuery(queryKey, () =>
    FooApi.getImage(params),
  );
};

export { useGetImage };

 

이미지를 랜더해주는 컴포넌트에서는 위의 useGetImage 훅의 data response 에서 받아온 url을 Next.js <Image/> 컴포넌트의 url prop 으로 넘겨주기만 하면 될 것이라 생각했습니다.

 

하지만 보안상 이미지가 저장된 url 이 아닌, api 요청시 실시간으로 복호화한 이미지 자체를 받아와야하는 것으로 요건이 정리되면서,
바이너리 데이터로 가져온 이미지를 보여주어야 했습니다. 그래서 추가적인 코드를 <Image/> 에 넣어주었습니다.

 

<Image src={`data:image/png;base64,${data.response.image}`} />

 

위와 같이 src에 base64 형태로 이미지를 표현하겠다고 작성하면 이미지가 보여지는 것입니다.
물론 api 문제가 없고 다른 코드들을 잘 작성했다는 가정하에...!

 

에러발생?

결과적으로 위 코드는 제대로 동작하지 않았습니다. api 콜 시 확인한 바이너리 데이터가 전부 깨져있었거든요.(복선...)

 

아 정말 백엔드 뭐하는거야~ 복호화 좀 잘 해주지~~

스웨거에서도 안되면 한마디 해야겠네~^^


백엔드 개발자 대학동기라 센척해봤습니다..ㅎ

 

그러나 스웨거에서는 너무나도 선명히 잘 보이는 이미지..

 

역시 못하는건 나여따..

 

Blob 형태로 변환

스웨거에서는 잘보인다니..

 

크롬 개발자 도구로 스웨거 이미지를 보니 Blob 형태로 변환되어 이미지가 보여졌습니다.

Blob 은 Binary Large Object 로 큰용량의 이진데이터를 저장가능한 객체라고 볼 수 있습니다.

 

구글링으로 Blob 형태로 이미지를 변환하는 방법을 우선 찾았습니다.

 

const FooApi = {
  getImage: (params: GetFooImageParamsType) => {
    return requester.get<GetFooImageResponseType>(
      `${BASE_URL}/get-image`,
      { 
        params,
        responseType: 'arraybuffer',
      },
    );
  }
};

const useGetImage = (params: GetFooImageParamsType) => {
  const queryKey = ['foo-image', params];

  return useQuery(queryKey, () =>
    FooApi.getImage(params),
  ).then((response) => {
    const blob = new Blob([response.data], { type: 'image/png' });
    const image = URL.createObjectURL(blob);

    return image;
  })
};

export { useGetImage };

 

처음 코드와 달라진 점은 두군데입니다.

 

첫번째는 axios get 요청시 responseType: 'arraybuffer' 를 추가하는 것입니다.
Arraybuffer 란 자바스크립트에서 구현된 버퍼인데, 메모리의 일정공간을 할당하여 바이너리 데이터를 저장합니다.

 

두번째로 이렇게 가져온 바이너리 데이터를 Blob 형태로 전환하는 것입니다.

useQuery()로 가져온 데이터를 Blob 형태로 반환 할 수 있도록 코드를 작성했습니다.

 

결과 값을 확인해보니 성공적으로 잘 전환이 되었습니다만...

 

여전히 에러발생...! ㄷㄷ

 

문제는 Blob 변환이 아니었다?

왜 안되는 걸까요?

문제를 해결하는 과정에서 간과했던 부분이 있었습니다. 근본적인 문제해결의 열쇠를 쥐고 있는 것이었죠.

 

맨 처음 보았던 문제는 바이너리 데이터가 깨졌다는 사실입니다. 처음에는 이 부분을 중요하게 생각하지 않았습니다.

Blob 형태로 변환하든, 하지않든 이미지 그 자체인 바이너리가 깨졌다면 변환 후 결과도 동일할 것입니다.

 

그렇게 고민한 끝에 한가지 놓친 부분을 찾았습니다.

 

문제 해결

바이너리 데이터가 깨졌다는 것은 온전한 데이터가 넘어오지 못했을 수 있다는 것입니다.

 

위에서 작성한 코드는 getImage() 함수를 성공적으로 호출한 후 then() 으로 넘어가 response.data 를 blob으로 변환시키고 있습니다. 하지만 함수 호출에 성공한 후 다음 프로세스를 진행하는 것이 아닌, response 로 받는 바이너리 데이터의 로드가 끝난 후 다음 프로세스를 진행하는 것이 맞겠지요.

 

아래와 같이 async / await 를 사용하여 코드를 정리하니 더 명확하게 이전 문제가 무엇이었는지 보입니다.

 

const useGetImage = (params: GetFooImageParamsType) => {
  const queryKey = ['foo-image', params];

  return useQuery(queryKey, async () => {
    const response = await FooApi.getImage(
      params,
    );

    const blob = new Blob([response.data], { type: 'image/png' });
    const image = URL.createObjectURL(blob);
    return image;
  });
};

 

결과는 성공적 ^^...

 

이런 기본적인 것을 간과했다니..

나 2년차 맞냐 진짜

 

 

마무리하며

기본에 충실하자..