Skip to content

Instantly share code, notes, and snippets.

@feiskyer
Created February 16, 2025 05:55
Show Gist options
  • Select an option

  • Save feiskyer/8191cc90a0cb41aadd384498981101e4 to your computer and use it in GitHub Desktop.

Select an option

Save feiskyer/8191cc90a0cb41aadd384498981101e4 to your computer and use it in GitHub Desktop.

Revisions

  1. feiskyer created this gist Feb 16, 2025.
    92 changes: 92 additions & 0 deletions http-timeout-test.go
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,92 @@
    package main

    /*
    * Existing TCP timeout:
    * 1) Set http.Client.Timeout to 1m: 1m
    * 2) Set http2.ReadIdleTimeout=30s, http2.PingTimeout=15s, http.Client.Timeout=1m: 45s
    * 3) Without both: 15m
    *
    * New TCP timeout: 30s (per dialer setting)
    *
    * Notes:
    * http2.ReadIdleTimeout - The interval of health check for idle connections.
    * http2.PingTimeout - The timeout for server to respond to a client ping.
    * http.Client.Timeout - The timeout includes connection time, any redirects, and reading the response body.
    */

    import (
    "crypto/tls"
    "fmt"
    "io"
    "net"
    "net/http"
    "time"

    "golang.org/x/net/http2"
    )

    func doRequest(client *http.Client, url string) {
    startTime := time.Now()
    req, err := http.NewRequest("GET", url, nil)
    if err != nil {
    fmt.Printf("HTTP NewRequest failed with duration: %v, error: %v\n", time.Since(startTime), err)
    return
    }

    rsp, err := client.Do(req)
    if err != nil {
    fmt.Printf("HTTP Client Do request failed with duration: %v, error: %v\n", time.Since(startTime), err)
    return
    }

    buf, err := io.ReadAll(rsp.Body)
    if err != nil {
    fmt.Printf("HTTP read body failed with duration: %v, error: %v\n", time.Since(startTime), err)
    return
    }
    defer rsp.Body.Close()

    fmt.Printf("HTTP succeed with duration: %v, resp: %s\n", time.Since(startTime), string(buf)[:15])
    }

    func main() {
    tr := &http.Transport{
    Proxy: http.ProxyFromEnvironment,
    ForceAttemptHTTP2: false, // default is true
    MaxIdleConns: 64, // default is 100
    MaxIdleConnsPerHost: 64, // default is 2
    IdleConnTimeout: 90 * time.Second, // same as default
    TLSHandshakeTimeout: 10 * time.Second, // same as default
    ExpectContinueTimeout: 1 * time.Second, // same as default
    DialContext: (&net.Dialer{
    Timeout: 30 * time.Second,
    KeepAlive: 30 * time.Second,
    }).DialContext,
    TLSClientConfig: &tls.Config{
    InsecureSkipVerify: true,
    MinVersion: tls.VersionTLS12,
    Renegotiation: tls.RenegotiateNever,
    },
    }

    t2, err := http2.ConfigureTransports(tr)
    if err != nil {
    fmt.Printf("time: %v, http2 ConfigureTransports failed: %v\n", time.Now(), err)
    return
    }
    // ReadIdleTimeout configures the interval of health check for idle connections.
    // If the connection is idle for this duration, the client will send a ping to the server.
    t2.ReadIdleTimeout = 30 * time.Second
    // PingTimeout configures the timeout for server to respond to a client ping.
    // Response exceeding this timeout will cause the connection to be closed.
    t2.PingTimeout = 15 * time.Second

    client := &http.Client{
    Transport: tr,
    Timeout: time.Minute,
    }

    for i := 0; i < 500; i++ {
    doRequest(client, "https://www.google.com")
    }
    }