登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  Golang >  Go问答

Go Goldmark Markdown 中的原始 HTML 为什么不输出

来源:17golang原创

时间:2026-10-13 16:23:12 372浏览 收藏

Go Goldmark Markdown 中的原始 HTML 不输出,通常不是 Markdown 写错了,而是 HTML renderer 的默认安全策略在生效。Goldmark 默认不渲染原始 HTML,也不会直接放行潜在危险链接;只有在输入已经可信、并且业务确实需要保留 HTML 标签时,才应通过 html.WithUnsafe() 明确打开这个能力。

官方地址:https://github.com/yuin/goldmark

要点速览
  • 原始 HTML 是否输出由 renderer 控制,单纯调整 Markdown 文本或扩展列表不会改变默认安全边界。
  • html.WithUnsafe() 会让原始 HTML 和潜在危险链接按输入内容输出,不能对陌生用户提交的 Markdown 直接开启。
  • 受信任模板可以在 goldmark.WithRendererOptions 中配置;不可信内容应继续保持默认设置,或先经过专门的 HTML 清洗器。

先确认:消失的是原始 HTML,不是普通 Markdown

我第一次遇到这个问题时,输入里同时放了一个加粗语法和一个原始 标签。转换结果中的加粗仍然存在,只有标签没有出现在 HTML 中,这个现象说明解析器并没有整体失效,问题点更靠近输出阶段。

package main

import (
    "bytes"
    "fmt"

    "github.com/yuin/goldmark"
)

func main() {
    source := []byte("**保留的 Markdown**\n\n原始 HTML")
    var output bytes.Buffer

    // 默认实例保留 Markdown 语法,但不会直接输出原始 HTML 标签。
    if err := goldmark.Convert(source, &output); err != nil {
        // 转换失败要保留错误,避免把空结果误判为 HTML 被过滤。
        panic(err)
    }
    fmt.Print(output.String())
}

这个最小例子适合用来判断问题性质:如果 **保留的 Markdown** 正常变成 ,而 没有按原样出现,就不要先去改输入字符串,也不要误以为必须增加 GFM 扩展。

Goldmark Markdown 输入经过解析器和 HTML renderer 后,普通语法与原始 HTML 处在不同输出边界的静态说明图
图1:Goldmark 解析器、HTML renderer 与原始 HTML 输出边界的结构说明图,不是运行截图。

真正的开关在 HTML renderer 的安全默认值

Goldmark 官方文档把 html.WithUnsafe 定义为 renderer 选项:默认情况下,原始 HTML 和潜在危险链接不会被渲染;启用后才会按输入内容输出。也就是说,extension.GFM 可以补充表格、删除线、任务列表和自动链接等语法,但它不是“允许原始 HTML”的开关。

输入或配置默认表现是否解决原始 HTML 不输出适用判断
普通 Markdown 语法正常渲染不需要解决标题、段落、强调等内容
extension.GFM增加 GFM 语法不是专用开关表格、任务列表等扩展
默认 HTML renderer不输出原始 HTML是问题来源不可信 Markdown 的安全默认值
html.WithUnsafe()按输入输出原始 HTML 和危险链接能解决显示问题仅限已经信任或已清洗的内容

因此,排查时我会先问一句:这段 Markdown 来自代码仓库内的模板,还是来自评论、工单、富文本编辑器或外部接口?前一种情况通常可以考虑打开 renderer 选项;后一种情况不能为了让标签显示就无条件放开。

受信任内容怎样显式开启原始 HTML

如果 Markdown 是随程序发布的文档、内部维护的模板,或者在进入转换流程前已经完成了可信来源控制,可以把选项放在 Goldmark 实例创建处。关键是使用 goldmark.WithRendererOptions 传入 HTML renderer 的选项,而不是给输入字符串加转义。

package main

import (
    "bytes"
    "fmt"

    "github.com/yuin/goldmark"
    "github.com/yuin/goldmark/renderer/html"
)

func main() {
    source := []byte("**标题**\n\n已发布")

    markdown := goldmark.New(
        // 只对已确认可信的 Markdown 实例打开原始 HTML 输出。
        goldmark.WithRendererOptions(html.WithUnsafe()),
    )

    var output bytes.Buffer
    if err := markdown.Convert(source, &output); err != nil {
        // 让上层能够区分转换错误和空内容,不能静默吞掉错误。
        panic(err)
    }
    fmt.Print(output.String())
}

这个配置改变的是 renderer 的输出策略:输入中的 已发布 会保留为 HTML。若还需要 GFM 语法,应把 extension.GFM 放进 goldmark.WithExtensions;它和 html.WithUnsafe 负责的是两类不同问题。

Goldmark 受信任模板与不可信 Markdown 在 WithUnsafe 配置前后的输入边界和清洗责任关系说明图
图2:受信任模板、不可信输入、renderer 配置和清洗责任的边界说明图,不是运行截图。

不可信 Markdown 不要用 WithUnsafe 硬修

公开评论、用户昵称、工单描述和第三方同步内容都不能默认当作可信模板。WithUnsafe 的语义不是“只让安全的 span 显示”,而是把 renderer 的这层保护交给调用方承担。官方文档也建议对不可信内容使用 HTML sanitizer,例如 Bluemonday 一类的清洗方案。

更稳妥的分层方式是:

  1. 先按来源给 Markdown 标注可信级别,不要让所有请求共享一个全局开启了 WithUnsafe 的 renderer。
  2. 确实需要保留少量标签时,先使用允许列表清洗,再将清洗后的结果交给转换流程。
  3. 把“原始 HTML 是否显示”和“HTML 是否允许哪些标签、属性、协议”拆成两个责任,不要用一个布尔配置代替策略。
  4. 对不可信内容继续使用默认 renderer;如果产品需要富文本,优先采用明确的受限语法或专用渲染方案。
// renderTrustedTemplate 只接收应用内维护的模板,不接收用户评论或外部同步内容。
func renderTrustedTemplate(source []byte) (string, error) {
    markdown := goldmark.New(
        // 可信模板允许保留必要的 HTML 标签;调用方必须保证输入来源受控。
        goldmark.WithRendererOptions(html.WithUnsafe()),
    )

    var output bytes.Buffer
    if err := markdown.Convert(source, &output); err != nil {
        // 将转换错误返回给上层,由上层决定是否记录和回退。
        return "", err
    }
    return output.String(), nil
}

这个函数名故意把 TrustedTemplate 写进边界里。它不是安全过滤器,也不能拿来处理用户输入;这样做的价值是让调用点更容易接受代码审查,避免未来把同一个 renderer 复用到评论接口。

发布前按三项边界检查配置

我会把最后的判断压缩成三项,而不是只检查页面上有没有显示标签:

  • 配置边界:只有确实需要原始 HTML 的实例才传入 html.WithUnsafe(),默认实例保持默认值。
  • 输入边界:确认内容来自版本化模板、内部文档或经过清洗的来源;评论、表单和外部同步内容不能直接归为可信。
  • 输出边界:明确最终 HTML 会被谁消费;如果会进入浏览器页面,必须继续考虑标签、属性和 URL 协议的清洗责任。

如果只是想让 Markdown 里的链接、表格或删除线生效,优先配置对应的 Goldmark 扩展,不要直接打开 WithUnsafe。只有在“原始 HTML 本身就是模板能力”的需求成立,并且输入来源受控时,才使用上面的显式配置。

常见问题

为什么加了 extension.GFM,HTML 还是不显示?

因为 GFM 扩展负责一组 Markdown 语法,原始 HTML 的放行由 HTML renderer 的 WithUnsafe 选项负责,两者不是同一个开关。

WithUnsafe 只允许 span 和 class 吗?

不是。它代表按输入内容渲染原始 HTML 和潜在危险链接,不是针对某一个标签的允许列表。需要精细控制时,应在转换前使用 HTML 清洗策略。

把 HTML 标签写成 Markdown 转义形式可以吗?

转义只能让标签以文字显示,不能达到保留 HTML 结构的目的。如果目标只是展示标签文本,转义是正确选择;如果目标是渲染标签,则需要重新评估输入信任级别和 renderer 配置。

应不应该全局复用开启 WithUnsafe 的 Goldmark 实例?

不建议把它作为所有入口的默认实例。更清楚的做法是为可信模板单独封装实例,普通用户输入继续使用默认 renderer,并在代码层面让两条路径有明显名称差异。

一句话总结:Goldmark 原始 HTML 不输出是默认安全边界,不是解析失败;用 html.WithUnsafe() 可以解决受信任模板的显示问题,但不应跳过输入来源和最终 HTML 消费场景的判断。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>