web-dev-qa-db-fra.com

Réutilisation des connexions http à Golang

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?

57
sicr

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:

  • Lire jusqu'à ce que la réponse soit complète (c'est-à-dire _ioutil.ReadAll(resp.Body))
  • Appelez Body.Close()
77
Matt Self

É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.

33
DrJosh9000

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.

31
bn00d

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.

8
zzzz

à 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
}
1
Billy Yuan

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)
    // ...
}

0
cyaconi