:用 stream::Get 與 open_stream 實現(xiàn)逐塊讀取、SSE 與反向代理)
后端網(wǎng)絡(luò)【免費下載鏈接】cpp-httplibA C header-only HTTP/HTTPS server and client library項目地址https://gitcode.com/GitHub_Trending/cp/cpp-httplib點擊查看免費下載本篇文章以 cpp-httplib 官方文檔 README-stream.md 為核心系統(tǒng)講解該庫新增的Streaming API基于迭代器的stream::Get()/stream::Result高層接口以及直接操作 socket 的open_stream()/StreamHandle底層接口。讀完本文你將掌握如何用極低內(nèi)存代價逐塊消費 HTTP 響應(yīng)體落地 LLM 流式輸出、SSEServer-Sent Events、大文件下載與反向代理等真實場景并理解這些 API 在 httplib.h 中的底層實現(xiàn)原理。一、什么是 cpp-httplib 流式 API傳統(tǒng)上Client::Get()會等待整個響應(yīng)體完整接收并緩沖到內(nèi)存中這對小響應(yīng)和 Keep-Alive 連接復(fù)用很友好但遇到超大文件或無限流式輸出如大模型逐字生成時內(nèi)存與首字節(jié)延遲都不可接受。cpp-httplib 為此提供了流式擴展數(shù)據(jù)直接從網(wǎng)絡(luò) socket 讀取一次只保留當(dāng)前一個數(shù)據(jù)塊在內(nèi)存中配合迭代器風(fēng)格的循環(huán)逐塊處理實現(xiàn)真正的 socket 級流式true socket-level streaming。流式 API 特別適用于以下幾類場景LLM / AI 流式響應(yīng)如 ChatGPT、Claude、Ollama 等以 JSON Lines 逐行輸出的接口Server-Sent EventsSSE實時推送大文件下載及下載進度跟蹤反向代理實現(xiàn)把上游響應(yīng)體原樣轉(zhuǎn)發(fā)給下游。使用前請先記住三條重要約束無 Keep-Alive每次stream::Get()都會使用一條專用連接響應(yīng)體讀完后連接即關(guān)閉需要連接復(fù)用時請改用Client::Get()。只能迭代一次next()方法只能從頭到尾遍歷響應(yīng)體一遍。Result 非線程安全stream::Get()可以在多個線程同時被調(diào)用但返回的stream::Result只能由單個線程使用。二、Quick Start第一個流式客戶端在項目目錄中引入頭文件后即可用與Client::Get()幾乎相同的寫法發(fā)起流式請求#include httplib.h int main() { httplib::Client cli(http://localhost:8080); // Get streaming response auto result httplib::stream::Get(cli, /stream); if (result) { // Process response body in chunks while (result.next()) { std::cout.write(result.data(), result.size()); } } return 0; }核心循環(huán)是while (result.next())next()每次從 socket 讀入一塊數(shù)據(jù)并返回true讀到流結(jié)束返回falseresult.data()指向當(dāng)前塊起始位置result.size()給出當(dāng)前塊字節(jié)數(shù)。整個過程中內(nèi)存中只保留一個塊配合std::cout.write()即可把響應(yīng)原樣輸出。需要說明的是stream::Get只是open_stream(GET, ...)的便捷封裝源碼見 httplib.h默認塊大小chunk_size 8192字節(jié)。除了 GETstream命名空間還提供Post/Put/Patch/Delete/Head/Options等對應(yīng)方法均支持傳Headers、Params與請求體見 httplib.h。三、API 分層從高層到低層四層接口cpp-httplib 針對不同使用訴求提供了四層 API從上到下抽象層級遞減、控制粒度遞增┌─────────────────────────────────────────────┐ │ SSEClient │ ← SSE-specific, parsed events │ - on_message(), on_event() │ │ - Auto-reconnect, Last-Event-ID │ ├─────────────────────────────────────────────┤ │ stream::Get() / stream::Result │ ← Iterator-based streaming │ - while (result.next()) { ... } │ ├─────────────────────────────────────────────┤ │ open_stream() / StreamHandle │ ← General-purpose streaming │ - handle.read(buf, len) │ ├─────────────────────────────────────────────┤ │ Client::Get() │ ← Traditional, full buffering └─────────────────────────────────────────────┘選擇建議如下使用場景推薦 API需要自動重連的 SSESSEClient見 README-sse.mdLLM 流式輸出JSON Linesstream::Get()大文件下載stream::Get()或open_stream()反向代理open_stream()小響應(yīng) Keep-AliveClient::Get()其中SSEClient命名空間sse實現(xiàn)于 httplib.h 起的SSEMessage/SSEClient會把流按 SSE 規(guī)范解析成帶event/data/id字段的消息對象并內(nèi)置自動重連與Last-Event-ID續(xù)傳邏輯是 SSE 場景下的最高層選擇。四、低層 API 參考StreamHandle 與 open_streamStreamHandle是流式 API 的底層句柄接管 socket 連接的所有權(quán)數(shù)據(jù)直接從網(wǎng)絡(luò)讀取。聲明位于 httplib.h。// Open a stream (takes ownership of socket) httplib::Client cli(http://localhost:8080); auto handle cli.open_stream(GET, /path); // Check validity if (handle.is_valid()) { // Access response headers immediately int status handle.response-status; auto content_type handle.response-get_header_value(Content-Type); // Read body incrementally char buf[4096]; ssize_t n; while ((n handle.read(buf, sizeof(buf))) 0) { process(buf, n); } }注意使用open_stream()時連接專用于流式傳輸不支持 Keep-Alive需要連接復(fù)用的場景請改用client.Get()。StreamHandle 成員一覽成員類型說明responsestd::unique_ptrResponse含響應(yīng)頭的 HTTP 響應(yīng)對象errorError請求失敗時的錯誤碼is_valid()bool響應(yīng)有效時返回 trueread(buf, len)ssize_t直接從 socket 讀取最多l(xiāng)en字節(jié)get_read_error()Error獲取最近一次讀錯誤has_read_error()bool檢查是否發(fā)生讀錯誤open_stream 的底層實現(xiàn)細節(jié)從 httplib.h 的ClientImpl::open_stream()實現(xiàn)可以看到它做了完整的事前準(zhǔn)備目標(biāo)編碼與緩沖發(fā)送路徑保持一致空Params時直接用path否則調(diào)用append_query_params()追加查詢參數(shù)再經(jīng)detail::encode_request_target()編碼保證無論走哪個 API同樣的path產(chǎn)生同樣的請求行連接準(zhǔn)備在socket_mutex_保護下檢查現(xiàn)有 socket 是否存活is_socket_aliveSSL 下還會檢查對端是否關(guān)閉失效則斷開重建并通過setup_proxy_connection()處理代理所有權(quán)轉(zhuǎn)移transfer_socket_ownership_to_handle()httplib.h把 socket 描述符及 SSL 會話從Client移交給StreamHandle客戶端自身的socket_置為INVALID_SOCKET從此連接生命周期完全由句柄掌控請求寫出先在內(nèi)存BufferStream中組裝請求行與請求頭write_request_linecheck_and_write_headers校驗通過后再一次性寫入網(wǎng)絡(luò)隨后寫入請求體若有避免被拒絕的頭污染線上數(shù)據(jù)響應(yīng)解析讀取響應(yīng)行與響應(yīng)頭并做與普通路徑相同的 framing 檢查HEAD、204、304 可合法攜帶無體 framing 頭Content-Length沖突視為Error::Read傳輸語義識別根據(jù)響應(yīng)頭設(shè)置BodyReader的content_length/chunked標(biāo)志is_chunked_transfer_encoding()判定分塊傳輸并對Content-Encoding創(chuàng)建對應(yīng)的解壓器見下節(jié)。StreamHandle::read()在存在解壓器時走read_with_decompression()路徑httplib.h否則直接調(diào)用detail::read_body_content()按 Content-Length / chunked 語義讀取當(dāng)分塊流讀完后還會解析 trailerparse_trailers_if_needed()httplib.h。測試 test/test.cc 中的StreamHandleTest驗證了is_valid()對response與error的組合判定邏輯。五、高層 API 參考stream::Get() 與 stream::Resultstream::Result是對StreamHandle的迭代器式封裝聲明于 httplib.h實現(xiàn)于 httplib.h使用起來更符合直覺#include httplib.h httplib::Client cli(http://localhost:8080); cli.set_follow_location(true); // ... // Simple GET auto result httplib::stream::Get(cli, /path); // GET with custom headers httplib::Headers headers {{Authorization, Bearer token}}; auto result httplib::stream::Get(cli, /path, headers); // Process the response if (result) { while (result.next()) { process(result.data(), result.size()); } } // Or read entire body at once auto result2 httplib::stream::Get(cli, /path); if (result2) { std::string body result2.read_all(); }stream::Result 成員一覽成員類型說明operator bool()bool響應(yīng)有效時返回 trueis_valid()bool與operator bool()等價status()intHTTP 狀態(tài)碼headers()const Headers響應(yīng)頭get_header_value(key, def)std::string獲取響應(yīng)頭值可帶默認值has_header(key)bool檢查響應(yīng)頭是否存在next()bool讀取下一塊數(shù)據(jù)讀完返回 falsedata()const char*當(dāng)前塊數(shù)據(jù)指針size()size_t當(dāng)前塊大小read_all()std::string把剩余響應(yīng)體全部讀入字符串error()Error獲取連接/請求錯誤read_error()Error獲取最近一次讀錯誤has_read_error()bool檢查是否發(fā)生讀錯誤實現(xiàn)細節(jié)上next()httplib.h在句柄無效或已結(jié)束時直接返回false否則確保內(nèi)部緩沖區(qū)不小于chunk_size_后調(diào)用handle_.read()n 0則更新current_size_并返回true讀到 0 或負值時置finished_ true并返回false。read_all()不過是反復(fù)next()并把每塊append到字符串——這解釋了只能迭代一次的約束一旦讀完finished_即被置位。此外stream::Result是**僅移動move-only**類型拷貝構(gòu)造與拷貝賦值被刪除移動構(gòu)造/賦值可用httplib.h因此把它放入 lambda 捕獲列表或返回時需使用std::move。六、實戰(zhàn)示例示例 1SSEServer-Sent Events客戶端用stream::Get()逐塊讀取并實時輸出事件流每讀一塊立即flush#include httplib.h #include iostream int main() { httplib::Client cli(http://localhost:1234); auto result httplib::stream::Get(cli, /events); if (!result) { return 1; } while (result.next()) { std::cout.write(result.data(), result.size()); std::cout.flush(); } return 0; }需要自動重連、事件解析與Last-Event-ID續(xù)傳的完整 SSE 客戶端可參考倉庫示例 example/ssecli-stream.cc它在主循環(huán)中不斷調(diào)用httplib::stream::Get(cli, path, headers)連接失敗或讀錯誤后按retry_ms間隔重連并通過parse_sse_line()按 SSE 規(guī)范把緩沖內(nèi)容切分成event/data/id/retry字段遇到空行即完成一個事件對 204/404/401/403 等永久性錯誤則直接退出不再重連。它還會校驗Content-Type是否為text/event-stream并在流結(jié)束后用result.read_error()區(qū)分正常結(jié)束與連接中斷。示例 2LLM 流式響應(yīng)以本地 Ollama 服務(wù)的/api/generate為例逐塊接收模型生成內(nèi)容并在讀取結(jié)束后檢查錯誤#include httplib.h #include iostream int main() { httplib::Client cli(http://localhost:11434); // Ollama auto result httplib::stream::Get(cli, /api/generate); if (result result.status() 200) { while (result.next()) { std::cout.write(result.data(), result.size()); std::cout.flush(); } } // Check for connection errors if (result.read_error() ! httplib::Error::Success) { std::cerr Connection lost\n; } return 0; }result.status()在響應(yīng)頭到達后即可獲得無需等待整個響應(yīng)體配合read_error()可區(qū)分流正常結(jié)束與中途斷開兩種收尾形態(tài)。示例 3大文件下載與進度顯示邊讀邊寫磁盤并用累計字節(jié)數(shù)輸出進度#include httplib.h #include fstream #include iostream int main() { httplib::Client cli(http://example.com); auto result httplib::stream::Get(cli, /large-file.zip); if (!result || result.status() ! 200) { std::cerr Download failed\n; return 1; } std::ofstream file(download.zip, std::ios::binary); size_t total 0; while (result.next()) { file.write(result.data(), result.size()); total result.size(); std::cout \rDownloaded: (total / 1024) KB std::flush; } std::cout \nComplete!\n; return 0; }由于每個塊處理完即被覆蓋即使文件達數(shù) GB內(nèi)存占用也穩(wěn)定在一個塊默認 8 KB附近。倉庫測試 test/test.cc 中的PostLarge用例即用stream::Post讀取 100 KB 響應(yīng)并斷言累計字節(jié)數(shù)精確等于100 * 1024驗證了塊拼接的完整性。示例 4反向代理流式轉(zhuǎn)發(fā)服務(wù)端 handler 中打開到上游的流把狀態(tài)碼、響應(yīng)頭與響應(yīng)體原樣轉(zhuǎn)發(fā)給下游客戶端#include httplib.h httplib::Server svr; svr.Get(/proxy/(.*), [](const httplib::Request req, httplib::Response res) { httplib::Client upstream(http://backend:8080); auto handle upstream.open_stream(/ req.matches[1].str()); if (!handle.is_valid()) { res.status 502; return; } res.status handle.response-status; res.set_chunked_content_provider( handle.response-get_header_value(Content-Type), handle std::move(handle) mutable { char buf[8192]; auto n handle.read(buf, sizeof(buf)); if (n 0) { sink.write(buf, static_castsize_t(n)); return true; } sink.done(); return true; } ); }); svr.listen(0.0.0.0, 3000);關(guān)鍵在于handle std::move(handle)把StreamHandle移動進set_chunked_content_provider的 lambda使 socket 生命周期覆蓋整個響應(yīng)發(fā)送過程每次回調(diào)從上游讀一塊、經(jīng)sink.write()寫到下游直到read()返回非正值時調(diào)用sink.done()收尾。這正是反向代理場景讀到什么轉(zhuǎn)發(fā)什么的典型實現(xiàn)。七、與既有 API 的對比特性Client::Get()open_stream()stream::Get()響應(yīng)頭可用時機完整接收后立即可用立即可用響應(yīng)體讀取方式一次性整體緩沖直接從 socket 讀取迭代器式讀取內(nèi)存占用整個響應(yīng)體在內(nèi)存極小可控極小可控Keep-Alive 支持? 支持? 不支持? 不支持壓縮處理自動處理自動處理自動處理最適合場景小響應(yīng)、連接復(fù)用底層流式控制便捷流式讀取八、流式 API 的特性清單真正的 socket 級流式數(shù)據(jù)直接從網(wǎng)絡(luò) socket 讀取不經(jīng)整包緩沖低內(nèi)存占用任意時刻內(nèi)存中只有當(dāng)前一個數(shù)據(jù)塊壓縮支持gzip、brotli、zstd 自動解壓分塊傳輸完整支持 chunked transfer encoding含 trailer 解析SSL/TLS 支持HTTPS 連接同樣可用。關(guān)于壓縮與解壓從源碼看open_stream()讀取響應(yīng)頭中的Content-Encoding后調(diào)用detail::create_decompressor()創(chuàng)建解壓器httplib.h若是已知編碼但當(dāng)前構(gòu)建未啟用對應(yīng)后端如未開啟 brotli返回Error::UnsupportedContentEncoding未知編碼則原樣透傳。解壓路徑read_with_decompression()內(nèi)部使用 8192 字節(jié)的壓縮緩沖kDecompressionBufferSize分塊喂給解壓器并受payload_max_length即客戶端的set_payload_max_length約束防止解壓炸彈。分塊傳輸側(cè)detail::ChunkedDecoderhttplib.h按 RFC 9112 解析 chunk-size、chunk-ext 與 trailer并對畸形分塊返回讀取錯誤。測試 test/test.cc 覆蓋了 open_stream 對 gzip 解壓、未知編碼、默認請求頭Host / User-Agent / Accept-Encoding、POST 的 Content-Type 行為、大響應(yīng)與分塊響應(yīng)讀取等場景test/test.cc 則驗證了 brotli 壓縮內(nèi)容經(jīng)open_stream自動解壓后內(nèi)容一致。StreamApiTestfixturetest/test.cc為所有stream::*測試統(tǒng)一起了一個本地 HTTP 服務(wù)覆蓋 Get/Post/Put/Patch 的基本讀寫、查詢參數(shù)、自定義頭與 404 狀態(tài)碼等斷言是理解 API 行為最直觀的參照。九、Keep-Alive 行為與選型提醒流式 APIstream::Get()/open_stream()在流的整個生命周期內(nèi)接管 socket 所有權(quán)這意味著流式連接不支持 Keep-AliveStreamHandle析構(gòu)時 socket 隨即關(guān)閉StreamHandle的connection_與socket_stream_均由其獨占持有見 httplib.h需要連接復(fù)用的場景請使用標(biāo)準(zhǔn)client.Get()API。// Use for streaming (no Keep-Alive) auto result httplib::stream::Get(cli, /large-stream); while (result.next()) { /* ... */ } // Use for Keep-Alive connections auto res cli.Get(/api/data); // Connection can be reused選型時把握一條主線小響應(yīng)、高頻短請求選Client::Get()享受連接復(fù)用大響應(yīng)、實時流、逐塊處理選stream::Get()需要直接控制 socket 讀取粒度、做代理轉(zhuǎn)發(fā)選open_stream()純 SSE 且有重連訴求直接選SSEClient。十、相關(guān)資源流式 API 是 cpp-httplib 近期新增能力需求源頭對應(yīng)上游 issue #2269原始功能請求本倉庫中的落地實現(xiàn)集中在 httplib.h 的stream命名空間與ClientImpl::open_stream。SSE 高層客戶端文檔見 README-sse.md實現(xiàn)位于 httplib.h。帶自動重連的流式 SSE 客戶端示例example/ssecli-stream.cc。流式 API 的完整測試test/test.ccopen_stream()測試與 test/test.ccstream::*測試。編譯運行本倉庫示例時直接#include httplib.h即可header-only無需鏈接額外庫example/目錄下提供Makefile與各示例源碼可參照構(gòu)建。贊分享后端網(wǎng)絡(luò)【免費下載鏈接】cpp-httplibA C header-only HTTP/HTTPS server and client library項目地址https://gitcode.com/GitHub_Trending/cp/cpp-httplib點擊查看免費下載相關(guān)推薦實時數(shù)據(jù)推送新范式cpp-httplib實現(xiàn)Server-Sent Events(SSE)全指南實時數(shù)據(jù)推送新范式cpp httplib實現(xiàn)Server Sent Events SSE 全指南 你是否還在為Web應(yīng)用的實時數(shù)據(jù)更新煩惱傳統(tǒng)輪詢效率低下后端網(wǎng)絡(luò)cpp-httplib HTTP重定向處理301、302與最佳實踐cpp httplib HTTP重定向處理301、302與最佳實踐 你是否在C HTTP服務(wù)開發(fā)中遇到過重定向邏輯混亂、客戶端兼容性問題或性能瓶頸作為c后端網(wǎng)絡(luò)3行代碼搞定cpp-httplib響應(yīng)頭獲取實戰(zhàn)指南3行代碼搞定cpp httplib響應(yīng)頭獲取實戰(zhàn)指南 你是否還在為C HTTP客戶端獲取響應(yīng)頭信息而煩惱本文將用最簡潔的方式帶你掌握cpp httpl后端網(wǎng)絡(luò)上一篇Liquidsoap與FFmpeg集成指南解鎖高級媒體處理與HLS流媒體能力下一篇CrowdSec 威脅情報共享協(xié)議比較TAXII vs STIX vs MISP創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考