4

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)
    }
}
jps
  • 20,041
  • 15
  • 75
  • 79
Tabriz Atayi
  • 5,880
  • 7
  • 28
  • 33

1 Answers1

5

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)
cperez08
  • 709
  • 4
  • 9
  • 1
    ahh my bad, is not the server panicking is the client because you are reading a nil response due to the timeout, in your example the client never gets answer from server, thus, r is nil and the panic. two things here you can do for now: - client error handling `if err, ok := err.(net.Error); ok && err.Timeout() { ` - server handler timeout `Handler: http.TimeoutHandler(http.HandlerFunc(slowHandler), 1*time.Second, "I timed out!\n"),` this one will return 503 but will return as soon as the timeout is completed – cperez08 Oct 31 '20 at 16:44
  • Lets assume the this service is public service and it takes unpredictable time and there can be many clients. Some can assign 1 sec others more. Your code does not work in that case, – Tabriz Atayi Oct 31 '20 at 23:06
  • I am not sure if what you're saying is even possible, the only thing I can think about out is receiving a parameter/header in the request and then adjusting the context timeout according to that, otherwise you cannot know what's the client timeout set up. – cperez08 Nov 01 '20 at 08:01
  • Unfortunately , you cannot get client timeout from request header. – Tabriz Atayi Nov 01 '20 at 18:43
  • I meant a custom header/parameter that the client need to know before hand – cperez08 Nov 01 '20 at 19:04