94

現在、Go で 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 を見ると、投稿ごとに新しい接続が発生し、多数の同時接続が開いているように見えます。

この場合、接続を再利用する正しい方法は何ですか?

4

11 に答える 11

118

応答が完了するまで読んでいて、必ず呼び出してClose()ください。

例えば

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

繰り返します...http.Client接続の再利用を確実にするために、次のことを確認してください。

  • 応答が完了するまで読み取ります (つまりioutil.ReadAll(resp.Body))
  • 電話Body.Close()
于 2013-07-30T17:27:08.147 に答える
50

誰かがそれを行う方法についてまだ答えを見つけている場合、これが私がやっている方法です.

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

プレイグラウンドに行く: https://play.golang.org/p/cYWdFu0r62e

要約すると、別のメソッドを作成して HTTP クライアントを作成し、それを変数に割り当ててから、それを使用してリクエストを行います。注意してください

defer response.Body.Close() 

これにより、関数の実行の最後にリクエストが完了した後に接続が閉じられ、クライアントを何度でも再利用できます。

ループでリクエストを送信する場合は、ループでリクエストを送信する関数を呼び出します。

プロキシ設定の追加など、クライアント トランスポート設定を変更する場合は、クライアント設定を変更します。

これが誰かを助けることを願っています。

于 2015-06-12T16:12:30.067 に答える
14

IIRC、デフォルトのクライアント接続を再利用します。あなたは応答を閉じていますか?

読み取りが完了したら、呼び出し元は resp.Body を閉じる必要があります。resp.Body が閉じられていない場合、クライアントの基盤となる RoundTripper (通常はトランスポート) は、後続の「キープアライブ」要求のためにサーバーへの永続的な TCP 接続を再利用できない場合があります。

于 2013-07-30T13:50:40.347 に答える
4

もう 1 つの方法init()は、シングルトン メソッドを使用して http クライアントを取得することです。sync.Once を使用すると、すべてのリクエストで 1 つのインスタンスのみが使用されるようになります。

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

于 2019-05-13T13:08:44.267 に答える
0

これは GO http 呼び出しに非常に便利な機能です。接続を維持し、この接続を再開することができます。

    var (
        respReadLimit       = int64(4096)
    )
    
    // Try to read the response body so we can reuse this connection.
    func (c *Client) drainBody(body io.ReadCloser) error {
        defer body.Close()
        _, err := io.Copy(ioutil.Discard, io.LimitReader(body, respReadLimit))
        if err != nil {
            return err
        }
        return nil
    }
于 2021-05-10T20:03:21.070 に答える