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

傅小灰發表於2023-04-23

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作為服務端實現流式響應。

返回文字內容

服務端

[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);
    }
}

客戶端

  1. C# HttpClient
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 bytes = new byte[20];
    int writeLength = 0;
    while ((writeLength = stream.Read(bytes, 0, bytes.Length)) > 0)
    {
    	Console.Write(Encoding.UTF8.GetString(bytes, 0, writeLength));
    }
    Console.WriteLine();
    Console.WriteLine("END");
}

HttpCompletionOption列舉有兩個值,預設情況下使用的是ResponseContentRead

  • ResponseContentRead:等到整個響應完成才完成操作

  • ResponseHeadersRead:一旦獲取到響應頭即完成操作,不用等到整個內容響應

  1. js XMLHttpRequest
<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只會在響應全部完成後觸發。

返回圖片

服務端

[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);
    }
}

ImageResponse

相關文章