英文:
Go: ResponseController deadline not respected
问题
我有一些代码,我试图在写入客户端时如果写入时间过长就取消写入。我以为我可以使用http.ResponseController来实现这个功能,因为它有一个SetWriteDeadline方法。
不幸的是,在下面的代码中,rw.Write
在截止时间过后并不返回错误。
有没有其他方法可以取消写入呢?
package main
import (
"log"
"net/http"
"time"
)
func main() {
http.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) {
log.Println(r.Method, r.URL.Path)
rc := http.NewResponseController(rw)
dl := time.Now().Add(3 * time.Second)
if err := rc.SetWriteDeadline(dl); err != nil {
log.Println(err)
return
}
log.Println("write deadline:", dl)
var total int
for _, b := range []byte("What!\n") {
time.Sleep(time.Second)
n, err := rw.Write([]byte{b})
if err != nil {
log.Println(err)
return
}
total += n
log.Println("bytes written", n)
}
log.Println(r.Method, r.URL.Path, "write total", total)
})
http.ListenAndServe(":8080", nil)
}
$ go run main.go
2023/04/26 12:24:40 GET /
2023/04/26 12:24:40 write deadline: 2023-04-26 12:24:43.303884739 -0700 PDT m=+11.5891
16304
2023/04/26 12:24:41 bytes written 1
2023/04/26 12:24:42 bytes written 1
2023/04/26 12:24:43 bytes written 1
2023/04/26 12:24:44 bytes written 1
2023/04/26 12:24:45 bytes written 1
2023/04/26 12:24:46 bytes written 1
2023/04/26 12:24:46 GET / write total 6
$ curl http://localhost:8080/
curl: (52) Empty reply from server
英文:
I have some code where I'm trying to cancel writing back to the client if writing takes too long. I thought I could use http.ResponseController for this, since it has a SetWriteDeadline method.
Unfortunately, in the code below, rw.Write
doesn't return an error after the deadline has passed.
Is there another way I can cancel the write?
package main
import (
"log"
"net/http"
"time"
)
func main() {
http.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) {
log.Println(r.Method, r.URL.Path)
rc := http.NewResponseController(rw)
dl := time.Now().Add(3 * time.Second)
if err := rc.SetWriteDeadline(dl); err != nil {
log.Println(err)
return
}
log.Println("write deadline:", dl)
var total int
for _, b := range []byte("What!\n") {
time.Sleep(time.Second)
n, err := rw.Write([]byte{b})
if err != nil {
log.Println(err)
return
}
total += n
log.Println("bytes written", n)
}
log.Println(r.Method, r.URL.Path, "write total", total)
})
http.ListenAndServe(":8080", nil)
}
$ go run main.go
2023/04/26 12:24:40 GET /
2023/04/26 12:24:40 write deadline: 2023-04-26 12:24:43.303884739 -0700 PDT m=+11.5891
16304
2023/04/26 12:24:41 bytes written 1
2023/04/26 12:24:42 bytes written 1
2023/04/26 12:24:43 bytes written 1
2023/04/26 12:24:44 bytes written 1
2023/04/26 12:24:45 bytes written 1
2023/04/26 12:24:46 bytes written 1
2023/04/26 12:24:46 GET / write total 6
$ curl http://localhost:8080/
curl: (52) Empty reply from server
答案1
得分: 0
> SetWriteDeadline设置写入响应的截止时间。在超过截止时间后,对响应主体的写入将不会阻塞,但如果数据已被缓冲,则可能成功。
问题中应用程序写入响应写入器的数据量很小,因此写入成功是因为数据被缓冲。
curl显示的“服务器返回的空响应”错误表明,当服务器在处理程序返回时将响应刷新到底层网络连接时,截止时间得到了遵守。
使用SetWriteDeadline来防止慢客户端。
使用带有截止时间的上下文来限制服务器生成响应的时间:
⋮
dl := time.Now().Add(3 * time.Second)
if err := rc.SetWriteDeadline(dl); err != nil {
log.Println(err)
return
}
// 当截止时间到达或服务器检测到客户端已断开连接时,此上下文将被取消。
ctx, cancel := context.WithDeadline(r.Context(), dl)
defer cancel()
var total int
for _, b := range []byte("What!\n") {
// 检查上下文是否被取消。在循环中使用Err(),如本示例所示,或在select中使用<-ctx.Done()。
if err := ctx.Err(); err != nil {
log.Println(err)
return
}
⋮
英文:
The documentation for SetWriteDeadline says:
> SetWriteDeadline sets the deadline for writing the response. Writes to the response body after the deadline has been exceeded will not block, but may succeed if the data has been buffered.
The writes to the response writer succeed in the question because the small amount of data written by the application is buffered.
The "Empty reply from server" error from curl shows that the deadline is honored when the server flushes the response to the underlying network connection on return from the handler.
Use SetWriteDeadline to protect against a slow client.
Use a context with deadline to limit the time used by the server to generate the response:
⋮
dl := time.Now().Add(3 * time.Second)
if err := rc.SetWriteDeadline(dl); err != nil {
log.Println(err)
return
}
// This context is canceled when the deadline has
// passed or the server detects that the client is gone.
ctx, cancel := context.WithDeadline(r.Context(), dl)
defer cancel()
var total int
for _, b := range []byte("What!\n") {
// Check context cancelation. Use Err() if in a loop
// as in this example or <-ctx.Done() when in a select.
if err := ctx.Err(); err != nil {
log.Println(err)
return
}
⋮
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论