97 votes

Réutiliser les connexions http en Go

Je m'efforce actuellement de trouver un moyen de réutiliser les connexions lors de la création de messages HTTP en Go.

J'ai créé un transport et un client comme ceci :

// Create a new transport and HTTP client
tr := &http.Transport{}
client := &http.Client{Transport: tr}

Je passe ensuite ce pointeur client dans une goroutine qui envoie plusieurs messages au même point de terminaison, comme suit :

r, err := client.Post(url, "application/json", post)

En regardant netstat, il semble qu'il y ait une nouvelle connexion pour chaque message, ce qui entraîne un grand nombre de connexions simultanées ouvertes.

Quelle est la manière correcte de réutiliser les connexions dans ce cas ?

122voto

Matt Self Points 3210

Assurez-vous que vous lire jusqu'à ce que la réponse soit complète ET appeler Close() .

par exemple

res, _ := client.Do(req)
io.Copy(ioutil.Discard, res.Body)
res.Body.Close()

Encore une fois... Pour assurer http.Client la réutilisation des connexions, assurez-vous de :

  • Lire jusqu'à ce que la réponse soit complète (c.-à-d. ioutil.ReadAll(resp.Body) )
  • Appelez Body.Close()

50voto

bn00d Points 95

Si quelqu'un cherche encore des réponses sur la façon de procéder, voici comment je m'y prends.

package main

import (
  "bytes"
  "io/ioutil"
  "log"
  "net/http"
  "time"
)

func httpClient() *http.Client {
    client := &http.Client{
        Transport: &http.Transport{
            MaxIdleConnsPerHost: 20,
        },
        Timeout: 10 * time.Second,
    }

    return client
}

func sendRequest(client *http.Client, method string) []byte {
    endpoint := "https://httpbin.org/post"
    req, err := http.NewRequest(method, endpoint, bytes.NewBuffer([]byte("Post this data")))
    if err != nil {
        log.Fatalf("Error Occured. %+v", err)
    }

    response, err := client.Do(req)
    if err != nil {
        log.Fatalf("Error sending request to API endpoint. %+v", err)
    }

    // Close the connection to reuse it
    defer response.Body.Close()

    body, err := ioutil.ReadAll(response.Body)
    if err != nil {
        log.Fatalf("Couldn't parse response body. %+v", err)
    }

    return body
}

func main() {
    c := httpClient()
    response := sendRequest(c, http.MethodPost)
    log.Println("Response Body:", string(response))
}

Go Playground : https://play.golang.org/p/cYWdFu0r62e

En résumé, je crée une méthode différente pour créer un client HTTP et l'assigner à une variable, puis l'utiliser pour effectuer des requêtes. Notez que le

defer response.Body.Close() 

Cela fermera la connexion une fois la requête terminée à la fin de l'exécution de la fonction et vous pourrez réutiliser le client autant de fois que vous le souhaitez.

Si vous voulez envoyer une demande dans une boucle, appelez la fonction qui envoie la demande dans une boucle.

Si vous voulez changer quoi que ce soit dans la configuration de transport du client, comme ajouter une configuration de proxy, faites un changement dans la configuration du client.

J'espère que cela aidera quelqu'un.

40voto

John Campbell Points 1

Edit : Ceci est plus une note pour les personnes qui construisent un Transport et un Client pour chaque requête.

Edit2 : Changement de lien vers godoc.

Transport est la structure qui contient les connexions à réutiliser ; voir https://godoc.org/net/http#Transport ("Par défaut, Transport met en cache les connexions pour une réutilisation future.")

Ainsi, si vous créez un nouveau Transport pour chaque demande, il créera de nouvelles connexions à chaque fois. Dans ce cas, la solution consiste à partager l'instance de transport entre les clients.

13voto

zzzz Points 23017

IIRC, le client par défaut fait les connexions de réutilisation. Est-ce que vous fermez le réponse ?

Les appelants doivent fermer resp.Body lorsqu'ils ont fini de le lire. 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 TCP persistante avec le serveur pour une demande "keep-alive" ultérieure.

4voto

Billy Yuan Points 804

Sur le 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.

Donc, si vous voulez réutiliser les connexions TCP, vous devez fermer Body à chaque fois après la lecture jusqu'à la fin. De plus, avec defer vous pouvez vous assurer Body.Close() s'appelle après tout. Une fonction ReadBody(io.ReadCloser) est proposé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
}

Et n'appelle pas Close comme ci-dessous :

res, _ := client.Do(req)
io.Copy(ioutil.Discard, res.Body) // what if io.Copy panics, res.Body.Close() will not called.
res.Body.Close()

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X