golang多次读取http request body的问题分析

 更新时间:2024年01月28日 10:31:39   作者:MinutkiBegut  
这篇文章主要给大家分析了golang多次读取http request body的问题,文中通过代码示例和图文介绍的非常详细,对大家的学习或工作有一定的帮助,需要的朋友可以参考下

问题起因

使用postman发送了一个http请求,对每个请求都有一个对应的context:

type APIContext struct {
	Action   string
	ID       string
	Type     string
	Link     string
	Method   string
	Version  *APIVersion
	Request  *http.Request
	Response http.ResponseWriter
	...
}

其中Request成员变量是golang1.17.3版本http库中定义的Request结构(这里贴出部分成员变量):

type Request struct {
    Method string
    URL *url.URL
    Header Header
    Body io.ReadCloser
    GetBody func() (io.ReadCloser, error)
    Response *Response
    ctx context.Context
    ...
}

请求处理的代码使用ReadAll方法读取Request.Body,debug发现读取出来的字节切片为空:

func ApiHandler() (error) {
    ...
    bodyBytes, err := ioutil.ReadAll(request.Request.Body)
    // fmt.Printf("bodyBytes: %+v", bodyBytes) 结果为[]
    if err != nil {
        ...
    }
    ...
}

问题探究

我把这个问题发给了gpt

在这里插入图片描述

gpt回答说可能是由于Body已经被读取过一次,事实上,我的代码之前确实使用过ReadBody方法读取了一次:

func ApiHandler2() (error) {
    input, err := parse.ReadBody(request.Request)
    ...
}

这个parse.ReadBody是公司的库代码,在此不深入分析

出于好奇,我问了gpt官方库中ioutil.ReadAll()方法能否多次读取Request.Body

在这里插入图片描述

gpt回答ReadAll()方法读取了一次就会消耗掉Request.Body,不能再次读取,并提供两种方法再次读取:

  • 将读取的Request.Body缓存到一个变量bodyBytes(字节切片类型),后续需要读取
  • 使用该变量使用ioutol.NopCloser方法写回到Request.Body

问题溯源

来研究一下ioutil.ReadAll()源码:

// ReadAll reads from r until an error or EOF and returns the data it read.
// A successful call returns err == nil, not err == EOF. Because ReadAll is
// defined to read from src until EOF, it does not treat an EOF from Read
// as an error to be reported.
func ReadAll(r Reader) ([]byte, error) {
	b := make([]byte, 0, 512)
	for {
		if len(b) == cap(b) {
			// Add more capacity (let append pick how much).
			b = append(b, 0)[:len(b)]
		}
		n, err := r.Read(b[len(b):cap(b)])
		b = b[:len(b)+n]
		if err != nil {
			if err == EOF {
				err = nil
			}
			return b, err
		}
	}
}

函数的作用是初始化一个字节切片缓冲区,不断调用Read方法读取数据,直到EOF为止

缓冲区b的初始大小只有512个字节,如果缓冲区满(len(b)==cap(b)),则向b添加一个0元素触发切片的扩容机制,并去掉添加的"0"元素([:len(b)]),之后一直读取数据,可能缓冲区又会满,会继续扩容的操作,直到读取到EOF

从对ReadAll()方法的分析可以得知,使用ReadAll函数处理数据时,内存消耗随着数据的增大而增加,处理较大数据时,会触发多次扩容机制,需要分配大量内存。加载一个10M的文件,可能就需要50M的内存分配

io.Reader 接口中的 Read 方法的定义如下:

type Reader interface {
    Read(p []byte) (n int, err error)
}

这个方法接收一个字节数组 p 作为参数,返回两个值,一个是 n 表示读取的字节数,另一个是 err 表示可能出现的错误。不同的数据源类型实现方式不同

Request.Body只能读取一次的原因是因为,在第一次对其进行读取时,指针已经移动到了 EOF(End Of File)位置,再次读取时就无法再次从头开始读取了

题外话

关于ReadAll()方法消耗内存的替代方案有两种:

  • 使用io.ReadFile函数
  • 使用io.Copy函数

ReadFile函数定义如下:

func ReadFile(filename string) ([]byte, error){}

传参为待加载文件的路径,返回为文件的内容

  • io.Copy会拷贝数据,少于ReadAll的内存消耗

以上就是golang多次读取http request body的问题分析的详细内容,更多关于golang多次读取http request body的资料请关注脚本之家其它相关文章!

相关文章

  • Go语言中循环与判断的语法、用法与最佳实践

    Go语言中循环与判断的语法、用法与最佳实践

    本文详细介绍了Go语言的if条件判断、for循环和switch语句的用法,包括基本语法、高级用法和实战技巧,并指出了一些常见的错误和最佳实践,通过本文,读者可以掌握Go语言中的循环和条件判断的核心用法,需要的朋友可以参考下
    2026-04-04
  • Go语言中一些不常见的命令参数详解

    Go语言中一些不常见的命令参数详解

    这篇文章主要给大家介绍了关于Go语言中一些不常见的命令参数的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧。
    2017-12-12
  • 如何解决goland,idea全局搜索快捷键失效问题

    如何解决goland,idea全局搜索快捷键失效问题

    这篇文章主要介绍了如何解决goland,idea全局搜索快捷键失效问题,快捷键失效,可能是快捷键冲突,也或者是快捷键被修改成其他了。在settings下查看快捷键是否被修改,下文详细介绍需要的朋友可以参考下
    2022-04-04
  • 一文彻底理解Golang闭包实现原理

    一文彻底理解Golang闭包实现原理

    闭包对于一个长期写Java的开发者来说估计鲜有耳闻,光这名字感觉就有点"神秘莫测"。这篇文章的主要目的就是从编译器的角度来分析闭包,彻底搞懂闭包的实现原理,需要的可以参考一下
    2022-10-10
  • Go语言使用组合的方式实现多继承的方法

    Go语言使用组合的方式实现多继承的方法

    这篇文章主要介绍了Go语言使用组合的方式实现多继承的方法,实例分析了多继承的原理与使用组合方式来实现多继承的技巧,需要的朋友可以参考下
    2015-02-02
  • 使用Go语言判断二叉树是否对称的方法小结

    使用Go语言判断二叉树是否对称的方法小结

    二叉树Binary Tree一种特殊的树,是结点的一个有限集合,且所有结点最多有2个子结点,即度只能是0,1,2,判断二叉树是否对称需比较左右子树结构与值,递归法直接对比子节点,迭代法用队列模拟递归,本文通过代码示例讲解的非常详细,需要的朋友可以参考下
    2025-07-07
  • Golang实现HTTP编程请求和响应

    Golang实现HTTP编程请求和响应

    本文主要介绍了Golang实现HTTP编程请求和响应,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-08-08
  • Go语言实现lru淘汰策略和超时过期

    Go语言实现lru淘汰策略和超时过期

    缓存的大小是有限的,当添加数据发现剩余缓存不够时,需要淘汰缓存中的部分数据,本文主要介绍了Go语言实现lru淘汰策略和超时过期,感兴趣的可以了解一下
    2024-02-02
  • Golang操作sqlite3数据库的详细教程

    Golang操作sqlite3数据库的详细教程

    最近会使用到sqlite3,这里作个记录,记性越来越差就是这样,下面这篇文章主要给大家介绍了关于Golang操作sqlite3数据库的详细教程,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2023-04-04
  • Go语言实现可选参数的方法小结

    Go语言实现可选参数的方法小结

    这篇文章主要为大家详细介绍了Go语言实现可选参数的一些常见方法,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下
    2024-02-02

最新评论