🌐 Раздел 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-атак).

Связанные темы