Лирическое вступление

Чтобы писать посты в блог нужен редактор. Чтобы посты читали в них нужно добавлять картинки. Чтобы добавлять картинки в посты в редакторе должна быть кнопка «Добавить картинку». И она у меня есть, но, как в том анекдоте, есть один нюанс* и состоит он в том, что редактор постов является самописным Vue приложением в основе которого Vue2Editor который, в свою очередь, построен вокруг редактора Quill (говно, не используйте). Как следствие самописности, реализация таких вещей как загрузка картинок начиная от выбора картинки с помощью диалогового окна или драг-н-дропа до отправки её на сервер ложатся на мои плечи.


Самая суть

Итак, у меня есть код, который загружает картинку на сервер.


handleImageAdded: async function(file) {
    if (!file) return

    const formData = new FormData()
    formData.append('image', file)

    const headers = new Headers({
        'Content-Type': 'multipart/form-data',
        user: /* имя пользователя */,
        token: /* токен пользователя */
    })

    try {
        const result = await fetch(
            '/api/endpoint/for/image',
            {
                headers,
                method: 'POST',
                body: formData
            }
        )

        const url = result.data.url
        // … код вставки картинки в пост
    } catch (error) {
        console.error(error)
    }
}


Кто понял где тут ошибка, тот молодец и дальше может не читать.

Тем, кто не понял скажу, что источник проблемы в отправляемых заголовках. Точнее в Content-Type. Далее в нескольких предложениях я рефлексирую на тему как я допустил такую ошибку. Если вы не любите воду в блогах, то листайте до подзаголовка «Решение»


Посыпаю голову пеплом

Мой основной язык это JavaScript. В последний раз на PHP много лет назад и, когда, в своём переписывании движка сайта с нуля, я дошёл до необходимости загрузить файл на сервер. Я естественно вбил в гугл «PHP загрузка файлов». Первая же ссылка привела меня сюда. Если взглянуть на форму из Примера №1, то можно увидеть, что в тэге <form> указаны несколько свойств. Если они указаны в самом первом примере, рассудил я, который, по идее, должен быть самым простым и минимально рабочим, значит надо их явно передавать. Тем более там первой строчкой идёт комментарий с КАПСЛОКОМ где выделены слова ДОЛЖЕН БЫТЬ. Логично? Логично! А вот и нет!


Решение

Проблема в заголовке Content-Type. Он есть. Он правильный, но с ним что-то не так. Что?

wrong content-type

Не так с ним то, что он не полон. Если не устанавливать его в коде, то браузер выставит его самостоятельно и выглядеть он будет вот так:

right content-type

То есть, даже если мы программно выгружаем файл, то умница-браузер всё равно сам подставит правильный заголовок за нас. Как говаривали на Руси: —«Не лезь вперёд батьки в пекло!». Очень мудрые слова.


Итого есть два стула: на одном вам придётся руками ставить Content-Type и boundary. Это сложно, долго и бессмысленно. На втором вам просто нужно не добавлять заголовок в коде. Выбор за вами.


К столь простому решению меня привёл вот этот комментарий

______

* Если вы не знаете этого анекдота и вы не являетесь тургеневской барышней или HR который по этому посту пытается оценить меня как возможного кандидата, то обязательно загуглите этот анекдот. Если вы HR и рассматриваете меня как возможного кандидата, то не делайте этого.