在golang中,在多个请求之后多次关闭响应体。

huangapple go评论118阅读模式
英文:

Close response body multiple times after multiple requests in golang

问题

这篇帖子中指出,应该关闭response.Body以避免资源泄漏。在http package godoc的概述示例中也有展示。

在我的测试代码中,我在同一个函数中多次使用resp, err := http.DefaultClient.Do(req)发送多个请求来尝试一个API。这样做是不是一个不好的做法?在这种情况下,我是在每个请求后写defer resp.Body.Close(),还是只写一次?

url := server.URL + "/ticket/add"
reader = strings.NewReader(`{"id": "test1", "detail": "test1"}`)
req, err := http.NewRequest("POST", url, reader)
assert.Nil(t, err)

resp, err := http.DefaultClient.Do(req)
assert.Nil(t, err)
defer resp.Body.Close()

assert.Equal(t, http.StatusCreated, resp.StatusCode)
// 添加一个相同id的ticket
reader = strings.NewReader(`{"id": "test1"}`)
req, err = http.NewRequest("POST", url, reader)
assert.Nil(t, err)

resp, err = http.DefaultClient.Do(req)
assert.Nil(t, err)
assert.Equal(t, http.StatusInternalServerError, resp.StatusCode)

相关问题,在服务器端即func(w http.ResponseWriter, r *http.Request)内部,是否也需要关闭请求体(request body)?

英文:

In this post, it is pointed out that response.Body should be closed to avoid resource leak. It is also shown in the overview examples in http package godoc.

In my test code, I send multiple requests to try an API with

resp, err := http.DefaultClient.Do(req)

multiple times in the same function. Is this a bad practice? In this case, do I write defer resp.Body.Close() after each of them, or just once?

url := server.URL + "/ticket/add"                                       
reader = strings.NewReader(`{"id": "test1", "detail": "test1"}`)        
req, err := http.NewRequest("POST", url, reader)                        
assert.Nil(t, err)               
                                   
resp, err := http.DefaultClient.Do(req)                                 
assert.Nil(t, err)                                                      

defer resp.Body.Close()                                                 

assert.Equal(t, http.StatusCreated, resp.StatusCode)                    
// add a ticket with same id                                            
reader = strings.NewReader(`{"id": "test1"}`)                           
req, err = http.NewRequest("POST", url, reader)                         
assert.Nil(t, err)                                                      

resp, err = http.DefaultClient.Do(req)                                  
assert.Nil(t, err)                                                      
assert.Equal(t, http.StatusInternalServerError, resp.StatusCode)   

A related question, on the server side, i.e., inside the func(w http.ResponseWriter, r *http.Request), is it necessary to close the request body as well?

答案1

得分: 5

是的,你需要关闭两个响应。将一个调用推迟到resp.Body.Close不会对另一个产生影响。在每种情况下,*http.Response是不同的,它们都可以被推迟关闭。

在服务器端,你不需要关闭Request.Body -- 根据http.Request文档

// 服务器将关闭请求体。ServeHTTP处理程序不需要关闭它。
英文:

Yes, you need to close both responses. Deferring one call to resp.Body.Close does not somehow effect the other. The *http.Response is different in each case, and they both can be deferred.

On the server side, you do not need to close the Request.Body -- from the http.Request documentation:

// The Server will close the request body. The ServeHTTP
// Handler does not need to.

答案2

得分: 0

不良实践

如果你没有重用resp变量,那么很明显你正在处理不同的响应实例,每个实例都必须关闭。

> Do发送一个HTTP请求并返回一个HTTP响应(实例)...

因此,不良实践是不执行多个请求,而是重用同一个变量来处理多个响应。这会导致代码不明显,并产生永远不会被销毁的不可达对象。

延迟执行

> 延迟语句将一个函数调用推入列表中。保存的调用列表在包围函数返回后执行。

如果你计划了单个或多个带有对同一变量的引用的延迟执行,那么只有最后一个分配给该变量对象的方法将被执行(对于多个延迟执行,会执行多次)。

带有示例的Playground

关闭Response.Body

根据Response文档

> 关闭Body是调用者的责任。

因此,通常你必须关闭每个Response.Body

垃圾回收和终结(编辑)

垃圾收集器调用绑定到收集对象终结器以关闭文件、连接和执行其他清理操作。默认情况下,Body对象没有绑定终结器。

你改进后的代码片段:

// ...大量的代码
resp_one, err := http.DefaultClient.Do(req)
// 顺便说一句,`assert.Nil`只返回断言是否成功(布尔值),而不会终止测试。
if assert.Nil(t, err) == true {
    defer resp_one.Body.Close()
}

// ...大量的代码
resp_two, err = http.DefaultClient.Do(req)
if assert.Nil(t, err) == true {
    defer resp_two.Body.Close()
}
英文:

Bad practice

If you did not reuse resp variable, it would be obvious that you are dealing with different response instances each of which must be closed.

> Do sends an HTTP request and returns an HTTP response (instance)...

So, the bad practice is not to do several requests but to reuse the same variable for several responses. It causes code unobviousness. And produces unreachable objects which never be finalized.

Deferred execution

> A defer statement pushes a function call onto a list. The list of saved calls is executed after the surrounding function returns.

If you have scheduled single or several deferred executions with reference to the same variable only the last assigned to the variable object's method will be executed (several times for several defers).

Playground with an example

Closing Response.Body

From Response documentation:

> It is the caller's responsibility to
close Body.

So, typically you must close each Response.Body.

Garbage collecting and finalization (edit)

Garbage collector invokes bind to collecting objects finalizers to close files, connections and to do other cleanup actions. And there is no finalizers bind to Body object by default.

Your improved snippet:

// ...a lot of code
resp_one, err := http.DefaultClient.Do(req)                                 
 // BTW, `assert.Nil` just returns whether the assertion was successful (bool) not terminates a test.
if assert.Nil(t, err) == true {
    defer resp_one.Body.Close()                                                 
}

// ...a lot of code
resp_two, err = http.DefaultClient.Do(req)                                  
if assert.Nil(t, err) == true {
    defer resp_two.Body.Close()
}

huangapple
  • 本文由 发表于 2016年12月30日 00:32:23
  • 转载请务必保留本文链接:https://go.coder-hub.com/41383622.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定