Не удаётся запустить эмбеддинг

Здравствуйте! Я пытаюсь внедрить комментарии Discourse на свой сайт, следуя руководству по внедрению, но уперся в тупик :frowning:

Симптомы

Я пробовал в Firefox и Chrome. В обоих случаях загружается iframe Discourse с надписью «Загрузка обсуждения…», но процесс зависает, и в консоли разработчика постоянно появляются ошибки JavaScript.

В Firefox я получаю ошибку, связанную с заголовком X-Frame-Options:

При загрузке «https://discourse.29th.local/embed/comments?embed_url=https%3A%2F%2Fpersonnel.29th.local%2F%23enlistments%2F11927» обнаружен недопустимый заголовок X-Frame-Options: «ALLOWALL» не является допустимой директивой.

За ней следует ошибка DOMException в embed-application.js:7:

Uncaught DOMException: Указана недопустимая или некорректная строка

Эти две ошибки повторяются примерно каждые 30 секунд. В вкладке «Сеть» (Network) нет неудачных запросов.

В Chrome я не получаю ошибку X-Frame-Options. Через несколько секунд появляется ошибка о том, что целевой источник не совпадает с источником окна-получателя:

Не удалось выполнить 'postMessage' на 'DOMWindow': Указанный целевой источник ('https://discourse.29th.local') не совпадает с источником окна-получателя ('https://personnel.29th.local').

Я видел множество тем на meta, посвященных этой ошибке, и пробовал все шаги по устранению неполадок, но безрезультатно.

Моя конфигурация

Я следовал руководству по установке Discourse на Mac с одним небольшим исключением: вместо глобальной установки postgres, redis и mailcatcher на свой ноутбук я запускаю их в контейнерах Docker с открытыми портами. Discourse не знает, что они работают в контейнерах Docker, а не на «голом железе». Rails/Discourse установлен глобально и не работает в контейнере Docker.

Отдельно от этого мое пользовательское веб-приложение работает в стеке Docker Compose. Часть этого стека включает сервер nginx, который маршрутизирует personnel.29th.local в соответствующий контейнер-источник, а discourse.29th.local — на host.docker.internal:3000 (это магическое имя хоста, которое контейнеры Docker могут использовать для доступа к localhost хоста).

(Как я упоминаю ниже, я убрал слой nginx из уравнения, но получил ту же ошибку)

Возможная проблема здесь заключается в том, что мое веб-приложение является одностраничным приложением (SPA) на JavaScript. Страница, на которую внедряются комментарии Discourse, — это https://personnel.29th.local/#enlistments/1234, и серверного рендеринга нет. Если бы это была проблема, я бы ожидал ошибку при работе с краулером, и тогда я бы согласился на то, чтобы Discourse просто ссылался на мое приложение, а не пытался его сканировать. Но ошибки, которые он показывает, похоже, не связаны с неудачами сканирования.

Устранение неполадок

Я установил хост для внедрения в разделе Администрирование > Настройка > Внедрение как personnel.29th.local. Сначала в примере кода внедрения для discourseUrl отображался адрес http://localhost:3000/, поэтому я запустил rails console и выполнил:

SiteSetting.force_hostname = "discourse.29th.local"
SiteSetting.port = 443

И включил опцию «Принудительно использовать https» в панели администратора. Это исправило URL в примере кода внедрения.

Я также добавил https://personnel.29th.local в качестве домена CORS в разделе cors origins настроек.

Сейчас я запускаю Discourse со следующей командой:

DISCOURSE_DEV_HOSTS=discourse.29th.local,host.docker.internal DISCOURSE_ENABLE_CORS=true bundle exec rails server

Я также пробовал отключить политику безопасности контента (Content Security Policy) в панели настроек.

Я посмотрел логи на https://discourse.29th.local/logs/, но не нашел никаких ошибок и ничего о Sidekiq.

Кстати о Sidekiq: у меня есть сообщение на панели администратора об обновлениях:

Проверка обновлений не выполнялась. Убедитесь, что Sidekiq запущен.

Поэтому я выполнил Sidekiq.redis { |r| puts r.flushall } в консоли Rails, получил OK, перезапустил сервер Rails, но сообщение и общая проблема не изменились. Я покопался в кэше Redis и не нашел ничего, связанного с этой страницей.

Я также попытался упростить задачу, убрав слой nginx из уравнения: вернул SiteSetting.force_hostname и SiteSetting.port к значению nil, отключил принудительный https, получил доступ к моему веб-приложению и Discourse через localhost, добавил мое веб-приложение в список хостов для внедрения и хостов CORS в Discourse (http://localhost:8080), но получил ту же ошибку, только с другими хостами:

Не удалось выполнить 'postMessage' на 'DOMWindow': Указанный целевой источник ('http://localhost:3000') не совпадает с источником окна-получателя ('http://localhost:8080').

Я использую версию 2.6.0.beta6 ( 60bc38e6a8 ), которую получил, клонировав ветку master согласно руководству по установке Discourse на Mac пару недель назад и выполнив git pull origin master сегодня.

Я также удалил директорию tmp и перезапустил сервер.

Я также пошел на прогулку, кричал в подушку и плакал под столом.

Надеюсь, я охватил все возможные варианты. Надеюсь, кто-то сможет помочь!

Мне жаль слышать, что у вас возникают такие трудности с настройкой.

Discourse недостаточно умен, чтобы прокраулировать SPA, поэтому это кажется мне наиболее вероятной причиной. Можете ли вы попробовать воспроизвести проблему на сайте со статическим контентом?

Мы не можем поддерживать каждую индивидуальную установку, поэтому рекомендую вам упростить ваш стек до тех пор, пока всё не заработает, а затем постепенно добавлять остальные компоненты.

Спасибо за ваш ответ, и не беспокойтесь, я знаю, что это того стоит!

Я упростил задачу, используя сайт с серверным рендерингом (Rails) и полностью отказавшись от слоя nginx. Мое приложение работает на порту 3001, а Discourse — на порту 3000.

Мой код для встраивания выглядит так:

<script type="text/javascript">
      DiscourseEmbed = { discourseUrl: 'http://localhost:3000/',
                         discourseEmbedUrl: 'http://localhost:3001/enlistments/1' };
    
      (function() {
        var d = document.createElement('script'); d.type = 'text/javascript'; d.async = true;
        d.src = DiscourseEmbed.discourseUrl + 'javascripts/embed.js';
        (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(d);
      })();
</script>

Я добавил localhost:3001 в список разрешенных хостов для встраивания в разделе Администрирование > Настройка > Встраивание, а http://localhost:3001 — в список имен хостов в разделе Администрирование > Настройки > CORS.

Ошибка та же, но с обновленными именами хостов:

Не удалось выполнить 'postMessage' в DOMWindow: предоставленное целевое происхождение ('http://localhost:3000') не совпадает с происхождением окна получателя ('http://localhost:3001').

Стек сейчас максимально упрощен :thinking: Я предполагаю, что это какая-то проблема с конфигурацией? Есть какие-то идеи?

Некоторые дополнительные результаты отладки:

Я вручную создал тему и заменил discourseEmbedUrl: 'http://localhost:3001/enlistments/<%= @enlistment.id %>' на topicId: 14 в своём JavaScript-сниппете, и комментарии загрузились. Это говорит о том, что проблема не в CORS или X-Frame, а (а) в чём-то связанном со скрапингом, и (б) возможно, в том, как обрабатываются ошибки при встраивании.

Чтобы изучить проблему скрапинга, я открыл новую страницу, к которой ранее не обращался (и, следовательно, скрапинг на ней ещё не должен был быть выполнен). Я наблюдал за консолью Rails моего приложения и загрузил страницу. В логах я увидел запись /enlistments/6 один раз. Я ждал, пока в консоли JavaScript не появится сообщение об ошибке; к этому моменту Discourse должен был попытаться выполнить скрапинг, но в логах консоли Rails моего приложения не было никаких других записей о попытках доступа.

В эндпоинте /logs Discourse ошибок не было, равно как и в логах Rails самого Discourse.

Я подумал, что, возможно, Discourse не может получить доступ к моему сайту, поэтому зашёл в консоль Rails на своём приложении Discourse и выполнил:

± |master U:3 ?:2 ✗| → rails c
Loading development environment (Rails 6.0.3.3)
[1] pry(main)> require "net/http"
=> false
[2] pry(main)> url = URI.parse("http://localhost:3001/enlistments/6")
=> #<URI::HTTP http://localhost:3001/enlistments/6>
[3] pry(main)> req = Net::HTTP.new(url.host, url.port)
=> #<Net::HTTP localhost:3001 open=false>
[4] pry(main)> res = req.request_head(url.path)
=> #<Net::HTTPOK 200 OK readbody=true>
[5] pry(main)>

Пока я это делал, я увидел запись доступа в логах сервера Rails моего приложения. Это подтвердило, что Discourse может обращаться к моему приложению.

Теперь я думаю, что проблема может быть в Sidekiq или планировании задач? :man_shrugging: Но я не уверен, как это отладить. Я никогда раньше не использовал Sidekiq.

Я ещё раз просмотрел данные Redis (используя TablePlus — графический интерфейс для работы с базами данных, поддерживающий Redis) и увидел около трёх строк с ключом вида default:logster-env-96404aef1da0c422fc32e3bb82d85fbc и значением вроде:

[
  {
    "hostname": "myhostname",
    "process_id": 7188,
    "application_version": "60bc38e6a8914a10341a32ff9909e69faa65ffef",
    "params": {
      "embed_url": "http: //localhost:3001/enlistments/11927"
    },
    "HTTP_HOST": "localhost:3000",
    "REQUEST_URI": "/embed/comments?embed_url=http%3A%2F%2Flocalhost%3A3001%2Fenlistments%2F11927",
    "REQUEST_METHOD": "GET",
    "HTTP_USER_AGENT": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.60 Safari/537.36",
    "HTTP_ACCEPT": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
    "HTTP_REFERER": "http://localhost:3000/embed/comments?embed_url=http%3A%2F%2Flocalhost%3A3001%2Fenlistments%2F11927",
    "time": 1606253787041
  }
]

Тип данных — LIST, а TTL равен -1. Похоже, это означает, что задача запускается?

Я заглянул в /sidekiq, но не нашёл упоминания этой задачи и никакой очереди с именем RetrieveTopic :frowning:

Диапазон возможных причин уже сужается, но буду рад помощи, если у кого-то возникнут идеи!

Привет, @eviltrout, есть какие-нибудь идеи для дальнейшего устранения неполадок, теперь, когда я упростил настройку до самого базового?

Это почти наверняка проблема с конфигурацией. Возможно, ваш Discourse на localhost:3000 считает, что у него другое имя хоста. Вы можете проверить это в консоли с помощью:

Discourse.base_url

Также стоит проверить логи sidekiq по адресу /sidekiq.

@eviltrout Я добавил несколько строк отладки в библиотеку TopicRetriever и подтвердил, что invalid_url? возвращает false (что указывает на корректность URL). Discourse.base_url действительно установлен в http://localhost:3000. Я полагаю, что задание RetrieveTopic где-то тихо завершается с ошибкой, и я пытаюсь выяснить, где именно. В /logs нет записей об ошибках, а в /sidekiq нет никаких ссылок на получение тем или каких-либо логов.

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

Здравствуйте, спасибо за расследование. Я считаю, что у меня точно такая же проблема (вставка работает для существующей темы, но не работает при создании темы). Это работает в моей рабочей среде, но не на моей машине разработки.

Моя настройка — стек Docker, и я убедился, что всё доступно как для Discourse, так и для Sidekiq. На данный момент я начинаю думать, что когда Discourse пытается разобрать URL (это также не работает, когда onebox пытается получить превью ссылки в сообщении), это каким-то образом зависит от внешнего сервиса, который не может увидеть локальные экземпляры… Возможно ли это?

@wilson29thid, вы нашли что-нибудь на своей стороне с тех пор?

Я столкнулся с той же проблемой на своей продакшн-машине, но не на машине разработчика. Что-то подсказывает мне, что не так уж важно, какую из них вы используете.

Нет, боюсь, я так и не разобрался в этом и в итоге использовал API Discourse для создания темы вручную :pensive_face:

Я тоже, возможно, так сделаю. Хорошая новость в том, что вы можете контролировать количество создаваемых потоков до того, как люди начнут комментировать ваши статьи…