goldmark是go中markdown渲染服务最稳妥的选择,需显式启用扩展、处理html转义与xss防护,并复用单例实例以保障性能与安全。

Go 里做 Markdown 渲染服务,goldmark 是当前最稳妥的选择——它标准兼容、可扩展、无 CGO 依赖,且维护活跃。别用 blackfriday(已归档)或 markdown(功能弱、不更新),也别在生产环境硬套 github.com/yuin/goldmark 的默认配置直接上 HTTP 服务。
用 goldmark 渲染 Markdown 字符串为 HTML
默认配置下 goldmark 不启用表格、脚注、任务列表等常用扩展,直接调用会丢内容。必须显式添加扩展,并注意 HTML 输出是“未转义”的原始片段,不能直接塞进网页模板的 {{.Content}} 里(得用 {{.Content | safeHTML}} 或等价机制)。
常见错误现象:goldmark.Parse() 返回的节点树没被正确渲染;表格渲染成纯文本;代码块丢失语言标识;链接没加 rel="noopener"。
- 用
goldmark.WithExtensions()启用extension.Table、extension.Footnote、extension.TaskList - 用
html.WithUnsafe()允许原始 HTML 输入(如用户提交含<img>),但需自行过滤 XSS(见下节) - 用
html.WithHardWraps()把换行转成<br>,否则段内换行会被忽略 - 示例关键片段:
md := goldmark.New( goldmark.WithExtensions(extension.Table, extension.TaskList), goldmark.WithRendererOptions(html.WithHardWraps(), html.WithUnsafe()), )
给渲染结果加基础 XSS 防护
启用了 html.WithUnsafe() 就等于放行所有 HTML 标签,但用户输入不可信。goldmark 本身不做过滤,得在渲染前或渲染后介入。推荐在解析后、渲染前遍历 AST 节点,删掉 ast.KindHTMLBlock 和 ast.KindHTMLSpan ——这是最轻量、最可控的方式,比用正则清洗 HTML 输出更可靠。
使用场景:用户提交的 Markdown 可能混入恶意 <script></script> 或 onerror= 属性,而你又不想引入完整 HTML sanitizer 库(如 bluemonday)增加复杂度。
- 不要依赖客户端过滤,服务端必须校验
- 不要只过滤字符串里的
<script></script>,HTML 注入可通过<img src="x" onerror="...">触发 - 用
ast.Walk()遍历,对匹配节点返回ast.WalkStop并调用parent.RemoveChild() - 若必须保留部分 HTML(如 `
封装成 HTTP 服务时的性能与并发注意点
goldmark.New() 创建的是重量级实例,含缓存、扩展注册、选项解析等开销。每请求都 new 一个会明显拖慢吞吐,尤其在高并发下。必须复用单例,但要注意:它的 Convert() 方法是并发安全的。
容易踩的坑:http.HandlerFunc 里每次 new goldmark 实例;用 sync.Pool 管理 goldmark(没必要且易错);忽略输入长度限制导致 OOM。
- 全局声明一个
var md goldmark.Markdown = goldmark.New(...)即可 - 对输入加长度限制(如
if len(body) > 1024*1024 { http.Error(w, "too large", http.StatusBadRequest) }) - 避免在
Convert()前做大量字符串拼接(如注入 CSS 类名),应改用html.WithRenderNodeHook()在渲染阶段注入 - 若需不同配置(如预览 vs 发布),建多个命名实例(
mdPreview,mdPublish),而非运行时切换
真正难的不是把 Markdown 变成 HTML,而是决定哪些 HTML 元素该留、哪些该砍,以及怎么在不牺牲性能的前提下让这个决策稳定生效。很多团队卡在“先快速上线再加固”,结果 XSS 漏洞随第一个用户投稿就暴露了。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











