it-swarm.asia

إعادة استخدام اتصالات HTTP في Golang

أعاني حاليًا لإيجاد طريقة لإعادة استخدام الاتصالات عند إنشاء مشاركات HTTP في Golang.

لقد أنشأت وسيلة نقل وعميل مثل ذلك:

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

بعد ذلك ، أقوم بتمرير مؤشر العميل هذا إلى goroutine مما يؤدي إلى نشر مشاركات متعددة في نفس نقطة النهاية مثل:

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

بالنظر إلى netstat ، يبدو أن هذا يؤدي إلى اتصال جديد لكل منشور مما يؤدي إلى فتح عدد كبير من الاتصالات المتزامنة.

ما هي الطريقة الصحيحة لإعادة استخدام الاتصالات في هذه الحالة؟

63
sicr

يجب عليك التأكد من أنك تقرأ حتى اكتمال الاستجابة قبل الاتصال Close().

مثلا.

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

لضمان إعادة استخدام اتصال http.Client ، تأكد من القيام بأمرين:

  • قراءة حتى اكتمال الاستجابة (على سبيل المثال ، ioutil.ReadAll(resp.Body))
  • اتصل بـ Body.Close()
81
Matt Self

تحرير: هذا هو أكثر من ملاحظة للأشخاص الذين يبنون النقل والعميل لكل طلب.

Edit2: تم تغيير الرابط إلى godoc.

Transport هي البنية التي تحتفظ باتصالات لإعادة استخدامها ؛ راجع https://godoc.org/net/http#Transport ("بشكل افتراضي ، تخزن وسائل النقل اتصالات لإعادة استخدامها في المستقبل.")

لذلك إذا قمت بإنشاء نقل جديد لكل طلب ، فسيؤدي ذلك إلى إنشاء اتصالات جديدة في كل مرة. في هذه الحالة ، يكون الحل هو مشاركة مثيل النقل واحد بين العملاء.

34
DrJosh9000

إذا كان أي شخص لا يزال يجد إجابات على كيفية القيام بذلك ، فهذه هي الطريقة التي أقوم بها.

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

اذهب إلى الملعب: http://play.golang.org/p/oliqHLmzSX

باختصار ، أقوم بإنشاء طريقة مختلفة لإنشاء عميل HTTP وتعيينه إلى متغير عمومي ومن ثم استخدامه لتقديم طلبات. لاحظ ال

defer response.Body.Close() 

سيؤدي ذلك إلى إغلاق الاتصال وإعداده لإعادة الاستخدام مرة أخرى.

امل ان يساعد هذا احد.

34
bn00d

IIRC ، العميل الافتراضي هل إعادة استخدام الاتصالات. هل تغلق الرد ؟

يجب على المتصلين إغلاق resp.Body عند الانتهاء من القراءة منه. إذا لم يتم إغلاق resp.Body ، فقد يتعذر على RoundTripper الأساسي (النقل عادةً) للعميل إعادة استخدام اتصال مستمر TCP للخادم لطلب "المحافظة على الحياة" اللاحق.

9
zzzz

عن الجسم

// 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
}
3
Billy Yuan

طريقة أخرى ل 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)
    // ...
}

0
cyaconi