Подтвердить что ты не робот

Повторное использование http-соединений в Голанге

В настоящее время я пытаюсь найти способ повторного использования соединений при создании сообщений HTTP в Голанге.

Я создал транспорт и клиент так:

// 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, это, как представляется, приведет к новому соединению для каждого сообщения, в результате чего будет открыто большое количество одновременных подключений.

Каков правильный способ повторного использования соединений в этом случае?

4b9b3361

Ответ 1

Вы должны убедиться, что вы прочитали, пока ответ не будет завершен, прежде чем вызывать Close().

например.

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

Для обеспечения повторного использования повторного использования http.Client обязательно выполните две вещи:

  • Чтение до завершения ответа (т.е. ioutil.ReadAll(resp.Body))
  • Вызов Body.Close()

Ответ 2

Изменение: Это больше примечание для людей, которые создают Транспорт и Клиент для каждого запроса.

Edit2: изменена ссылка на godoc.

Transport - это структура, которая содержит соединения для повторного использования; см. https://godoc.org/net/http#Transport ("По умолчанию Transport кэширует соединения для последующего повторного использования.")

Поэтому, если вы создаете новый транспорт для каждого запроса, он будет каждый раз создавать новые подключения. В этом случае решение состоит в том, чтобы разделить один экземпляр Transport между клиентами.

Ответ 3

Если кто-то все еще находит ответы на вопрос о том, как это сделать, вот как я это делаю.

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

Go Play: http://play.golang.org/p/oliqHLmzSX

В общем, я создаю другой метод для создания HTTP-клиента и назначения его глобальной переменной, а затем используя его для выполнения запросов. Обратите внимание

defer response.Body.Close() 

Это закроет соединение и снова настроит его на повторное использование.

Надеюсь, это поможет кому-то.

Ответ 4

IIRC, клиент по умолчанию повторно использует соединения. Вы закрываете ответ?

Звонящие должны закрыть resp.Body, когда закончите читать с него. Если resp.Body не закрыт, Клиент, лежащий в основе RoundTripper (обычно Transport), возможно, не сможет повторно использовать постоянное TCP-соединение с сервером для последующего запроса "keep-alive".

Ответ 5

о теле

// It is the caller responsibility to
// close Body. The default HTTP client Transport may not
// reuse HTTP/1.x "keep-alive" TCP connections if the Body is
// not read to completion and closed.

Поэтому, если вы хотите повторно использовать TCP-соединения, вам нужно закрыть Тело каждый раз после чтения до завершения. Для этого предлагается функция 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
}

Ответ 6

Другой подход к 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)
    // ...
}

Ответ 7

Существует два возможных способа:

  1. Используйте библиотеку, которая внутренне повторно использует и управляет файловыми дескрипторами, связанными с каждым запросом. Http Client делает то же самое внутри, но тогда у вас будет контроль над тем, сколько одновременных подключений открывается и как управлять вашими ресурсами. Если вам интересно, посмотрите на реализацию netpoll, которая внутренне использует epoll/kqueue для управления ими.

  2. Легким было бы, вместо объединения сетевых подключений, создать рабочий пул для ваших goroutines. Это было бы легким и лучшим решением, которое не помешало бы вашей текущей кодовой базе и потребовало бы незначительных изменений.

Предположим, вам нужно сделать запрос n POST после получения запроса.

enter image description here

enter image description here

Вы можете использовать каналы, чтобы реализовать это.

Или просто вы можете использовать сторонние библиотеки.
Например: https://github.com/ivpusic/grpool