Web

[WEB] Content-Type: ์›น ๊ฐœ๋ฐœ์ž๊ฐ€ ๊ผญ ์•Œ์•„์•ผ ํ•  ๋ชจ๋“  ๊ฒƒ! ๐Ÿ“š

xeunnie 2025. 1. 19. 01:00
728x90
๋ฐ˜์‘ํ˜•

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!");
    }
}

๐Ÿ› ๏ธ ์‹ค์ˆ˜ํ•˜๊ธฐ ์‰ฌ์šด ์ ๊ณผ ์ฃผ์˜์‚ฌํ•ญ

  1. application/x-www-form-urlencoded์™€ multipart/form-data ์ฐจ์ด
    • application/x-www-form-urlencoded๋Š” ํ…์ŠคํŠธ ๊ธฐ๋ฐ˜์˜ ๋ฐ์ดํ„ฐ ์ „์†ก์— ์ ํ•ฉํ•˜์ง€๋งŒ, ํŒŒ์ผ ์—…๋กœ๋“œ์—๋Š” ๋ถ€์ ํ•ฉ
    • multipart/form-data๋Š” ํ…์ŠคํŠธ์™€ ํŒŒ์ผ ๋ฐ์ดํ„ฐ๋ฅผ ํ•จ๊ป˜ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Œ
  2. 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);
            }
        };
    }
}

 


๐Ÿ› ๏ธ ์‹ค์ˆ˜ํ•˜๊ธฐ ์‰ฌ์šด ์ ๊ณผ ์ฃผ์˜์‚ฌํ•ญ

  1. Content-Type ๋ˆ„๋ฝ
    • ์„œ๋ฒ„์—์„œ ์‘๋‹ต ํ—ค๋”์— Content-Type์„ ์„ค์ •ํ•˜์ง€ ์•Š์œผ๋ฉด ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๋ฐ์ดํ„ฐ๋ฅผ ์ œ๋Œ€๋กœ ์ฒ˜๋ฆฌํ•˜์ง€ ๋ชปํ•  ์ˆ˜ ์žˆ๋‹ค.
  2. ์ž˜๋ชป๋œ MIME ํƒ€์ž…
    • ์˜ˆ๋ฅผ ๋“ค์–ด, JSON ๋ฐ์ดํ„ฐ๋ฅผ ์ „์†กํ•˜๋ฉด์„œ application/x-www-form-urlencoded๋กœ ์„ค์ •ํ•˜๋ฉด ํŒŒ์‹ฑ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.
  3. CORS์™€์˜ ์—ฐ๊ด€์„ฑ
    • Preflight ์š”์ฒญ์—์„œ Content-Type์ด ๋‹จ์ˆœ ์š”์ฒญ ์กฐ๊ฑด(application/x-www-form-urlencoded, multipart/form-data, text/plain)์„ ๋งŒ์กฑํ•˜์ง€ ์•Š์œผ๋ฉด ๋ธŒ๋ผ์šฐ์ €๊ฐ€ CORS ์š”์ฒญ์„ ์ฐจ๋‹จํ•  ์ˆ˜ ์žˆ๋‹ค.
  4. ์••์ถ•๋œ ๋ฐ์ดํ„ฐ
    • 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๋Š” ๊ฐ๊ฐ ๋‹จ์ˆœ ๋ฐ์ดํ„ฐ ์ „์†ก๊ณผ ํŒŒ์ผ ์—…๋กœ๋“œ์—์„œ ์ž์ฃผ ์‚ฌ์šฉ๋˜๋ฏ€๋กœ, ์ƒํ™ฉ์— ๋งž๊ฒŒ ์„ ํƒํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

 

๐ŸŒท์ „์„ค์˜ ๊ฐœ๋ฐœ์ž๊ฐ€ ๋˜์–ด๋ด…์‹œ๋‹น!๐ŸŒท

728x90
๋ฐ˜์‘ํ˜•