오늘 업무를 재개하면서 관리 페이지에서 몇 가지 눈에 띄는 부분이 있었습니다. 개선할 수 있는 부분에 대한 몇 가지 생각을 정리해 보았습니다.
allowed_internal_hosts는 비공개 환경에서 임베딩이 안정적으로 작동하기 위한 핵심 설정입니다. 이 섹션에서 관련 설정으로 명시적으로 나열되어야 하며, 그 중요성에 대해서는 의문의 여지가 없습니다.
제공된 구성 스니펫은 많은 유용한 정보를 포함하고 있어 매우 도움이 됩니다. 이를 활용하여 추가적인 가이드를 제공할 수 있다고 생각합니다.
첫 번째 문단
현재:
다음 HTML 코드를 사이트에 붙여넣어 Discourse 토픽을 생성하고 임베드하세요. EMBED_URL을 임베드하려는 페이지의 정준 URL(canonical URL)로 교체하십시오.
대안:
댓글이 표시될 페이지의 원하는 위치에 다음 HTML을 붙여넣으세요.
discourseEmbedUrl은 해당 페이지의 URL이며, Discourse에서 링크로 연결될 대상입니다. 페이지가 처음 로드될 때 Discourse는 해당 URL에 대한 토픽을 찾거나 생성하고 콘텐츠로 다시 링크합니다.
두 번째 문단
현재:
스타일을 사용자 정의하려면 주석 해제 후 CLASS_NAME을 테마의 임베디드 CSS에 정의된 CSS 클래스로 교체하십시오.
대안:
className 속성을 사용하여 임베드된 iframe 내부의 <html> 태그에 사용자 정의 클래스를 추가합니다. 스타일을 적용하려면 /admin/customize/themes로 이동하여 테마의 편집 버튼을 클릭한 뒤 코드 편집 버튼을 누르고 고급 보기를 선택하세요. 사용자 정의 CSS를 임베디드 CSS 섹션에 추가합니다.
세 번째 문단
현재:
DISCOURSE_USERNAME을 토픽을 생성해야 할 작성자의 Discourse 사용자 이름으로 교체하십시오. Discourse는 name 속성이 discourse-username 또는 author로 설정된 <meta> 태그의 content 속성을 통해 사용자를 자동으로 조회합니다. discourseUserName 매개변수는 비활성화(deprecated)되었으며 Discourse 3.2에서 제거될 예정입니다.
대안:
참고: 토픽은 실제 Discourse 사용자가 생성합니다. 표시 이름이나 작성자 문자열이 아닙니다. 유효하고 존재하는 계정이여야 합니다. 어떤 사용자가 사용되는지를 결정하는 방법은 세 가지가 있습니다:
- 기본 폴백 — /admin/customize/embedding/posts_and_topics에서 설정
- 호스트별 오버라이드 — /admin/customize/embedding/ 하위에서 설정
- URL별 제어 — 페이지에 존재하는 Discourse
USERNAME을 가진 <meta name="discourse-username" content="USERNAME"> 태그를 추가
기존 Discourse 사용자의 사용자 이름만 작동합니다. 메타 태그의 사용자가找不到되지 않으면 Discourse는 호스트 수준 또는 글로벌 기본값으로 폴백합니다. 여기에 소개된 <meta> 태그 방식은 토픽 생성에 사용되는 Discourse 사용자를 프로그램 방식으로 URL별로 제어할 수 있게 해줍니다. 예를 들어, 사이트의 블로그 글 작성자를 일치하는 Discourse 사용자 계정으로 매핑할 수 있습니다.
구성 스니펫 섹션
접이식 “구성 스니펫” 섹션은 놓치기 쉽습니다. 시각적으로 제목과 유사하며, 미묘한 화살표는 직관적이지 않습니다. 눈에 띄는 색상으로 처리된 “자세히 알아보기” 링크와 달리, 이 섹션은 마치 숨겨져 있는 것처럼 보입니다.
이것이 논쟁의 여지가 있다는 것을 알겠습니다. 일부는 UI가 깔끔하고 충분하다고 느낄 수 있습니다. 하지만 저는 이것이 클릭 가능한 것을 깨달을 때까지 이 섹션을 너무 자주 놓쳤습니다. 이는 특히 예시에 의존하는 초보자를 위해 약간의 시각적 단서 개선이나 비접기(non-collapsed) 기본값 설정이 큰 도움이 될 수 있음을 보여줍니다.
스니펫
현재:
<div id='discourse-comments'></div>
<meta name='discourse-username' content='DISCOURSE_USERNAME'>
<script type="text/javascript">
DiscourseEmbed = {
discourseUrl: 'https://discourse.your-site.com/',
discourseEmbedUrl: 'EMBED_URL',
// className: 'CLASS_NAME',
};
(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>
대안:
<div id="discourse-comments"></div>
<!-- 선택 사항: 토픽을 생성할 Discourse 사용자 계정을 지정 -->
<!-- 생략할 경우, Discourse는 호스트별 또는 글로벌 기본 사용자로 폴백합니다 -->
<meta name="discourse-username" content="DISCOURSE_USERNAME" />
<script type="text/javascript">
DiscourseEmbed = {
discourseUrl: 'https://discourse.mydomain.com/', // 끝부분 슬래시 필수
discourseEmbedUrl: window.location.href, // 또는 하드코딩된 정준 URL 문자열
// className: 'my-iframe-theme another-class',
// discourseReferrerPolicy: 'strict-origin-when-cross-origin',
// topicId: '1234',
};
(function() {
const d = document.createElement('script');
d.type = 'text/javascript';
d.async = true;
d.src = `${DiscourseEmbed.discourseUrl}javascripts/embed.js`;
document.head.appendChild(d);
})();
</script>
DiscourseEmbed 옵션 — 간략한 설명
-
discourseUrl — 필수
Discourse 인스턴스의 전체 URL입니다. 끝부분에 슬래시가 있어야 하며, 예: https://discourse.mydomain.com/
-
discourseEmbedUrl — 필수
댓글이 임베드되는 현재 페이지의 전체 URL입니다. Discourse가 이를 통해 토픽을 식별하고 콘텐츠에 링크합니다.
-
className — 선택 사항
iframe 내부의 <html> 요소에 사용자 정의 CSS 클래스를 추가합니다. Discourse 테마의 “임베디드 CSS” 섹션에서 스타일을 정의하세요.
-
discourseReferrerPolicy — 선택 사항
기본값은 no-referrer-when-downgrade입니다. 참고: Referrer-Policy
-
topicId — 선택 사항
설정된 경우, Discourse는 해당 토픽을 직접 사용합니다. 그렇지 않으면 discourseEmbedUrl과 일치하는 토픽을 찾거나, 없으면 생성합니다.
DiscourseEmbed 컨텍스트 문서화
discourseEmbedUrl은 Discourse 서버에서 접근 가능해야 합니다. iframe이 로드될 때 Discourse는 해당 URL의 페이지를 가져와 토픽을 생성하거나 위치를 파악합니다. 이는 공개 플랫폼에 호스팅된 웹사이트에서 원활하게 작동합니다.
하지만 로컬에서 개발 중이라면, 임베딩이 "토론 로드 중…"에 멈추거나 아예 표시되지 않을 수 있습니다. 이는 로컬 개발 URL(예: localhost)이 Discourse의 SSRF 보호나 force_https 옵션, 또는 누락된 임베드 가능 호스트에 의해 차단될 수 있기 때문입니다.
기존 사이트의 새로운 기능을 개발 중이라면, discourseEmbedUrl을 프로덕션 URL로 지정하는 것이 하나의 해결책입니다. embed_any_origin이 활성화되어 있으면, iframe이 다른 오리진에서 서빙되더라도 Discourse는 임베딩이 작동하도록 허용합니다. 댓글이 존재하면 로드되고, 없으면 “토론 계속” 버튼이 표시됩니다.
대안적으로, 로컬 도메인(예: localhost)을 임베딩 호스트에 추가했다면 embed_any_origin이 전혀 필요 없을 수 있습니다. 하지만 여전히 localhost를 임베드 가능 호스트로 추가해야 합니다.
주의할 점: force_https 설정이 활성화되어 있고 개발 사이트가 TLS를 사용하지 않으면 임베딩이 실패합니다. 이 경우 개발 중 force_https를 비활성화하거나 테스트를 위해 별도의 Discourse 인스턴스를 구동하는 것을 고려해 보세요.
참고: discourseEmbedUrl이 공개적으로 접근 가능하고 임베딩이 여전히 "토론 로드 중…"을 표시하면서 토픽을 생성하지 않는다면, 도메인이 Discourse의 SSRF 보호에 의해 차단되었을 수 있습니다.
이러한 문제는 로컬 DNS 해석이 이루어지는 환경(예: Docker, Kubernetes, 내부 DNS 서버가 있는 LAN)에서 Discourse 인스턴스가 실행될 때 자주 발생합니다. 이 경우 Discourse는 사이트의 도메인을 로컬 IP 주소(예: 127.0.0.1 또는 192.168.x.x)로 해석하고 이를 안전하지 않다고 판단할 수 있습니다.
접근을 허용하려면 도메인을 allowed_internal_hosts 사이트 설정에 추가하세요. 이는 도메인을 안전하게 가져올 수 있도록 명시적으로 표시하여 SSRF 필터링을 우회합니다.
차단된 IP 범위의 전체 목록은 Discourse 소스 코드에서 확인할 수 있습니다.
현재:
oneboxing 등 기타 목적으로 Discourse가 안전하게 크롤링할 수 있는 내부 호스트 목록
대안:
내부 IP로 해석되는 호스트를 Discourse가 크롤링할 수 있도록 허용합니다. 사이트가 로컬 DNS(예: Docker, LAN, Kubernetes) 뒤에 실행될 때 필요합니다. SSRF 보호가 액세스를 차단하는 경우 댓글 임베딩, 토픽 생성, oneboxing에 필수적입니다.
고려 사항
이러한 제안 중 일부는 공식 문서에 대한 링크로 더 적합할 수 있습니다. 사실, 이 게시물만으로도 충분한 역할을 할 수 있으며, 다른 게시물처럼 인덱싱되어야 하기 때문입니다. 다른 제안들은 적절한 풀 리퀘스트를 필요로 할 수 있으며, 저는 언젠가 처리하겠지만 오늘은 아닐 것입니다.
그럼에도 불구하고, 제가 작성한 내용에는 기술적 부정확성이 있을 수 있습니다. 대부분은 실무 경험을 바탕으로 했지만, 동작을 오해했거나 캐싱에 속았거나(아, 캐싱…), 단순히 무언가를 놓쳤을 수 있습니다. 그 부분은 숙련된 Discourse 베테랑들의 판단에 맡기겠습니다.