При интеграции веб-приложений с базой 1С разработчики нередко сталкиваются с ситуацией: скрипт отправляет асинхронный запрос через метод .ajax() библиотеки jQuery, запрос успешно доходит до платформы, в модуле HTTP-сервиса отрабатывает обработчик, возвращается статус 200 OK, но на стороне браузера управление сразу передается в функцию error. При этом в консоли браузера может не отображаться явного текста ошибки, либо выводится сообщение о блокировке политики Cross-Origin Resource Sharing (CORS).
Разберем по шагам, почему браузер отклоняет технически успешные ответы от 1С, как правильно сформировать CORS-заголовки, как обойти специфику preflight-запросов метода OPTIONS и какие нюансы следует учитывать при обмене данными.
Когда веб-страница загружена с одного адреса (например, корпоративного портала), а HTTP-сервис 1С опубликован на другом домене, порту или протоколе, вступает в действие механизм безопасности браузера. Браузер запрещает скрипту читать ответы со стороннего ресурса, если сервер явно не разрешил такой доступ с помощью специальных HTTP-заголовков.
Кроме того, если запрос не является простым (содержит нестандартные заголовки, такие как Authorization, или заголовок Content-Type со значением application/json), браузер перед основным запросом (GET или POST) автоматически посылает предварительный запрос методом OPTIONS (так называемый preflight-запрос). Если HTTP-сервис 1С не умеет отвечать на OPTIONS или не возвращает подтверждающие заголовки, браузер сразу прерывает цепочку выполнения и вызывает обработчик ошибки.
Проанализируем программное решение на стороне 1С. Чтобы браузер принял ответ, каждый возвращаемый объект HTTPСервисОтвет должен содержать заголовки семейств Access-Control-Allow-*. Создадим вспомогательную процедуру для заполнения этих заголовков и добавим шаблон для обработки запросов OPTIONS.
Посмотрим на пример реализации в модуле HTTP-сервиса:
Процедура ЗаполнитьЗаголовки(Запрос, Ответ)
// Обеспечиваем работу контекстной подсказки в конфигураторе
Если Ложь Тогда Запрос = Новый HTTPЗапрос; КонецЕсли;
Ответ.Заголовки.Вставить("Access-Control-Allow-Headers", "Authorization, Content-Type, Accept");
Ответ.Заголовки.Вставить("Access-Control-Allow-Methods", "GET, POST, OPTIONS, PUT, DELETE");
Ответ.Заголовки.Вставить("Access-Control-Allow-Credentials", "true");
Origin = Запрос.Заголовки.Получить("Origin");
Если Origin = Неопределено Тогда
Ответ.Заголовки.Вставить("Access-Control-Allow-Origin", "*");
Иначе
Ответ.Заголовки.Вставить("Access-Control-Allow-Origin", Origin);
КонецЕсли;
КонецПроцедуры
Функция ШаблонOptions(Запрос)
Ответ = Новый HTTPСервисОтвет(200);
ЗаполнитьЗаголовки(Запрос, Ответ);
Возврат Ответ;
КонецФункции
Разберем подробнее, что делают указанные строки:
Access-Control-Allow-Headers — сообщает браузеру, какие именно заголовки разрешено отправлять клиентскому скрипту (включая авторизацию и формат передаваемых данных).Access-Control-Allow-Credentials и правило для Origin — критически важная связка. По спецификации W3C, если в ответе указан заголовок Access-Control-Allow-Credentials: true, заголовок Access-Control-Allow-Origin ни в коем случае не должен содержать маску * (звездочку). Он обязан строго и явно возвращать точный адрес запрашивающего сайта, полученный из заголовка Origin входящего запроса. Если передать * при включенных credentials, браузер гарантированно заблокирует ответ.OPTIONS — в составе объекта метаданных HTTPСервис для соответствующего шаблона URL необходимо создать метод OPTIONS и связать его с функцией-обработчиком ШаблонOptions. Этот метод просто возвращает пустой статус 200 со всеми разрешающими заголовками.Обратим внимание на строку, вызвавшую интерес в обсуждении:
Если Ложь Тогда Запрос = Новый HTTPЗапрос; КонецЕсли;
В платформе 1С параметры процедур и функций не имеют строгой типизации. Из-за этого встроенный редактор кода конфигуратора не может заранее определить, что переменная Запрос является объектом HTTPСервисЗапрос, и не предлагает список его свойств и методов при нажатии точки. Конструкция с заведомо ложным условием никогда не выполняется в рантайме и не тратит ресурсы, однако синтаксический анализатор конфигуратора считывает тип создаваемого объекта и включает полноценное автодополнение кода (IntelliSense).
При реализации preflight-запросов разработчики часто сталкиваются со следующей проблемой: браузер отправляет запрос OPTIONS анонимно, без заголовка Authorization и файлов cookie. Если публикация 1С на веб-сервере закрыта базовой аутентификацией, веб-сервер возвращает код 401 Unauthorized до того, как управление дойдет до функции ШаблонOptions в модуле 1С.
Рассмотрим пути обхода этого ограничения:
OPTIONS в конфигурации веб-сервера (Nginx, Apache или IIS) перед 1С. Веб-сервер сразу отдает статус 200 OK и необходимые заголовки CORS, не передавая запрос расширению 1С и не требуя учетных данных.default.vrd можно прописать пользователя по умолчанию с минимальными правами (атрибуты usr и pwd). Тогда платформа примет анонимный OPTIONS без вызова окна авторизации.Если заголовки CORS настроены верно, но ответ всё равно отклоняется клиентским скриптом, проанализируем следующие факторы:
parsererror в jQuery): если в конфигурации AJAX-запроса на клиенте указано свойство dataType: "json", библиотека ожидает валидную JSON-строку. Если 1С возвращает пустое тело со статусом 200 или обычный текст, парсер jQuery падает, и вызывается error. В таком случае в 1С обязательно нужно передавать валидную строку (например, {}) через Ответ.УстановитьТелоИзСтроки("{}") и выставлять заголовок Content-Type: application/json; charset=utf-8. Либо, если данных нет, возвращать HTTP-статус 204 No Content.https://, а сервис 1С опубликован по незащищенному http://, браузер заблокирует такой запрос по соображениям безопасности вне зависимости от заголовков CORS.Чтобы навсегда избавиться от проблем с CORS, preflight-запросами OPTIONS и передачей авторизационных заголовков, на практике часто используют обратный прокси-сервер (Reverse Proxy на базе Nginx или Apache). При таком подходе веб-интерфейс и HTTP-сервисы 1С размещаются на одном домене (например, веб-приложение доступно по пути /, а запросы к 1С проксируются по пути /api/1c/). В этом случае все запросы считаются однодоменными (Same-Origin), механизм CORS полностью отключается, а клиентский скрипт стабильно получает ответы от сервисов 1С.