Reusing Keep-Alive Connection in case of Timeout in Golang - go

/* Keep Alive Client*/
HttpClient{
Client: &http.Client{
Transport: &http.Transport{
Dial: (&net.Dialer{
Timeout: dialTimeout,
KeepAlive: dialTimeout * 60,
}).Dial,
DisableKeepAlives: false,
MaxIdleConnsPerHost: idleConnectionsPerHost,
},
},
Timeout: 5 * time.Second,
}
/* Execute Request */
timeoutContext, cancelFunction := context.WithTimeout(context.Background(), self.Timeout)
defer cancelFunction()
if response, err = self.Client.Do(request.WithContext(timeoutContext)); err == nil {
defer response.Body.Close()
/* Check If Request was Successful */
statusCode = response.StatusCode
if response.StatusCode == http.StatusOK {
/* Read Body & Decode if Response came & unmarshal entity is supplied */
if responseBytes, err = ioutil.ReadAll(response.Body); err == nil && unmarshalledResponse != nil {
//Process Response
}
} else {
err = errors.New(fmt.Sprintf("Non 200 Response. Status Code: %v", response.StatusCode))
}
}
In Golang whenever a request times out in a Keep-Alive connection that connection is lost and is Reset. For the above code incase of timeout Seeing Packets in Wireshark reveals that RST is sent by the client hence connection is no longer reused.
Event tried using Timeout of Httpclient rather than ContextWithTimeout but having similar findings where connection gets reset.
Anyway we can retain established keep-alive connection even in case of a Timeout of request.

The net/http client closes the connection on timeout because the connection cannot be reused.
Consider what would happen if the connection is reused. If the client receives none of the response or a partial response from the server before timeout, then the next request will read some amount of the previous response.
To keep the connection alive on timeout, implement the timeout in application code. Continue to use a longer timeout in the net/http client to handle cases where the connection is truly stuck or dead.
result := make(chan []byte)
go func() {
defer close(result)
resp, err := client.Do(request)
if err != nil {
// return
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusOK {
p, err := ioutil.ReadAll(resp.Body)
if err != nil {
return
}
result <- resp
}
}()
select {
case p, ok <- result:
if ok {
// p is the response body
}
case time.After(timeout):
// do nothing
}

Related

Unable to create a handler that responds with error in Go to cause a retry

I am trying to verify if the go-retryablehttp execution performs retries as per the specified config.
The verification methodology is to create a test that
creates a retryable client
creates a new request
creates a new server with the error handler
serve the request
verify the retry count.
The above is what I have tried to capture in the below code block
//function that returns 500 error
func InternalServerErrorHandler(w http.ResponseWriter, r *http.Request) {
http.Error(w, fmt.Sprintf("test_%d_body", http.StatusInternalServerError), http.StatusInternalServerError)
}
func TestCreateToolsClient(t *testing.T) {
//create a new server
ts := httptest.NewServer(http.HandlerFunc(InternalServerErrorHandler))
defer ts.Close()
//create a request
request, err := retryablehttp.NewRequest(http.MethodGet, ts.URL, nil)
if err != nil {
log.Fatal(err)
}
//create a retryable client
var options retryablehttp.Options
options.RetryWaitMin = 10 * time.Millisecond
options.RetryWaitMax = 50 * time.Millisecond
options.RetryMax = 6
options.Timeout = 60000 * time.Millisecond
retryableClient := retryablehttp.NewClient(options)
retryCount := -1
// to verify from stdout if the # of retry actually is getting counted
retryableClient.RequestLogHook = func(req *http.Request, retryNumber int) {
retryCount = retryNumber
log.Println("Retrying")
}
//execute the request
response, err := retryableClient.Do(request)
if err != nil {
return
}
//verify
require.Equal(t, http.StatusInternalServerError, response.StatusCode)
require.Equal(t, 2, retryCount)
}
My understanding is
every retryableClient.Do(request) should take time=Timeout if there is error
given that the handler returns error, it should make the retry attempt equal to the options.RetryMax = 6 times
I tried debugging the code, and turns out
// Attempt the request
resp, err = c.HTTPClient.Do(req.Request)
here has err as nil.
Unsure what am I doing wrong.
I have created a go playground here
Okay, I figured this out.
Go playground with solution here
My version of go is Go 1.17.
If you run the above code in go playground (which has Go version 1.19), the retry works.
For Go 1.17, retryable-http(v1.0.2) does not handle status error codes
func DefaultRetryPolicy() func(ctx context.Context, resp *http.Response, err error) (bool, error) {
return func(ctx context.Context, resp *http.Response, err error) (bool, error) {
// do not retry on context.Canceled or context.DeadlineExceeded
//fmt.Printf("jkajsuiohsd %v\n", ctx.Err())
if ctx.Err() != nil {
return false, ctx.Err()
}
if err != nil {
if v, ok := err.(*url.Error); ok {
// Don't retry if the error was due to too many redirects.
if redirectsErrorRegex.MatchString(v.Error()) {
return false, nil
}
// Don't retry if the error was due to an invalid protocol scheme.
if schemeErrorRegex.MatchString(v.Error()) {
return false, nil
}
// Don't retry if the error was due to TLS cert verification failure.
if _, ok := v.Err.(x509.UnknownAuthorityError); ok {
return false, nil
}
}
// The error is likely recoverable so retry.
return true, nil
}
//EXPECT HANDLING BASED ON STATUS CODES, BUT ABSENT
return false, nil
}
}
For Go 1.19, retryable-http(v2.1) implements the functionality under baseRetryPolicy as shown here
func baseRetryPolicy(resp *http.Response, err error) (bool, error) {
if err != nil {
if v, ok := err.(*url.Error); ok {
// Don't retry if the error was due to too many redirects.
if redirectsErrorRe.MatchString(v.Error()) {
return false, v
}
// Don't retry if the error was due to an invalid protocol scheme.
if schemeErrorRe.MatchString(v.Error()) {
return false, v
}
// Don't retry if the error was due to TLS cert verification failure.
if notTrustedErrorRe.MatchString(v.Error()) {
return false, v
}
if _, ok := v.Err.(x509.UnknownAuthorityError); ok {
return false, v
}
}
// The error is likely recoverable so retry.
return true, nil
}
// 429 Too Many Requests is recoverable. Sometimes the server puts
// a Retry-After response header to indicate when the server is
// available to start processing request from client.
if resp.StatusCode == http.StatusTooManyRequests {
return true, nil
}
// Check the response code. We retry on 500-range responses to allow
// the server time to recover, as 500's are typically not permanent
// errors and may relate to outages on the server side. This will catch
// invalid response codes as well, like 0 and 999.
//THIS PART HERE FLAGS RETRIES ON STATUS CODES!
if resp.StatusCode == 0 || (resp.StatusCode >= 500 && resp.StatusCode != http.StatusNotImplemented) {
return true, fmt.Errorf("unexpected HTTP status %s", resp.Status)
}
return false, nil
}
Finally, one has the following options.
Move to Go 1.19
Try using go-retryablehttp v2.01
If you were to use Go.17, create a custom retry policy by updating the CheckRetry function as below.
func TestCreateToolsClient(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(InternalServerErrorHandler))
defer ts.Close()
request, err := retryablehttp.NewRequest(http.MethodGet, ts.URL, nil)
if err != nil {
log.Fatal(err)
}
var options retryablehttp.Options
options.RetryWaitMin = 10 * time.Millisecond
options.RetryWaitMax = 50 * time.Millisecond
options.RetryMax = 6
options.Timeout = 60 * time.Second
//options.Timeout = 30000 * time.Millisecond
retryableClient := retryablehttp.NewClient(options)
retryCount := -1
// to verify from stdout if the # of retry actually is getting counted
retryableClient.RequestLogHook = func(req *http.Request, retryNumber int) {
retryCount = retryNumber
log.Println("Retrying")
}
// A regular expression to match the error returned by net/http when the
// configured number of redirects is exhausted. This error isn't typed
// specifically so we resort to matching on the error string.
redirectsErrorRe := regexp.MustCompile(`stopped after \d+ redirects\z`)
// A regular expression to match the error returned by net/http when the
// scheme specified in the URL is invalid. This error isn't typed
// specifically so we resort to matching on the error string.
schemeErrorRe := regexp.MustCompile(`unsupported protocol scheme`)
// A regular expression to match the error returned by net/http when the
// TLS certificate is not trusted. This error isn't typed
// specifically so we resort to matching on the error string.
notTrustedErrorRe := regexp.MustCompile(`certificate is not trusted`)
retryableClient.CheckRetry = func(_ context.Context, resp *http.Response, err error) (bool, error) {
if err != nil {
if v, ok := err.(*url.Error); ok {
// Don't retry if the error was due to too many redirects.
if redirectsErrorRe.MatchString(v.Error()) {
return false, v
}
// Don't retry if the error was due to an invalid protocol scheme.
if schemeErrorRe.MatchString(v.Error()) {
return false, v
}
// Don't retry if the error was due to TLS cert verification failure.
if notTrustedErrorRe.MatchString(v.Error()) {
return false, v
}
if _, ok := v.Err.(x509.UnknownAuthorityError); ok {
return false, v
}
}
// The error is likely recoverable so retry.
return true, nil
}
// 429 Too Many Requests is recoverable. Sometimes the server puts
// a Retry-After response header to indicate when the server is
// available to start processing request from client.
if resp.StatusCode == http.StatusTooManyRequests {
return true, nil
}
// Check the response code. We retry on 500-range responses to allow
// the server time to recover, as 500's are typically not permanent
// errors and may relate to outages on the server side. This will catch
// invalid response codes as well, like 0 and 999.
if resp.StatusCode == 0 || (resp.StatusCode >= 500 && resp.StatusCode != http.StatusNotImplemented) {
return true, fmt.Errorf("unexpected HTTP status %s", resp.Status)
}
return false, nil
}

Golang http client failing with: dial tcp <some ip>: connect: operation timed out

I have a program in Go which takes around 10k urls (same base url, simply different resource) and request response for them from 10k goroutines.
At some point, I start receiving :
Get "https://.....": dial tcp <some_ip>: connect: operation timed out
I can't understand whether that's due to some Go limitation, my local machine (macbook) limitation, or the limitations of the Server(s).
And how can it be solved.
My code is simple :
var transport = &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
Dial: (&net.Dialer{
Timeout: 0,
KeepAlive: 0,
}).Dial,
TLSHandshakeTimeout: 10 * time.Second,
}
var httpClient = &http.Client{Transport: transport}
func main() {
for id := 1; id <= total; id++ {
myWaitGroup.Add(1)
go checkUri(myBaseUrl, id, some_chan)
}
myWaitGroup.Wait()
}
func checkUri(baseUrl string, id int, myChan string) {
defer myWaitGroup.Done()
url := fmt.Sprintf(`%s/%d.json`, baseUrl, id)
req, _ := http.NewRequest(http.MethodGet, url, nil)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Connection", "close")
response, err := httpClient.Do(req)
if err != nil {
fmt.Println("ERROR: http remote call, err:", err)
return
} else {
defer response.Body.Close()
if response.StatusCode != 200 {
fmt.Printf("ERROR: remote call status code %d [%s]\n", response.StatusCode, url)
return
} else {
.. io.ReadAll(response.Body)
myChan <- fmt.Sprintf(string(b))
..
}
}
}

HTTP request through https Proxy in Go doesn't work

I'm going to send http request via ssl proxy in Go.
Unfortunately, it always fails. Here is my code I have tried.
proxyUrl, err := url.Parse("https://" + proxyURLStr)
transport := &http.Transport{
Proxy: http.ProxyURL(proxyUrl),
}
//adding the Transport object to the http Client
client := &http.Client{
Transport: transport,
Timeout: 60 * time.Second,
}
request, err := http.NewRequest("GET", "https://test.com", nil)
if err != nil {
log.Print("Create Request Error - test.com ")
return -2, invalid_data
}
random_agent := browser.Random()
request.Header.Set("User-Agent", random_agent)
// Make request
response, err := client.Do(request)
if err != nil {
log.Println(err)
current := time.Now()
elapsed := current.Sub(start)
log.Printf("test.com is not available. %02d, %02d ", int(elapsed.Minutes()), int(elapsed.Seconds()))
return -2, invalid_data
}
I get this error:
net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers)
I coded the same function in Python and tested it — works good.
But the Go code does not work.

Unusually High Amount of TCP Connection Timeout Errors

I am using a Go TCP Client to connect to our Go TCP Server.
I am able to connect to the Server and run commands properly, but every so often there will be an unusually high amount of consecutive TCP connection errors reported by my TCP Client when trying to either connect to our TCP Server or sending a message once connected:
dial tcp kubernetes_node_ip:exposed_kubernetes_port:
connectex: A connection attempt failed because the connected party did not properly
respond after a period of time, or established connection failed because connected
host has failed to respond.
read tcp unfamiliar_ip:unfamiliar_port->kubernetes_node_ip:exposed_kubernetes_port
wsarecv: A connection attempt failed because the connected party did not properly
respond after a period of time, or established connection failed because connected
host has failed to respond.
I say "unusually high" because I assume that the number of times these errors occur should be very minimal (about 5 or less within the hour). Note that I am not dismissing the possibility of this being caused by connection instabilities, as I have also noticed that it is possible to run several commands in rapid succession without any errors.
However, I am still going to post my code in case I am doing something wrong.
Below is the code that my TCP Client uses to connect to our server:
serverAddress, err := net.ResolveTCPAddr("tcp", kubernetes_ip+":"+kubernetes_port)
if err != nil {
fmt.Println(err)
return
}
// Never stop asking for commands from the user.
for {
// Connect to the server.
serverConnection, err := net.DialTCP("tcp", nil, serverAddress)
if err != nil {
fmt.Println(err)
continue
}
defer serverConnection.Close()
// Added to prevent connection timeout errors, but doesn't seem to be helping
// because said errors happen within just 1 or 2 minutes.
err = serverConnection.SetDeadline(time.Now().Add(10 * time.Minute))
if err != nil {
fmt.Println(err)
continue
}
// Ask for a command from the user and convert to JSON bytes...
// Send message to server.
_, err = serverConnection.Write(clientMsgBytes)
if err != nil {
err = merry.Wrap(err)
fmt.Println(merry.Details(err))
continue
}
err = serverConnection.CloseWrite()
if err != nil {
err = merry.Wrap(err)
fmt.Println(merry.Details(err))
continue
}
// Wait for a response from the server and print...
}
Below is the code that our TCP Server uses to accept client requests:
// We only supply the port so the IP can be dynamically assigned:
serverAddress, err := net.ResolveTCPAddr("tcp", ":"+server_port)
if err != nil {
return err
}
tcpListener, err := net.ListenTCP("tcp", serverAddress)
if err != nil {
return err
}
defer tcpListener.Close()
// Never stop listening for client requests.
for {
clientConnection, err := tcpListener.AcceptTCP()
if err != nil {
fmt.Println(err)
continue
}
go func() {
// Add client connection to Job Queue.
// Note that `clientConnections` is a buffered channel with a size of 1500.
// Since I am the only user connecting to our server right now, I do not think
// this is a channel blocking issue.
clientConnections <- clientConnection
}()
}
Below is the code that our TCP Server uses to process client requests:
defer clientConnection.Close()
// Added to prevent connection timeout errors, but doesn't seem to be helping
// because said errors happen within just 1 or 2 minutes.
err := clientConnection.SetDeadline(time.Now().Add(10 * time.Minute))
if err != nil {
return err
}
// Read full TCP message.
// Does not stop until an EOF is reported by `CloseWrite()`
clientMsgBytes, err := ioutil.ReadAll(clientConnection)
if err != nil {
err = merry.Wrap(err)
return nil, err
}
// Process the message bytes...
My questions are:
Am I doing something wrong in the above code, or is the above decent enough for basic TCP Client-Server operations?
Is it okay that both the TCP Client and TCP Server have code that defers closing their one connection?
I seem to recall that calling defer inside a loop does nothing. How do I properly close Client connections before starting new ones?
Some extra information:
Said errors are not logged by the TCP Server, so aside from
connection instabilities, this might also be a
Kubernetes/Docker-related issue.
It seems this piece of code does not act as you think it does. The defer statement on the connection close will only happen when the function returns, not when an iteration ends. So as far as I can see here, you are creating a lot of connections on the client side, it could be the problem.
serverAddress, err := net.ResolveTCPAddr("tcp", kubernetes_ip+":"+kubernetes_port)
if err != nil {
fmt.Println(err)
return
}
// Never stop asking for commands from the user.
for {
// Connect to the server.
serverConnection, err := net.DialTCP("tcp", nil, serverAddress)
if err != nil {
fmt.Println(err)
continue
}
defer serverConnection.Close()
// Added to prevent connection timeout errors, but doesn't seem to be helping
// because said errors happen within just 1 or 2 minutes.
err = serverConnection.SetDeadline(time.Now().Add(10 * time.Minute))
if err != nil {
fmt.Println(err)
continue
}
// Ask for a command from the user and send to the server...
// Wait for a response from the server and print...
}
I suggest to write it this way:
func start() {
serverAddress, err := net.ResolveTCPAddr("tcp", kubernetes_ip+":"+kubernetes_port)
if err != nil {
fmt.Println(err)
return
}
for {
if err := listen(serverAddress); err != nil {
fmt.Println(err)
}
}
}
func listen(serverAddress string) error {
// Connect to the server.
serverConnection, err := net.DialTCP("tcp", nil, serverAddress)
if err != nil {
fmt.Println(err)
continue
}
defer serverConnection.Close()
// Never stop asking for commands from the user.
for {
// Added to prevent connection timeout errors, but doesn't seem to be helping
// because said errors happen within just 1 or 2 minutes.
err = serverConnection.SetDeadline(time.Now().Add(10 * time.Minute))
if err != nil {
fmt.Println(err)
return err
}
// Ask for a command from the user and send to the server...
// Wait for a response from the server and print...
}
}
Also, you should keep a single connection open, or a pool of connections, instead of opening and closing the connection right away. Then when you send a message you get a connection from the pool (or the single connection), and you write the message and wait for the response, then you release the connection to the pool.
Something like that:
res, err := c.Send([]byte(`my message`))
if err != nil {
// handle err
}
// the implementation of send
func (c *Client) Send(msg []byte) ([]byte, error) {
conn, err := c.pool.Get() // returns a connection from the pool or starts a new one
if err != nil {
return nil, err
}
// send your message and wait for response
// ...
return response, nil
}

Websocket waiting for message with Timeout

I want to create a Websocket connection via GO. This connection follows a clearly defined pattern: The client should "authenticate" (enter data) themself immediately after creating the connection. If the client does not do it, the connection will be closed after a short period.
My current code contains this initial timeout (initTimeout) and the maximum timeout for all connections. While those timers can easily be checked, i am not sure how i can combine the timers with waiting for a message which blocks the execution.
ws, err := upgrader.Upgrade(w, r, nil)
initTimeout := time.NewTicker(time.Duration(30) * time.Second)
maxTimeout := time.NewTicker(time.Duration(45) * time.Minute)
for {
select {
case <- initTimeout.C:
ws.WriteMessage(websocket.TextMessage, []byte("No input received"))
ws.Close()
case <- maxTimeout.C:
ws.WriteMessage(websocket.TextMessage, []byte("Maximum timeout"))
ws.Close()
default:
mt, message, err := c.ReadMessage()
// will this block the timers?
}
}
Use the read deadline to implement the timeouts:
ws, err := upgrader.Upgrade(w, r, nil)
if err != nil {
// handle error
}
// Read the initial message with deadline of 30 seconds
ws.SetReadDeadline(time.Now().Add(30 * time.Second))
mt, message, err := ws.ReadMessage()
if err != nil {
// Handle the error which might be a deadline exceeded error.
}
// process the initial message
// ...
for {
// Read next message with deadline of 45 minutes
ws.SetReadDeadline(time.Now().Add(45 * time.Minute))
mt, message, err = ws.ReadMessage()
if err != nil {
// Handle the error which might be a deadline exceeded error.
}
// process message
// ....
}

Resources