You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Golang net的HttpServer读取连接时提前收到EOF问题

问题描述

代码实现

HTTP请求读取逻辑

func (r RequestReader) ReadHttpRequest(reader io.Reader) (string, error) {
    var request []byte
    var buffer = make([]byte, 1024)

    for {
        nRead, err := reader.Read(buffer)
        if err == io.EOF {
            if len(request) > 0 {
                break
            } else {
                return "", errors.New("end of file got before content")
            }
        }
        if err != nil {
            return "", errors.New("Reading request failed")
        }

        request = append(request, buffer[:nRead]...)

        if detectEndOfHttpRequest(buffer[:nRead]) {
            break
        }
    }

    return string(request), nil
}

func detectEndOfHttpRequest(buffer []byte) bool {
    return strings.IndexAny(string(buffer), "\r\n\r\n") != -1
}

连接处理协程

func SpawnHandler(readChannel <-chan net.Conn, routeDispatcher *routing.RouteDispatcher) {
    for {
        select {
        case conn := <-readChannel:
            reader := requestReader.RequestReader{}
            request, err := reader.ReadHttpRequest(conn)
            if err != nil {
                fmt.Printf("Could not read request, error was %s, closing connection...", err)
                conn.Close()
                continue
            }

            // 处理请求逻辑
        }
    }
}

服务器主逻辑

func (h HttpServer) Serve(host, path string) {
    connection, err := net.Listen("tcp", "0.0.0.0:8000")
    if err != nil { /* 错误处理 */ }

    fmt.Println("Accepting connections..")
    for true {
        clientConnection, err := connection.Accept()
        if err != nil { /* 错误处理 */ }

        select {
        case h.connectionsQueue <- clientConnection:
        default:
            // 返回429 TOO MANY REQUESTS响应
        }
    }
}

测试现象

使用hey工具测试,命令:

hey -n 1000 -c 40 -q 100 -m GET http://localhost:8000/ -disable-keepalive
  • 并发40个请求发送者处理1000个请求时,服务器日志至少出现20次:

    Could not read request, error was end of file got before content,

  • 单并发(-c=1)时无此问题。
  • 额外现象:尽管服务器关闭了这些无内容的连接且未返回响应,hey仍显示所有1000个请求均成功(状态码200)。

疑问

为何无长连接的并发场景下,RequestReader会在读取到实际请求内容前就收到EOF?是代码存在bug,还是hey会提前关闭连接,或是客户端写入前读取就可能收到EOF?


问题分析与解决

核心原因

  1. 连接读取时机与客户端主动关闭:并发量高时,服务器工作协程可能在客户端还未发送任何请求数据时就开始读取连接,此时客户端可能因连接调度、网络延迟等原因主动关闭了空连接,导致服务器读取到io.EOF且无请求内容。
  2. 请求结束检测的逻辑缺陷:detectEndOfHttpRequest中使用strings.IndexAny会匹配\r或\n任意单个字符,而非完整的"\r\n\r\n"分隔符,这是潜在bug,但不是本次EOF问题的直接原因。
  3. hey的响应判定逻辑:无长连接模式下,客户端会以服务器关闭连接作为响应结束信号,即使服务器未返回数据,hey也可能误判请求成功。

修复方案

  1. 调整EOF错误处理:
    客户端未发送数据就关闭连接属于正常终止场景,无需报错,直接返回空结果即可:

    if err == io.EOF {
        if len(request) > 0 {
            break
        } else {
            return "", nil
        }
    }
    
  2. 修复请求结束检测:
    用strings.Contains替代strings.IndexAny,确保检测完整的请求结束分隔符,同时还要考虑跨缓冲区的情况(比如分隔符拆分在两个缓冲区):

    func detectEndOfHttpRequest(buffer []byte) bool {
        return strings.Contains(string(buffer), "\r\n\r\n")
    }
    
  3. 完善429响应逻辑:
    队列满时要向客户端发送完整的429响应后再关闭连接,避免客户端异常:

    default:
        _, err := clientConnection.Write([]byte("HTTP/1.1 429 Too Many Requests\r\nContent-Length: 0\r\n\r\n"))
        if err != nil {
            // 错误处理
        }
        clientConnection.Close()
    }
    

内容的提问来源于stack exchange,提问作者Miguel Pais

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 12:55:38