Reputation: 6120
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
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