Content-Type ํบ์๋ณด๊ธฐ! ๐
์น ๊ฐ๋ฐ์ ํ๋ค ๋ณด๋ฉด HTTP ์์ฒญ๊ณผ ์๋ต ํค๋์ Content-Type์ด ๊ผญ ๋ฑ์ฅํ์ฃ . HTTP ํต์ ์์ Content-Type์ ํด๋ผ์ด์ธํธ์ ์๋ฒ ๊ฐ ๋ฐ์ดํฐ ํ์์ ๋ช ์์ ์ผ๋ก ์ง์ ํด์ฃผ๋ ํค๋์ ํต์ฌ ์์์ ๋๋ค. ๋ฐ์ดํฐ๊ฐ ์ด๋ค ํํ๋ก ์ ๋ฌ๋๋์ง, ๊ทธ๋ฆฌ๊ณ ์ด๋ฅผ ์ด๋ป๊ฒ ์ฒ๋ฆฌํด์ผ ํ๋์ง ์๋ ค์ฃผ๋ ์ญํ ์ ํ์ฃ . ์ค๋์ Content-Type์ ์ญํ , MIME ํ์ , application/x-www-form-urlencoded์ multipart/form-data๋ฅผ ํฌํจํด, Content-Type์ ๋ค์ํ ํ์ ๊ณผ ํ์ฉ๋ฒ์ ๋ค๋ค๋ณด๊ฒ ์ต๋๋ค.
๐ฏ Content-Type์ด๋?
Content-Type์ HTTP ํค๋์ ํ ์ข ๋ฅ๋ก, ์๋ฒ์ ํด๋ผ์ด์ธํธ๊ฐ ๋ฐ์ดํฐ๋ฅผ ์ฃผ๊ณ ๋ฐ์ ๋ ๋ฐ์ดํฐ์ ํ์์ ์ง์ ํฉ๋๋ค. ์ ์ก๋๋ ๋ฐ์ดํฐ์ MIME ํ์ (Multipurpose Internet Mail Extensions)์ ๋ํ๋ ๋๋ค. ์ด๋ฅผ ํตํด ์๋ฒ์ ํด๋ผ์ด์ธํธ๋ ๋ฐ์ดํฐ ํ์์ ์ดํดํ๊ณ ์ ์ ํ ์ฒ๋ฆฌํ ์ ์์ต๋๋ค.
์๋ฅผ ๋ค์ด, ํด๋ผ์ด์ธํธ๊ฐ ์๋ฒ๋ก JSON ๋ฐ์ดํฐ๋ฅผ ์ ์กํ๊ฑฐ๋, ์๋ฒ๊ฐ ํด๋ผ์ด์ธํธ๋ก HTML ๋ฌธ์๋ฅผ ๋ณด๋ผ ๋ Content-Type์ ์ฌ์ฉํด ๋ฐ์ดํฐ ํ์์ ์ ์ํฉ๋๋ค.
์ญํ
- ๋ฐ์ดํฐ ํ์ ์ง์ : ์๋ฒ์ ํด๋ผ์ด์ธํธ๊ฐ ์ฃผ๊ณ ๋ฐ๋ ๋ฐ์ดํฐ๊ฐ ์ด๋ค ํ์์ธ์ง ์๋ ค์ค
- ๋ฐ์ดํฐ ํ์ฑ ํํธ ์ ๊ณต: ์ ์ก๋ ๋ฐ์ดํฐ์ ์ ์ ํ ํ์ฑ๊ณผ ๋ ๋๋ง์ ํ ์ ์๋๋ก ๋์
- ํ์ผ ์ฒ๋ฆฌ: ํ์ผ ๋ค์ด๋ก๋๋ ์ ๋ก๋ ์ ๋ธ๋ผ์ฐ์ ๋ Content-Type์ ๊ธฐ์ค์ผ๋ก ํ์ผ ์ฒ๋ฆฌ ๋ฐฉ์์ ๊ฒฐ์
๐ก ์ฃผ์ MIME ํ์ ๊ณผ ์์
MIME(Multipurpose Internet Mail Extensions) ํ์ ์ ๋ฐ์ดํฐ ํ์์ ๋ํ๋ด๋ ํ์คํ๋ ๋ฌธ์์ด์ ๋๋ค. Content-Type์ MIME ํ์ ์ ์ฌ์ฉํด ๋ฐ์ดํฐ๋ฅผ ์ ์ํฉ๋๋ค.
1๏ธโฃ ํ ์คํธ ๊ด๋ จ
MIME ํ์ | ์ค๋ช | ์์ |
text/html HTML | HTML ๋ฌธ์ | ์น ๋ธ๋ผ์ฐ์ ์์ ๋ ๋๋ง ๊ฐ๋ฅ |
text/plain | ์ผ๋ฐ ํ ์คํธ | ํ์ผ ํ ์คํธ ๋ทฐ์ด๋ก ํ์ธ ๊ฐ๋ฅ |
text/css | CSS ํ์ผ | ์คํ์ผ ์ ์ ํ์ผ |
text/javascript | JavaScript ํ์ผ | ์คํฌ๋ฆฝํธ ์คํ ๊ฐ๋ฅ |
2๏ธโฃ ์ ํ๋ฆฌ์ผ์ด์ ๊ด๋ จ
MIME ํ์ | ์ค๋ช | ์์ |
application/json | JSON ํ์ ๋ฐ์ดํฐ | API ์๋ต, ๋ฐ์ดํฐ ์ ์ก |
application/xml | XML ํ์ ๋ฐ์ดํฐ | ๋ฐ์ดํฐ ๊ตํ, ๋ฌธ์ ์ ์ |
application/pdf | PDF ๋ฌธ์ | ๋ฌธ์ ๋ทฐ์ด๋ก ํ์ธ ๊ฐ๋ฅ |
application/octet-stream | ๋ฐ์ด๋๋ฆฌ ๋ฐ์ดํฐ(ํ์ผ ๋ค์ด๋ก๋) | ์คํ ํ์ผ ๋ฑ |
3๏ธโฃ ๋ฉํฐ๋ฏธ๋์ด ๊ด๋ จ
MIME ํ์ | ์ค๋ช | ์์ |
image/jpeg | JPEG | ์ด๋ฏธ์ง ํ๋กํ ์ฌ์ง, ๋ฐฐ๋ ์ด๋ฏธ์ง |
image/png | PNG | ์ด๋ฏธ์ง ํฌ๋ช ๋ฐฐ๊ฒฝ ์ด๋ฏธ์ง |
video/mp4 | MP4 | ๋น๋์ค ๋์์ ์คํธ๋ฆฌ๋ฐ |
audio/mpeg | MP3 | ์ค๋์ค ์์ ํ์ผ |
4๏ธโฃ Form ๋ฐ์ดํฐ ์ ์ก ๊ด๋ จ ํ์
application/x-www-form-urlencoded
์ด ํ์์ HTML <form>์์ ๊ธฐ๋ณธ์ ์ผ๋ก ์ฌ์ฉํ๋ ํ์ ์ผ๋ก, ํค-๊ฐ ์ ๋ฐ์ดํฐ๋ฅผ ์ ์กํฉ๋๋ค. ๋ฐ์ดํฐ๋ฅผ URL ์ธ์ฝ๋ฉ(ํค์ ๊ฐ์ key=value&key2=value2 ํ์์ผ๋ก ๋ณํ)ํ์ฌ ์๋ฒ๋ก ์ ๋ฌํฉ๋๋ค.
์ฌ์ฉ ์์
<form method="POST" action="/submit" enctype="application/x-www-form-urlencoded">
<input type="text" name="username" value="Alice">
<input type="password" name="password" value="1234">
<button type="submit">Submit</button>
</form>
HTTP ์์ฒญ
POST /submit HTTP/1.1
Content-Type: application/x-www-form-urlencoded
username=Alice&password=1234
ํน์ง
- ๋จ์ํ ๋ฐ์ดํฐ ์ ์ก์ ์ ํฉํ๋ค.
- ๋ฐ์ดํฐ๊ฐ URL-encoded ํ์์ผ๋ก ์ ์ก๋๋ค.
- ๋๋์ ํ์ผ ์ ๋ก๋์๋ ๋ถ์ ํฉํ๋ค.
multipart/form-data
ํ์ผ ์ ๋ก๋๋ฅผ ํฌํจํ ๋ณต์กํ ๋ฐ์ดํฐ ์ ์ก์ ์ํด ์ฌ์ฉ๋ฉ๋๋ค. ๋ฐ์ดํฐ๋ฅผ ์ฌ๋ฌ ํํธ๋ก ๋๋์ด ์ ์กํ๋ฉฐ, ๊ฐ ํํธ๋ ์์ฒด ํค๋์ ๋ฐ๋๋ฅผ ๊ฐ์ง๋๋ค. ํ์ผ๊ณผ ํ ์คํธ ๋ฐ์ดํฐ๋ฅผ ๋์์ ์ ์กํ ์ ์๋ ๊ฐ๋ ฅํ ํ์์ ๋๋ค.
์ฌ์ฉ ์์
<form method="POST" action="/upload" enctype="multipart/form-data">
<input type="text" name="username" value="Chloe">
<input type="file" name="profilePicture">
<button type="submit">Upload</button>
</form>
HTTP ์์ฒญ
POST /upload HTTP/1.1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryxyz
------WebKitFormBoundaryxyz
Content-Disposition: form-data; name="username"
Chloe
------WebKitFormBoundaryxyz
Content-Disposition: form-data; name="profilePicture"; filename="avatar.png"
Content-Type: image/png
(binary data)
------WebKitFormBoundaryxyz--
ํน์ง
- ํ์ผ๊ณผ ํ ์คํธ ๋ฐ์ดํฐ๋ฅผ ๋์์ ์ ์ก ๊ฐ๋ฅ
- ๊ฐ ๋ฐ์ดํฐ ํํธ๊ฐ ํค๋๋ฅผ ํฌํจ, ๋ ๋ฆฝ์ ์ผ๋ก ์ฒ๋ฆฌ๋จ
- ํ์ผ ์ ๋ก๋์์ ๊ฐ์ฅ ๋ง์ด ์ฌ์ฉ๋๋ ๋ฐฉ์
๐ React ์ Spring Boot ๋ฅผ ํ์ฉํ ์์
React ํด๋ผ์ด์ธํธ: Form ๋ฐ์ดํฐ ์ ์ก
import React, { useState } from 'react';
import axios from 'axios';
const FormExample = () => {
const [username, setUsername] = useState('');
const [file, setFile] = useState(null);
const handleSubmit = async (e) => {
e.preventDefault();
// FormData ๊ฐ์ฒด ์์ฑ
const formData = new FormData();
formData.append('username', username);
formData.append('file', file);
// axios ์์ฒญ
await axios.post('/api/upload', formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
});
alert('Form submitted!');
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
placeholder="Enter username"
onChange={(e) => setUsername(e.target.value)}
/>
<input
type="file"
onChange={(e) => setFile(e.target.files[0])}
/>
<button type="submit">Submit</button>
</form>
);
};
export default FormExample;
Spring Boot ์๋ฒ: Form ๋ฐ์ดํฐ ์ฒ๋ฆฌ
@RestController
@RequestMapping("/api")
public class UploadController {
@PostMapping("/upload")
public ResponseEntity<String> handleFileUpload(
@RequestParam("username") String username,
@RequestParam("file") MultipartFile file) {
// ํ์ผ ์ ๋ณด ์ถ๋ ฅ
System.out.println("Username: " + username);
System.out.println("File Name: " + file.getOriginalFilename());
return ResponseEntity.ok("File uploaded successfully!");
}
}
๐ ๏ธ ์ค์ํ๊ธฐ ์ฌ์ด ์ ๊ณผ ์ฃผ์์ฌํญ
- application/x-www-form-urlencoded์ multipart/form-data ์ฐจ์ด
- application/x-www-form-urlencoded๋ ํ ์คํธ ๊ธฐ๋ฐ์ ๋ฐ์ดํฐ ์ ์ก์ ์ ํฉํ์ง๋ง, ํ์ผ ์ ๋ก๋์๋ ๋ถ์ ํฉ
- multipart/form-data๋ ํ ์คํธ์ ํ์ผ ๋ฐ์ดํฐ๋ฅผ ํจ๊ป ์ฒ๋ฆฌํ ์ ์์
- CORS์ Preflight ์์ฒญ
- Content-Type์ด application/x-www-form-urlencoded๊ฐ ์๋ ๊ฒฝ์ฐ, ๋ธ๋ผ์ฐ์ ๋ Preflight ์์ฒญ์ ๋ณด๋ธ๋ค.
- Spring Boot์์๋ CORS ์ค์ ์ด ํ์ํ ์ ์๋ค.
@Configuration
public class CorsConfig {
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("http://localhost:3000")
.allowedMethods("POST", "GET", "PUT", "DELETE")
.allowCredentials(true);
}
};
}
}
๐ ๏ธ ์ค์ํ๊ธฐ ์ฌ์ด ์ ๊ณผ ์ฃผ์์ฌํญ
- Content-Type ๋๋ฝ
- ์๋ฒ์์ ์๋ต ํค๋์ Content-Type์ ์ค์ ํ์ง ์์ผ๋ฉด ํด๋ผ์ด์ธํธ๊ฐ ๋ฐ์ดํฐ๋ฅผ ์ ๋๋ก ์ฒ๋ฆฌํ์ง ๋ชปํ ์ ์๋ค.
- ์๋ชป๋ MIME ํ์
- ์๋ฅผ ๋ค์ด, JSON ๋ฐ์ดํฐ๋ฅผ ์ ์กํ๋ฉด์ application/x-www-form-urlencoded๋ก ์ค์ ํ๋ฉด ํ์ฑ ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ค.
- CORS์์ ์ฐ๊ด์ฑ
- Preflight ์์ฒญ์์ Content-Type์ด ๋จ์ ์์ฒญ ์กฐ๊ฑด(application/x-www-form-urlencoded, multipart/form-data, text/plain)์ ๋ง์กฑํ์ง ์์ผ๋ฉด ๋ธ๋ผ์ฐ์ ๊ฐ CORS ์์ฒญ์ ์ฐจ๋จํ ์ ์๋ค.
- ์์ถ๋ ๋ฐ์ดํฐ
- Gzip์ด๋ Brotli๋ก ์์ถ๋ ๋ฐ์ดํฐ๋ฅผ ์ ์กํ ๊ฒฝ์ฐ Content-Encoding๊ณผ ํจ๊ป ์ฌ์ฉํด์ผ ํฉ๋๋ค.
โจ Content-Type ์ต์ ํ์ฉ ํ
1๏ธโฃ ์ ํํ MIME ํ์ ์ค์
- ๋ฐ์ดํฐ์ ํ์์ ๋ง๋ MIME ํ์ ์ ์ค์ ํ์ธ์. ์๋ฅผ ๋ค์ด, JSON ๋ฐ์ดํฐ๋ ๋ฐ๋์ application/json์ผ๋ก ์ค์ ํด์ผ ํฉ๋๋ค.
2๏ธโฃ ๋ธ๋ผ์ฐ์ ํธํ์ฑ ๊ณ ๋ ค
- ๋ธ๋ผ์ฐ์ ๋ง๋ค MIME ํ์ ์ฒ๋ฆฌ ๋ฐฉ์์ด ๋ค๋ฅผ ์ ์์ผ๋ฏ๋ก ์ฃผ์ ๋ธ๋ผ์ฐ์ ์์ ํ ์คํธํด๋ณด์ธ์.
3๏ธโฃ ๋ณด์ ๊ฐํ
- ์๋ชป๋ MIME ํ์ ์ ์ ์ฉํ ๊ณต๊ฒฉ(XSS ๋ฑ)์ ๋ฐฉ์งํ๋ ค๋ฉด ํค๋ ๋ณด์ ์ค์ ์ ๊ฐํํ์ธ์!
app.use((req, res, next) => {
res.setHeader('X-Content-Type-Options', 'nosniff');
next();
}); // Express.js ์์
4๏ธโฃ Preflight ์์ฒญ ๊ด๋ฆฌ
- Preflight ์์ฒญ์ ์ต์ ํํ๋ ค๋ฉด ๋จ์ ์์ฒญ ์กฐ๊ฑด์ ์ถฉ์กฑํ๊ฑฐ๋ Access-Control-Max-Age๋ฅผ ์ค์ ํด ์บ์ฑ์ ํ์ฉํ์ธ์.
๐ ๋ง๋ฌด๋ฆฌ
Content-Type์ HTTP ํต์ ์ ํต์ฌ ์์๋ก, ๋ฐ์ดํฐ ์ ์ก์ ์ฑ๊ณต ์ฌ๋ถ๋ฅผ ๊ฒฐ์ ์ง๋ ์ค์ํ ์ญํ ์ ํฉ๋๋ค. ํนํ application/x-www-form-urlencoded์ multipart/form-data๋ ๊ฐ๊ฐ ๋จ์ ๋ฐ์ดํฐ ์ ์ก๊ณผ ํ์ผ ์ ๋ก๋์์ ์์ฃผ ์ฌ์ฉ๋๋ฏ๋ก, ์ํฉ์ ๋ง๊ฒ ์ ํํด์ผ ํฉ๋๋ค.
๐ท์ ์ค์ ๊ฐ๋ฐ์๊ฐ ๋์ด๋ด ์๋น!๐ท