이전 포스팅에서 인코딩 깨짐 이슈로, Node.js 라이브러리를 통해서 해결했지만,
다른 팀원들과 상의한 결과 하나의 api router
로 서버와 post
,delete
, patch
통신을 최대한 간단하게 할 수 있게 했으면 좋겠다는 의견이 나왔다.
쉽게 말하면 Content-Type
이 multipart/form-data
가 아니라 application/json
타입이였으면 한다는 것이다.
그렇다면 file
객체를 string
타입의 데이터로 변환을 해야하는 상황인 것이다.
이전 포스팅에서 File Web APIs를 통해서 파일객체는 자바스크립트 내에서 파일데이터의 정보를 다룰 수 있는 객체라고 이야기 했는데,
그렇다면 어떻게 File객체의 실제 데이터 접근은 어떻게 할까이다.
URL.createObjectURL vs FileReader 여기에 URL.createObjectURL
설명이 있기에 패스하겠다.
파일의 내용을( raw Data
Buffer
) 읽고 조작하는 Web APIs이다.
FileReader.abort()
: 파일 읽기를 중단.FileReader.readAsArrayBuffer()
: 파일을 읽고, 그 결과값은 ArrayBuffer
형태를 저장 - 데이터를 일정한 크기로 잘라 서버로 보낼 때 사용FileReader.readAsBinaryString()
: 파일을 읽고, 그 결과값 속성에 파일의 원시 이진 데이터 형태를 저장 - deprecatedFileReader.readAsDataURL()
: 파일을 읽고, 그 결과값 속성에 파일을 나타내는 URL을 저장FileReader.readAsText()
: 파일을 읽고, 그 결과값은 파일의 텍스트 문자열 형태를 저장FileReader
는 비동기 매커니즘이기 때문에, 이벤트 핸들러를 등록하여 사용할 수 있다.
총 6개의 이벤트 핸들러가 있다.
FileReader.onabort
: 읽기 중단 시, 트리거 됨FileReader.onerror
: 읽는 도중 오류 발생시, 트리거 됨FileReader.onload
: 읽기 완료 시(성공만), 트리거 됨FileReader.onloadstart
: 읽기 시작 시, 트리거 됨FileReader.onloadend
: 읽기 완료 시( 성공, 실패 ) , 트리거됨FileReader.onprogress
: 읽는 도중, 트리거 됨지난 포스팅에서 getBase64
util 함수를 만들었었는데, 이것을 다시 한번 가져와 보자
function getBase64(file: File) {
return new Promise<string>((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => resolve(<string>reader.result);
reader.onerror = error => reject(error);
});
}
FileReader
에서 readAsDataURL
메소드로 file을 파싱하여 실제 데이터를 url로 변환 하는 함수를 만들었는데, 다음과 같은 이유다.
출저 : mdn
base64인코딩 된 string data가 result속성에 담긴다.
그렇다 , 실제 파일의 데이터를 string
으로 변환 하기 때문에 맨 상단에 써 놓았던, Content-Type: application/json
으로 보낼 수 가 있다.
URL데이터를
Blob
으로 변환 할 수 있지만, 우리는Content-Type
을 통일 하고 싶기 때문에...
다시 돌아와서 Client
코드를 다시 작성하자면
// client
interface FieldValues {
text: string
file : FileList
}
//...생략
const submitBase64 = async (value: FieldValues) => {
const _file = await getBase64(value.file[0]);
// _file타입은 string이므로 `application/json`
const result = await axios.post("/api/binary/base64", {
...value,
file: _file,
});
};
//...생략
그리고 나서 api router의 터미널에서 확인해 보면
이렇게 json
형태로 받아와 진다.
{file: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAv0AAAJcCAIAA...' }
dataURL
은 총 4가지 파트로 구성된다.
data:[<mediatype>][;base64],<data>
data:
MIME타입
base64
,
이하여기서 흐름을 놓칠 수가 있는데, api router로 보내는 이유는 mixed content error
때문이다.
그렇기에 api router
에서 formData를 합의한 Content-Type:application/json
으로 받아오고
그 다음 api router에서 formData를 만들어 다시 서버측에 보내줘야 우리가 원하는 Goal인 것이다.
따라서
// api router
function base64ToBuffer(base64String: string) {
const base64Data = base64String.replace(/^data:image\/png;base64,/, "");
return Buffer.from(base64Data, "base64"); // base64Data를 Buffer로 변환
}
export default async function handler(
req: NextApiRequest,
res: NextApiResponse,
) {
const { body } = req;
const fileBuffer = base64ToBuffer(body.file);
const formData = new FormData();
formData.append("file", fileBuffer);
formData.append("text", body.text);
//...생략
}
Buffer는 Node.js 환경에서 Blob
객체가 제공되지 않는다 대신 Buffer
를 통해서 인코딩 된 데이터를 바이너리 데이터로 변환 할 수 있다.
그 이후에 FormData
에 추가 할 수 있다.
Buffer.from(객체,인코딩:?)
의 메소드를 사용해서 base64데이터를 buffer 즉, 바이너리 데이터로 변환 할 수 있다.
이 메소드에는 2가지 인자가 필요 한데
객체
: 문자열
, 버퍼
, 배열
또는 배열 버퍼
를 넣을 수 있다.
인코딩
: 객체가 문자열이면 인코딩을 지정하는 사용된다. 선택적인 매개변수이며 default는 utf8
이다. 헷갈릴 수 있는데, 문자열의 인코딩이 무엇인지 적어두는 것이다.
따라서 나는 문자열인 base64Data
를 넣어줬고, base64
로 인코딩된 문자열을 디코딩하여 Buffer
로 만들라는 의미이다.
위의 변환하는 코드는 샘플로 내가 두고두고 볼 예제로 만들어 두었기에,
위와 유사한?(우리는 File[]
이거라 조금은 다르다) 코드로 작성하였더니 통신이 잘 되었다.
formData 통신을 위해서
Buffer
base64
Blob
File Web APis
FileReader Web Apis
formidable
bodyParser
rawBody
req.on
multipart/form-data
HTTP Protocol
이렇게 많은 것을 알아 보았다.
그 중에서 node.js
쪽으로도 내 web 생태계의 영역이 확장 되어서 뿌듯하다.
내가 맡은 구현에 대한 공모전 작성이 끝났다. 2024년 6월 19일 ~ 2024년 8월 3일
총 43편의 포스팅을 했는데,
요번 프로젝트는 서울시에서 하는 공모전이고, 실제 서버쪽과 통신하는 귀중한 시간이기도 했고, app router
로 넘어가는 과도기 적인 상태에서의 프로젝트였던 것 같다.
이제 이력서 넣어보자....