๐ฆ Multipart/form-data ์ง์ ํ์ ๋ง๋ค๋ฉฐ ์๋ฆฌ ์ดํดํ๊ธฐ
์น ๊ฐ๋ฐ์ ํ๋ค ๋ณด๋ฉด ํ์ผ ์ ๋ก๋ ๊ธฐ๋ฅ์ ๊ตฌํํ ์ผ์ด ๋ง์ต๋๋ค. ๋ณดํต์ Express์์ multer ๊ฐ์ ๋ฏธ๋ค์จ์ด๋ฅผ ์ฐ๋ฉด ์์ฝ๊ฒ ๋๋๋๋ฐ์. ํ์ง๋ง ์ด๋ฒ์๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฐ์ง ์๊ณ , ์ง์ multipart/form-data๋ฅผ ํ์ฑํด๋ณด๋ฉด์ ๋์ ์๋ฆฌ๋ฅผ ์ดํดํด๊ณ ์ ํ์ต๋๋ค.
1. ์ multipart/form-data์ธ๊ฐ?
์ผ๋ฐ์ ์ธ HTML <form>์ ๊ธฐ๋ณธ์ ์ผ๋ก application/x-www-form-urlencoded ๋ฐฉ์์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ๋ณด๋
๋๋ค.
ํ์ง๋ง ํ์ผ ์
๋ก๋์ฒ๋ผ ๋์ฉ๋ ์ด์ง ๋ฐ์ดํฐ๊ฐ ํฌํจ๋๋ฉด ๋จ์ URL ์ธ์ฝ๋ฉ์ผ๋ก๋ ์ฒ๋ฆฌ๊ฐ ์ด๋ ต์ต๋๋ค.
์ด๋ ์ฌ์ฉํ๋ ๋ฐฉ์์ด ๋ฐ๋ก multipart/form-data์
๋๋ค.
์ฌ๋ฌ ๊ฐ์ ํํธ๋ฅผ ํ๋์ ์์ฒญ์ ๋ด๊ณ , ๊ฐ ํํธ๋ฅผ boundary(๊ฒฝ๊ณ ๋ฌธ์์ด) ๋ก ๊ตฌ๋ถํ๋ ๊ตฌ์กฐ๋ฅผ ๊ฐ์ง๋๋ค.
2. ์์ฒญ ์์ ์ดํด๋ณด๊ธฐ
์๋ฅผ ๋ค์ด, ์ฌ์ฉ์๊ฐ username๊ณผ profile ์ด๋ฏธ์ง๋ฅผ ์
๋ก๋ํ๋ค๊ณ ํด๋ด
์๋ค. ์์ฒญ์ ๋๋ต ๋ค์๊ณผ ๊ฐ์ด ์ ์ก๋ฉ๋๋ค.
const formData = new FormData();
formData.append("title", "์ค๋์ ์ ์ฌ");
formData.append("content", "๊น๋ฐฅ์ฒ๊ตญ์์ ๋ผ๋ฉด ๋จน์๋ค ๐");
formData.append("date", "2025-10-01");
formData.append("photo", document.querySelector("input[type=file]").files[0]);
fetch("/upload", {
method: "POST",
body: formData
});
POST /upload HTTP/1.1
Host: example.com
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryABC123
Content-Length: 51234
------WebKitFormBoundaryABC123
Content-Disposition: form-data; name="title"
์ค๋์ ์ ์ฌ
------WebKitFormBoundaryABC123
Content-Disposition: form-data; name="content"
๊น๋ฐฅ์ฒ๊ตญ์์ ๋ผ๋ฉด ๋จน์๋ค ๐
------WebKitFormBoundaryABC123
Content-Disposition: form-data; name="date"
2025-10-01
------WebKitFormBoundaryABC123
Content-Disposition: form-data; name="photo"; filename="lunch.jpg"
Content-Type: image/jpeg
(์ฌ๊ธฐ์ lunch.jpg ๋ฐ์ด๋๋ฆฌ ๋ฐ์ดํฐ)
------WebKitFormBoundaryABC123--
- boundary ๋ ๊ฐ ๋ฐ์ดํฐ ํํธ๋ฅผ ๊ตฌ๋ถํ๋ ๋ฌธ์์ด ์ด๊ณ
- ๊ฐ ํํธ ๋ ํค๋ + ๊ณต๋ฐฑ์ค + ์ค์ ๋ฐ์ดํฐ ๋ก ์ด๋ฃจ์ด์ ธ ์์ต๋๋ค.
3. ํ์ฑ ์ ์ฐจ ์ ๋ฆฌ
- Content-Type ํค๋์์ boundary ์ถ์ถ→ boundary = ----WebKitFormBoundaryX
- multipart/form-data; boundary=----WebKitFormBoundaryX
- body๋ฅผ boundary ๊ธฐ์ค์ผ๋ก split
- ๊ฐ ํํธ์์ ํค๋/๋ณธ๋ฌธ ๋ถ๋ฆฌ
- \r\n\r\n ๊ธฐ์ค์ผ๋ก ํค๋์ ๋ฐ์ดํฐ ๊ตฌ๋ถ
- ํค๋ ํด์
- Content-Disposition์์ name, filename ์ถ์ถ
- Content-Type์ด ์์ผ๋ฉด ํ์ผ ๋ฐ์ดํฐ
- ๋ฐ์ดํฐ ์ ์ฅ
- ์ผ๋ฐ ํ๋ → ๋ฌธ์์ด
- ํ์ผ → filename, contentType, ๋ด์ฉ(Buffer)
4. ๊ฐ๋จ ํ์ ๊ตฌํ (string split ๋ฒ์ )
์๋๋ ์ดํด๋ฅผ ์ํด AI ์๊ฒ ๋ถํํ ๊ฐ๋จ ๋ฌธ์์ด ๊ธฐ๋ฐ ๊ฐ๋จ ํ์์
๋๋ค. ( ์ค์ ๋ก๋ Buffer ๊ธฐ๋ฐ์ผ๋ก ํ์ฅ์ด ํ์ )
์ฒซ ํค๋ ๊ธฐ์ค์ผ๋ก ๋ฐ์ด๋๋ฆฌ ๋ฌธ์์ด์ ํ์
ํ๊ณ ,
boundary ๊ธฐ์ค์ผ๋ก ๋ฐ๋ ํํธ๋ฅผ ์ชผ๊ฐ๋ ๋ชจ์ต์ ํ์ธํ ์ ์์ต๋๋ค.
function getBoundary(contentType) {
const match = contentType.match(/boundary=(.+)$/);
return match && match[1];
}
function parseMultipart(body, contentType) {
const boundary = getBoundary(contentType);
const rawParts = body.split(`--${boundary}`);
const result = {};
for (const rawPart of rawParts) {
if (rawPart.trim() === '' || rawPart === '--') continue;
const [rawHeaders, rawBody] = rawPart.split('\r\n\r\n');
if (!rawHeaders) continue;
const nameMatch = rawHeaders.match(/name="([^"]+)"/);
const filenameMatch = rawHeaders.match(/filename="([^"]+)"/);
const contentTypeMatch = rawHeaders.match(/Content-Type: (.+)/);
const name = nameMatch?.[1];
const filename = filenameMatch?.[1];
const contentType = contentTypeMatch?.[1];
let value = rawBody.trim();
if (filename) {
result[name] = { filename, contentType, content: value };
} else {
result[name] = value;
}
}
return result;
}
์คํ ์์
const body = `
------WebKitFormBoundaryX
Content-Disposition: form-data; name="username"
seoyeon
------WebKitFormBoundaryX
Content-Disposition: form-data; name="profile"; filename="me.png"
Content-Type: image/png
(binary-data-here)
------WebKitFormBoundaryX--
`;
const contentType = "multipart/form-data; boundary=----WebKitFormBoundaryX";
console.log(parseMultipart(body, contentType));
์ถ๋ ฅ:
{
username: "seoyeon",
profile: {
filename: "me.png",
contentType: "image/png",
content: "(binary-data-here)"
}
}
'๐ WEB' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
| RAG ์ฐ๋ ์น ์ฑ (0) | 2026.01.23 |
|---|---|
| ์น ์ฌ์ดํธ ์ต์ ํ ๋ฐฉ๋ฒ (0) | 2026.01.19 |
| Node.js๋ก ๊ณ ์ฑ๋ฅ ์น์๋ฒ ๋ง๋ค๊ธฐ: Cluster์ Worker Threads (0) | 2025.09.28 |
| HTTP ํจํท ๋ถ์ํ๊ธฐ (0) | 2025.09.18 |
| HTTP ํจํท ๊ตฌ์กฐ, ์์ฒญ ํค๋/๋ฐ๋ (0) | 2025.09.17 |