当前位置:首页 > 文章列表 > Golang > Go教程 > Go textproto.DotReader 怎样还原点转义正文

Go textproto.DotReader 怎样还原点转义正文

来源:17golang原创 2026-11-13 22:16:11 0浏览 收藏

Go 的 textproto.Reader.DotReader() 会把点编码正文还原成普通字节流:删除行首用于转义的一个点,把 CRLF 行尾转换为 LF,并吃掉单独一行的结束点。读取完这个正文后,继续使用同一个 textproto.Reader 解析后续响应,避免绕过它共享的缓冲区。

如果你看到正文里多了一个点,或后续响应被误当成正文,先检查代码是否把协议分帧当成了普通字符串处理。下面用一段原创的内存输入说明这个问题;场景用于解释故障,不代表真实线上事故,也没有声称执行过示例。

官方文档:https://pkg.go.dev/net/textproto#Reader.DotReader

症状:正文多一个点,下一条响应却读不到

假设一个按行传输的服务返回报表正文,正文之后还有状态行。业务原本想保留 .note 这样的行首字符,却直接拼接了网络中收到的文本,于是界面显示为 ..note。有人又改成按点切分,结果内容中的普通句点被截断,末尾的状态行也难以定位。

这个示例中的问题可以按阅读顺序拆开:先是把原始行当作正文,随后尝试手工删点,最后发现读取位置已经错开。要修复的是“当前正文的终点和转义规则”,不是删除整篇文本里的点。

根因:传输分帧和正文字符混在了一起

点编码把正文组织成若干以 CRLF 结束的行。单独一行 .\r\n 是正文结束标记;真实正文如果以点开头,发送方会再补一个点。结束标记属于传输层,解码后的正文不应包含它。普通行内的句点没有行首转义含义。

线上的一行(转义写法)还原后的内容含义
report\r\nreport\n仅转换行尾
..note\r\n.note\n去掉一个转义点
...\r\n..\n保留正文原有的两个点
\r\n\n空行仍属于正文
.\r\n无正文内容结束当前文本块

DotReader 的解码职责包括行首转义、换行归一和结束行识别。这三个规则不能由全局字符串替换代替;它也不保留原始 CRLF 字节,因此不适合直接用于要求字节完全一致的原始报文归档。

点编码正文与行首转义、行尾、结束标记的静态关系
原创结构图:点编码的三类组成及解码职责。具体转义规则以正文表格为准,图示不表示执行顺序或运行结果。

修复:让 DotReader 独占当前正文

先创建一个 bufio.Reader,再交给 textproto.NewReader。取得 DotReader() 后,把它读到当前正文的结束,再调用协议读取器的其他方法。下面采用小型固定输入和 io.ReadAll,便于观察正文与后续响应的分界。

package main

import (
    "bufio"
    "fmt"
    "io"
    "log"
    "net/textproto"
    "strings"
)

func main() {
    // 仅构造协议文本:正文结束后还保留一条响应。
    wire := "report\r\n..note\r\n...\r\n\r\n.\r\n250 ready\r\n"
    buffered := bufio.NewReader(strings.NewReader(wire))
    protocol := textproto.NewReader(buffered)

    // 当前正文只能通过该解码器读取,结束标记不会进入 body。
    body, err := io.ReadAll(protocol.DotReader())
    if err != nil {
        log.Fatalf("正文不完整,停止继续解析:%v", err)
    }

    // 必须沿用原来的缓冲读取器,不能另包一层读取底层输入。
    reply, err := protocol.ReadLine()
    if err != nil {
        log.Fatalf("读取后续响应失败:%v", err)
    }
    fmt.Printf("正文=%q\n后续响应=%q\n", body, reply)
}

按规则推导,正文应是字符串 "report\n.note\n..\n\n",后续响应应为 "250 ready"。这里给的是示例输入对应的预期内容,不是本机执行日志。正常的正文结束由 DotReader 返回 io.EOF;io.ReadAll 将这种正常结束表现为成功的 nil 错误。

这里的 protocol 和 buffered 共享同一个缓冲区。底层输入可能已经被预读,另建一个读取器直接访问原始连接会绕过尚在缓冲区里的数据。DotReader 返回的是受协议读取器管理的 io.Reader,不是独立副本,也没有可供调用的 Close 方法。

底层输入、共享缓冲区、协议读取器和正文解码接口的依赖结构
原创结构图:正文接口和后续行解析共用一份缓冲输入。连线仅表示归属与依赖;必须先完成当前正文再读后续响应。

防复发:把完整读取与异常退出分开

最容易遗漏的限制是生命周期:官方文档明确,返回的正文 Reader 只在下一次调用原 textproto.Reader 的方法之前有效。不要保存旧的 DotReader,同时又调用 ReadLine、ReadMIMEHeader 或另一个 DotReader;也不要让多个 goroutine 竞争同一个输入游标。

读取到正常结束行,才说明当前点编码块完整。若底层输入在结束行之前耗尽,官方实现会把这种底层 EOF 转成 io.ErrUnexpectedEOF。这时即使已经收到一部分正文,也不应把它标记为完整,或直接把当前位置视为下一条响应的起点。其他网络或下游写入错误同样要向上返回。

真实的大正文可以使用 io.Copy(destination, protocol.DotReader()) 避免整块驻留内存,但仍要检查复制错误。若目标写入提前失败,当前正文可能尚未读完;应按业务策略终止会话,或在明确允许的情况下受限地读完剩余正文,不能无条件继续解析后续响应。对网络输入同时设置合理的连接读取期限和消息大小限制,避免无限等待或无限接收。

不要把“只读前 N 字节”当作“已经消费完整正文”。在解码器外层设置的读取上限只能限制交付给业务的正文量,触及上限可能早于结束点;位于底层输入的限制则限制所覆盖的原始流量,作用范围可能还包含后续响应。大小预算和分帧完成是两个独立条件。

相关问题

DotReader 和 ReadDotBytes 怎么选?

小文本需要一次拿到解码后的字节切片时,可以使用 ReadDotBytes。需要边读边写或自行限制处理量时,选择 DotReader;是否完整仍由读取错误和结束标记决定。

ReadDotLines 会保留每行换行符吗?

不会。它返回按行拆分的字符串切片,每个元素末尾的 CRLF 或 LF 被移除。若正文里有空行,对应元素可以是空字符串。

正文中的单独一个点怎样传输?

正文行若只有一个点,线上应发送两个点再跟 CRLF。单个点加 CRLF 已被用作结束行,接收方无法把同一段字节同时解释成正文。

可以先读一小段,然后直接调用 ReadLine 吗?

不要把它当成保存了剩余正文的切换方式。旧 DotReader 在调用原读取器的其他方法后失效;需要完整正文时应显式读完,提前退出时应明确处理剩余数据和会话状态。

官方实现参考:https://go.dev/src/net/textproto/reader.go

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Go uuid 标准库怎样生成并解析 UUIDGo uuid 标准库怎样生成并解析 UUID
上一篇
Go uuid 标准库怎样生成并解析 UUID
Go image/jpeg 怎样设置编码质量并保持 EXIF 边界
下一篇
Go image/jpeg 怎样设置编码质量并保持 EXIF 边界
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    1016次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    1019次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    1146次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    1120次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    961次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码