當前位置: 妍妍網 > 碼農

告別等待!HTTP分塊Chunk傳輸讓客戶端響應更迅速數據即時呈現

2024-03-22碼農

0x01 前言

HTTP 分塊傳輸(Chunked Transfer Encoding)是一種 HTTP 協定在數據傳輸時的編碼格式,它允許將數據分成若幹個塊進行傳輸。每個傳輸的塊都包含大小資訊和實際的數據內容。讓伺服器發送大型檔或流數據時不必一開始就發送全部內容,而是可以分成一塊一塊的數據來發送。這樣可以節省頻寬和記憶體,特別是對於需要長時間連線的情況。

分塊傳輸編碼

HTTP/1.1200 OK
Content-Type: text/plain
Transfer-Encoding: chunked
25\r\n
This is the data in the first chunk.
\r\n
This is the second chunk in the response.
\r\n
The final chunk of data
\r\n
0\r\n
\r\n

在這個例子中,伺服器使用 Transfer-Encoding: chunked 頭部來告知客戶端它將使用分塊傳輸編碼。響應的主體被分成多個塊,每個塊前面都有一個十六進制的數位標記其大小,緊跟著是十六進制的換行符 \r\n 。最後一個塊的大小為 0 ,表示數據已經結束,隨後的 \r\n 是HTTP響應的最後的空行。

HTTP 分塊傳輸常用於在不知道響應內容長度情況下傳輸數據。例如,當伺服器需要生成大量數據或數據需要動態生成時,它可以使用 HTTP 分塊傳輸來在響應正在生成時向客戶端傳輸數據。

HTTP 分塊傳輸中,每個塊使用十六進制數表示的大小資訊開頭,並以 '\r\n' 換行符結束。該大小資訊表示塊包含的字節數。塊的實際數據由該大小資訊所指定的數量的字節組成,並以 '\r\n' 結束。最後一個塊的大小資訊為 0 ,後面跟著一個空白的 '\r\n' ,表示所有的塊都已傳輸完成。

HTTP 分塊傳輸不僅適用於響應內容的傳輸,還可以用於請求數據的發送,在滲透的過程中,當我們遇到網站存在 waf 的情況,我們就可以利用 HTTP 分塊傳輸來繞過 waf 的檢測。

0x02 傳輸格式

HTTP分塊傳輸是HTTP協定的一種數據編碼方式,其傳輸格式如下:

chunk-size[chunk-extension]CRLF
chunk-dataCRLF
...
chunk-size[chunk-extension]CRLF
chunk-dataCRLF
0CRLF
footer1-name:footer1-valueCRLF
footer2-name:footer2-valueCRLF
CRLF

其中, chunk-size 表示當前塊的字節數,並以十六進制數位表示, chunk-extension 可選,表示當前塊的擴充套件資訊,使用分號和值進行分隔。 CRLF 表示回車換行符。 chunk-data 表示當前塊的數據內容。所有塊的數據內容累加起來就是完整的實體正文。

最後一個長度為 0 的塊表示傳輸結束。

在傳輸過程中,每個塊都需要以 chunk-size 開始並加上字尾 CRLF ,接下來是 chunk-data ,最後也要以 CRLF 結束。

如果有多個塊,則依次傳輸,每個塊之間也要用 CRLF 分隔。

在所有塊發送完成後,可以選擇添加一個或多個實體報頭欄位,這些欄位被稱為報尾或尾部( footers )。報尾同樣需要以 CRLF 結束。

總之, HTTP 分塊傳輸用於在 HTTP 協定中動態傳輸數據,其傳輸格式是由塊大小和塊數據組成,可以在數據生成過程中逐步傳輸數據,提高效率和安全性。

0x03 套用案例

客戶端和伺服端在進行 HTTP 分塊傳輸時,需要註意以下幾點:

  1. 客戶端需要在請求頭部添加 Transfer-Encoding: chunked ,告知伺服端使用分塊傳輸方式。

  2. 伺服端需要在響應頭部添加 Transfer-Encoding: chunked ,告知客戶端使用分塊傳輸方式。

  3. 伺服端需要將所有數據按照塊的格式進行封裝並行送給客戶端。

伺服端

伺服端使用 workerman/http-client 實作。 workerman/http-client 是一個異步http客戶端元件。所有請求響應異步非阻塞,內建連線池,訊息請求和響應符合PSR7規範。

使用 Moonshot 提供基於 HTTP 的 API 服務接入。【更多了解 】

安裝

composer require workerman/http-client

發送HTTP分塊Chunk數據

<?php
/**
 * @desc HTTP分塊Chunk傳輸響應給客戶端
 * @author Tinywan(ShaoBo Wan)
 * @date 2024/3/21 22:53
 */
declare(strict_types=1);
require_once __DIR__. '/../vendor/autoload.php';
use Workerman\Connection\TcpConnection;
use Workerman\Http\Client;
use Workerman\Protocols\Http\Chunk;
use Workerman\Protocols\Http\Request;
use Workerman\Protocols\Http\Response;
use Workerman\Worker;
$worker = new Worker('http://0.0.0.0:8782/');
$worker->onMessage = function (TcpConnection $connection, Request $request) {
$http = new Client();
$http->request('https://api.moonshot.cn/v1/chat/completions', [
'method' => 'POST',
'data' => json_encode([
'model' => 'moonshot-v1-8k',
'stream' => true,
'messages' => [['role' => 'user''content' => 'PHP語言實作一個氣泡排序演算法']],
]),
'headers' => [
'Content-Type' => 'application/json',
'Authorization' => 'Bearer sk-xxxxxxxxxxxxxxxx',
],
'progress' => function ($buffer) use ($connection) {
// 返回空的chunk代表響應結束
$connection->send(new Chunk($buffer));
},
'success' => function ($response) use ($connection) {
// 返回空的chunk代表響應結束
$connection->send(new Chunk(''));
},
]);
$response = new Response(200, [
'Transfer-Encoding' => 'chunked',
], '');
// 設定跨域問題
$response->header('Access-Control-Allow-Origin''*');
$connection->send($response);
};
Worker::runAll();

執行服務

啟動workerman提供服務

# php chunk.php start
Workerman[chunk.php] start in DEBUG mode
------------------------------------------- WORKERMAN --------------------------------------------
Workerman version:4.1.15 PHP version:7.4.27 Event-Loop:\Workerman\Events\Event
-------------------------------------------- WORKERS ---------------------------------------------
proto user worker listen processes status
tcp root none http://0.0.0.0:8782/ 1 [OK] 
--------------------------------------------------------------------------------------------------
Press Ctrl+C to stop. Start success.

服務地址: http://127.0.0.1:8782

客戶端

<!DOCTYPE html>
<html>
<head>
<metacharset="UTF-8">
<title>開源技術小棧 Stream Chunked HTTP Data</title>
</head>
<body>
<h1>HTTP分塊Chunk傳輸響應給客戶端</h1>
<divid="data-container"></div>
<script>
// 定義接收數據的URL
const url = 'http://127.0.0.1:8782/';
// 發起請求並處理分塊數據
functionfetchChunkedData() {
fetch(url, {
method'GET',
headers: {
'Accept''text/plain; chunks=true'// 某些伺服器可能需要這個頭來明確請求分塊數據
}
})
.then(response => {
if (!response.ok) {
thrownewError('Network response was not ok ' + response.statusText);
}
return response.body;
})
.then(body => {
// 將ReadableStream轉換為閱讀器
const reader = body.getReader();
processChunks(reader);
})
.catch(error => {
console.error('There has been a problem with your fetch operation:', error);
});
}
// 處理分塊數據
functionprocessChunks(reader{
// 讀取數據塊
reader.read().then(({done, value}) => {
if (done) {
console.log('Stream complete');
return;
}
// 將Uint8Array轉換為字串
const chunk = new TextDecoder('utf-8').decode(value);
// 將數據塊顯示在頁面上
const dataContainer = document.getElementById('data-container');
dataContainer.insertAdjacentHTML('beforeend', chunk);
// 遞迴讀取下一個數據塊
processChunks(reader);
});
}
// 呼叫函式開始獲取數據
fetchChunkedData();



</script>
</body>
</html>

效果

伺服端輸出

客戶端輸出