Файл открылся, курсор мигает, таблица пересчитывается, коллега видит ваши правки. Вы закрываете вкладку, открываете документ снова, а там вчерашняя версия. Первая реакция понятна: «редактор сломан». Но редактор как раз работает. Он честно принял ваш текст, и вся его работа происходит в сессии на стороне Document Server. Сломано другое: дорога, по которой готовый результат должен вернуться в Nextcloud.

Успешное открытие ничего не говорит об этой дороге. Оно доказывает лишь, что браузер дотянулся до редактора, а редактор получил исходный файл. Сохранение устроено иначе: инициатором становится сам Document Server, и он обращается к Nextcloud уже не как к странице в браузере, а как сервер к серверу. Поэтому статья посвящена обратному маршруту: куда идёт запрос, что в нём лежит, кто скачивает файл и когда пора говорить «всё в порядке».

Два маршрута, которые легко принять за один

Когда вы нажимаете на документ в Nextcloud, происходит примерно следующее. Nextcloud отдаёт страницу, в которую встроен редактор, и параметры: какой файл открыть, по какому адресу его взять, куда сообщать о результатах. Браузер загружает интерфейс с адреса Document Server и начинает с ним разговор. Всё это происходит у вас на глазах, и любой сбой видно сразу: пустая рамка, ошибка загрузки, предупреждение о смешанном содержимом.

Второй маршрут невидим. Document Server держит состояние документа у себя, а когда пользователи расходятся, формирует итоговый файл и посылает на сервер Nextcloud POST-запрос. Это не вызов из браузера, поэтому никакие настройки вашего ноутбука, расширения и разрешения в панели браузера на него не влияют. Он выходит из машины с Document Server и должен прийти на машину с Nextcloud.

В описании официального коннектора это условие названо прямо: Document Server должен быть достижим и из Nextcloud, и из браузеров пользователей, и сам обязан уметь отправлять POST в Nextcloud. Три направления, не два. Типичная засада в том, что первые два проверяются сами собой при каждом открытии файла, а третье не проверяется ничем, пока кто-то не потеряет правки.

Отсюда первый практический вывод: доступность в одну сторону не доказывает доступность в другую. Коннектор различает внешние и внутренние адреса компонентов, и каждый из них отвечает на свой вопрос: «как до меня доберётся браузер» или «как до меня доберётся соседний сервер». Один и тот же адрес не обязан подходить для обоих случаев. К этому мы вернёмся, когда дойдём до адресов.

callbackUrl: письмо, которое редактор отправляет обратно

При открытии документа Nextcloud передаёт редактору параметр callbackUrl. Через него Document Server сообщает хранилищу, что происходит с документом. По описанию обработчика обратных вызовов, редактор делает на этот адрес POST с телом в формате JSON, и в теле есть поле status. Именно оно подсказывает, что делать дальше.

Для диагностики сохранения достаточно трёх значений:

  • 2 — изменённый документ готов к сохранению. Это тот самый сигнал, после которого интеграция обязана забрать файл и записать его;
  • 4 — документ закрыт без изменений. Записывать нечего, и отсутствие новой версии в таком случае нормально;
  • 6 — относится к принудительному сохранению (force save), когда результат запрошен, не дожидаясь закрытия документа.

Это важное различение. Если вы открыли файл, ничего не поменяли и закрыли, обратный путь «не сработает» по определению: придёт статус 4, сохранять нечего. Жалоба «ничего не сохраняется» иногда вырастает именно из такой проверки. Поэтому тестовая правка должна быть заметной: допишите уникальную строку, например с текущим временем, чтобы потом не гадать, та ли это версия.

Обратите внимание, что статус 2 приходит не в момент последнего нажатия клавиши. Редактор отправляет его после того, как пользователи завершили работу с документом и сессия закрывается. Так что между «закрыл вкладку» и появлением новой версии в Nextcloud проходит время, и если проверять мгновенно, можно увидеть старый файл просто потому, что письмо ещё в пути. Разумная пауза перед выводами — это не суеверие, а следствие того, как устроена сессия.

Четыре звена, а не одна галочка

Сохранение — это цепочка, и каждое звено может порваться независимо:

  1. callback дошёл до Nextcloud;
  2. Nextcloud получил файл результата по адресу из этого callback;
  3. файл записан в хранилище Nextcloud;
  4. Document Server получил ответ с error:0.

Порядок здесь не условность. При сохранении тело callback содержит поле url, по которому лежит готовый файл, и интеграция должна сама сходить по этому адресу, скачать результат и записать в хранилище. Документация на обработчик прямо формулирует и последний шаг: при успешной обработке хранилище отвечает JSON с error:0. Ответ говорит о том, что запись состоялась, а не о том, что запрос принят. Поэтому его нельзя выдавать раньше четвёртого звена.

Эта последовательность сразу превращает расплывчатое «не сохраняет» в четыре вопроса, на каждый из которых можно ответить по журналу. Ниже — как выглядит каждое звено в наблюдаемых признаках.

Где оборвалась цепочка сохранения
ЗвеноКак проверитьЧто значит отсутствие признака
1. Callback дошёлЗапись о POST-запросе от Document Server в журнале веб-сервера, который стоит перед Nextcloud, в момент закрытия документаЗапрос не вышел из Document Server или не прошёл по сети, прокси или правилам доступа; до кода Nextcloud дело не дошло
2. Результат скачанВ журнале Document Server нет ошибок на этапе обращения к Nextcloud, а в журнале Nextcloud виден разбор запроса без отказаCallback пришёл, но файл по адресу из url недоступен для того, кто его скачивает
3. Файл записанВ истории версий файла в Nextcloud появилась новая версия, изменилось время измененияСкачивание удалось, но запись не прошла: права, место, блокировка файла, ошибка приложения
4. Выдан ответ error:0Ответ на callback содержит JSON с нулевой ошибкой, а журнал Document Server не жалуется на сохранениеЗапись состоялась, но ответ не дошёл или пришёл неправильным; редактор считает сохранение неудавшимся

Таблица читается сверху вниз и останавливается на первом провале. Если на втором звене пусто, смотреть на третье бессмысленно: туда ничего не дошло. Такая последовательность экономит часы, потому что не позволяет прыгать между гипотезами про права на каталог и про сертификаты одновременно.

Звено первое: был ли вообще звонок

Самое информативное место для начала — журнал доступа веб-сервера, который стоит перед Nextcloud. В момент закрытия документа там должен появиться POST от адреса Document Server на конечную точку коннектора. Что именно это за путь, зависит от версии коннектора, поэтому ориентируйтесь не на запомненный адрес, а на то, что POST пришёл ровно в момент закрытия сессии и со стороны Document Server.

Если записи нет, проблема лежит между машинами. Проверяйте с самого сервера Document Server, а не с вашего рабочего компьютера, потому что обратный путь начинается именно там:

curl -sS -o /dev/null -w '%{http_code}\n' https://cloud.example.com/status.php

Адрес здесь, разумеется, ваш. Если из контейнера или с машины Document Server команда зависает, возвращает ошибку сертификата или разрешает имя в неожиданный адрес, у вас готов диагноз. Частые причины такого сбоя: имя Nextcloud внутри сети указывает на публичный адрес, до которого сервер не может дотянуться изнутри, или в проверочной цепочке стоит самоподписанный сертификат, которому Document Server не доверяет.

Тут легко соблазниться отключением проверки сертификатов, чтобы «просто заработало». Как временный шаг для локализации сбоя это допустимо, а как постоянное решение — нет: сервер, который принимает любой сертификат, перестаёт быть уверенным, с кем разговаривает, а обмен идёт как раз теми данными, которые вы хотите сохранить. Правильный исход — добавить доверие к нужному центру сертификации или выпустить нормальный сертификат.

Заметьте и ограничение метода: успешный ответ status.php подтверждает сетевую достижимость и работоспособность имени, но не доказывает, что сохранение пройдёт. Это проверка звена один и только его. Двести в журнале здесь так же мало говорит о результате, как зелёный индикатор состояния сервиса: приложение может отвечать, а запись файла при этом падать.

Звено второе: адрес, по которому лежит результат

В callback при сохранении приходит ссылка на готовый файл. Её формирует Document Server, и в ней зашит тот адрес, под которым он сам себя знает. Если Document Server настроен так, что считает себя доступным по внешнему имени, а из Nextcloud это имя недоступно или ведёт не туда, то callback дойдёт, а скачивание результата сорвётся. Со стороны пользователя картина идентична: правки пропали.

Теперь про адреса в целом. Возьмём условный сценарий: Nextcloud и Document Server стоят на одном VPS, снаружи оба доступны по HTTPS через общий обратный прокси. Пользователь открывает cloud.example.com, редактор подгружается с office.example.com. Для браузера всё прекрасно. Но когда Document Server обращается к Nextcloud по cloud.example.com, ему приходится идти через внешний интерфейс: DNS, затем прокси, затем снова на ту же машину. На некоторых хостингах такой «заход с петлёй» не работает вовсе, на других работает, но через другой сертификат или другие правила. Поэтому в настройках коннектора есть возможность указать отдельные внутренние адреса, по которым компоненты обращаются друг к другу, не выходя наружу.

Принцип выбора простой. Для каждого направления нужен тот адрес, который работает из точки, откуда идёт запрос:

  • браузер идёт к Document Server по внешнему адресу;
  • Nextcloud ходит к Document Server по адресу, доступному серверу Nextcloud, и это может быть внутренний адрес;
  • Document Server ходит к Nextcloud по адресу, доступному серверу Document Server, и это тоже может быть внутренний, причём не совпадающий с внешним.

Если эти три адреса записаны в голове как один, обратный путь обязательно сломается в самый неудобный момент, например после переезда одного компонента на другую машину. Запишите таблицу «кто к кому ходит и по какому имени» и сверяйте её с настройками. Это занимает десять минут и убирает целый класс загадок.

JWT: когда подпись не сходится

JWT добавляет к обмену подпись. Document Server и Nextcloud договариваются об общем секрете, и каждое сообщение подтверждается этим секретом. По описанию коннектора, при включённом JWT секрет в настройках коннектора и в настройках Document Server должен совпадать, и проверка должна быть включена с обеих сторон. Поломка здесь обычно банальна: секрет поменяли в одном месте и забыли во втором.

Опасность в том, что JWT ломает направления избирательно. Бывает, что редактор открывается нормально, потому что подписанный конфиг при открытии проходит проверку, а подписанный callback при сохранении отвергается, или наоборот. Сообщения идут разными путями и в разные стороны, так что «открылось» не гарантирует «сохранится». Именно поэтому секрет нужно сверять напрямую, а не делать выводов по поведению интерфейса.

Практическая проверка проходит в три шага:

  1. откройте настройки коннектора в Nextcloud и настройки Document Server и убедитесь, что JWT включён в обоих местах;
  2. сравните секреты побайтно, не глазами: лишний пробел или перевод строки при копировании часто остаётся незамеченным. Проще всего заново вставить значение в оба места из одного источника;
  3. после изменения перезапустите затронутые службы, чтобы не тестировать старую конфигурацию, и повторите пробное сохранение с уникальной строкой.

Что делать, если после этого сохранение всё равно не проходит, а в журнале видны отказы по подписи? Не отключайте JWT насовсем. Без подписи любой, кто знает адрес обработчика, может послать туда произвольное тело с произвольным url, и сервер Nextcloud пойдёт скачивать то, что ему подсунули. Отключать проверку можно разве что на минуту, чтобы понять, она ли виновата, и затем немедленно вернуть. Найти расхождение секретов дешевле, чем разбираться с последствиями открытой двери.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации Россия, США, Франция и Великобритания. Оплата картой РФ и по СБП.

АРЕНДОВАТЬ СЕРВЕР

Звено третье: запись в хранилище

Допустим, callback дошёл, подпись принята, результат скачан. Теперь Nextcloud должен положить файл на место. Это обычная файловая операция, и мешают ей обычные причины:

  • у пользователя, от имени которого идёт запись, нет прав на изменение файла или каталога, например в общей папке с доступом только на чтение;
  • на диске или в квоте пользователя закончилось место, и итоговый файл не помещается;
  • файл заблокирован другим процессом или ещё не снят блокировкой после предыдущей сессии;
  • хранилище, подключённое как внешнее, временно недоступно.

Наблюдаемый признак в этой точке один: в журнале Nextcloud есть запись об ошибке записи, а в истории версий файла новых версий нет. Если журнал молчит, а версии нет, значит до записи не дошли, и вернитесь на звено выше. Если новая версия появилась, а в редакторе всё равно висит сообщение о проблеме, переходите к последнему звену.

Здесь же пригодится осознанный подсчёт места. Условный пример: документ весит 120 МБ, а у пользователя до квоты осталось 100 МБ. Редактирование выглядит нормально, пока файл открыт, ведь расход растёт только на этапе записи итога. В момент сохранения места не хватит, а до пользователя это дойдёт лишь как «правки пропали». Для крупных таблиц и презентаций с картинками стоит учитывать, что в хранилище лягут и новая версия, и предыдущие версии, если включено версионирование.

Звено четвёртое: ответ error:0 и почему его нельзя дарить авансом

Document Server считает сохранение завершённым только после ответа JSON, в котором ошибка равна нулю. Минимальная форма выглядит так:

{"error":0}

Из этого факта вытекает обратная опасность. Если на ответ влияет что-то посередине, например самодельный скрипт, прокси со своим обработчиком или «заплатка», которая возвращает error:0 всегда, то редактор больше не получит повода сообщить о проблеме. Пользователь увидит спокойный интерфейс, закроет документ, и данные исчезнут без единого предупреждения. Это самый неприятный вид сбоя: ошибка есть, а сигнала о ней нет.

Документация на обработчик говорит об этом прямо: ошибки обработки нельзя маскировать успешным ответом. Для практики это даёт простое правило: ответ error:0 выдаётся строго после успешной записи файла, а любой сбой на пути (скачивание, запись) должен вернуться как ошибка. Тогда редактор честно покажет, что сохранить не удалось, и пользователь успеет скопировать содержимое, а не узнает о потере на следующий день.

Если вы работаете с готовым коннектором, то собственный обработчик писать не нужно: логика цепочки уже внутри, и примеры из справочника по API не заменяют её. Но правило полезно как проверка на здравый смысл. Если где-то на пути стоит конструкция, которая отвечает вместо Nextcloud, уберите её на время диагностики. Иначе вы будете смотреть на зелёные ответы и не увидите, что реальная запись так и не произошла. Точно так же не принимайте за доказательство ответ 200: он говорит о том, что что-то вернуло страницу, но не о том, что файл лёг на диск.

Прокси и паузы: когда сохранение просто не успевает

Остался сбой, который выглядит случайным: мелкие документы сохраняются, крупные нет. Или днём всё в порядке, а вечером, когда сервер загружен, начинаются потери. Часто виноват таймаут на обратном прокси.

Вспомним, как устроено звено четыре: Nextcloud отвечает error:0 только после записи. Значит, пока он скачивает результат и пишет его на диск, ответ не отправлен, а соединение от Document Server (или от прокси, который стоит на пути) открыто и молчит. Если между этими машинами стоит nginx, то работает его параметр proxy_read_timeout. По документации модуля proxy, он ограничивает паузу между двумя последовательными операциями чтения, а не общую продолжительность ответа.

Для нашего случая это означает следующее. Пока ответ не начал идти, любая пауза в молчании засчитывается целиком. Возьмём условный пример: proxy_read_timeout стоит на 60 секунд, а запись крупного документа вместе со скачиванием занимает 75 секунд. Ответ сформируется на 75-й секунде, но прокси закроет соединение на 60-й и вернёт ошибку шлюза. Файл при этом может даже успешно записаться, а Document Server получит сбой и решит, что сохранение не состоялось. Вы получаете странную картину: версия в Nextcloud появилась, а в редакторе предупреждение. Или, если запись оборвана вместе с соединением, не появилось ничего.

Дополнительная тонкость: в той же документации сказано, что без буферизации ответ передаётся по мере получения. Это относится к потоковой передаче данных клиенту и объясняет, почему для длинных, но равномерно идущих ответов таймаут чтения обычно не срабатывает: данные приходят, пауз нет. С коротким ответом {"error":0}, который отдаётся единым куском после долгой обработки, всё наоборот: вся долгая часть — это одна сплошная пауза. Поэтому буферизация тут ничем не помогает, помогает только ограничение, которое покрывает время обработки.

Что проверить:

  • во всех проксях на пути от Document Server к Nextcloud найдите значения таймаутов чтения. Их может быть несколько: на внешнем прокси, на внутреннем, в самом веб-сервере Nextcloud;
  • сравните их со временем сохранения самого тяжёлого документа, который реально бывает у вас. Время можно оценить по журналу доступа: оно записывается рядом с запросом, если включено в формате журнала;
  • если запас мал, увеличьте таймаут для пути обратного вызова, а не для всего сайта. Бесконечные таймауты прячут настоящие зависания.

Кстати, в журнале nginx такой сбой обычно отмечен ошибкой шлюза или обрывом соединения с вышестоящим сервером в то самое время, когда закрывается документ. Совпадение по времени с закрытием сессии — самый надёжный признак. Пока ответ не оборван, он будет выглядеть как обычный успех, а вот сообщение о таймауте — как раз тот редкий случай, когда журнал сам указывает пальцем.

Какая машина под это нужна

Document Server и Nextcloud можно держать как на одном сервере, так и на разных: коннектор допускает оба варианта. Для первых экспериментов удобнее первое, потому что адреса и сертификаты проще контролировать, а сетевых сюрпризов меньше. Но два приложения на одной машине делят память и процессор, и именно в момент сохранения нагрузка складывается: редактор собирает итог, Nextcloud скачивает и записывает его, веб-сервер держит соединения.

Если вы разворачиваете это на VPS MAATRIX, обратите внимание на то, какой доступ вам даёт машина: root-доступ позволяет править настройки прокси и смотреть журналы, а это обязательные инструменты для всей диагностики выше. Накопитель NVMe ускоряет запись версий, а процессоры AMD EPYC помогают с конвертацией, но конкретную ёмкость нужно мерить на своих документах, а не выводить из цифр тарифа. Линейка тарифов начинается с Ba (1 vCPU, 2 ГБ RAM, 40 ГБ NVMe) и идёт через Uab (2 vCPU, 4 ГБ, 80 ГБ) и Heka (4 vCPU, 8 ГБ, 160 ГБ) до Maat (8 vCPU, 16 ГБ, 240 ГБ) и Djed (16 vCPU, 32 ГБ, 320 ГБ). Это ресурсы машины, а не обещание, сколько человек одновременно будет редактировать документы.

В публичном каталоге есть Nextcloud с установкой, но точное наличие и операционная система уточняются в форме заказа. Если Nextcloud у вас ещё не развёрнут, начните с материала о том, как установить и настроить Nextcloud на VPS, и только после этого подключайте редактор: тогда обратный путь будет проверяться на чистой, понятной конфигурации.

Пробное сохранение, которое что-то доказывает

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

  1. Подготовьте метку. Откройте тестовый документ и допишите в конец строку с текущим временем. Запомните эту строку и время, когда вы закроете вкладку.
  2. Закройте документ и подождите. Статус 2 приходит по завершении сессии, поэтому дайте системе время. Если вы закрыли документ и сразу проверяете версию, вы можете смотреть на ещё не пришедший результат.
  3. Найдите callback. В журнале доступа веб-сервера перед Nextcloud должен быть POST от Document Server около момента закрытия. Нет записи — работайте со звеном один: имена, маршрутизация, сертификаты, правила доступа.
  4. Убедитесь, что результат скачан. Если callback есть, а версии нет, в журналах Document Server и Nextcloud ищите отказ при получении файла: чаще всего адрес из url недоступен тому, кто его скачивает, либо подпись JWT не принята.
  5. Проверьте запись. В истории версий файла должна появиться новая запись. Если её нет, а скачивание прошло, смотрите права, квоту, блокировки и доступность подключённого хранилища.
  6. Проверьте ответ. Редактор не должен показывать сообщений об ошибке сохранения, а в журнале прокси на этот запрос не должно быть ошибок шлюза или обрывов. Помните: время обработки равно паузе ответа, и она должна укладываться в proxy_read_timeout на каждом прокси по пути.
  7. Откройте документ заново. Это единственная проверка, которую нельзя заменить ничем другим: ваша метка должна быть на месте.

Прогоните эту последовательность дважды: с маленьким документом и с самым тяжёлым, который у вас бывает. Маленький покажет логические ошибки (секрет, адрес, права), тяжёлый — ошибки временные (таймаут, место на диске). Если проходит только маленький, вы почти наверняка упёрлись в паузу, а не в конфигурацию.

Когда обратный путь замкнулся

Хорошее завершение диагностики выглядит скромно. Вы дописываете строку с меткой, закрываете вкладку, через несколько секунд открываете файл снова, и метка стоит на месте. Рядом в истории версий лежит свежая запись, в журнале веб-сервера видна пара событий: POST от Document Server и успешная обработка без ошибок шлюза, а редактор ни разу не пожаловался. В этот момент все четыре звена подтверждены не догадкой, а наблюдением: callback дошёл, результат скачан, файл записан, ответ error:0 выдан уже после записи.

Вот этот набор признаков и стоит считать мерилом исправной работы. Не открытие документа, не зелёный индикатор и не код 200, а повторное открытие с вашей правкой на месте и журнал, по которому весь обратный путь читается от первого запроса до последнего ответа.