임베딩이 작동하지 않아요

안녕하세요, 임베딩 가이드를 따라 제 웹사이트에 Discourse 댓글을 임베드하려고 시도하고 있는데, 벽에 부딪혔습니다. :frowning:

증상

Firefox와 Chrome에서 모두 시도해 보았습니다. 두 브라우저 모두에서 "Discussion 로딩 중…"이라는 Discourse iframe이 로드되지만 거기서 멈추며, 개발자 콘솔에 반복적인 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”은 유효한 지시문이 아닙니다.

그 다음 embed-application.js:7에서 DOMException 오류가 발생합니다:

Uncaught DOMException: An invalid or illegal string was specified

이 두 오류는 약 30초마다 반복됩니다. 네트워크 탭에는 실패한 요청이 없습니다.

Chrome에서는 X-Frame-Options 오류가 발생하지 않습니다. 몇 초 후, 대상 원본이 수신 창 원본과 일치하지 않는다는 오류가 발생합니다:

Failed to execute 'postMessage' on 'DOMWindow': The target origin provided ('https://discourse.29th.local') does not match the recipient window's origin ('https://personnel.29th.local').

메타 사이트에서 이 오류에 대한 많은 주제들을 확인했고, 모든 문제 해결 단계를 시도해 보았지만 실패했습니다.

제 환경 구성

macOS용 Discourse 설치 가이드를 따랐지만, 하나의 예외가 있습니다: 노트북에 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 레이어를 제거했는데도 동일한 오류가 발생했습니다.)

여기서 하나의 함정은 제 웹 앱이 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

그리고 관리자 대시보드에서 "force https"를 켰습니다. 이로 인해 샘플 임베드 코드의 URL이 수정되었습니다.

또한 설정의 cors origins 섹션에 https://personnel.29th.local을 CORS 도메인으로 추가했습니다.

현재 저는 다음 명령으로 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 관련해서, 관리자 대시보드에 업데이트에 대한 메시지가 하나 있습니다:

A check for updates has not been performed. Ensure sidekiq is running.

그래서 rails console에서 Sidekiq.redis { |r| puts r.flushall }를 실행했고 OK가 반환되었으며, rails 서버를 재시작했지만 메시지도 전체적인 문제도 변하지 않았습니다. redis 캐시를 뒤져 보았지만 이 페이지에 대한 내용은 보이지 않았습니다.

또한 nginx 레이어를 방정식에서 제거하여 단순화해 보기도 했습니다: SiteSetting.force_hostname과 SiteSetting.port를 nil로 되돌리고, force https를 끄고, localhost를 통해 웹 앱과 Discourse에 접근하며, 웹 앱을 Discourse의 임베드 가능 호스트와 CORS 호스트명(http://localhost:8080)에 추가했지만, 호스트만 다를 뿐 동일한 오류가 발생했습니다:

Failed to execute 'postMessage' on 'DOMWindow': The target origin provided ('http://localhost:3000') does not match the recipient window's origin ('http://localhost:8080').

저는 며칠 전 macOS용 Discourse 설치 가이드에 따라 master 브랜치를 클론하고 오늘 git pull origin master를 실행하여 가져온 2.6.0.beta6 ( 60bc38e6a8 ) 버전을 실행하고 있습니다.

또한 tmp 디렉토리를 삭제하고 서버를 재시작했습니다.

또한 산책도 하고, 베개에 소리도 질렀고, 책상 아래에서 울기도 했습니다.

모든 측면을 커버하고 있기를 바랍니다. 누군가 도움을 줄 수 있기를 바랍니다!

설치를 하는 데 큰 어려움을 겪고 계시다니 안타깝습니다.

디스커스는 SPA를 크롤링할 만큼 지능적이지 않으므로, 제 생각에는 이것이 가장 의심스러운 부분입니다. 정적 콘텐츠를 포함하는 사이트로 재현해 볼 수 있을까요?

우리는 모든 맞춤형 설치를 지원할 수 없으므로, 작동할 때까지 스택을 더 단순화해 보신 후, 그때부터 구성 요소를 하나씩 추가해 보시는 것을 권장합니다.

답변해 주셔서 감사합니다. 걱정 마세요, 가치가 있을 거라는 걸 잘 알고 있습니다!

nginx 레이어를 모두 생략하고 서버 렌더링(rails) 사이트를 사용하도록 단순화했습니다. 현재 앱은 포트 3001에서, discourse는 포트 3000에서 실행 중입니다.

내장(embed) 코드는 다음과 같이 렌더링됩니다:

<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을, _관리자 > 설정 > cors_에서 호스트 이름에 http://localhost:3001을 추가했습니다.

호스트 이름은 업데이트되었지만 에러는 동일합니다:

Failed to execute 'postMessage' on 'DOMWindow': The target origin provided ('http://localhost:3000') does not match the recipient window's origin ('http://localhost:3001').

스택은 이제 최대한 단순해졌습니다 :thinking: 어떤 종류의 설정 문제인 것 같습니다. 혹시 아이디어가 있으신가요?

추가 디버깅 결과:

JavaScript 스니펫에서 discourseEmbedUrl: 'http://localhost:3001/enlistments/<%= @enlistment.id %>'를 topicId: 14로 교체하고 수동으로 토픽을 생성해 보았습니다. 그랬더니 댓글이 로드되었습니다. 이는 CORS나 X-Frame- 문제가 아니라, (a) 스크래핑과 관련된 문제이며, (b) 임베딩 중 오류 처리 방식에 문제가 있을 수 있음을 시사합니다.

스크래핑 문제를 조사하기 위해 이전에 접근을 시도해 본 적 없는 새 페이지(따라서 스크래핑이 시도되지 않았어야 하는 페이지)를 방문했습니다. 제 앱의 rails 콘솔을 관찰한 채로 페이지를 로드했습니다. 로그에 /enlistments/6이 한 번 표시되었습니다. JavaScript 콘솔에 오류 메시지가 던져질 때까지 기다렸는데, 이때쯤이면 Discourse가 스크래핑을 시도했을 시간이었지만, 제 앱의 rails 콘솔에는 다른 접근 시도 로그가 표시되지 않았습니다.

Discourse의 /logs 엔드포인트에도 오류가 없었고, Discourse의 rails 로그에서도 확인할 수 있는 오류가 없었습니다.

아마도 Discourse가 제 웹사이트에 접근하지 못하는 것 같아서, Discourse 앱의 rails 콘솔에 로그인하여 다음을 실행해 보았습니다:

± |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)
4=> #<Net::HTTPOK 200 OK readbody=true>
[5] pry(main)>

이 작업을 수행하는 동안 제 앱의 rails 서버에서 액세스 로그를 확인할 수 있었습니다. 이는 Discourse가 제 앱에 도달할 수 있음을 확인시켜 주었습니다.

이제 sidekiq 또는 작업 스케줄링 관련 문제인 것 같습니다. :man_shrugging: 하지만 어떻게 디버깅해야 할지 모르겠습니다. 저는 sidekiq을 사용해 본 적이 없거든요.

redis 데이터를(TablePlus라는 redis와 호환되는 데이터베이스 GUI를 사용하여) 다시 살펴보니, key 값이 default:logster-env-96404aef1da0c422fc32e3bb82d85fbc와 같은 값인 행이 약 3개 있고, value 값은 다음과 같은 값을 가지고 있습니다.

[
  {
    "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
  }
]

type은 LIST이고 ttl은 -1입니다. 즉, 작업이 초기화되고 있다는 뜻일까요?

/sidekiq을 뒤져 보았지만 이 작업에 대한 언급도, RetrieveTopic이라는 이름의 큐도 찾을 수 없습니다. :frowning:

확실히 범위를 좁혀 가고 있지만, 혹시 머릿속에 떠오르는 아이디어가 있다면 도움이 필요합니다!

안녕하세요 @eviltrout, 세팅을 기본 사항으로 단순화했으니 추가적인 문제 해결에 대해 아이디어가 있으신가요?

거의 확실히 설정 문제입니다. localhost:3000에서 실행 중인 Discourse가 다른 호스트명으로 설정되어 있을 수 있습니다. 콘솔에서 다음 명령어로 확인할 수 있습니다:

Discourse.base_url

또한 /sidekiq에서 sidekiq 로그를 확인해 보세요.

@eviltrout TopicRetriever 라이브러리에 디버깅 코드를 추가하여 invalid_url?이 false임을 확인했습니다(유효하다는 의미입니다). Discourse.base_url은 실제로 http://localhost:3000으로 설정되어 있습니다. RetrieveTopic 잡이 어딘가에서 조용히 실패하고 있다고 생각하며, 그 위치를 추적하고 있습니다. /logs에는 에러 로그가 없고, /sidekiq에도 토픽 검색 관련 내용이나任何形式的 로그가 없습니다.

죄송합니다. 지금은 더 이상 아이디어가 없습니다. 현재 코드가 프로덕션 환경에서 정상적으로 작동하고 있으므로, 환경 설정이나 플러그인, 또는 설치 관련 문제일 가능성이 있습니다.

안녕하세요, 조사해 주셔서 감사합니다. 저 역시 정확히 같은 문제를 겪고 있는 것 같습니다 (기존 주제에서는 임베드가 정상 작동하지만, 주제 생성 시에는 실패합니다). 이 문제는 프로덕션 환경에서는 정상적으로 작동하지만, 제 개발 머신에서는 작동하지 않습니다.

제 환경은 도커 스택이며, discourse와 sidekiq 양쪽 모두에서 모든 것이 가시적으로 확인되도록 했습니다. 이 시점에서 Discourse가 URL을 파싱하려고 할 때(포스트에서 링크의 미리보기를 가져오려는 onebox 시도 시에도 실패합니다) 로컬 인스턴스를 참조하지 못하는 외부 서비스에 의존하고 있는 것은 아닌지 의심이 듭니다. 이런 가능성이 있을까요?

@wilson29thid, 그 이후로 당신 쪽에서 뭔가 발견한 것이 있나요?

개발 머신이 아니라 프로덕션 머신에서 동일한 문제를 겪었습니다. 어떤 머신을 사용하느냐는 사실 큰 차이가 없을 것 같습니다.

아니요, 저는 이 문제를 끝내 해결하지 못했고 결국 discourse의 API를 사용해 주제를 수동으로 생성했어요 :pensive_face:

나도 실제로 그렇게 할 수도 있겠다. 좋은 점은 사람들이 내 글에 댓글을 달기 전에 생성되는 스레드 수를 조절할 수 있다는 거야…