整体回顾
服务端的目标是:
- 对客户端的请求进行处理
- 管理客户端上传的文件
于客户端进行数据交换,我们需要引入网络,所以我们引入第三方库----httplib.h库,搭建服务端。在进行网络通讯时,数据需要被系列化和反序列化,否则有数据丢失的风险,还需引入json库
在管理文件时,需要进行热点管理,把非热点文件进行压缩也需要引入第三方库 ----bundle.h,对文件进行压缩。
因此我们可以给服务端的各种功能实现划分模块,逐步实现服务端的整体功能。
要对上传的文件的文件进行管理,我们需要---- 文件管理模块,
同时,在将文件管理后,我们需要对其进行更一步的热带管理模块,热点管理的实现是在文件管理模块的实现之上的。
在本地测试好了上述两个模块后,我们可以进行网络通讯了。
需要一个网络通讯模块,通过其搭建我们的服务端,
能进行网络通讯后,还需要一个业务处理模块,处理客户端发送过来的请求,并予以响应。
逐步实现
先创建一个库,util.hpp工具库文件
里面有我们自己实现的几个util工具类。
先创建一个库,util.hpp工具库文件
里面有我们自己实现的几个util工具类。
工欲善其事,必先利其器。
utill.hpp
class FileUtil{}
创建一个FileUtil 类,文件工具类,对系统的文件接口进行封装,便于我们对文件快捷操作,提供对文件的增删查改
class FileUtil{ private: std::string _name; public: FileUtil(const std::string &name); size_t FileSize();// 文件大小 time_t LastATime(); // 最后一次查看文件时间 time_t LastMTime(); // 最后一次修改文件的时间 std::string FileName(); //文件名字 bool GetPosLen(std::string *content, size_t pos, size_t len); //获取文件流pos 后len个长度的数据 bool GetContent(std::string *content); //获取文件内容 bool SetContent(std::strint *content); //写入文件 bool Compress(const std::string &packname); //压缩文件 bool UnCompress(const std::string &filename); //解压文件 bool Exists(); //判断文件是否存在 bool CreateDirectory(); //创建一个目录 bool ScanDirectory(std::vector<std::string> *arry); //查看目录下的文件内容 }
- FileUtile 工具提供了对文件的增删查改的功能,
- 也提供了对目录的查看功能,和创建目录的功能
其中,对文件压缩解压缩时,我们需要借用bundle.h库的函数,如何使用bundle库里的函数,在GitHub上有完整的教程。
同时,我们在查看目录时,需要借助filesystem库的使用,但是只有在c++17以上的版本才支持filesystem:
注意:
在Windows下,我们要选择了vs2017以上的版本
在Linux下,我们需要将gcc进行升级,7.3版本
class jsonutil
jsonutil类为网络通讯时的数据提供系列化和反序列化的功能,当然需要引入json库
至此,我们的基础工具已经完善,可以以此为基础,更一步完善服务端的功能。
config.hpp
项目配置信息的管理,启动项目时,会自动从 .conf文件加载项目的配置信息。需要修改部分内容时,不需要在代码上修改,只需要修改配置文件,然后重启服务器即可。
采用json 格式将配置信息存放在Cloud.conf中,当启动服务器时,由服务器从.conf文件中读取关键数据。
Cloud.conf 文件
{
“hot_time” : 30,
“server_port” : 9090,
“server_ip” : “1.1.1.1”,
“url_prefix” : “/download/”,
“arc_suffix” : “.lz”,
“pack_dir” : “./packdir/”,
“back_dir” : “./backdir/”,
“manager_file” : “./back.dat”
}
#define CONFIG_FILE "./cloud.conf" class Config{ private: time_t _hot_time; int _server_port; std::string _server_ip; std::string _url_prefix; std::string _arc_suffix; std::string _pack_dir; std::string _back_dir; std::string _manager_file;//备份信息的配置管理 private: static std::mutex _mutex; static Config *_instance; Config(); public: bool ReadConfig(const std::string &filename); int GetHotTime(); int GetServerPort(); std::string GetServerIp(); std::string GetURLPrefix(); std::string GetArcSuffix(); std::string GetPackDir(); std::string GetBackDir(); std::string GetManagerFile(); public: static Config *GetInstance(); };
且,在实现配置信息类时,我们采取单例模式。
data.hpp
data.hpp是数据管理模块的主要部分。
要管理文件数据,就得先对文件的信息进行组织。
struct BackupInfo{}
typedef struct BackupInfo { bool pack_flag; // 文件是否被压缩的标识 time_t atime; // 最后一次查看时间 time_t mtime; // 最后一次修改时间 size_t fsize; //文件大小 std::string real_path; // 文件在服务器上的真实存储路径 std::string url; // 客户端访问文件时的请求url std::string packpath; // 压缩包存储路径 bool FillBackupInfo(const std::string &realpath){} }BackupInfo;
有了这些数据后,我们能准确的描述一个文件,并可以很好的进行管理。
上传的文件信息都以BackuoInfo的模式,以json的格式存储在backup_file中,当程序启动时,需要去文件加载数据到内存。同时,在新上传文件后,我们也需要将文件数据永久化存储到backup_file中。也需要支持对已经被管理的文件信息的增删查改(我们暂时不支持对信息的删除,现在只涉及最基础的功能实现,更多功能在已经构建好整个框架后会进一步实现)。
整个数据管理模块,也为让上层迅速查找文件的备份信息
class DateManager{}
class DataManager{ private: FileUtil _backup_file; pthread_rwlock_t _rwlock; // 读写锁 std::unordered_map<std::string, BackupInfo> _table; public: DataManager(); bool InitLoad();//初始化程序运行时从文件读取数据 bool Storage(); //每次有信息改变则需要持久化存储一次 bool Insert(const std::string &key, const BackupInfo &val); bool Update(const std::string &key, const BackupInfo &val); bool GetOneByURL(const std::string &key, BackupInfo *info); bool GetOneByRealPath(const std::string &key, BackupInfo *info); bool GetAll(std::vector<BackupInfo> *arry); };
其具体实现内容在项目日志时已经说过,在此不再重复。
注意:
我们是对 _table 加上了rwlock 读写锁,因为这里的并发访问场景更多的是读读,读写场景,能提高服务器运行速度。
同时,加锁的原因是:
在httplib库中,使用了线程池的技术,当服务端accept一个客户端后,会另起一个线程在服务端处理请求,所以, _table属于临界资源,需要加锁保护。
我们的Storage()是覆盖式存储,是将 内存中 _table里的所有数据进行反序列,将 backup_file里的内容进行覆盖。
hot.hpp
热点管理模块就压要简单一点,很大一部分工作在数据管理模块已经完成。
循环遍历目录下的所有文件,然后通过文件最后一次修改时间来判断该文件是否为热点文件,然后压缩至指定目录即可。
extern cloud::DataManager *_data; class HotManager{ private: std::string _back_dir; std::string _pack_dir; std::string _pack_suffix; time_t _hot_time; public: HotManager(); bool HotJudge(const std::string &file); bool RunModule(); };
因为数据管理是要在多个模块中访问的,因此将其作为全局数据定义。
service.hpp
云备份项目中 ,业务处理模块是针对客户端的业务请求进行处理,并最终给与响应。而整个过程中包含以下要实现
的功能:
- 借助网络通信模块httplib库搭建http服务器与客户端进行网络通信
- 针对收到的请求进行对应的业务处理并进行响应(文件上传,列表查看,文件下载(包含断点续传))
响应给客户端的 rsp在之前的项目日志里也有描述。
class Service{ private: int _server_port; std::string _server_ip; std::string _url_prefix; httplib::Server _srv; private: static void Upload(const httplib::Request &req, httplib::Response &rsp); static void List(const httplib::Request &req, httplib::Response &rsp); static void Download(const httplib::Request &req,httplib::Response &rsp); public: Service(); bool RunModule(); }
注意:
业务处理的回调函数没有传入参数的地方(大概是因为,回调函数的模板被固定化了),因此无法直接访问外部的数据管理模块数据,因此将数据管理模块的对象定义为全局数据,在这里声明一下,就可以在任意位置访问了,且回调函数必须为静态函数,(类内函数成员变量会隐藏一个this指针)。
文件上传函数和文件列表查看函数都按照思路来写。
文件下载函数有部分事项需要注意:
1 . 服务端要判断是否需要进行断点重传,判断条件:
有If-Range字段且,这个字段的值与请求文件的最新etag一致则符合断点续传
也就是说需要在客户端请求里有If-Range字段,且在这段时间内,文件的数据内容没有被修改过。
这是Download 函数正常的响应rsp:
HTTP/1.1 200 OK Content-Length: 100000 ETags: "filename-size-mtime一个能够唯一标识文件的数据" Accept-Ranges: bytes
Accept-Ranges报头:
服务器使用 HTTP 响应头 Accept-Ranges 标识自身支持范围请求 (partial
requests)。字段的具体值用于定义范围请求的单位。
当浏览器发现Accept-Ranges头时,可以尝试继续中断了的下载,而不是重新开始。
这是Download执行断点续传的rsp:
HTTP/1.1 206 Partial Content Content-Length: Content-Range: bytes 89-999/100000 Content-Type: application/octet-stream ETag: "inode-size-mtime一个能够唯一标识文件的数据" Accept-Ranges: bytes
httplib内部实现了对于区间请求也就是断点续传请求的处理
只需要我们用户将文件所有数据读取到rsp.body中,它内部会自动根据请求
区间,从body中取出指定区间数据进行响应,并且会自动填充rsp内容。
代码
代码里边会有博主的一些思考和理解,各位大佬见笑了😅
util.hpp
#pragma once #include <iostream> #include <string> #include <fstream> #include <vector> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <memory> #include <experimental/filesystem> #include "bundle.h" #include <jsoncpp/json/json.h> namespace Cloud { namespace fs = std::experimental::filesystem; class FileUtil { private: std::string _Filename; public: FileUtil(std::string fname) : _Filename(fname) { } int64_t Filesize() // 提取文件大小 { struct stat st; if (stat(_Filename.c_str(), &st) < 0) { std::cerr << "get Filesize fail" << std::endl; return -1; } return st.st_size; } std::string Filename() // 提取文件名 { // /a/b/文件名 size_t pos = _Filename.find_last_of("/"); if (pos == std::string::npos) { return _Filename; } return _Filename.substr(pos + 1); } time_t LastMtime() // 提取文件最后一次的修改时间(文件内容) { struct stat st; if (stat(_Filename.c_str(), &st) < 0) { std::cerr << "get File LastMtime fail\n" << std::endl; return -1; } return st.st_mtime; } time_t LastAtime() // 提取文件最后一次的访问时间 { struct stat st; if (stat(_Filename.c_str(), &st) < 0) { std::cerr << "get File LastAtime fail\n" << std::endl; return -1; } return st.st_atime; } time_t LastCtime() // 提取文件最后一次的修改时间(文件内容 || 文件属性) { struct stat st; if (stat(_Filename.c_str(), &st) < 0) { std::cerr << "get File LastCtime fail\n" << std::endl; return -1; } return st.st_ctime; } bool Remove() { remove(_Filename.c_str()); return true; } bool GetPosLen(std::string &body, size_t pos, size_t len) { size_t fsize = this->Filesize(); if (pos + len > fsize) { std::cout << "get file len is error\n"; return false; } std::ifstream ifs; ifs.open(_Filename, std::ios::binary); if (ifs.is_open() == false) { std::cout << "read open file failed!\n"; return false; } ifs.seekg(pos, std::ios::beg); body.resize(len); ifs.read(&body[0], len); if (ifs.good() == false) { std::cout << "get file content failed\n"; ifs.close(); return false; } ifs.close(); return true; } bool GetContent(std::string &body) { size_t fsize = this->Filesize(); return GetPosLen(body, 0, fsize); } bool SetContent(const std::string &body) { std::ofstream ofs; ofs.open(_Filename, std::ios::binary); if (ofs.is_open() == false) { std::cout << "write open file failed!\n"; return false; } ofs.write(&body[0], body.size()); if (ofs.good() == false) { std::cout << "write file content failed!\n"; ofs.close(); return false; } ofs.close(); return true; } bool Compress(const std::string &packname) { // 1. 获取源文件数据 std::string body; if (this->GetContent(body) == false) { std::cout << "compress get file content failed!\n"; return false; } // 2. 对数据进行压缩 std::string packed = bundle::pack(bundle::LZIP, body); // 3. 将压缩的数据存储到压缩包文件中 FileUtil fu(packname); if (fu.SetContent(packed) == false) { std::cout << "compress write packed data failed!\n"; return false; } return true; } bool UnCompress(const std::string &filename) { // 将当前压缩包数据读取出来 std::string body; if (this->GetContent(body) == false) { std::cout << "uncompress get file content failed!\n"; return false; } // 对压缩的数据进行解压缩 std::string unpacked = bundle::unpack(body); // 将解压缩的数据写入到新文件 FileUtil fu(filename); if (fu.SetContent(unpacked) == false) { std::cout << "uncompress write packed data failed!\n"; return false; } return true; } bool Exists() { return fs::exists(_Filename); } bool CreateDirectory() { if (this->Exists()) return true; return fs::create_directories(_Filename); } bool ScanDirectory(std::vector<std::string> &arry) { CreateDirectory(); for (auto &p : fs::directory_iterator(_Filename)) { if (fs::is_directory(p) == true) { continue; } // relative_path 带有路径的文件名 arry.push_back(fs::path(p).relative_path().string()); } return true; } }; class jsonutil { public: static bool Serialize(const Json::Value &root, std::string &str) // 序列化 { Json::StreamWriterBuilder swb; std::unique_ptr<Json::StreamWriter> sw(swb.newStreamWriter()); std::stringstream ss; if (sw->write(root, &ss) != 0) { std::cout << "json write failed!\n"; return false; } str = ss.str(); return true; } static bool UnSerialize(const std::string &str, Json::Value &root) // 反序列化 { Json::CharReaderBuilder crb; std::unique_ptr<Json::CharReader> cr(crb.newCharReader()); std::string err; bool ret = cr->parse(str.c_str(), str.c_str() + str.size(), &root, &err); if (ret == false) { std::cout << "parse error: " << err << std::endl; return false; } return true; } }; }
data.hpp
#pragma once #include <unordered_map> #include <pthread.h> #include <iostream> #include "util.hpp" #include "config.hpp" #include <string> #include <stdio.h> // 服务端要管理文件数据,需要 先描述,在组织, 构建一个文件属性结构体,通过这个结构体来管理所有的文件 namespace Cloud { typedef struct BackupInfo { bool pack_flag; // 文件是否被压缩的标识 time_t atime; // 最后一次查看时间 time_t mtime; // 最后一次修改时间 size_t fsize; std::string real_path; // 文件在服务器上的真实存储路径 std::string url; // 客户端访问文件时的请求url std::string packpath; // 压缩包存储路径 bool FillBackupInfo(const std::string &realpath) { FileUtil ft(realpath); if (ft.Exists() == false) { std::cerr << "fill backupinfo:file not exists" << std::endl; return false; } Config *cf = Config::Getinstance(); pack_flag = false; atime = ft.LastAtime(); mtime = ft.LastMtime(); fsize = ft.Filesize(); real_path = realpath; // ./backdir/a.txt -> /download/a.txt url = cf->GetDownloadPrefix() + ft.Filename(); // ./packdir/a.txt -> ./packdir/a.txt.lz packpath = cf->GetPackDir() + ft.Filename() + cf->GetPackFileSuffix(); return true; } } BackupInfo; class DateManager { private: std::string _backup_file; // 文件的信息都会以json的格式存放在 一个backup文件里 pthread_rwlock_t _rwlock; // 对backup文件会存在并发访问的问题 ------->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ????????????????? 不懂为什么有锁 std::unordered_map<std::string, BackupInfo> _table; // 以hash结构,url 为key ,BackupInfo 为val ,查找迅速 public: // 从文件中读取数据进行初始化 对文件增删查改 对文件的永久化储存 DateManager() { // printf("准备Datemanager初始化\n"); _backup_file = Config::Getinstance()->GetBackupFile(); pthread_rwlock_init(&_rwlock, nullptr); initload(); // std::cout<<"初始化成功"<<std::endl; } bool insert(const BackupInfo &bf) { // ? 我自己的思路 --------- 传入一个 filename ,然后在insert函数中,自己填充BackupInfo数据 ,在插入进_table中 ----》 可以减少上层的工作量 (我觉得) pthread_rwlock_wrlock(&_rwlock); std::string url = bf.url; _table[url] = bf; pthread_rwlock_unlock(&_rwlock); Storage(); return true; } bool update(const BackupInfo &bf) { // ? 问题同 insert函数 pthread_rwlock_wrlock(&_rwlock); std::string url = bf.url; _table[url] = bf; pthread_rwlock_unlock(&_rwlock); Storage(); return true; } bool GetOneByURL(const std::string &url, BackupInfo *info) { pthread_rwlock_rdlock(&_rwlock); auto it = _table.find(url); if (it != _table.end()) { *info = it->second; } else { pthread_rwlock_unlock(&_rwlock); return false; } pthread_rwlock_unlock(&_rwlock); return true; } bool GetOneByRealpath(const std::string &realpath, BackupInfo *info) { // std::cout<<"准备拿锁"<<std::endl; pthread_rwlock_rdlock(&_rwlock); // std::cout<<"拿到锁了"<<std::endl; std::unordered_map<std::string, BackupInfo>::iterator it = _table.begin(); // std::cout<<"找到初始位置了"<<std::endl; for (; it != _table.end(); ++it) { if (it->second.real_path == realpath) { *info = it->second; pthread_rwlock_unlock(&_rwlock); return true; } } pthread_rwlock_unlock(&_rwlock); return false; } void GetAll(std::vector<BackupInfo> *arry) { pthread_rwlock_wrlock(&_rwlock); for (auto it = _table.begin(); it != _table.end(); ++it) { arry->push_back(it->second); } pthread_rwlock_unlock(&_rwlock); } bool Storage() // 每次有信息改变则需要持久化存储一次 { // 1. 获取所有数据 std::vector<BackupInfo> arry; this->GetAll(&arry); // 2. 添加到Json::Value Json::Value root; for (int i = 0; i < arry.size(); i++) { Json::Value item; item["pack_flag"] = arry[i].pack_flag; item["fsize"] = (Json::Int64)arry[i].fsize; item["atime"] = (Json::Int64)arry[i].atime; item["mtime"] = (Json::Int64)arry[i].mtime; item["real_path"] = arry[i].real_path; item["packpath"] = arry[i].packpath; item["url"] = arry[i].url; root.append(item); // 添加数组元素 } // 3. 对Json::Value序列化 std::string body; jsonutil::Serialize(root, body); // 4. 写文件 FileUtil fu(_backup_file); fu.SetContent(body); return true; } bool initload() 初始化程序运行时从文件读取数据 -------> 为什么不从备份目录中提取数据? --- 备份目录下的文件会被压缩至压缩目录 { // 1. 从文件中读取数据 // printf("准备读数据\n"); FileUtil ft(_backup_file); // printf("读数据成功\n"); if (ft.Exists() == false) // 如果文件不存在,说明还没有数据存入数据文件,也就是还没有创建数据文件 { // printf("文件不存在\n"); return true; } std::string str; // printf("准备获得文\n"); ft.GetContent(str); // printf("获得文成功\n"); // 2. 将数据反序列化 // printf("准备序列化\n"); Json::Value root; jsonutil::UnSerialize(str, root); // printf("反序列化成功\n"); // 3. 将数据插入_table // printf("准备插入数据:%d\n",root.size()); for (int i = 0; i < root.size(); i++) { // std::cout<<"开始插入数据"<<std::endl; BackupInfo info; info.pack_flag = root[i]["pack_flag"].asBool(); info.fsize = root[i]["fsize"].asInt64(); info.atime = root[i]["atime"].asInt64(); info.mtime = root[i]["mtime"].asInt64(); info.packpath = root[i]["packpath"].asString(); info.real_path = root[i]["real_path"].asString(); info.url = root[i]["url"].asString(); // std::cout<<"插入:"<<info.url<<std::endl; insert(info); } return true; } ~DateManager() { pthread_rwlock_destroy(&_rwlock); } }; }
hot.hpp
#pragma once #include <unistd.h> #include "data.hpp" #include <iostream> extern Cloud::DateManager *_data; namespace Cloud { class HotManager { private: std::string _back_dir; std::string _pack_dir; std::string _pack_suffix; int _hot_time; public: HotManager() { Config *cng = Config::Getinstance(); _back_dir = cng->GetBackDir(); _pack_dir = cng->GetPackDir(); _pack_suffix = cng->GetPackFileSuffix(); _hot_time = cng->GetHotTime(); FileUtil tmp1(_back_dir); FileUtil tmp2(_pack_dir); tmp1.CreateDirectory(); tmp2.CreateDirectory(); } bool HotJudge(const std::string &filename) // 返回true 说明为非热点文件 { FileUtil fu(filename); time_t curtime = time(NULL); if (curtime - fu.LastAtime() > _hot_time) return true; return false; } void RunModel() // 不断循环检测 back_dir 目录下的文件 ,进行热点管理 { while (true) { // 1. 遍历备份目录,获取所有文件名 FileUtil fu(_back_dir); std::vector<std::string> arry; fu.ScanDirectory(arry); // std::cout<<"准备判断是否为热点文件"<<std::endl;//,没什么问题 // 2. 判断文件是否为热点文件 // std::cout<<arry.size()<<std::endl; for (const auto &it : arry) { // std::cout<<"开始遍历判断是否为热点文件"<<std::endl; // std::cout<<it<<std::endl; if (HotJudge(it) == false) { // std::cout<<"不是热点文件"<<std::endl; continue; } // 获取文件的备份信息 BackupInfo info; // std::cout<<"准备执行HOT里的GetonebyRealpath"<<std::endl; if (_data->GetOneByRealpath(it, &info) == false) { // std::cout<<"准备执行fillBackupInfo"<<std::endl; info.FillBackupInfo(it); } // 3. 对非热点文件进行压缩 FileUtil tmp(it); tmp.Compress(info.packpath); // 4. 删除源文件,修改备份信息 tmp.Remove(); info.pack_flag = true; _data->update(info); } // std::cout<<"准备进入睡眠"<<std::endl; usleep(1000); } } }; }
service.hpp
#pragma once #include <errno.h> #include <string> #include "data.hpp" #include "hot.hpp" #include "httplib.h" // 服务端构建服务器, 为客户端提供 上传文件(upload) 下载文件(get) 文件列表查看()三个req // 并对客户端 进行响应 响应上传成功 响应下载的文件数据 响应一个展示文件备份列表的前端页面 extern Cloud::DateManager *_data; namespace Cloud { std::string totimestring(const time_t &tm) { struct tm *tmp = localtime(&tm); char buffer[1024]; snprintf(buffer, sizeof(buffer), "%d-%d-%d %d:%d:%d", tmp->tm_year + 1900, tmp->tm_mon + 1, tmp->tm_yday, tmp->tm_hour, tmp->tm_min, tmp->tm_sec); return buffer; } class Service { private: std::string _server_ip; uint16_t _server_port; std::string _download_prefix; // 我自己粗略认为这是充当客户端请求下载文件时url中的前一部分 httplib::Server _server; public: Service() { printf("Service 开始初始化\n"); Config *config = Config::Getinstance(); _server_ip = config->GetServerIp(); _server_port = config->GetServerPort(); _download_prefix = config->GetDownloadPrefix(); printf("ser 初始化成功\n"); } void RunModule() { printf("Server RunModulem 开始\n"); _server.Post("/Upload", Upload); _server.Get("/", Showlist); _server.Get("/Showlist", Showlist); // 下载文件需要匹配具体的文件名,需要借用正则表达式 _server.Get(_download_prefix + ".*", Download); printf("server 开始listen\n"); std::cout << _server_ip << "::" << _server_port << std::endl; if (_server.listen(_server_ip.c_str(), _server_port) == false) { std::cout << "listen error" << errno << std::strerror(errno) << std::endl; } } private: static void Upload(const httplib::Request &req, httplib::Response &rsp) // 上传文件数据 { // 1. 对req进行反序列化(httplib已经帮我们做过了) ,提取数据 printf("收到一个upload请求\n"); auto ret = req.has_file("file"); // 判断req请求中是否包含 上传的文件字段 if (ret == false) { rsp.status = 400; return; } // 2. 拿到文件名,拿到文件数据 const auto &file = req.get_file_value("file"); std::string filename = file.filename; std::string filecontent = file.content; // 3. 将其保存至 backdir目录下 , std::string backdir = Config::Getinstance()->GetBackDir(); std::cout << "backfilename:" << backdir + filename << std::endl; FileUtil fu(backdir + filename); fu.SetContent(filecontent); // 修该组织文件备份的管理信息 BackupInfo info; info.FillBackupInfo(backdir + filename); _data->insert(info); // 4. 同时填充rsp rsp.status = 200; printf("upload 完成\n"); return; } // 唯一标识符 filename-filesize-lastmtime static std::string GetETag(const BackupInfo &info) { FileUtil fu(info.real_path); std::string etag = fu.Filename(); etag += "-"; etag += std::to_string(info.fsize); etag += "-"; etag += std::to_string(info.mtime); return etag; } // static void Download(const httplib::Request &req, httplib::Response &rsp) // { // printf("收到一个Download请求\n"); // // 1. 从req中提取url,通过url找到 获取文件备份信息 // BackupInfo info; // _data->GetOneByURL(req.path, &info); // std::cout << req.path << std::endl; // std::cout << info.packpath << ":" << info.real_path << std::endl; // std::cout << info.pack_flag << std::endl; // printf("提取到info信息\n"); // // 3. 判断是否被压缩 // if (info.pack_flag == true) // { // printf("在解压缩文件\n"); // // 4. 如果被压缩,需要进行解压缩,同时修改备份信息 // FileUtil fu(info.packpath); // fu.UnCompress(info.real_path); // fu.Remove(); // info.pack_flag = false; // _data->update(info); // printf("解压缩完成\n"); // } // bool retrans = false; // std::string old_etag; // if (req.has_header("If-Range") == true) // { // old_etag = req.get_header_value("If-Range"); // // 有If-Range字段且,这个字段的值与请求文件的最新etag一致则符合断点续传 // if (old_etag == GetETag(info)) // { // retrans = true; // } // } // printf("retrans:%d\n", retrans); // // 5. 填充rsp 设置响应头部字段: ETag, Accept-Ranges: bytes // printf("填充rsp中\n"); // FileUtil fu(info.real_path); // if (retrans == false) // { // fu.GetContent(rsp.body); // rsp.set_header("Accept-Ranges", "bytes"); // 告诉客户端支持断点重传功能 // rsp.set_header("ETag", GetETag(info)); // etag 是一个标识文件的数据 // rsp.set_header("Content-Type", "application/octet-stream"); // 告诉客户实际返回的内容的内容类型 // rsp.status = 200; // } // else // { // // 需要进行断点续传 // // httplib内部实现了对于区间请求也就是断点续传请求的处理 // // 只需要我们用户将文件所有数据读取到rsp.body中,它内部会自动根据请求 // // 区间,从body中取出指定区间数据进行响应 // // 也就是说,下边的代码可以省略,但是我们需要知道httplib 库给我做了什么工作 // fu.GetContent(rsp.body); // rsp.set_header("Accept-Ranges", "bytes"); // rsp.set_header("ETag", GetETag(info)); // rsp.set_header("Content-Type", "application/octet-stream"); // // rsp.set_header("Content-Range", "bytes start-end/fsize"); // rsp.status = 206; // } // printf("Download 请求完毕\n"); // return; // } static void Download(const httplib::Request &req, httplib::Response &rsp) { // 1. 获取客户端请求的资源路径path req.path // 2. 根据资源路径,获取文件备份信息 printf("收到一个Download请求\n"); BackupInfo info; _data->GetOneByURL(req.path, &info); // 3. 判断文件是否被压缩,如果被压缩,要先解压缩, if (info.pack_flag == true) { FileUtil fu(info.packpath); fu.UnCompress(info.real_path); // 将文件解压到备份目录下 // 4. 删除压缩包,修改备份信息(已经没有被压缩) fu.Remove(); info.pack_flag = false; _data->update(info); } bool retrans = false; std::string old_etag; if (req.has_header("If-Range")) { old_etag = req.get_header_value("If-Range"); // 有If-Range字段且,这个字段的值与请求文件的最新etag一致则符合断点续传 if (old_etag == GetETag(info)) { retrans = true; } } printf("retrans:%d\n", retrans); // 4. 读取文件数据,放入rsp.body中 FileUtil fu(info.real_path); if (retrans == false) { fu.GetContent(rsp.body); // 5. 设置响应头部字段: ETag, Accept-Ranges: bytes rsp.set_header("Accept-Ranges", "bytes"); rsp.set_header("ETag", GetETag(info)); rsp.set_header("Content-Type", "application/octet-stream"); rsp.status = 200; } else { // httplib内部实现了对于区间请求也就是断点续传请求的处理 // 只需要我们用户将文件所有数据读取到rsp.body中,它内部会自动根据请求 // 区间,从body中取出指定区间数据进行响应 // std::string range = req.get_header_val("Range"); bytes=start-end fu.GetContent(rsp.body); rsp.set_header("Accept-Ranges", "bytes"); rsp.set_header("ETag", GetETag(info)); rsp.set_header("Content-Type", "application/octet-stream"); // rsp.set_header("Content-Range", "bytes start-end/fsize"); rsp.status = 206; // 区间请求响应的是206***** } printf("Download 请求结束\n"); } static void Showlist(const httplib::Request &req, httplib::Response &rsp) { // 1. 获取所有的文件备份信息 printf("收到一个showlist请求\n"); std::vector<BackupInfo> arry; _data->GetAll(&arry); //std::cout << "文件信息准备完毕,size:" << arry.size() << std::endl; // 2. 根据这些文件备份信息组织html页面 std::stringstream ss; ss << "<html><head><title>Download</title></head>"; ss << "<body><h1>Download</h1><table>"; for (auto &a : arry) { ss << "<tr>"; std::string filename = FileUtil(a.real_path).Filename(); ss << "<td><a href='" << a.url << "'>" << filename << "</a></td>"; ss << "<td align='right'>" << totimestring(a.atime) << "</td>"; ss << "<td align='right'>" << a.fsize / 1024 << "k" << "</td>"; ss << "</tr>"; } ss << "</table></body></html>"; //std::cout << "文件信息填充完毕,开始填写rsq" << std::endl; // 3. 填充rsp响应 rsp.body = ss.str(); rsp.status = 200; rsp.set_header("Content-Type", "text/html"); printf("showlist请求完毕\n"); return; } }; }