사내 챗봇 프로젝트 구현 중에 ppt, pdf, jpeg 등 다양한 형태의 이미지를 다운로드하는 기능이 있었습니다.
처음에는 단순하게 Blob URL을 a태그에 넣고 클릭 이벤트 전달해야지라는 생각으로 구현했다가 오류가 발생했습니다. 그러다가 찾아본 내용과 해결 방법을 기록하려고합니다!!(다음에는 실수하지 말자 ㅎㅎ...)
처음에는 브라우저 상에서 다운로드를 구현했기 때문에 당연히 정상적으로 다운로드가 됐었습니다. 하지만 앱에서 웹 사이트를 접속하여 다운로드를 진행하면 (Error Can only download HTTP/HTTPS Uri: Blob )
에러와 함께 다운로드가 진행되지 않았습니다.
오류 내용은 'HTTP/HTTPS만 다운로드 할 수 있습니다.' 내용이였습니다.
Blob URL은 일반 Http, Https URL과 달리 blob:550e8400-e29b-41d4-a716-446655440000#aboutABBA:
과 같은 형태의 URL로 존재하는데 이 URL이 HTTP, HTTPS와 달라서 발생했던 문제라고 생각했습니다.
이유를 먼저 적자면 Android에는 Blob 객체가 없기 때문입니다.
없는 형태로 데이터를 전달하려고하니 에러가 발생했던 것입니다. 이걸 모른채로 계속 Blob으로 전달하니 오류가 발생했던 것이였습니다..
현재는 Android만 구현되어 있기 때문에 Android 해결 방법만 기술하겠습니다.
해결방법으로 생각했던 내용은 2가지가 있었습니다.
Android에서 @JavascriptInterface
라는 기능을 구현하면 브라우저와 앱 간의 데이터 전달이 가능하다는 내용을 알게되었습니다. 다만 이는, 앱에서 브릿지를 구현해야했기 때문에 이 부분은 안드로이드 개발자 분께 말씀 드렸었습니다.
async function blobToBase64(blob) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onloadend = () => resolve(reader.result.split(",")[1]);
reader.onerror = reject;
reader.readAsDataURL(blob);
});
}
// Blob을 Base64로 변환 후 Android로 전송
async function sendBlobToAndroid(blob) {
const base64Data = await blobToBase64(blob);
window.AndroidInterface.saveFile(base64Data, "example.txt", "text/plain");
}
Android
class WebAppInterface(private val context: Context) {
@JavascriptInterface
fun saveFile(base64Data: String, fileName: String, mimeType: String) {
val decodedBytes = Base64.decode(base64Data, Base64.DEFAULT)
val file = File(context.getExternalFilesDir(null), fileName)
file.writeBytes(decodedBytes)
Log.d("WebView", "파일 저장 완료: ${file.absolutePath}")
}
}
Blob은 Android에서 처리할 수 없어 Base64의 형태로 전환 후, 앱에서 파일을 다운로드하는 방식으로 해결할 수 있었습니다. 하지만 이 방식은 추가적인 기능 구현과 테스트가 필요했기에 시간이 부족했던 저희 팀은 다른 방법을 찾았습니다.
두 번째 해결 방안은 인코딩 된 데이터를 Blob으로 변환 후 다운로드 하는 것이 아닌, API 호출 시 Nginx의 Reverse Proxy를 활용하여 디렉토리 경로에서 직접 파일을 다운로드하는 방식이였습니다.
server {
listen 80;
server_name example.com;
location /download/ {
root /app/downloads/files; # 파일이 위치한 디렉토리
autoindex on; # 파일 목록을 보여줌 (필요 없으면 off)
autoindex_exact_size off;
autoindex_localtime on;
# 특정 파일만 허용 (보안 목적)
location ~* \.(pdf|zip|jpg|png|docx|pptx)$ {
allow all;
}
# 실행 파일 차단 (보안 목적)
location ~* \.(php|sh|exe|bat|pl|cgi)$ {
deny all;
}
# 파일 다운로드 강제 적용
add_header Content-Disposition 'attachment';
}
error_page 404 /404.html;
}
위의 방식대로 백엔드로 리소스를 요청하면 Nginx 서버의 /app/downloads/files
에서 파일을 가져오게됩니다.
안드로이드에서 Blob 객체를 지원을 안한다는건 처음 알게되었는데 @JavascriptInterface라는 구현체로 데이터를 전달할 수 있다는 것도 알게되어 신기했습니다. RN같은 경우에는 window.ReactNativeWebView라는 객체도 생긴다고 하는데 나중에 RN, Fultter를 배우게 된다면 그 때 더 자세하게 학습해봐야할 것 같습니다.