Golang中HTTP在handler中如何主动响应客户端并断开链接
有一些场景里,我们在响应完客户端请求后还需要同步处理一些业务。在PHP中我们可以使用 fastcgi_finish_request()
来把 response 都 flush 到客户端后主动断开连接,然后继续处理业务。那么在Golang中如何实现呢?
当然我们可以选择用goroutine来跑剩下的业务,那么如果限定必须在请求中同步处理呢?
思路一样,都是主动 flush 掉数据,然后断开连接。但是使用Golang实现HTTP服务端时,请求处理函数一般都是 func (w http.ResponseWriter, r *http.Request)
的形式,注意入参是 http.ResponseWriter
,所以我们不能直接在 handler 中将 Respnse
断开掉。这时候需要用到劫持 http.Hijacker
方法来让 HTTP handler
获取整个连接的掌管权限。
如下例所示:
package main
import (
"fmt"
"net/http"
"time"
)
func testHandler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Step 1\n")) // 浏览器不会立即输出Step1
time.Sleep(5 * time.Second) // 转圈5秒
w.Write([]byte("Step 2")) // Step1和Step2同时输出
}
func testFlushHandler(w http.ResponseWriter, r *http.Request) {
b := "Step 1. 立即响应client, handler继续处理"
w.Header().Set("Content-Length", fmt.Sprintf("%d", len(b))) // 要设置Content-Length否则客户端可能会认为返回的数据不完整
w.Write([]byte(b))
flushBody(w) // 立即flush并断开连接
time.Sleep(5 * time.Second)
fmt.Println("Do something...") // 之后处理自己的业务
}
func flushBody(w http.ResponseWriter) bool {
if f, ok := w.(http.Flusher); ok {
f.Flush()
if hj, ok := w.(http.Hijacker); ok { // 从ResponseWriter获取链接控制权
if conn, _, err := hj.Hijack(); err == nil {
if err := conn.Close(); err == nil {
return true
}
}
}
}
return false
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/test", testHandler) // 测试不立即响应
mux.HandleFunc("/flush", testFlushHandler) // 测试立即响应后同步处理业务
http.ListenAndServe(":10088", mux)
}
http.ResponseWriter
实现了 http.Flusher
接口,可以讲缓存区先 Flush()
到客户端中。然后劫持整个连接,将其断开。
注意最好设置一下头部 Content-Length
, 否则某些客户端会认为响应的数据不完整。
常规流程里,链接的断开是交给底层HTTP库来操作的。可以参看net.http.server.go 1750行
defer func() {
if err := recover(); err != nil && err != ErrAbortHandler {
const size = 64 << 10
buf := make([]byte, size)
buf = buf[:runtime.Stack(buf, false)]
c.server.logf("http: panic serving %v: %v\n%s", c.remoteAddr, err, buf)
}
// 如果没有被劫持,则在此关闭连接
if !c.hijacked() {
c.close()
c.setState(c.rwc, StateClosed)
}
}()