如何实现经url.QueryEscape处理的包装io.Reader?代码报错分析
如何包装io.Reader实现URL查询转义输出并避免越界panic
原代码触发slice bounds out of range panic的核心原因是:URL转义后的内容长度大概率会超过原输入长度(比如空格转成%20,长度从1变为3;特殊字符如&转成%26,长度翻倍)。原实现直接将转义结果复制到传入的p切片,却返回转义后的完整长度,当转义长度超过p的容量时,调用者尝试读取p[:len(escaped)]就会触发越界错误。
正确实现思路
需要通过内部缓存来暂存未输出的转义内容,不能依赖传入的p来承载全部转义结果。每次Read调用遵循以下逻辑:
- 优先输出缓存中剩余的转义内容
- 缓存为空时,从原Reader读取数据并转义
- 将转义内容填充到
p中,剩余部分存入缓存 - 处理EOF:原Reader读完且缓存清空后才返回EOF
完整实现代码
import ( "io" "net/url" ) type queryEscapeReader struct { r io.Reader buffer []byte // 暂存未输出的转义后内容 } // NewQueryEscapeReader 创建一个带URL查询转义的io.Reader包装器 func NewQueryEscapeReader(r io.Reader) io.Reader { return &queryEscapeReader{r: r} } func (q *queryEscapeReader) Read(p []byte) (int, error) { // 先输出缓存中的剩余内容 if len(q.buffer) > 0 { written := copy(p, q.buffer) q.buffer = q.buffer[written:] if len(q.buffer) == 0 { q.buffer = nil // 清空缓存释放内存 } return written, nil } // 从原Reader读取数据,用临时缓冲区避免占用p的空间 tempBuf := make([]byte, len(p)) n, err := q.r.Read(tempBuf) if err != nil && err != io.EOF { return 0, err } // 对读取到的内容执行URL查询转义 escapedStr := url.QueryEscape(string(tempBuf[:n])) escapedBytes := []byte(escapedStr) // 将转义内容写入p,剩余部分存入缓存 written := copy(p, escapedBytes) if written < len(escapedBytes) { q.buffer = escapedBytes[written:] } // 原Reader已读完且缓存为空时,返回EOF if err == io.EOF && len(q.buffer) == 0 { return written, io.EOF } return written, nil }
关键改进说明
- 内部缓存:解决转义内容长度超过
p容量的问题,确保所有转义内容都能被输出 - 临时缓冲区:避免直接使用传入的
p存储原数据,防止转义后内容无法全部放入p - 严谨的EOF处理:保证原Reader的所有内容都完成转义并输出后,才返回EOF,避免截断数据
内容的提问来源于stack exchange,提问作者dillon
相关产品推荐
相关产品推荐

