저는 현재 Golang에서 HTTP 게시물을 만들 때 연결을 재사용하는 방법을 찾기 위해 고군분투하고 있습니다.
다음과 같이 전송 및 클라이언트를 만들었습니다.
// Create a new transport and HTTP client
tr := &http.Transport{}
client := &http.Client{Transport: tr}
그런 다음이 클라이언트 포인터를 같은 엔드 포인트에 여러 게시물을 만드는 고 루틴에 전달합니다.
r, err := client.Post(url, "application/json", post)
netstat를 살펴보면 모든 게시물에 대해 새로운 연결이 발생하여 많은 수의 동시 연결이 열려있는 것으로 보입니다.
이 경우 연결을 재사용하는 올바른 방법은 무엇입니까?
답변
당신이 있는지 확인 응답이 완료 될 때까지 읽기 및 전화 Close()
.
예 :
res, _ := client.Do(req)
io.Copy(ioutil.Discard, res.Body)
res.Body.Close()
다시 … http.Client
연결 재사용 을 보장하려면 다음을 확인 하십시오.
- 응답이 완료 될 때까지 (즉, 읽기
ioutil.ReadAll(resp.Body)
) - 요구
Body.Close()
답변
누군가가 그것을하는 방법에 대한 답을 여전히 찾고 있다면, 이것이 내가하는 방법입니다.
package main
import (
"bytes"
"io/ioutil"
"log"
"net/http"
"time"
)
var httpClient *http.Client
const (
MaxIdleConnections int = 20
RequestTimeout int = 5
)
func init() {
httpClient = createHTTPClient()
}
// createHTTPClient for connection re-use
func createHTTPClient() *http.Client {
client := &http.Client{
Transport: &http.Transport{
MaxIdleConnsPerHost: MaxIdleConnections,
},
Timeout: time.Duration(RequestTimeout) * time.Second,
}
return client
}
func main() {
endPoint := "https://localhost:8080/doSomething"
req, err := http.NewRequest("POST", endPoint, bytes.NewBuffer([]byte("Post this data")))
if err != nil {
log.Fatalf("Error Occured. %+v", err)
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
response, err := httpClient.Do(req)
if err != nil && response == nil {
log.Fatalf("Error sending request to API endpoint. %+v", err)
}
// Close the connection to reuse it
defer response.Body.Close()
// Let's check if the work actually is done
// We have seen inconsistencies even when we get 200 OK response
body, err := ioutil.ReadAll(response.Body)
if err != nil {
log.Fatalf("Couldn't parse response body. %+v", err)
}
log.Println("Response Body:", string(body))
}
Go Playground : http://play.golang.org/p/oliqHLmzSX
요약하면 HTTP 클라이언트를 만들고이를 전역 변수에 할당 한 다음이를 사용하여 요청하는 다른 방법을 만들고 있습니다. 참고
defer response.Body.Close()
이렇게하면 연결이 닫히고 다시 사용할 준비가됩니다.
이것이 누군가를 도울 수 있기를 바랍니다.
답변
편집 : 이것은 모든 요청에 대해 전송 및 클라이언트를 구성하는 사람들을위한 메모입니다.
Edit2 : 링크를 godoc로 변경했습니다.
Transport
재사용을 위해 연결을 보유하는 구조체입니다. https://godoc.org/net/http#Transport를 참조 하십시오 ( “기본적으로 Transport는 향후 재사용을 위해 연결을 캐시합니다.”).
따라서 각 요청에 대해 새 전송을 생성하면 매번 새로운 연결이 생성됩니다. 이 경우 해결 방법은 클라이언트간에 하나의 전송 인스턴스를 공유하는 것입니다.
답변
IIRC, 기본 클라이언트 는 연결을 재사용합니다. 응답 을 닫으 십니까?
발신자는 읽기가 끝나면 resp.Body를 닫아야합니다. resp.Body가 닫히지 않으면 클라이언트의 기본 RoundTripper (일반적으로 전송)가 후속 “연결 유지”요청을 위해 서버에 대한 영구 TCP 연결을 재사용하지 못할 수 있습니다.
답변
몸에 관하여
// It is the caller's responsibility to
// close Body. The default HTTP client's Transport may not
// reuse HTTP/1.x "keep-alive" TCP connections if the Body is
// not read to completion and closed.
따라서 TCP 연결을 재사용하려면 읽기 완료 후 매번 Body를 닫아야합니다. 이와 같이 ReadBody (io.ReadCloser) 함수를 제안합니다.
package main
import (
"fmt"
"io"
"io/ioutil"
"net/http"
"time"
)
func main() {
req, err := http.NewRequest(http.MethodGet, "https://github.com", nil)
if err != nil {
fmt.Println(err.Error())
return
}
client := &http.Client{}
i := 0
for {
resp, err := client.Do(req)
if err != nil {
fmt.Println(err.Error())
return
}
_, _ = readBody(resp.Body)
fmt.Println("done ", i)
time.Sleep(5 * time.Second)
}
}
func readBody(readCloser io.ReadCloser) ([]byte, error) {
defer readCloser.Close()
body, err := ioutil.ReadAll(readCloser)
if err != nil {
return nil, err
}
return body, nil
}
답변
에 대한 또 다른 접근 방식 init()
은 단일 메서드를 사용하여 http 클라이언트를 가져 오는 것입니다. sync.Once를 사용하면 모든 요청에 하나의 인스턴스 만 사용되도록 할 수 있습니다.
var (
once sync.Once
netClient *http.Client
)
func newNetClient() *http.Client {
once.Do(func() {
var netTransport = &http.Transport{
Dial: (&net.Dialer{
Timeout: 2 * time.Second,
}).Dial,
TLSHandshakeTimeout: 2 * time.Second,
}
netClient = &http.Client{
Timeout: time.Second * 2,
Transport: netTransport,
}
})
return netClient
}
func yourFunc(){
URL := "local.dev"
req, err := http.NewRequest("POST", URL, nil)
response, err := newNetClient().Do(req)
// ...
}
답변
여기서 누락 된 부분은 “고 루틴”입니다. 전송에는 자체 연결 풀이 있으며 기본적으로 해당 풀의 각 연결은 재사용되지만 (본문이 완전히 읽고 닫힌 경우) 여러 고 루틴이 요청을 보내는 경우 새 연결이 생성됩니다 (풀에는 모든 연결이 사용 중이며 새 연결이 생성됩니다. ). 이를 해결하려면 호스트 당 최대 연결 수를 제한해야합니다 Transport.MaxConnsPerHost
( https://golang.org/src/net/http/transport.go#L205 ).
아마도 당신은 설정 IdleConnTimeout
및 / 또는 ResponseHeaderTimeout
.