Search code examples
httpgoservertimeout

HTTP server HandleFunc loop on timeout?


I am working on a Go app that has a web server. I was trying to add timeouts and encountered an issue. Here's a sample code I made to reproduce it because posting the actual code would be impossible:

package main

import (
    "fmt"
    "html/template"
    "net/http"
    "time"
)

var layout *template.Template

func main() {
    router := http.NewServeMux()
    server := &http.Server{
        Addr:         ":8888",
        Handler:      router,
        ReadTimeout:  5 * time.Second,
        WriteTimeout: 1 * time.Second,
        IdleTimeout:  15 * time.Second,
    }

    router.HandleFunc("/", home)

    var err error
    layout, err = template.ParseFiles("./layout.html")
    if err != nil {
        fmt.Printf("Error1: %+v\n", err)
    }

    server.ListenAndServe()
}

func home(w http.ResponseWriter, r *http.Request) {
    fmt.Println("responding")
    err := layout.Execute(w, template.HTML(`World`))
    if err != nil {
        fmt.Printf("Error2: %+v\n", err)
    }
    time.Sleep(5 * time.Second)
}

layout.html: Hello {{.}}!

When I run it and visit 127.0.0.1:8888, the browser stays loading, and the home() which is triggering the timeout, starts over again and it does it 10 times before it stops and the browser shows a connection reset error.

I was expecting that after a timeout, the func would end immediately, the connection be closed and the browser stop loading and show an error.

How can I achieve this?


Solution

  • immediately response use goroutines and context timeout

    package main
    
    import (
        "context"
        "fmt"
        "html/template"
        "net/http"
        "time"
    )
    
    var layout *template.Template
    var WriteTimeout = 1 * time.Second
    
    func main() {
        router := http.NewServeMux()
        server := &http.Server{
            Addr:         ":8889",
            Handler:      router,
            ReadTimeout:  5 * time.Second,
            WriteTimeout: WriteTimeout + 10*time.Millisecond, //10ms Redundant time
            IdleTimeout:  15 * time.Second,
        }
        router.HandleFunc("/", home)
        server.ListenAndServe()
    }
    
    func home(w http.ResponseWriter, r *http.Request) {
        fmt.Printf("responding\n")
        ctx, _ := context.WithTimeout(context.Background(), WriteTimeout)
        worker, cancel := context.WithCancel(context.Background())
        var buffer string
        go func() {
            // do something
            time.Sleep(2 * time.Second)
            buffer = "ready all response\n"
            //do another
            time.Sleep(2 * time.Second)
            cancel()
            fmt.Printf("worker finish\n")
        }()
        select {
        case <-ctx.Done():
            //add more friendly tips
            w.WriteHeader(http.StatusInternalServerError)
            return
        case <-worker.Done():
            w.Write([]byte(buffer))
            fmt.Printf("writed\n")
            return
        }
    }