ASP.NET Core Web API 流式返回,逐字显示

Websocket、SSE(Server-Sent Events)和长轮询(Long Polling)都是用于网页和服务端通信的技术。

Websocket是一种全双工通信协议,能够实现客户端和服务端之间的实时通信。它基于TCP协议,并且允许服务器主动向客户端推送数据,同时也允许客户端向服务器发送数据。

SSE是一种单向通信协议,允许服务器向客户端推送数据,但不支持客户端向服务器发送数据。SSE建立在HTTP协议上,通过在HTTP响应中使用特殊的Content-Type和事件流(event stream)格式来实现。

长轮询是一种技术,客户端向服务器发送一个请求,并且服务器保持连接打开直到有数据可以返回给客户端。如果在指定的时间内没有数据可用,则服务器会关闭连接,客户端需要重新建立连接并再次发起请求。

New Bing聊天页面是通过WebSocket进行通信。

Open AI的ChatGPT接口则是通过SSE协议由服务端推送数据

事实上,以上几种方式包括长轮询,都可以实现逐字显示的效果。那还有没有其他的办法可以实现这种效果了呢?

流式响应

当客户端返回流的时候,客户端可以实时捕获到返回的信息,并不需要等全部Response结束了再处理。

下面就用ASP.NET Core Web API作为服务端实现流式响应。

返回文本内容

服务端

csharp 复制代码
[HttpPost("text")]
public async Task Post()
{
    string filePath = "文档.txt";
    Response.ContentType = "application/octet-stream";
    var reader = new StreamReader(filePath);
    var buffer = new Memory<char>(new char[5]);
    int writeLength = 0;
    //每次读取5个字符写入到流中
    while ((writeLength = await reader.ReadBlockAsync(buffer)) > 0)
    {
        if (writeLength < buffer.Length)
        {
        	buffer = buffer[..writeLength];
        }
        await Response.WriteAsync(buffer.ToString());
        await Task.Delay(100);
    }
}

客户端

  • C# HttpClient
csharp 复制代码
public async void GetText()
{
    var url = "http://localhost:5000/config/text";
    var client = new HttpClient();
    using HttpRequestMessage httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, url);
    var response = await client.SendAsync(httpRequestMessage, HttpCompletionOption.ResponseHeadersRead);
    await using var stream = await response.Content.ReadAsStreamAsync();
    var streamReader = new StreamReader(stream);
    var buffer = new Memory<char>(new char[5]);
    int writeLength = 0;
    while ((writeLength = await streamReader.ReadBlockAsync(buffer)) > 0)
    {
        if (writeLength < buffer.Length)
        {
            buffer = buffer[..writeLength];
        }
        Console.Write(buffer.ToString());
    }
    Console.WriteLine();
    Console.WriteLine("END");
}

HttpCompletionOption枚举有两个值,默认情况下使用的是ResponseContentRead

ResponseContentRead:等到整个响应完成才完成操作

ResponseHeadersRead:一旦获取到响应头即完成操作,不用等到整个内容响应

js XMLHttpRequest

csharp 复制代码
<script>
    var div = document.getElementById("content")
    var url = "http://localhost:5000/config/text"
    var client = new XMLHttpRequest()
    client.open("POST", url)
    client.onprogress = function (progressEvent) {
        div.innerText = progressEvent.target.responseText
    }
    client.onloadend = function (progressEvent) {
        div.append("END")
    }
    client.send()

</script>

用axios请求就是监听onDownloadProgress了。

浏览器是通过Response
Header中的Content-Type来解析服务端响应体的。如果后端接口没有设置Response.ContentType =
"application/octet-stream",onprogress只会在响应全部完成后触发。

返回图片

服务端

csharp 复制代码
[HttpGet("img")]
public async Task Stream()
{
    string filePath = "pixelcity.png";
    new FileExtensionContentTypeProvider().TryGetContentType(filePath, out string contentType);
    Response.ContentType = contentType ?? "application/octet-stream";
    var fileStream = System.IO.File.OpenRead(filePath);
    var bytes = new byte[1024];
    int writeLength = 0;
    while ((writeLength = fileStream.Read(bytes, 0, bytes.Length)) > 0)
    {
        await Response.Body.WriteAsync(bytes, 0, writeLength);
        await Task.Delay(100);
    }
}
相关推荐
To_OC5 小时前
从一头雾水到跑通全流程:我用一个周末啃透了JWT登录鉴权
前端·后端·http
前端兰博6 小时前
05-Redis
redis·后端
databook7 小时前
从手动检查到自动监控:一个数据质量工作流的实现
后端·python·数据分析
【JAVA】玩家7 小时前
Spring核心原理全解析:从零到生产实战
java·后端·spring
excel7 小时前
研究 Vue 3 源码的收获
前端·vue.js
东风破_7 小时前
从 Neo4j 到 GraphRAG:用 Text-to-Cypher 构建图检索 RAG
人工智能·后端
可乐鸡翅yeah_9 小时前
业务中 M3U8 水印相关坑,硬水印和动态水印区别
前端·网络·数据库·ffmpeg·m3u8在线
lerhxx9 小时前
AI 应用如何高效优雅地恢复中断?—— "连接解耦 + 状态持久化"
前端·javascript
计算机魔术师9 小时前
METR 演示 AI 智能体如何篡改 Inspect 评估记录以掩盖不当行为
前端
llqbzllll9 小时前
Spring AI 工具调用不是反射一下就结束:用 2.0.1 跑通失败恢复与调用上限
人工智能·后端