🌐 Розділ 6 · Питання #16

Що таке Content-Type header

Мережевий протокол передає тіло повідомлення як потік «сирих» байтів. Заголовок Content-Type пояснює одержувачу, за якими правилами та яким парсером ці байти слід інтерпретувати:


🟢 Junior Level

Content-Type — це заголовок сутності (Entity Header) протоколу HTTP (RFC 9110, розділ 8.3), що вказує клієнту або серверу тип даних (Media Type / MIME Type), переданих у тілі (body) HTTP-запиту або відповіді.

Принцип роботи

Мережевий протокол передає тіло повідомлення як потік «сирих» байтів. Заголовок Content-Type пояснює одержувачу, за якими правилами та яким парсером ці байти слід інтерпретувати:

POST /api/v1/users HTTP/1.1
Content-Type: application/json; charset=utf-8

{"name": "Ivan", "age": 30}

Без цього заголовка сервер не знає: перед ним звичайний текст, JSON-документ, XML чи бінарний PDF-файл.

Різниця між Content-Type та Accept

  • Content-Type: Описує формат даних, які відправник надсилає прямо зараз у поточному тілі запиту/відповіді.
  • Accept: Описує формати даних, які відправник готовий прийняти у відповідному повідомленні.

Найбільш поширені медіа-типи (MIME types)

MIME Type Призначення
application/json Стандарт де-факто для REST API (RFC 8259).
application/xml Документи у форматі XML.
application/x-www-form-urlencoded Стандартні HTML-форми (пари key=value&key2=val2).
multipart/form-data Завантаження файлів та складених даних (з boundary).
text/plain / text/html Звичайний текст або HTML-сторінки.
application/octet-stream Довільні бінарні дані (байти).

🟡 Middle Level

Механіка вибору HttpMessageConverter у Spring MVC

У Spring MVC за читання тіла запиту з урахуванням Content-Type відповідає ланцюжок інтерфейсів HttpMessageConverter<T>:

                       HTTP Request (Content-Type: application/json)
                                       │
                                       ▼
                               DispatcherServlet
                                       │
                                       ▼
                       RequestResponseBodyMethodProcessor
                                       │
                      ┌────────────────┴────────────────┐
                      ▼                                 ▼
         MappingJackson2HttpMessageConverter    ByteArrayHttpMessageConverter
         - canRead(UserDto.class, JSON) -> TRUE  - canRead(...) -> FALSE
                      │
                      ▼
         objectMapper.readValue(stream, UserDto.class)
  1. Spring перебирає список зареєстрованих конвертерів у порядку пріоритету.
  2. Для кожного конвертера викликається метод canRead(targetClass, mediaType).
  3. Перший конвертер, який повернув true (наприклад, MappingJackson2HttpMessageConverter), читає InputStream і здійснює десеріалізацію.
  4. Якщо відповідний конвертер не знайдено — Spring генерує HttpMediaTypeNotSupportedException, що трансформується у статус 415 Unsupported Media Type.

Атрибут consumes у Spring контролерах

За допомогою анотацій @PostMapping / @PutMapping можна явно обмежити підтримувані формати:

@PostMapping(value = "/upload", consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Void> processJson(@RequestBody UserDto dto) { ... }

Якщо клієнт надішле запит із Content-Type: application/xml, Spring MVC поверне 415 Unsupported Media Type ще до входу в метод контролера.

Параметри заголовка: charset та boundary

  1. charset=utf-8: За специфікацією RFC 8259 для application/json кодування UTF-8 є обов’язковим стандартом за замовчуванням, тому передача charset для JSON є надлишковою. Для text/plain або text/html зазначення charset критично важливе, інакше браузер застосує застарілий ISO-8859-1.
  2. boundary у multipart/form-data:
    Content-Type: multipart/form-data; boundary=---------------------------974767299852498929531610575
    

    Роздільник boundary генерується клієнтом і розмежовує частини складеного запиту (текстові поля форми та потоки байтів завантажуваних файлів).

Безпека: MIME Sniffing та захист X-Content-Type-Options: nosniff

Історично браузери намагалися «вгадати» справжній формат файлу за його вмістом (magic bytes), ігноруючи надісланий сервером Content-Type. Це призводило до вразливостей Cross-Site Scripting (XSS):

  • Зловмисник завантажував аватарку з розширенням .png, всередині якої сховано тег <script>alert('XSS')</script>.
  • Сервер віддавав файл як Content-Type: image/png.
  • Браузер користувача виконував «sniffing», вирішував, що це HTML/JavaScript, і виконував шкідливий код.

Захист: Сервер завжди зобов’язаний надсилати заголовок відповіді:

X-Content-Type-Options: nosniff

Цей заголовок забороняє браузеру змінювати тип контенту, гарантуючи сувору інтерпретацію.


🔴 Senior Level

Спеціалізовані MIME типи в Enterprise REST

У зрілих архітектурах застосовуються вузькоспеціалізовані медіа-типи:

  • application/problem+json (RFC 9457): Стандарт структури помилок (Problem Details), прийнятий за замовчуванням у Spring Boot 3.
  • application/merge-patch+json (RFC 7386): Сигналізує про часткове оновлення через злиття JSON-структур у методі PATCH.
  • application/json-patch+json (RFC 6902): Масив атомарних інструкцій add, remove, replace.
  • application/vnd.company.user.v2+json (Vendor-specific MIME): Ізольоване версіонування схеми через Content Negotiation.

Високопродуктивні бінарні формати

У Highload-системах та міжсервісних комунікаціях заміна JSON на бінарні формати дає економію мережевого трафіку в 3–5 разів та прискорення парсингу в 5–10 разів (за рахунок відсутності лексичного розбору рядків):

  • application/x-protobuf (Google Protocol Buffers)
  • application/avro (Apache Avro)
  • application/cbor (Concise Binary Object Representation)

Реалізація підтримки складених форматів у Spring Boot 3

Приклад контролера, що підтримує завантаження файлу разом із метаданими через multipart/form-data та JSON:

package com.example.controller;

import com.example.dto.DocumentMetadataDto;
import com.example.service.DocumentService;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;

@RestController
@RequestMapping("/api/v1/documents")
public class DocumentUploadController {

    private final DocumentService documentService;

    public DocumentUploadController(DocumentService documentService) {
        this.documentService = documentService;
    }

    // Приймає multipart/form-data: частину метаданих у JSON та бінарний файл
    @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    public ResponseEntity<Void> uploadDocument(
            @RequestPart("metadata") DocumentMetadataDto metadata,
            @RequestPart("file") MultipartFile file) throws IOException {

        // Перевіряємо валідність Content-Type завантажуваного файлу
        String fileContentType = file.getContentType();
        if (fileContentType == null || !fileContentType.equals(MediaType.APPLICATION_PDF_VALUE)) {
            return ResponseEntity.status(HttpStatus.UNSUPPORTED_MEDIA_TYPE).build();
        }

        documentService.save(metadata, file.getBytes());
        return ResponseEntity.status(HttpStatus.CREATED).build();
    }
}

🎯 Шпаргалка для інтерв’ю

30-секундна відповідь

Content-Type (RFC 9110) — це HTTP-заголовок, що вказує MIME-тип даних, які містяться в тілі поточного запиту або відповіді. Він необхідний одержувачу (серверу або клієнту) для вибору правильного десеріалізатора/парсера (наприклад, Jackson для application/json).

На відміну від Accept (який повідомляє, який формат клієнт очікує у відповіді), Content-Type повідомляє, який формат передається прямо зараз.

У Spring MVC заголовок обробляється через HttpMessageConverter та атрибут consumes. Якщо надіслано непідтримуваний тип контенту, сервер зобов’язаний повернути помилку 415 Unsupported Media Type.

4 каверзних питання з відповідями

  1. Який код HTTP-відповіді має повернути сервер, якщо клієнт надіслав тіло у форматі XML, а сервер приймає лише JSON? Відповідь: Сервер зобов’язаний повернути статус 415 Unsupported Media Type. Повернення помилки 400 Bad Request у такій ситуації є порушенням стандарту HTTP.

  2. Навіщо в заголовок multipart/form-data додається параметр boundary? Відповідь: Параметр boundary визначає унікальну послідовність символів (роздільник), яка слугує межею між окремими частинами складеного HTTP-запиту (наприклад, між JSON-метаданими та бінарним потоком байтів завантажуваного файлу).

  3. Чи обов’язково вказувати charset=utf-8 для application/json? Відповідь: Ні, за специфікацією RFC 8259 кодування UTF-8 є суворим стандартом за замовчуванням для формату JSON, тому параметр charset є надлишковим. Проте для типів text/plain та text/html зазначення кодування обов’язкове.

  4. Що таке MIME Sniffing і для чого потрібен заголовок X-Content-Type-Options: nosniff? Відповідь: MIME Sniffing — це механізм браузерів, що намагається перевизначити оголошений сервером Content-Type, аналізуючи байти вмісту. Зловмисники використовують це для XSS-атак (маскуючи виконуваний JavaScript під картинку). Заголовок nosniff змушує браузер суворо довіряти переданому Content-Type.

Червоні прапорці (чого категорично не можна говорити)

  • ❌ «Content-Type і Accept — це одне й те саме» (Content-Type описує тіло, що надсилається, Accept запитує формат відповіді).
  • ❌ «GET-запити обов’язково повинні містити Content-Type: application/json» (запити без тіла повідомлення не повинні містити заголовок Content-Type).
  • ❌ «Якщо клієнт надіслав непідтримуваний тип даних, сервер повинен вгадати формат самостійно» (сервер зобов’язаний повернути 415 Unsupported Media Type).
  • ❌ «Заголовок X-Content-Type-Options: nosniff потрібен для оптимізації швидкості парсингу JSON» (він потрібен виключно для безпеки проти XSS-атак).

Пов’язані теми