http: 在关闭的响应体上读取 – httptest.NewServer

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

http: read on closed response body - httptest.NewServer

问题

我正在努力理解如何使用httptest.NewServer进行测试,但是遇到了一个障碍。

在我的代码中,我正在向外部API发出GET请求,并希望使用httptest.NewServer编写一个测试。

这是我发出请求的代码(main.go):

package main

import (
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
)

type HTTPClient interface {
	Do(req *http.Request) (*http.Response, error)
}

type NewRequest interface {
	NewRequest(method string, url string, body io.Reader) (*http.Request, error)
}

var (
	Client HTTPClient
)

func init() {
	Client = &http.Client{}
}

func main() {
	url := "https://httpbin.org/get"
	GetData(url)
}

func GetData(url string) (*http.Response, error) {
	req, err := http.NewRequest(http.MethodGet, url, nil)
	if err != nil {
		log.Fatalln(err)
		return nil, err
	}

	resp, err := Client.Do(req)

	if err != nil {
		log.Fatalln(err)
		return nil, err
	}

	defer resp.Body.Close()

	responseBody, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Fatal(err)
		return nil, err
	}

	fmt.Println(resp.Status)
	fmt.Println(string(responseBody))
	return resp, nil
}

当我运行这段代码时,它可以正常工作。

这是我的测试文件:

package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/http/httptest"
	"testing"
)

func TestYourHTTPGet(t *testing.T){

	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w, `response from the mock server goes here`)
	}))
	defer ts.Close()

	mockServerURL := ts.URL

	resp, err := GetData(mockServerURL)
	if err != nil {
		fmt.Println("Error 1: ", err)
	}
	defer resp.Body.Close()

	responseBody, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Fatal("Error 2: ", err)
	}

	fmt.Println(resp.Status)
	fmt.Println(string(responseBody))
}

当我运行go test时,我收到错误消息:http: read on closed response body。如果我从main.go中删除defer resp.Body.Close(),测试将正确通过。

我不确定为什么会出现这种情况,希望有人能解释一下这里发生了什么。

英文:

I am trying to get to grips with testing using the httptest.NewServer and I am hitting a roadblock.

In my code I am making a GET request to an external API and I want to write a test for this using httptest.NewServer.

Here is my code making the request (main.go):

package main

import (
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
)

type HTTPClient interface {
	Do(req *http.Request) (*http.Response, error)
}

type NewRequest interface {
	NewRequest(method string, url string, body io.Reader) (*http.Request, error)
}

var (
	Client HTTPClient
)

func init() {
	Client = &http.Client{}
}

func main() {
	url := "https://httpbin.org/get"
	GetData(url)
}

func GetData(url string) (*http.Response, error) {
	req, err := http.NewRequest(http.MethodGet, url, nil)
	if err != nil {
		log.Fatalln(err)
		return nil, err
	}

	resp, err := Client.Do(req)

	if err != nil {
		log.Fatalln(err)
		return nil, err
	}

	defer resp.Body.Close()

	responseBody, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Fatal(err)
		return nil, err
	}

	fmt.Println(resp.Status)
	fmt.Println(string(responseBody))
	return resp, nil
}

When I run this it works fine.

Here is my test file:

package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/http/httptest"
	"testing"
)

func TestYourHTTPGet(t *testing.T){

	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w, `response from the mock server goes here`)
	}))
	defer ts.Close()

	mockServerURL := ts.URL

	resp, err := GetData(mockServerURL)
	if err != nil {
		fmt.Println("Error 1: ", err)
	}
	defer resp.Body.Close()

	responseBody, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Fatal("Error 2: ", err)
	}

	fmt.Println(resp.Status)
	fmt.Println(string(responseBody))
}

When I run go test I receive the error: http: read on closed response body. If I remove defer resp.Body.Close() from main.go the test passes correctly.

I am not sure why this is happening and was hoping that someone could explain what is going on here?

答案1

得分: 3

如@Cerise Limón所说,你在调用resp.Body.Close()两次,然后尝试读取已关闭的body。为了修复你的代码,你可以将body处理从GetData函数中移除,并在GetData之外进行处理,或者返回body并且在测试中不读取它。

main.go:

package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
)

var Client = &http.Client{}

func main() {
	url := "https://httpbin.org/get"

	status, data, err := GetData(url)
	if err != nil {
		log.Fatalln(err)
	}

	fmt.Println(status)
	fmt.Println(string(data))
}

func GetData(url string) (status string, body []byte, err error) {
	req, err := http.NewRequest(http.MethodGet, url, nil)
	if err != nil {
		return
	}

	resp, err := Client.Do(req)
	if err != nil {
		return
	}

	defer resp.Body.Close()

	body, err = ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Fatalln(err)
	}

	return resp.Status, body, nil
}

main_test.go:

package main

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	"testing"
)

func TestYourHTTPGet(t *testing.T){
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w, `response from the mock server goes here`)
	}))
	defer ts.Close()

	mockServerURL := ts.URL

	status, data, err := GetData(mockServerURL)
	if err != nil {
		fmt.Println("Error 1: ", err)
	}

	fmt.Println(status)
	fmt.Println(string(data))
}
英文:

As @Cerise Limón says you call resp.Body.Close() twice and then try to read closed body. To fix yor code you can remove body processing from GetData function and do it outside GetData or return the body and do not read it in test.

main.go:

package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
)

var Client = &http.Client{}

func main() {
	url := "https://httpbin.org/get"

	status, data, err := GetData(url)
	if err != nil {
		log.Fatalln(err)
	}

	fmt.Println(status)
	fmt.Println(string(data))
}

func GetData(url string) (status string, body []byte, err error) {
	req, err := http.NewRequest(http.MethodGet, url, nil)
	if err != nil {
		return
	}

	resp, err := Client.Do(req)
	if err != nil {
		return
	}

	defer resp.Body.Close()

	body, err = ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Fatalln(err)
	}

	return resp.Status, body, nil
}

main_test.go:

package main

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	"testing"
)

func TestYourHTTPGet(t *testing.T){
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w, `response from the mock server goes here`)
	}))
	defer ts.Close()

	mockServerURL := ts.URL

	status, data, err := GetData(mockServerURL)
	if err != nil {
		fmt.Println("Error 1: ", err)
	}

	fmt.Println(status)
	fmt.Println(string(data))
}

答案2

得分: 0

你的GetData()的返回值是一个指针。你在main.go中运行GetData(),当返回时,它会关闭resp.body。如果你再次读取它,就会引发http: read on closed response body的错误。

所以,如果你想再次读取响应体,你不应该返回*http.Response,而是应该克隆resp.body并返回。

英文:

Your GetData()'s return is a pointer. You run GetData() in main.go, when retun, it will close the resp.body. And if you read it again, it cause http: read on closed response body

So if you want read the body again, you should not return *http.Response, you should clone the resp.body to return

huangapple
  • 本文由 发表于 2021年11月25日 05:29:12
  • 转载请务必保留本文链接:https://go.coder-hub.com/70103171.html
匿名

发表评论

匿名网友

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

确定