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 获取整个连接的掌管权限。

如下例所示:

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)
    }
}()
回到顶部