LiteHub之文件下載與視頻播放

文件下載

前端請求

箭頭函數

//這個箭頭函數可以形象理解為,x流入(=>)x*x,
//自然而然=>前面的就是傳入參數,=>表示函數體
x => x * x//相當于
function (x) {return x * x;
}//如果參數不是一個,就需要用括號()括起來:
(x, y) => x * x + y * y

本項目的請求下載前端代碼為:

 function downloadFile(resourceId, filename, progressBar, statusText) {fetch('/resource/download', {method: 'POST',headers: { 'Content-Type': 'application/json' },body: JSON.stringify({ resourceId }) //通過post方式將要下載的文件路徑發送給后端}).then(response => {if (!response.ok) {throw new Error('下載失敗');}const contentLength = response.headers.get('Content-Length');const total = contentLength ? parseInt(contentLength, 10) : 0;//返回內容長度const reader = response.body.getReader(); //這個可以逐塊提供bodyconst chunks = [];let received = 0;const pump = () => reader.read().then(({ done, value }) => {if (done) {//如果讀取完成,整個文件已下載const blob = new Blob(chunks);//將所有小段chunks轉換成一個完成的blob(binary large object)const url = window.URL.createObjectURL(blob);//瀏覽器創建一個臨時的URL地址來獲取這個數據//如blob:http://localhost/17dfc4b1-df34-4a93-a6a7-6df9f1e85e0cconst a = document.createElement('a');a.href = url;a.download = filename;document.body.appendChild(a);a.click();//模擬點擊瀏覽器的下載行為document.body.removeChild(a);window.URL.revokeObjectURL(url);//避免內存泄露progressBar.style.width = '100%';statusText.textContent = '下載完成';return;}chunks.push(value);received += value.length;//更新下載進度if (total > 0) {const percent = Math.floor((received / total) * 100);progressBar.style.width = percent + '%';progressBar.textContent = percent + '%';statusText.textContent = `下載中 ${percent}%`;} else {statusText.textContent = `下載中(未知大小)`;}//遞歸調用 pump(繼續讀取下一段)return pump();});return pump();}).catch(error => {console.error('下載出錯:', error);progressBar.style.backgroundColor = 'red';statusText.textContent = '下載失敗';});}//類比
// 后端:用水龍頭一點點把水流出來
// 前端:接水并灌到瓶子里(Blob)
// createObjectURL:給這瓶水貼個標簽(blob URL)
// 點擊下載:把瓶子交給你下載
// revokeObjectURL:把標簽撕掉,清理內存

對于pump函數的理解,結合箭頭函數和promise

  1. reader.read()
    ○ 返回一個 Promise<{ done: boolean, value: Uint8Array }>。
    ○ done: true 表示讀取完了;
    ○ value 是當前讀取的一段數據(Uint8Array 格式)。
  2. 箭頭函數 () => reader.read().then(…)
    ○ 這是一個返回 Promise 的函數。
    ○ done: true 表示讀取完了;
    ○ value 是當前讀取的一段數據(Uint8Array 格式)。
  3. 箭頭函數 () => reader.read().then(({ done, value }) => { return dump()}
    ■ ()=>reader.read(),無參數傳入,執行reader.read(),返回reader.read()執行的結果{done,value}。
    ■ .then({ done, value })通過上一步接收這兩個數據,然后通過這兩個執行相應內容;
    ■ 如果done為false,表示還沒執行完成,chunks.push(value):把這一段加入緩存 ,更新進度條, 遞歸調用自身,繼續下一段讀取 (return pump())。

后端響應

 FileUtil file(filePath); 
if (!file.isValid()) //判斷請求的文件是否有效
{LOG_WARN << filePath << "not exist.";resp->setStatusLine(req.getVersion(), http::HttpResponse::k404NotFound, "Not Found");resp->setContentType("text/plain");std::string resp_info="File not found";resp->setContentLength(resp_info.size());resp->setBody(resp_info);
}
//設置相應頭
resp->setStatusLine(req.getVersion(), http::HttpResponse::k200Ok, "OK");
resp->setCloseConnection(false);
resp->setContentType("application/octet-stream");std::string filename = std::filesystem::path(filePath).filename().string();
LOG_INFO<<"filename:"<<filename;
resp->addHeader("Content-Disposition", "attachment; filename=\"" + filename + "\"");
//設置響應格式為文件類型,并添加文件的路徑
resp->setContentLength(file.size());
resp->setisFileResponse(filePath);

設計亮點

HttpResponse.h頭文件中

public:bool isFileResponse() const {return isFileResponse_;}std::string getFilePath() {return filePath_;}void setisFileResponse(const std::string& path){isFileResponse_ = true;filePath_ = path;}
private:
bool                               isFileResponse_; //判斷是否是文件,如果是,采用流式發送
std::string                        filePath_;

在httpserver的請求函數中判斷,如果是文件類型,就調用tcpconnection先將響應頭發送出去,然后將消息體分小塊發送,這里設置的是8kb;如果不是文件類型,直接將整個響應發送出去
HttpServer::onRequest函數中

// 給response設置一個成員,判斷是否請求的是文件,如果是文件設置為true,并且存在文件位置在這里send出去。
if (!response.isFileResponse())
{	//不是文件類型muduo::net::Buffer buf;response.appendToBuffer(&buf);conn->send(&buf);
}
else
{// 1. 構造響應頭muduo::net::Buffer headerBuf;response.appendToBuffer(&headerBuf);  // 只添加狀態行和頭部,不包含 bodyconn->send(&headerBuf);  // 先發 header// 2. 發送文件內容(分塊)const std::string filePath = response.getFilePath();std::ifstream file(filePath, std::ios::binary);// 以二進制模式打開文件if (file) {const size_t bufferSize = 8192; 			// 8KB 緩沖區char buffer[bufferSize];                  // 棧上分配緩沖區while (file) {                            // 循環直到文件讀取結束或出錯file.read(buffer, bufferSize);        // 讀取最多 bufferSize 字節到 bufferstd::streamsize bytesRead = file.gcount(); // 實際讀取的字節數if (bytesRead > 0) {conn->send(muduo::StringPiece(buffer, bytesRead));// 發送數據塊}}} else {// 文件打不開,補償錯誤提示muduo::net::Buffer errBuf;errBuf.append("HTTP/1.1 500 Internal Server Error\r\n\r\nFile open failed");conn->send(&errBuf);}
}

之所以是在httpserver上分塊發送數據流,是為了保證代碼較好的層次性,httpserver負責管理多個tcp連接,包括發送消息和接收消息等。

視頻播放

      // 從請求中獲取 Range 頭,例如 "bytes=1000-2000"std::string rangeHeader = req.getHeader("Range");LOG_INFO << "Range Header: " << rangeHeader;// 默認起始字節 start=0,結束字節 end=文件大小-1,表示完整文件std::streamsize start = 0, end = fileSize - 1;// 標記是否是分塊響應bool isPartial = false;if (!rangeHeader.empty()) {// 如果客戶端帶了 Range,則標記為分塊傳輸isPartial = true;long s = 0, e = -1;// 使用 sscanf 解析格式 bytes=<start>-<end>// 注意:用戶可能只寫了起始,沒有寫結束,所以要判斷 sscanf 返回值int n = sscanf(rangeHeader.c_str(), "bytes=%ld-%ld", &s, &e);start = s;if (n == 1 || e == -1) {// 如果只解析到 1 個數,或者結束為 -1,則表示讀到文件末尾end = fileSize - 1;} else {// 解析到兩個數,且結束不能超過文件大小end = std::min((std::streamsize)e, fileSize - 1);}// 合法性檢查:start 必須小于等于 end 且小于文件大小if (start > end || start >= fileSize) {// 如果不合法,返回 416 狀態碼(Requested Range Not Satisfiable)resp->setStatusLine(req.getVersion(), http::HttpResponse::k416RequestedRangeNotSatisfiable, "Requested Range Not Satisfiable");char rangeValue[64];// Content-Range 必須帶 "*/總大小"snprintf(rangeValue, sizeof(rangeValue), "bytes */%ld", fileSize);resp->addHeader("Content-Range", rangeValue);resp->setCloseConnection(true);resp->setContentType("text/plain");resp->setBody("Invalid Range");return;}}// 計算需要讀取的 chunkSizestd::streamsize chunkSize = end - start + 1;std::vector<char> buffer(chunkSize);// 如果需要分塊,最好這里限制一下 chunkSize,防止內存過大// 定位到要讀的起始位置file.seekg(start, std::ios::beg);// 從文件讀出 chunkSize 大小的數據到 bufferfile.read(buffer.data(), chunkSize);// === 構造響應 ===if (isPartial) {resp->setStatusLine(req.getVersion(), http::HttpResponse::k206PartialContent, "Partial Content");char rangeHeaderValue[128];snprintf(rangeHeaderValue, sizeof(rangeHeaderValue),"bytes %ld-%ld/%ld", start, end, fileSize);resp->addHeader("Content-Range", rangeHeaderValue);} else {resp->setStatusLine(req.getVersion(), http::HttpResponse::k200Ok, "OK");}resp->addHeader("Accept-Ranges", "bytes");// 無論是否分塊,都要告知支持分塊resp->setContentType("video/mp4");         // 設置內容類型為 mp4 視頻resp->setContentLength(buffer.size());     // 設置 Content-Lengthresp->setBody(std::string(buffer.begin(), buffer.end()));  // 把讀取的文件塊設置到響應體}

后端涉及對請求體中的range字段進行解析,判斷range字段的合法性,隨后根據range字段請求內容決定是返回部分內容還是全部內容。
請求所有內容:
在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述
依次拖動播放進度條,range字段發生改變,格式為–字段,這里是請求從某一時刻到視頻結束。
請求部分內容:
在這里插入圖片描述
這里請求的是從字節6000-18000大小的數據,返回的響應為
在這里插入圖片描述
這里的響應頭字段為206 partial content,表示響應返回的只是視頻的一部分數據。


range的合法性校驗
這里我手動指定range的范圍為6000-18000000000000,實際是超出了請求視頻的最大范圍,看看最后返回的什么。使用curl(這里因為是測試,所以去掉了權限的判定,實際上運行的時候使用curl是不可行的)
在這里插入圖片描述
可以看到這里返回的是文件的最大大小。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/88350.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/88350.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/88350.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

QT5使用cmakelists引入Qt5Xlsx庫并使用

1、首先需要已經有了Qt5Xlsx的頭文件和庫&#xff0c;并拷貝到程序exe路徑下&#xff08;以xxx.exe/3rdparty/qtxlsx路徑為例&#xff0c;Qt5Xlsx版本為0.3.0&#xff09;&#xff1b; 2、cmakelist中&#xff1a; # 設置 QtXlsx 路徑 set(QTXLSX_ROOT_DIR ${CMAKE_CURRENT_SOU…

醋酸鐠:閃亮的稀土寶藏,掀開科技應用新篇章

一、什么是醋酸鐠醋酸鐠是一種鐠的有機鹽&#xff0c;鐠是稀土金屬元素之一。作為一種重要的稀土化合物&#xff0c;醋酸鐠通常以水合物的形式存在&#xff0c;呈現淡黃色或無色結晶。鐠元素本身因其獨特的物理化學特性&#xff0c;在工業和科技領域有著廣泛應用&#xff0c;而…

深入解析JVM內存結構與垃圾回收機制

java是強類型高級語言JVM&#xff08;Java Virtual Machine&#xff0c;Java虛擬機&#xff09;是Java平臺的核心組件&#xff0c;它是一個虛擬的計算機&#xff0c;能夠執行Java字節碼&#xff08;bytecode&#xff09;。1、區域劃分JVM對Java內存的管理也是分區分塊進行&…

Java 流程控制詳解:從順序執行到跳轉語句,掌握程序邏輯設計

作為一名Java開發工程師&#xff0c;你一定知道&#xff0c;流程控制&#xff08;Flow Control&#xff09; 是編寫任何程序的核心。它決定了代碼的執行路徑、分支走向和循環次數。本文將帶你系統梳理 Java中的所有常用流程控制結構&#xff0c;包括&#xff1a;順序結構分支結…

面試150 環形鏈表

思路 采用雙指針法,slow指針每次走一步,fast指針每次走兩步&#xff0c;如果相遇的情況下&#xff0c;slow指針回到開始的位置,此時快慢指針各走一步&#xff0c;當相遇的時候也就是說明鏈表中有環。 # Definition for singly-linked list. # class ListNode: # def __init…

AI技術正在深度重構全球產業格局,其影響已超越工具屬性,演變為推動行業變革的核心引擎。

一、AI如何重塑AI的工作與行業&#xff08;AI助手領域&#xff09;能力升級理解與生成&#xff1a;基于LLM&#xff08;大語言模型&#xff09;&#xff0c;AI能處理開放式問題、撰寫報告、翻譯代碼&#xff0c;替代部分人類知識工作。個性化交互&#xff1a;通過用戶歷史對話分…

Kafka的無消息丟失配置怎么實現

那 Kafka 到底在什么情況下才能保證消息不丟失呢&#xff1f; Kafka 只對“已提交”的消息&#xff08;committed message&#xff09;做有限度的持久化保證。 第一個核心要素是“已提交的消息”。什么是已提交的消息&#xff1f;當 Kafka 的若干個 Broker 成 功地接收到一條…

集成CommitLInt+ESLint+Prettier+StyleLint+LintStaged

代碼可讀性低代碼 代碼規范落地難代碼格式難統一代碼質量低下 配置 ESLint ESLint 是一個用來識別 ECMAScript 并且按照規則給出報告的代碼檢測工具&#xff0c;使用它可以避免低級錯誤和統一代碼的風格。它擁有以下功能&#xff1a; 查出 JavaScript 代碼語法問題。根據配置…

尋找兩個正序數組的中位數(C++)

給定兩個大小分別為 m 和 n 的正序&#xff08;從小到大&#xff09;數組 nums1 和 nums2。請你找出并返回這兩個正序數組的 中位數 。算法的時間復雜度應該為 O(log (mn)) 。示例 1&#xff1a;輸入&#xff1a;nums1 [1,3], nums2 [2] 輸出&#xff1a;2.00000 解釋&#x…

Expected Sarsa 算法的數學原理

&#x1f31f; 一、Expected Sarsa 算法的數學原理 1. 什么是 Expected Sarsa&#xff1f; Expected Sarsa 是一種基于 時序差分&#xff08;Temporal Difference, TD&#xff09;學習 的強化學習算法&#xff0c;用于估計 動作值函數 ( q_{\pi}(s, a) )。它是 Sarsa 算法的一種…

Vue的watch和React的useEffect

參考文章&#xff1a;https://zhuanlan.zhihu.com/p/686329898

idea中合并git分支

1.把本地dev代碼合并到本地master代碼在提交代碼之前&#xff0c;先確保dev和master都拉取了最新的代碼都進行了Git->pull了這時候確保Local的第一個分支是master分支&#xff0c;然后選擇dev分支 ,鼠標右鍵-》Merge dev into master這時候會提示 有合并到本地master最新的代…

《Spring 中上下文傳遞的那些事兒》Part 7:異步任務上下文丟失問題詳解

&#x1f4dd; Part 7&#xff1a;異步任務上下文丟失問題詳解 在現代 Java 應用中&#xff0c;異步編程已經成為提升性能、解耦業務邏輯的重要手段。無論是使用 CompletableFuture、線程池&#xff08;ExecutorService&#xff09;、定時任務&#xff08;ScheduledExecutorSe…

大語言模型驅動智能語音應答:技術演進與架構革新

在智能客服、電話銀行等場景中&#xff0c;用戶時常遇到這樣的困境&#xff1a;“請描述您的問題...抱歉沒聽清&#xff0c;請重試...正在為您轉接人工”。傳統語音應答&#xff08;IVR&#xff09;系統受限于規則引擎與淺層語義理解&#xff0c;難以應對復雜多變的自然語言表達…

【Linux】內存管理

要求&#xff1a;1、編寫程序&#xff0c;實現如下功能。&#xff08;1&#xff09;隨機生成 1000000 個 0~1 之間的數&#xff1b;&#xff08;2&#xff09;統計分析這些數據&#xff0c;計算均值、方差和分布情況&#xff0c;分布情況按0.01 的步長進行統計&#xff1b;&…

蒼穹外賣—day1

文章目錄前言一、接口文檔導入與生成二、前端環境搭建三、后端環境搭建1. 了解項目結構2. 環境搭建常見問題總結前言 &#xff08;簡要說明筆記的目的&#xff1a;記錄搭建過程、關鍵配置和結構理解&#xff09; 一、接口文檔導入與生成 Apifox 導入 使用工具&#xff1a;https…

基于微信小程序的在線疫苗預約小程序源碼+論文

基于微信小程序的在線疫苗預約系統源碼論文代碼可以查看文章末尾??聯系方式獲取&#xff0c;記得注明來意哦~&#x1f339; 分享萬套開題報告任務書答辯PPT模板 作者完整代碼目錄供你選擇&#xff1a; 《SpringBoot網站項目》800套 《SSM網站項目》1200套 《小程序項目》600套…

Windows 11 安裝過程中跳過微軟賬戶創建本地賬戶

背景 在 Windows 11 的安裝和設置過程中&#xff0c;Microsoft 賬號登錄是默認的認證方式。然而&#xff0c;在某些情況下&#xff0c;可能需要繞過此步驟以創建本地賬戶。 微軟在 2025 年 3 月推送的 Windows 11 預覽版&#xff08;Build 26120.3653 和 Build 26200.5516&am…

利用DBeaver實現異構數據庫數據定時任務同步

1、背景 本需求需要實現抽取KingBaseEs數據庫的某幾張表數據&#xff0c;定時同步到MySQL中 2、工具準備 2.1 DBeaverEE25.1(必須要企業版&#xff0c;如果用社區版沒有定時任務功能) https://dbeaver.io/download/ 2.2 KingBaseEs數據庫及驅動 https://www.kingbase.com…

【TCP/IP】1. 概述

1. 概述1. 概述1.1 因特網及技術催生新時代1.1.1 信息化時代1.1.2 關鍵技術1.1.3 國家戰略1.2 網絡互聯的動機和技術1.2.1 網絡互聯的動機1.2.2 網絡互聯技術1.3 因特網的形成和發展1.3.1 國際因特網發展軌跡1.3.2 中國互聯網發展1.4 有關因特網的組織機構1.5 請求注解&#xf…