Je lutte actuellement pour trouver un moyen de réutiliser les connexions lors de la publication de publications HTTP dans Golang.
J'ai créé un transport et un client comme suit:
// Create a new transport and HTTP client
tr := &http.Transport{}
client := &http.Client{Transport: tr}
Je passe ensuite ce pointeur de client dans un goroutine qui effectue plusieurs publications sur le même point de terminaison, comme ceci:
r, err := client.Post(url, "application/json", post)
En regardant Netstat, il semble en résulter une nouvelle connexion pour chaque publication, ce qui entraîne un grand nombre de connexions simultanées ouvertes.
Quelle est la bonne façon de réutiliser les connexions dans ce cas?
Vous devez vous assurer de lire jusqu'à ce que la réponse soit complète avant d'appeler Close()
.
par exemple.
res, _ := client.Do(req)
io.Copy(ioutil.Discard, res.Body)
res.Body.Close()
Pour garantir la réutilisation de la connexion http.Client
, assurez-vous de faire deux choses:
ioutil.ReadAll(resp.Body)
)Body.Close()
Éditer: Ceci est plus une note pour les personnes qui construisent un transport et un client pour chaque demande.
Edit2: modification du lien vers godoc.
Transport
est la structure qui contient les connexions pour une réutilisation; voir https://godoc.org/net/http#Transport ("Par défaut, Transport met en cache les connexions pour une utilisation ultérieure.")
Ainsi, si vous créez un nouveau transport pour chaque demande, cela créera de nouvelles connexions à chaque fois. Dans ce cas, la solution consiste à partager l'instance de transport unique entre les clients.
Si quelqu'un trouve encore des réponses sur la façon de le faire, c'est comme ça que je le fais.
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))
}
Allez au terrain de jeu: http://play.golang.org/p/oliqHLmzSX
En résumé, je crée une méthode différente pour créer un client HTTP et l’assigne à une variable globale, puis je l’utilise pour faire des requêtes .
defer response.Body.Close()
Cela fermera la connexion et la préparera pour une nouvelle utilisation.
J'espère que cela aidera quelqu'un.
IIRC, le client par défaut ne réutilise les connexions. Fermez-vous la réponse ?
Les appelants doivent fermer resp.Body une fois la lecture terminée. Si resp.Body n'est pas fermé, le RoundTripper sous-jacent du client (généralement Transport) peut ne pas être en mesure de réutiliser une connexion persistante TCP au serveur pour une demande "persistante" ultérieure.
à propos du corps
// 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.
Ainsi, si vous souhaitez réutiliser les connexions TCP, vous devez fermer Body après chaque lecture. Une fonction ReadBody (io.ReadCloser) est suggérée comme ceci.
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
}
Une autre approche de init()
consiste à utiliser une méthode singleton pour obtenir le client http. En utilisant sync.Once, vous pouvez être sûr qu’une seule instance sera utilisée pour toutes vos demandes.
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)
// ...
}