nueijeel

[Android] 세모반 리팩토링 - PreSigned URL을 이용한 이미지 업로드 로직 구현 下 (이미지 저장 api 수정) 본문

Android/에러 및 문제 해결

[Android] 세모반 리팩토링 - PreSigned URL을 이용한 이미지 업로드 로직 구현 下 (이미지 저장 api 수정)

nueijeel 2024. 4. 9. 23:20

 

서론

 

지난번 포스팅에서

안드로이드에서 서버에 PreSigned URL을 요청해 AWS S3에 이미지를 직접 업로드 하는 과정을 정리해보았다.

 

 

[Android] 안드로이드에서 PreSigned URL을 이용해 AWS S3에 이미지 업로드 하기 (1)

세모반 프로젝트 배포 후 백엔드 개발자분께서 이미지 처리 로직 변경을 제안해주셔서 새로 구현한 방식에 대해 포스팅하려고 한다. 기존 이미지 처리 로직 기존에는 클라이언트에서 Post api를

nueijeel.tistory.com

 

 

이후 post 및 patch, put api를 백엔드쪽에서 수정해주시면서 (api request body에 파일 대신 저장된 이미지 경로를 전달)

안드로이드에서도 api가 구현된 부분을 수정하여 이미지 업로드 후 서버 통신으로 데이터를 전달하는 과정까지 완성이 됐다.

 

 

그런데 변경된 로직을 통해 데이터를 저장한 뒤 이미지를 불러오는 과정에서 아래와 같은 Glide 에러가 발생했다.

 

지정된 이미지 파일 경로로부터 리소스를 받아오는데 실패했다는 오류였다.

 

 

 

문제 원인 파악

 

이전에 백엔드측에서 이미지 업로드 처리를 담당했을 때도

이미지 업로드 후 화면에 출력할 때 위와 같은 형태의 이미지 경로를 받아 표시했었기 때문에

저 주소값이 없거나 잘못된 문제는 아닐 것 같았다.

 

 

지정된 폴더 경로로 가보면 업로드 시점에 파일이 생성되어 있었기 때문에

내가 생각하는 가장 유력한 원인은 이미지 업로드가 제대로 안되고 있는 문제였다.

 

데이터를 저장할 공간은 생기는데 데이터가 제대로 업로드가 안돼서 비어 있을거라고 추측했다.

 

 

 

그래서 기존 로직에서는 이미지 처리를 어떻게 하셨는지 백엔드 분께 질문을 드렸다.

 

 

백엔드에서는 안드로이드에서 전달된 multipart 형식 그대로 올리지 않고 inputstream을 사용하여 이미지를 업로드하셨다고 했다.

 

그래서 AWS S3에 이미지를 저장하려면 어떤 형태로 저장해야하는지 구글링을 하다가 이 페이지를 발견했다.

 

 

위 페이지에서는 안드로이드에서 Retrofit2 라이브러리를 사용해서 presigned url로 이미지를 업로드할 때 end point를 지정하는 방법에 대한 사례를 요청하고 있어서 나와 똑같은 상황의 질문은 아니었지만

답변을 살펴보니 인터페이스 정의 예시에 파일을 RequestBody 형식으로 전달하고 있는걸 확인할 수 있었다.

 

여러 방법을 시도하고 있다가 

위와 같이 인터페이스 구현부에서 파일을 Multipart 형식이 아닌 RequestBody 형식으로 전달되도록 수정해보았더니 성공적인 결과를 얻게 되었다.

 

 

 

RequestBody

RequestBody는 OkHttp의 추상 클래스로 http 요청 시 전달되는 데이터를 담고있다.

 

 

클래스 내부 코드를 살펴보면

String, ByteString, ByteArray, File 객체에 대한 확장함수를 제공해 객체를 RequestBody 형태로 변환할 수 있게 해준다.

 

abstract class RequestBody {

  abstract fun contentType(): MediaType?
  
  @Throws(IOException::class)
  open fun contentLength(): Long = -1L

  @Throws(IOException::class)
  abstract fun writeTo(sink: BufferedSink)
  
  ...

  companion object {
    ...
    
    @JvmStatic
    @JvmName("create")
    fun File.asRequestBody(contentType: MediaType? = null): RequestBody {
      return object : RequestBody() {
        override fun contentType() = contentType

        override fun contentLength() = length()

        override fun writeTo(sink: BufferedSink) {
          source().use { source -> sink.writeAll(source) }
        }
      }
    }

    ...
  }
}

 

이 중, File 객체를 RequestBody로 변환해주는 File.asRequestBody() 함수는 인자로 contentType을 전달받고, RequestBody를 반환하는 함수이다.

 

RequestBody를 상속받는 익명 클래스를 return하고, 익명 클래스는 RequestBody 클래스의 추상 메서드인 contentType(), contentLength(), writeTo() 메서드를 구현한다.

 

재정의된 contentType() 메서드에서는 확장함수 인자로 전달된 contentType을 반환한다.

 

재정의된 contentLength() 메서드에서는 기본값 대신 file 객체의 크기를 반환하는 length() 메서드를 호출해 현재 file 객체의 크기를 반환한다.

 

재정의된 writeTo() 메서드에서는 source() 메서드를 호출하여 file 객체의 source를 가져오고, source를 sink.writeAll() 함수의 인자로 전달해 데이터를 버퍼에 쓴다.

 

 

 

이미지 업로드 로직 수정

 

현재 안드로이드에서는 이미지 데이터를 uri 형식으로 처리하고 있기 때문에

uri를 file로 변환하고, 변환된 file을 asRequestBody() 함수를 통해 requestBody로 변환시켜 aws에 업로드하는 과정으로 수정했다.

 

Interface

기존 메서드 / 수정된 메서드

 

멀티파트 Request가 아니기 때문에 @Multipart 어노테이션을 제거했다.

이미지 데이터는 @Body 어노테이션으로 변경하고 RequestBody 형으로 바꿔주었다.

 

 

Repository + Impl

AWSS3Repository.kt
AWSS3RepositoryImpl.kt

 

레포지토리에 정의한 함수 인자도 수정해주고, 구현체에 override된 함수의 인자도 수정하여 인터페이스 메서드를 호출한다.

 

 

ViewModel

마찬가지로 ViewModel에 정의한 함수의 인자도 RequestBody 형태로 수정해준다.

 

 

View

View에서 AWS S3로 업로드할 Uri를 File 형태로 바꾸고

File 이름을 넣은 저장경로를 생성해 PreSigned URL을 요청하는 api를 호출한다.

 

통신이 성공적으로 이루어져 정상적으로 PreSignedURL이 반환되면

imageFile을 RequestBody로 변환하여 

앞서 수정한 uploadImageToS3 메서드의 인자로 전달한다.

 

 

전달된 데이터가 무사히 업로드 되면

저장된 이미지 경로를 서버로 전송해준다.

 

 

 

이렇게 이미지 업로드 형식을 수정하고 나니까

홈 화면에서도 Glide 오류 없이 업로드된 이미지를 정상적으로 출력하는 것을 확인할 수 있었다~!

 

오류때문에 몽자를 세번이나 등록해서 또또몽자가 되어버림.. ㅋㅋ

 

 

 

틈새 회고

 

내가 업로드 테스트를 구현하는 동안 근영님이 api 수정해주시고

수정본 배포해주시면 안드도 또 수정하고 테스트하고 사이사이 질문하고..

작업량이 일정하지 못하기도 했고 중간에 오류가 생기거나 텀이 있을 때도 있었기 때문에 기간은 보름정도 걸린 것 같다.

 

백엔드 관련해서는 무지했기 때문에 

이번 기회를 통해서 우리가 데이터를 보내면 백엔드 쪽에서 어떤 로직으로 데이터를 처리하고 저장하는지를 조금이나마 알 수 있었다.

 

또, 기존의 이미지 업로드 형식을 구현했을 때 Multipart에 대해서 꽤냐 얕은 지식만을 갖고 코드를 작성했다는 걸 깨달았다. 기능을 기간안에 구현해내는데 급해서 방법 몇 번 바꿔서 테스트 해보다가 되면 그냥 쓰고 나중에 공부해야지 하고 미뤄둔 게 많은 것 같다. 항상 느끼는 거지만 좀 더 깊이있는 공부를 할 필요가 있을 것 같다.

 

그래도 이 작업을 완성하면서 우리 서비스를 나은 방향으로 개선하는 데에 기여했다고 생각하니까 앱을 출시해서 지속적으로 업데이트 해나가는 것에 나름 보람을 느꼈다. 아직 한참 부족하지만 ㅎㅎ

 

개발자에게 새로운 서비스를 만들어내는 것과는 별개의 의미있는 과정인 것 같다!

 

 

 

 

참고

https://square.github.io/okhttp/3.x/okhttp/okhttp3/RequestBody.html

 

 

 

 

728x90