Свои веб-страницы на 1С 8.3: от публикации базы до приёма POST-запросов
Содержание
Чтобы сделать страницы в 1С, которые открываются в обычном браузере, нужны три вещи: HTTP-сервис в конфигурации, публикация базы на веб-сервере (Apache или IIS) и функция-обработчик, которая возвращает HTTPСервисОтвет с HTML в теле. Адрес такой страницы выглядит как http://сервер/имя_публикации/hs/корневой_URL/шаблон.
Если же нужен готовый интерфейс 1С в браузере, без своей вёрстки, хватит публикации веб-клиента. А если под «страницами» вы понимаете вкладки на форме документа или обработки, это группа «Страницы» в редакторе формы, о ней в отдельном разделе ближе к концу.
Какой способ сделать страницы выбрать
У платформы 8.3 несколько механизмов, которые отдают что-то наружу через веб. Для своих страниц подходят не все.
| Способ | Что получает пользователь | Когда выбирают | Ограничения |
|---|---|---|---|
| Веб-клиент | Формы 1С в браузере, как в тонком клиенте | Удалённая работа сотрудников в базе | Своей вёрстки нет, нужна лицензия на каждое подключение |
| HTTP-сервис | Любой HTML, JSON, файл, который вы сформировали кодом | Личный кабинет, витрина, анкета, отчёт по ссылке | Всю вёрстку и логику пишете сами |
| Web-сервис (SOAP) | XML по WSDL-описанию | Обмен с другими системами | Не для браузера |
| OData | Данные объектов по стандартному протоколу | Выгрузка в BI и внешние приложения | Страниц не формирует |
| Поле HTML-документа на форме | HTML внутри формы 1С | Красивый отчёт или справка прямо в базе | Видно только из клиента 1С |
Дальше речь про HTTP-сервисы: только они дают полностью свою страницу по своему адресу.
💡 Веб-клиент и HTTP-сервисы публикуются одной и той же процедурой. Можно включить оба варианта в одной публикации или только сервисы.
Публикация базы на веб-сервере для HTTP-сервисов
Без публикации HTTP-сервис существует только в конфигурации, браузер его не увидит.
Что установить заранее
- 🧩 Компонент платформы «Модули расширения веб-сервера». Его выбирают в установщике платформы. Без него пункт публикации есть, но веб-сервер не сможет загрузить модуль 1С.
- 🌐 Веб-сервер: IIS (входит в Windows Server) или Apache 2.4. Разрядность Apache и платформы должна совпадать: 64-битная платформа работает с 64-битным Apache.
- 🔑 Права: учётная запись, от имени которой работает веб-сервер, должна читать каталог
binплатформы, а для файловой базы ещё и писать в каталог базы.
Порядок публикации
- Запустите Конфигуратор от имени администратора Windows. Без этого платформа не сможет изменить настройки IIS или файл
httpd.conf. - Откройте
Администрирование → Публикация на веб-сервере. - Задайте имя публикации латиницей, например
shop. Оно станет частью адреса. - Выберите веб-сервер и каталог публикации. В каталог запишется файл
default.vrd. - На закладке HTTP-сервисов включите публикацию сервисов по умолчанию или отметьте нужные сервисы списком.
- Нажмите «Опубликовать» и согласитесь на перезапуск веб-сервера.
Перезапуск Apache или IIS обрывает все веб-сеансы, которые идут через этот сервер: веб-клиент, обмены через HTTP, мобильные приложения. На рабочем сервере публикуйте вне рабочих часов.
Файл default.vrd
Публикация — это по сути файл default.vrd плюс строки в конфигурации веб-сервера. Файл можно править вручную, например чтобы задать пользователя для анонимного доступа:
<?xml version="1.0" encoding="UTF-8"?>
<point xmlns="http://v8.1c.ru/8.2/virtual-resource-system"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
base="/shop"
ib="File="C:\Bases\Shop";Usr=web;Pwd=СложныйПароль;">
<httpServices publishByDefault="true"/>
</point>
Если Usr и Pwd в строке подключения нет, браузер запросит логин и пароль пользователя 1С (Basic-аутентификация). Для публичных страниц удобнее отдельный пользователь web с одной узкой ролью: чтение нужных справочников и запись только в регистр обращений.
Никогда не прописывайте в default.vrd пользователя с ролью «Полные права» — любой, кто найдёт адрес, получит доступ ко всему, что умеют ваши сервисы.
Создаём HTTP-сервис и обработчик GET-запроса
Структура в дереве конфигурации
HTTP-сервисы лежат в ветке Общие → HTTP-сервисы. Внутри сервиса три уровня:
- 📦 Сервис со свойством «Корневой URL», например
site. - 🧭 Шаблоны URL:
/,/items,/items/{code}. Фигурные скобки задают параметр. - ⚙️ Методы каждого шаблона: GET, POST, PUT, DELETE или
*(любой метод). У метода есть свойство «Обработчик», двойной клик по нему создаёт функцию в модуле сервиса.
Итоговый адрес шаблона /items/{code} при публикации shop:
http://localhost/shop/hs/site/items/00-00000012
Часть /hs/ добавляет платформа, её не нужно указывать в корневом URL.
Первая страница
Обработчик получает объект HTTPСервисЗапрос и обязан вернуть HTTPСервисОтвет:
Функция ГлавнаяGET(Запрос)
Html = "<!DOCTYPE html><html lang=""ru""><head><meta charset=""utf-8"">"
+ "<title>Каталог</title></head><body>"
+ "<h1>Каталог товаров</h1><p><a href=""items"">Открыть список</a></p>"
+ "</body></html>";
Ответ = Новый HTTPСервисОтвет(200);
Ответ.Заголовки.Вставить("Content-Type", "text/html; charset=utf-8");
Ответ.УстановитьТелоИзСтроки(Html, КодировкаТекста.UTF8, ИспользованиеByteOrderMark.НеИспользовать);
Возврат Ответ;
КонецФункции
Без заголовка Content-Type браузер может показать исходный текст вместо страницы или «кракозябры». Метку BOM лучше отключать: на некоторых браузерах и прокси она даёт лишний символ в начале документа.
После изменения конфигурации обновите конфигурацию базы данных. Повторно публиковать базу не нужно, если в публикации включены сервисы по умолчанию.
Параметры адреса и строки запроса
Запрос.ПараметрыURL["code"]— значение из шаблона/items/{code}.Запрос.ПараметрыЗапроса.Получить("q")— параметр после знака вопроса:/items?q=кабель. Если параметра нет, вернётсяНеопределено.Запрос.HTTPМетодиЗапрос.Заголовкипригодятся, когда один обработчик обслуживает метод*.
Генерация HTML-страницы из данных базы
Динамическая страница — это запрос к базе и сборка строки. Пример списка товаров с поиском:
Функция СписокGET(Запрос)
Поиск = Запрос.ПараметрыЗапроса.Получить("q");
ЗапросКБазе = Новый Запрос;
ЗапросКБазе.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 50
| Номенклатура.Код КАК Код,
| Номенклатура.Наименование КАК Наименование
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| НЕ Номенклатура.ЭтоГруппа
| И Номенклатура.Наименование ПОДОБНО &Шаблон
|УПОРЯДОЧИТЬ ПО
| Наименование";
ЗапросКБазе.УстановитьПараметр("Шаблон", "%" + ?(Поиск = Неопределено, "", Поиск) + "%");
Выборка = ЗапросКБазе.Выполнить().Выбрать();
Строки = Новый Массив;
Строки.Добавить("<!DOCTYPE html><html lang=""ru""><head><meta charset=""utf-8"">");
Строки.Добавить("<title>Товары</title></head><body><h1>Товары</h1><ul>");
Пока Выборка.Следующий() Цикл
Строки.Добавить("<li><a href=""items/" + ЭкранироватьHTML(СокрЛП(Выборка.Код)) + """>"
+ ЭкранироватьHTML(Выборка.Наименование) + "</a></li>");
КонецЦикла;
Строки.Добавить("</ul></body></html>");
Ответ = Новый HTTPСервисОтвет(200);
Ответ.Заголовки.Вставить("Content-Type", "text/html; charset=utf-8");
Ответ.УстановитьТелоИзСтроки(СтрСоединить(Строки), КодировкаТекста.UTF8, ИспользованиеByteOrderMark.НеИспользовать);
Возврат Ответ;
КонецФункции
Функция ЭкранироватьHTML(Знач Текст)
Текст = СтрЗаменить(Текст, "&", "&");
Текст = СтрЗаменить(Текст, "<", "<");
Текст = СтрЗаменить(Текст, ">", ">");
Текст = СтрЗаменить(Текст, """", """);
Возврат Текст;
КонецФункции
Три приёма, на которых держится такой код:
- Параметры запроса, а не склейка текста. Значение из адреса уходит в
УстановитьПараметр, поэтому подставить в запрос чужой фрагмент не получится. - Экранирование всего, что пришло из базы. Наименование вида
Кабель <3x2.5>без замены символов сломает вёрстку, а текст с тегом<script>выполнится в браузере посетителя. Амперсанд заменяют первым, иначе испортятся уже готовые<. - Массив строк и
СтрСоединить. На сотнях строк это заметно быстрее, чем многократноеHtml = Html + ....
💡 Если вёрстки много, держите HTML-заготовку в общем макете типа «Текстовый документ» с метками вроде #TITLE# внутри текста и заменяйте их через СтрЗаменить. Текст макета получают так: ПолучитьОбщийМакет("ШаблонСтраницы").ПолучитьТекст().
Карточку товара по шаблону /items/{code} делают так же. Если по коду ничего не нашлось, верните Новый HTTPСервисОтвет(404) с короткой страницей «Товар не найден»: браузеры и поисковики понимают код ответа, а не текст на странице.
Формы на странице: приём POST-запроса
Разметка формы
Обычная HTML-форма отправляет данные методом POST в формате application/x-www-form-urlencoded:
<form method="post" action="feedback">
<input name="name" required>
<input name="phone" type="tel">
<textarea name="text"></textarea>
<button type="submit">Отправить</button>
</form>
В конфигураторе у шаблона /feedback нужны два метода: GET показывает форму, POST её принимает.
Разбор тела запроса
Тело приходит строкой вида name=%D0%98%D0%B2%D0%B0%D0%BD&phone=.... Разбирают его так:
Функция РазобратьФорму(Запрос)
Поля = Новый Соответствие;
Тело = Запрос.ПолучитьТелоКакСтроку(КодировкаТекста.UTF8);
Для Каждого Пара Из СтрРазделить(Тело, "&", Ложь) Цикл
Части = СтрРазделить(Пара, "=");
Ключ = РаскодироватьСтроку(СтрЗаменить(Части[0], "+", " "), СпособКодированияСтроки.КодировкаURL);
Значение = "";
Если Части.Количество() > 1 Тогда
Значение = РаскодироватьСтроку(СтрЗаменить(Части[1], "+", " "), СпособКодированияСтроки.КодировкаURL);
КонецЕсли;
Поля.Вставить(Ключ, Значение);
КонецЦикла;
Возврат Поля;
КонецФункции
Плюс заменяют на пробел до раскодирования: браузер кодирует пробел именно плюсом.
Запись и переадресация
После записи данных верните не страницу, а переадресацию с кодом 303:
Функция ОбратнаяСвязьPOST(Запрос)
Поля = РазобратьФорму(Запрос);
Имя = СокрЛП(Поля.Получить("name"));
Если ПустаяСтрока(Имя) Тогда
Возврат Новый HTTPСервисОтвет(400);
КонецЕсли;
// Запись в регистр сведений, который вы добавили под обращения
// ...
Ответ = Новый HTTPСервисОтвет(303);
Ответ.Заголовки.Вставить("Location", "/shop/hs/site/feedback?ok=1");
Возврат Ответ;
КонецФункции
Схема «POST → 303 → GET» защищает от повторной отправки: если посетитель обновит страницу, браузер запросит адрес ?ok=1, а не отправит форму ещё раз.
Проверяйте каждое поле на сервере: длину, обязательность, формат телефона. Атрибут required в разметке обходится одним запросом через curl.
Если форма принимает файлы (multipart/form-data), тело удобнее читать как двоичные данные через Запрос.ПолучитьТелоКакДвоичныеДанные() и разбирать по разделителю из заголовка Content-Type. Это объёмнее, поэтому для загрузки файлов часто делают отдельную страницу с JavaScript, который отправляет файл отдельным запросом.
CSS, картинки и скрипты для страницы на 1С
Браузер загружает каждый ресурс отдельным запросом. Вариантов три.
| Вариант | Как сделать | Плюсы | Минусы |
|---|---|---|---|
| Встроить в страницу | <style> и <script> прямо в HTML | Ноль лишних запросов | Стили грузятся заново с каждой страницей |
| Отдавать из 1С | Шаблон /static/{file}, файлы в общих макетах | Всё внутри конфигурации, переносится вместе с ней | Каждый запрос занимает сеанс 1С |
| Отдавать веб-сервером | Отдельная папка в Apache или IIS | Быстро, кэшируется, базу не нагружает | Файлы живут отдельно от конфигурации |
Для второго варианта картинку кладут в общий макет типа «Двоичные данные», а стили в текстовый макет:
Функция СтатикаGET(Запрос)
Файл = Запрос.ПараметрыURL["file"];
Если Файл = "style.css" Тогда
Ответ = Новый HTTPСервисОтвет(200);
Ответ.Заголовки.Вставить("Content-Type", "text/css; charset=utf-8");
Ответ.УстановитьТелоИзСтроки(ПолучитьОбщийМакет("СтилиСайта").ПолучитьТекст(), КодировкаТекста.UTF8, ИспользованиеByteOrderMark.НеИспользовать);
ИначеЕсли Файл = "logo.png" Тогда
Ответ = Новый HTTPСервисОтвет(200);
Ответ.Заголовки.Вставить("Content-Type", "image/png");
Ответ.УстановитьТелоИзДвоичныхДанных(ПолучитьОбщийМакет("Логотип"));
Иначе
Возврат Новый HTTPСервисОтвет(404);
КонецЕсли;
Ответ.Заголовки.Вставить("Cache-Control", "max-age=86400");
Возврат Ответ;
КонецФункции
Имена файлов сверяются со списком явно. Не превращайте параметр file в путь на диске сервера: так через адрес вида ../../ можно вытащить чужие файлы.
Внешние библиотеки (Bootstrap, шрифты) подключайте ссылкой на CDN, если у посетителей есть интернет. Для внутренней сети надёжнее третий вариант таблицы.
Отладка и проверка веб-страниц 1С
Отладчик
Чтобы остановиться на точке останова в обработчике HTTP-сервиса:
- Включите отладку в публикации. В
default.vrdдобавьте строку<debug enable="true" protocol="tcp" url="tcp://localhost"/>для отладки по TCP. При отладке по HTTP указываютprotocol="http"и адрес сервера отладки, по умолчанию порт 1550. - В Конфигураторе откройте
Отладка → Подключениеи в настройках автоматического подключения отметьте HTTP-сервисы. - Поставьте точку останова в модуле сервиса и откройте страницу в браузере.
Отладку на рабочей публикации не включайте: пока вы стоите на точке останова, запрос висит, а посетитель видит бесконечную загрузку.
Проверка без браузера
curl показывает код ответа и заголовки, которые браузер прячет:
curl -i "http://localhost/shop/hs/site/items?q=кабель"
curl -i -X POST -d "name=Иван&phone=123" "http://localhost/shop/hs/site/feedback"
Ошибки внутри обработчика удобно писать в журнал регистрации через ЗаписьЖурналаРегистрации, оборачивая тело функции в Попытка ... Исключение. Тогда посетитель получит аккуратную страницу с кодом 500, а подробности останутся в базе.
Что означает ответ сервера
Вкладки-страницы на управляемой форме 1С
Иногда «сделать страницы» значит разложить реквизиты формы по закладкам. Это делается в редакторе формы без кода:
- В дереве элементов формы добавьте группу вида «Страницы».
- Внутри неё добавьте нужное число групп вида «Страница» и задайте каждой заголовок.
- Перетащите реквизиты в нужные страницы.
- В свойстве «Отображение страниц» выберите вид: закладки сверху, снизу или без закладок, если переключать будете кодом.
Переключение из модуля формы:
Элементы.ГруппаСтраницы.ТекущаяСтраница = Элементы.СтраницаДоставка;
Скрыть закладку можно свойством Видимость у группы-страницы. Событие «ПриСменеСтраницы» у группы «Страницы» срабатывает, когда пользователь щёлкает по закладке. В нём удобно подгружать данные тяжёлой вкладки только при её открытии.
В типовых конфигурациях форму не правят напрямую: вкладку добавляют в расширении конфигурации. Так она переживёт обновление релиза.