gogf / gf

GoFrame is a modular, powerful, high-performance and enterprise-class application development framework of Golang.
https://goframe.org
MIT License
11.79k stars 1.61k forks source link

net/ghttp: stream content might be splitted #3584

Open lcm1475 opened 6 months ago

lcm1475 commented 6 months ago

Go version

go version go1.21.5 darwin/arm64

GoFrame version

v2.6.1

Can this bug be reproduced with the latest release?

Option Yes

What did you do?

gf的流式输出在移动端会断开把一次的数据截断成2次发送,同样的代码放到gin就没问题 而且这个情况web看不出来,打印输出到web页面上是没问题的,移动端才能复现这个问题,Android,ios,Flutter,这些移动端必现,但是Android,ios,Flutter这些接入官方的openai,bing,文心,或者gin都是正常的,只有gf会断开, 把数据缩短好像断开的情况就很少了,但是实际业务中没办法缩短数据,一串json不可能分开发送 a3346610e45fc4df8ccc102090602e40 05a6970542c3396007cac39c59bfb9c5

What did you see happen?

数据会有断开的情况

What did you expect to see?

希望不要断开

lcm1475 commented 6 months ago
func (p *TestController) GetStreamTestV2(r *ghttp.Request) {
    r.Response.Header().Set("Content-Type", "text/event-stream")
    r.Response.Header().Set("Transfer-Encoding", "chunked")
    r.Response.Header().Set("Cache-Control", "no-cache")
    r.Response.Header().Set("Connection", "keep-alive")
    r.Response.Header().Set("X-Accel-Buffering", "no")

    for i := 0; i < 10; i++ {
        time.Sleep(500 * time.Millisecond)
        r.Response.Write("{\"choices\":[{\"message\":{\"content\":\"我是一条示例的数据\"}}],\"req_id\":\"202405161153160000621695B542BC2D9E\"}")
        r.Response.Flush()
    }

    return
}
lcm1475 commented 6 months ago

下面是gin的例子 同样输出的内容 gin不会断开

func main() {
    r := gin.Default()
    r.GET("/ping", func(c *gin.Context) {
        atomic.AddInt32(&GlobalVarDemo, 1)
        c.JSON(200, gin.H{
            "gggg": GlobalVarDemo,
        })
    })

    r.GET("/testStream", func(ctx *gin.Context) {
        w := ctx.Writer
        ctx.Writer.Header().Set("Content-Type", "text/event-stream")
        ctx.Writer.Header().Set("Transfer-Encoding", "chunked")
        ctx.Writer.Header().Set("Cache-Control", "no-cache")
        ctx.Writer.Header().Set("Connection", "keep-alive")
        ctx.Writer.Header().Set("X-Accel-Buffering", "no")
        for i := 0; i < 10; i++ {
            time.Sleep(500 * time.Millisecond)
            _, _ = fmt.Fprint(w, "data: %s\n\n", "{\"choices\":[{\"message\":{\"content\":\"我是一条示例的数据\"}}],\"req_id\":\"202405161153160000621695B542BC2D9E\"}")
            w.Flush()
        }
    })
    http.ListenAndServe(":8081", r)
    //r.Run()
}
hamster1963 commented 6 months ago
// 发送数据
request.Response.Writefln("data: " + gjson.New(sseData).String() + "\n")
request.Response.Flush()

试试用Writefln,在使用中还没有出现过截断

Issues-translate-bot commented 6 months ago

Bot detected the issue body's language is not English, translate it automatically. 👯👭🏻🧑‍🤝‍🧑👫🧑🏿‍🤝‍🧑🏻👩🏾‍🤝‍👨🏿👬🏿


//Send data request.Response.Writefln("data: " + gjson.New(sseData).String() + "\n") request.Response.Flush() Try using Writefln, no truncation has occurred during use.

lcm1475 commented 6 months ago

// 发送数据 request.Response.Writefln("data: " + gjson.New(sseData).String() + "\n") request.Response.Flush() 试试用Writefln,在使用中还没有出现过截断

试过了 不行 截断的情况只有在移动端才能复现

Issues-translate-bot commented 6 months ago

Bot detected the issue body's language is not English, translate it automatically. 👯👭🏻🧑‍🤝‍🧑👫🧑🏿‍🤝‍🧑🏻👩🏾‍🤝‍👨🏿👬🏿


// Send data request.Response.Writefln("data: " + gjson.New(sseData).String() + "\n") request.Response.Flush() Try using Writefln, it is not in use yet Truncation occurred

I tried it, but it didn’t work. The truncation situation can only be reproduced on the mobile terminal.

gqcn commented 2 months ago

@lcm1475 请尝试使用文档的v2.4以下版本的方式看是否复现:https://goframe.org/pages/viewpage.action?pageId=84084732

package main

import (
    "fmt"
    "net/http"
    "time"

    "github.com/gogf/gf/v2/frame/g"
    "github.com/gogf/gf/v2/net/ghttp"
)

func main() {
    s := g.Server()
    s.BindHandler("/", func(r *ghttp.Request) {
        rw := r.Response.RawWriter()
        flusher, ok := rw.(http.Flusher)
        if !ok {
            http.Error(rw, "Streaming unsupported!", http.StatusInternalServerError)
            return
        }
        r.Response.Header().Set("Content-Type", "text/event-stream")
        r.Response.Header().Set("Cache-Control", "no-cache")
        r.Response.Header().Set("Connection", "keep-alive")

        for i := 0; i < 100; i++ {
            _, err := fmt.Fprintf(rw, "data: %d\n", i)
            if err != nil {
                panic(err)
            }
            flusher.Flush()
            time.Sleep(time.Millisecond * 200)
        }
    })
    s.SetPort(8999)
    s.Run()
}