当前位置: 首页 > news >正文

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.dtcms.com/a/271832.html

相关文章:

  • 微信小程序控制空调之EMQX服务器安装与配置
  • 重新配置电脑中的环境变量
  • SpringBoot ThreadLocal 全局动态变量设置
  • 机器学习11——支持向量机上
  • 初学者对编译和链接的学习笔记(含预编译详解)
  • 广告匹配策略的智能化之路:人工智能大模型的方法和步骤
  • 多模态大语言模型arxiv论文略读(156)
  • vivo Pulsar 万亿级消息处理实践(3)-KoP指标异常修复
  • 快速上手MongoDB与.NET/C#整合
  • 【AI大模型】LLM模型架构深度解析:BERT vs. GPT vs. T5
  • searxng 对接openweb-UI实现大模型通过国内搜索引擎在线搜索
  • 搜索引擎vs向量数据库:LangChain混合检索架构实战解析
  • 计算机视觉 之 数字图像处理基础
  • 基于 SpringBoot + Vue 的 IT 技术交流和分享平台的设计与实现
  • TCP-与-UDP-协议详解:原理、区别与应用场景全解析
  • 北斗舞动在线监测装置:电力安全的“智慧守护者”
  • SpringMVC @ExceptionHandler 典型用法
  • 了解去中心化金融在现代经济中的作用——安全交易新时代
  • 编写bat文件自动打开chrome浏览器,并通过selenium抓取浏览器操作chrome
  • 双指针-18.四数之和-力扣(LeetCode)
  • linux系统---ISCSI存储服务
  • Language Models are Few-Shot Learners: 开箱即用的GPT-3(二)
  • 节点小宝:手机图片备份至电脑功能实测体验
  • 同一类型,每条数据,执行不同逻辑
  • 偏振相机,偏振图像是怎么样的
  • WebGPU了解
  • 智能体决策机制深度剖析:ReAct、Plan-and-Execute与自适应策略
  • 云蝠智能VoiceAgent重构企业电话客服体系
  • PLC框架-1.3.2 报文750控制汇川伺服的转矩上下限
  • 【前缀和 BFS 并集查找】P3127 [USACO15OPEN] Trapped in the Haybales G|省选-