Skip to content

Отвечать 400 вместо 500, когда обязательное тело запроса отсутствует или не разобралось #130

Description

@nixel2007

Симптом

Точка маршрута, читающая тело запроса, при запросе без тела отвечает 500 с текстом внутреннего исключения:

POST /greeter/postjsonbody   (без тела)
500  {Модуль .../hwapp/ИнтерактивныйКонтролПриветствия.os / Error in line: 18 / Value is not of object type}

POST /catalog/create         (без тела)
500  {Модуль .../hwapp/КонтролКаталога.os / Error in line: 62 / Value is not of object type}

То же самое ждёт запрос с телом, но без заголовка Content-Type: application/json, и запрос с телом, которое не разобралось.

Причина

ВходящийЗапрос.ТелоЗапросаОбъект возвращает Неопределено, если тела нет или тип содержимого не JSON:

Функция ТелоЗапросаОбъект(ТипТелаЗапроса) Экспорт
    Результат = Неопределено;

    Если ЗначениеЗаполнено(Тело)
            И СтрНайти(НРег(СокрЛП(Заголовки["Content-Type"])), "application/json") > 0 Тогда

        Результат = Парсеры.ДесериализоватьJson(Тело, ТипТелаЗапроса);

    КонецЕсли;

    Возврат Результат;
КонецФункции

Дальше Неопределено уходит в точку маршрута, та обращается к полю, и исключение всплывает уже из прикладного кода. Обработчик ловит его общим Исключение и отдаёт 500.

Почему это неправильно

Ошибка на стороне клиента: он не прислал тело или прислал неразбираемое. По HTTP это 400, а не 500 - 500 говорит «сломался сервер» и в мониторинге читается как отказ сервиса.

Плюс в теле ответа уезжает внутренняя диагностика с путём к файлу и номером строки. Наружу такому лучше не попадать.

Отдельно неудобно, что автор точки маршрута обязан помнить про проверку сам, а забыть её ничего не мешает - и тогда вместо внятного отказа получается стектрейс.

Предложение

Отвечать 400 до вызова точки маршрута, если тело обязательно, но отсутствует или не разобралось.

Обязательность объявляется аннотацией &Заполнено на параметре тела - её уже читает генератор спецификации, чтобы выставить requestBody.required:

Процедура ЗавестиТовар(Ответ, &Заполнено &Тип("ТоварКаталога") ТелоЗапросаОбъект) Экспорт

Тогда объявление начнёт не только описывать, но и обеспечивать: requestBody.required в документе и проверка на входе будут говорить одно и то же. Сейчас документ обещает обязательность, за которой ничего не стоит.

Без &Заполнено поведение остаётся прежним - в точку маршрута приходит Неопределено, и разбираться с ним автору.

О чём не забыть

  • Отличать «тела нет» от «тело есть, но не разобралось» - обе ситуации 400, но сообщения разные и это заметно помогает при отладке.
  • Не отдавать наружу внутреннюю диагностику.
  • Оба сервера должны вести себя одинаково. Общая точка - ОбработчикЗапросов.СформироватьОтвет, где формируются параметры точки маршрута.
  • Тесты на обоих серверах, нативном и прикладном.

Связано

  • Аннотация &Заполнено на параметре появилась в #124.
  • Сам validate ограничения на параметре метода не проверяет - autumn-library/autumn-validate#4. Здесь проверку делает winow, а не валидатор.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions