Tabriz Atayi
Tabriz Atayi

Reputation: 6120

Preventing context deadline exceeded (Client.Timeout exceeded while awaiting headers) error with HTTP 200 OK

I have a client and server. Server process the request more than 2 seconds. But client does not have so much time. It needs to get response in 1 seconds. That's why it responds with

Get "http://localhost:8888": context deadline exceeded (Client.Timeout exceeded while awaiting headers) panic: runtime error: invalid memory address or nil pointer dereference

Question? How can I change server.go in such a way that all errors are properly recovered and that the endpoint is always available? Note that the endpoint, the client is calling, should process request as quickly as possible and return a 200 OK as soon as it done.

client.go

package main

import (
    "fmt"
    "log"
    "net/http"
    "time"
)

func main() {

    c := &http.Client{Timeout: 2 * time.Second}

    res, err := c.Get("http://localhost:8888")
    if err != nil {
        log.Println(err)
    }
    var r []byte

    _, err = res.Body.Read(r)
    fmt.Println(string(r))
}

server.go

package main

import (
    "fmt"
    "io"
    "net/http"
    "time"
)

func slowHandler(w http.ResponseWriter, req *http.Request) {
    time.Sleep(2 * time.Second)
    io.WriteString(w, "I am slow!\n")
}

func main() {
    srv := http.Server{
        Addr:    ":8888",
        Handler: http.HandlerFunc(slowHandler),
    }

    if err := srv.ListenAndServe(); err != nil {
        fmt.Printf("Server failed: %s\n", err)
    }
}

Upvotes: 7

Views: 76112

Answers (1)

cperez08
cperez08

Reputation: 749

you can recover from panic by implementing a recovery Middleware, something like:

 defer func() {
      if err := recover(); err != nil {
        log.Println("recovered from panic", err)
        fmt.Fprintf(w, "recovered from panic")
      }
    }()

you can use this useful article as a guideline https://www.nicolasmerouze.com/middlewares-golang-best-practices-examples

EDIT

you can create a custom Middleware to handle a custom handler timeout

func timeOutMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        done := make(chan bool)
        ctx, cancelFunc := context.WithTimeout(r.Context(), time.Second*1)
        defer cancelFunc()
        go func() {
            next.ServeHTTP(w, r)
            close(done)
        }()
        select {
        case <-done:
            return
        case <-ctx.Done():
            w.WriteHeader(http.StatusOK)
            w.Write([]byte(`{"message": "handled time out"}`))
        }
    })

}

in order for this to work you need to sync with the client because if the client sets a lower timeout then will never get a proper response, or in the case the server sets a very low timeout this could happen as well.

also to fully read the response bytes use this

defer res.Body.Close()
body, _ := ioutil.ReadAll(res.Body)

Upvotes: 5

Related Questions