在Golang中重用http连接

时间:2013-07-30 13:45:47

标签: go

我目前正在努力寻找在Golang中发布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,这似乎导致每个帖子的新连接导致大量并发连接被打开。

在这种情况下重用连接的正确方法是什么?

10 个答案:

答案 0 :(得分:81)

在致电Close()之前,您应该确保阅读,直到回复完成

e.g。

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

要确保http.Client连接重用,请务必执行以下两项操作:

  • 阅读直至回复完成(即ioutil.ReadAll(resp.Body)
  • 致电Body.Close()

答案 1 :(得分:34)

编辑:对于为每个请求构建传输和客户端的人来说,这更像是一个注释。

Edit2:更改了指向godoc的链接。

Transport是保存连接以供重用的结构;请参阅https://godoc.org/net/http#Transport(“默认情况下,传输缓存连接以供将来重复使用。”)

因此,如果您为每个请求创建一个新的传输,它将每次创建新的连接。在这种情况下,解决方案是在客户端之间共享一个Transport实例。

答案 2 :(得分:34)

如果有人仍在寻找如何做到这一点的答案,那么我就是这样做的。

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

这将关闭连接并将其设置为可以再次使用。

希望这会对某人有所帮助。

答案 3 :(得分:9)

IIRC,默认客户端 重用连接。你关闭了response吗?

  

调用者在完成阅读后应该关闭resp.Body。如果resp.Body未关闭,则客户端的基础RoundTripper(通常为Transport)可能无法重新使用到服务器的持久TCP连接以用于后续“保持活动”请求。

答案 4 :(得分:3)

关于身体

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

答案 5 :(得分:0)

// avoid creating several instances, should be singleon OkHttpClient client = new OkHttpClient(); String weburl = bund.getString("url"); RequestBody requestBody = new MultipartBody.Builder() .setType(MultipartBody.FORM) .addFormDataPart("subscriptionUR", weburl) .build(); Request request = new Request.Builder() .url("http://"+weburl+"/API/ReportSubscription/GetAllReportSubscription") .post(requestBody) .build(); client.newCall(request).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { // Do something when request failed e.printStackTrace(); Log.d(TAG, "Request Failed."); } @Override public void onResponse(Call call, Response response) throws IOException { if(!response.isSuccessful()){ throw new IOException("Error : " + response); }else { Log.d(TAG,"Request Successful."); } // Read data in the worker thread final String data = response.body().string(); } }); 的另一种方法是使用单例方法来获取http客户端。通过使用sync。一旦确定,您的所有请求将只使用一个实例。

init()

答案 6 :(得分:0)

这里遗漏的是“ goroutine”。传输具有其自己的连接池,默认情况下,该池中的每个连接都将被重用(如果主体已完全读取并关闭),但是如果有多个goroutine正在发送请求,则将创建新的连接(该池中的所有连接均处于繁忙状态,并将创建新的连接) )。为了解决这个问题,您将需要限制每个主机的最大连接数:Transport.MaxConnsPerHosthttps://golang.org/src/net/http/transport.go#L205)。

可能您还想设置IdleConnTimeout和/或ResponseHeaderTimeout

答案 7 :(得分:0)

https://golang.org/src/net/http/transport.go#L196

您应将MaxConnsPerHost明确设置为http.ClientTransport确实重用了TCP连接,但是您应该限制MaxConnsPerHost(默认0表示没有限制)。

func init() {
    // singleton http.Client
    httpClient = createHTTPClient()
}

// createHTTPClient for connection re-use
func createHTTPClient() *http.Client {
    client := &http.Client{
        Transport: &http.Transport{
            MaxConnsPerHost:     1,
            // other option field
        },
        Timeout: time.Duration(RequestTimeout) * time.Second,
    }

    return client
}

答案 8 :(得分: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
    }

答案 9 :(得分:-1)

有两种可能的方法:

  1. 使用一个库,该库在内部重用和管理与每个请求关联的文件描述符。 Http Client内部执行相同的操作,但是您将可以控制打开多少个并发连接以及如何管理资源。如果您有兴趣,请查看netpoll实现,该实现在内部使用epoll / kqueue对其进行管理。

  2. 最简单的方法是,不必为网络连接建立池,而是为您的goroutine创建工作池。这很容易,并且 更好的解决方案,这不会妨碍您当前的代码库, 并且需要进行一些细微的更改。

让我们假设您在收到请求后需要发出n个POST请求。

enter image description here

enter image description here

您可以使用渠道来实现这一目标。

或者,您可以简单地使用第三方库。
喜欢: https://github.com/ivpusic/grpool