Fiber version
v1.12.0
Issue description
When a client makes a request and is slow in reading the response I want the connection to be closed. In the stdlib's http.Server there's a field called WriteTimeout and it says:
WriteTimeout is the maximum duration before timing out writes of the response.
And it does the trick:
package main
import (
"fmt"
"log"
"net/http"
"time"
)
func main() {
http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
time.Sleep(2 * time.Second) // Sleep 2s
if _, err := fmt.Fprintf(w, "hello world"); err != nil {
log.Printf("Error writing response: %v", err)
}
})
srv := &http.Server{
Addr: "localhost:8080",
WriteTimeout: time.Second, // Timeout after 1s
}
log.Fatal(srv.ListenAndServe())
}
Test:
$ curl -v localhost:8080/hello
* Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /hello HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.47.0
> Accept: */*
>
* Empty reply from server
* Connection #0 to host localhost left intact
curl: (52) Empty reply from server
Now, Fiber has the same field and description in its fiber.Settings. But it doesn't work.
package main
import (
"log"
"time"
"github.com/gofiber/fiber"
)
func main() {
app := fiber.New(&fiber.Settings{
WriteTimeout: time.Second, // Timeout after 1s
})
app.All("/hello", func(c *fiber.Ctx) {
time.Sleep(2 * time.Second) // Sleep 2s
c.SendString("hello world")
})
log.Fatal(app.Listen("localhost:8080"))
}
Test:
$ curl -v localhost:8080/hello
* Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /hello HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.47.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Fri, 19 Jun 2020 19:23:44 GMT
< Content-Type: text/plain; charset=utf-8
< Content-Length: 11
<
* Connection #0 to host localhost left intact
hello world
Given that the settings field is named the same and the Godoc is the same, I would also expect the same behavior.
Thanks for opening your first issue here! 馃帀 Be sure to follow the issue template! If you need help or want to chat with us, join us on Discord https://gofiber.io/discord
Hi @doingodswork, Fiber basically forwards the settings to Fasthttp and I tested it with the following snippet:
package main
import (
"fmt"
"log"
"time"
"github.com/valyala/fasthttp"
)
func main() {
server := &fasthttp.Server{
Handler: func(ctx *fasthttp.RequestCtx) {
fmt.Println("Sleeping for 3 seconds...")
time.Sleep(3 * time.Second)
ctx.Response.SetBodyString("Hello, World!")
},
ReadTimeout: 1 * time.Second,
WriteTimeout: 1 * time.Second,
IdleTimeout: 1 * time.Second,
}
log.Fatal(server.ListenAndServe(":3000"))
// http://localhost:3000/
}
And I got the same behaviour, @ReneWerner87 and I will take a closer look tomorrow, could have to do with the updateTimer fn in Fasthttp. Will keep you posted, thanks for reporting 馃憤
There is a difference between the fasthttp and net/http WriteTimout that is a design decision by the author of fasthttp.
_For net/http the WriteTimeout includes the time for the handler to process the request. For fasthttp this is not the case. For fasthttp it is really only a timeout for writing the response to the client._
I updated the comments to clarify this https://github.com/gofiber/fiber/pull/500
fasthttp WriteTimeout: _It is reset after the request handler has returned._
net/http WriteTimeout: _It is reset whenever a new request's header is read._
@ReneWerner87 created an internal Timeout middleware to mimic the net/http behaviour, and even allows you to specify different timeouts for different handlers.
package main
import (
"time"
"github.com/gofiber/fiber"
"github.com/gofiber/fiber/middleware"
)
func hello(c *fiber.Ctx) {
time.Sleep(5 * time.Second)
c.Send("Hello 馃憢!")
}
func main() {
app := fiber.New()
app.Get("/", middleware.Timeout(hello, 3 * time.Second))
app.Listen(3000)
}
This new middleware will be available in the next v1.12.1 tag that should be released next week.
I hope this answered your question @doinggodswork 馃憤
Sorry for the late reply (after you've been so quick!). I understand the difference and it makes sense! Thank you very much for the clarification!
The fasthttp behavior might even be more useful against "Slowloris" attacks.
Hey @Fenny, is this a good way to make global Timeout?
app.Use(timeout.New(func(c *fiber.Ctx) (err error) { return c.Next() }, 10*time.Second))
@hhuseyinpay It's better to use the ReadTimeout, WriteTimeout or IdleTimeout settings in Config since the Timeout middleware seems to have a data race issue.
Most helpful comment
There is a difference between the fasthttp and net/http
WriteTimoutthat is a design decision by the author of fasthttp._For net/http the WriteTimeout includes the time for the handler to process the request. For fasthttp this is not the case. For fasthttp it is really only a timeout for writing the response to the client._
I updated the comments to clarify this https://github.com/gofiber/fiber/pull/500
fasthttp
WriteTimeout: _It is reset after the request handler has returned._net/http
WriteTimeout: _It is reset whenever a new request's header is read._@ReneWerner87 created an internal Timeout middleware to mimic the
net/httpbehaviour, and even allows you to specify different timeouts for different handlers.This new middleware will be available in the next
v1.12.1tag that should be released next week.I hope this answered your question @doinggodswork 馃憤