BeWithYou

胡搞的技术博客

  1. 首页
  2. Golang
  3. Golang中HTTP在handler中如何主动响应客户端并断开链接

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` 获取整个连接的掌管权限。 如下例所示: ```go 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行` ```go 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) } }() ```
回到顶部