본문으로 건너뛰기
Kabkee.github.io
뒤로 가기

Electron에서 Axios로 받은 파일이 깨질 때: ipcMain에서 저장하기

2021년 11월 작성, 2026년 10월 보완. 당시에는 렌더러에서 ipcRenderer 를 직접 쓰는 방식(nodeIntegration: true)이었다. 지금 Electron은 contextIsolation 이 기본이라, preload에서 contextBridge 로 필요한 함수만 노출하고 ipcRenderer.invoke / ipcMain.handle 로 주고받는 방식을 권장한다. 아래 예제는 이 방식으로 다시 썼다.

목차

목차 펼치기

개발 환경

Vue CLI Plugin Electron Builder로 Electron 앱을 개발했다.

문제 상황

앱의 한 화면에서 Axios로 파일을 받아 fs.writeFile() 로 저장했더니, 파일은 만들어지는데 내용이 깨져서 열리지 않았다.

원인

Electron에는 두 종류의 프로세스가 있다. 창(화면)을 그리는 렌더러 프로세스와, 앱 전체를 관리하고 Node.js 기능을 마음껏 쓸 수 있는 메인 프로세스다. 둘은 ipcRenderer 와 ipcMain 이라는 IPC(Inter-Process Communication) 모듈로 데이터를 주고받는다.

당시에는 렌더러 프로세스에서 파일을 받아 바로 저장하려고 한 것을 원인으로 판단했고, 저장을 메인 프로세스로 옮겨서 해결했다.

지금 돌아보면 데이터의 형식도 함께 봐야 한다. 렌더러의 Axios는 브라우저처럼 동작해서, responseType 을 지정하지 않으면 응답을 문자열로 받는다. 바이너리 파일을 문자열로 받으면 그 시점에 이미 내용이 깨진다. 그래서 responseType: "arraybuffer" 로 받고, 메인 프로세스에서 Buffer 로 바꿔 저장하는 것이 안전하다.

해결 방법

  1. 렌더러에서 «다운로드» 버튼을 누르면 Axios로 파일을 ArrayBuffer 로 받는다.
  2. 받은 데이터를 IPC로 메인 프로세스에 넘긴다.
  3. 메인 프로세스에서 Buffer 로 바꿔 fs.writeFile() 로 저장한다.

preload.js

const { contextBridge, ipcRenderer } = require("electron");

// 렌더러에는 저장 함수 하나만 노출한다
contextBridge.exposeInMainWorld("fileApi", {
  save: (fileName, data) => ipcRenderer.invoke("save-file", fileName, data),
});

렌더러

import axios from "axios";

async function download(url) {
  // 바이너리는 반드시 arraybuffer로 받는다
  const res = await axios.get(url, { responseType: "arraybuffer" });
  const saved = await window.fileApi.save("report.pdf", res.data);
  console.log(saved ? "저장 완료" : "저장 취소");
}

메인 프로세스

const { ipcMain, dialog } = require("electron");
const fs = require("fs/promises");

ipcMain.handle("save-file", async (event, fileName, data) => {
  const { canceled, filePath } = await dialog.showSaveDialog({
    defaultPath: fileName,
  });
  if (canceled || !filePath) return false;

  await fs.writeFile(filePath, Buffer.from(data));
  return true;
});

정리

  1. 렌더러와 메인 프로세스의 역할을 구분한다. 파일 저장처럼 Node.js 기능이 필요한 일은 메인 프로세스가 맡는다.
  2. 바이너리 파일은 responseType: "arraybuffer" 로 받는다.
  3. 렌더러는 preload가 노출한 함수로 데이터를 넘기고, 메인 프로세스에서 Buffer.from() 으로 바꿔 저장한다.

참고자료


이 글 공유하기:

이전 글
Vue 2 + BootstrapVue: Bootstrap CSS를 scoped로 격리하기 (::v-deep + SCSS)
다음 글
Electron에서 만든 Windows BAT 파일의 한글 깨짐 해결: CP949 인코딩과 줄바꿈