作者简介:花想云 ,在读本科生一枚,C/C++领域新星创作者,新星计划导师,阿里云专家博主,CSDN内容合伙人…致力于 C/C++、Linux 学习。
专栏简介:本文收录于 C++项目——云备份
相关专栏推荐:C语言初阶系列、C语言进阶系列 、C++系列、数据结构与算法、Linux
项目Gitee链接:https://gitee.com/li-yuanjiu/cloud-backup
服务端业务处理模块中,将业务处理模块与网络通信模块进行了合并
,因为网络通信模块httplib
库已经帮我们完成。
借助httplib库完成
;文件上传请求
:客户端上传需要备份的文件——服务端响应上传文件成功;文件列表请求
:客户端浏览器请求一个备份文件的展示页面——服务端响应该页面;文件下载请求
:客户端通过展示页面,点击下载文件——服务端响应客户端要下载的文件数据。业务处理模块要对客户端的请求进行处理,那么我们就需要提前定义好客户端与服务端的通信,明确客户端发送什么样的请求,服务端处理后应该给与什么样的响应,而这就是网络通信接口的设计
。
客户端文件上传请求
POST /upload HTTP/1.1
Content-Length:11
Content-Type:multipart/form-data;boundary= ----WebKitFormBoundary+16字节随机字符
------WebKitFormBoundary
Content-Disposition:form-data;filename="a.txt";
hello world
------WebKitFormBoundary--
当服务器收到POST
方法的/upload
请求,我们则认为这是一个文件上传请求,解析请求后(由httplib
完成),得到文件数据,将数据写入文件中(创建对应的备份文件)。
服务端响应
HTTP/1.1 200 OK
Content-Length: 0
客户端文件列表查看请求
GET /list HTTP/1.1
Content-Length: 0
服务端响应
HTTP/1.1 200 OK
Content-Length:
Content-Type: text/html
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Page of Downloadtitle>
head>
<body>
<h1>Downloadh1>
<table>
<tr>
<td><a href="/download/a.txt"> a.txt a>td>
<td align="right"> 1994-07-08 03:00 td>
<td align="right"> 27K td>
tr>
table>
body>
html>
客户端文件下载请求
GET /download/a.txt http/1.1
Content-Length: 0
服务端响应
HTTP/1.1 200 OK
Content-Length: 100000
ETags: "filename-size-mtime一个能够唯一标识文件的数据"
Accept-Ranges: bytes
文件数据
Service
类主要包含以下成员:
class Service
{
public:
Service();
bool RunModule(); // 主逻辑执行函数
private:
// 文件上传请求处理函数
static void Upload(const httplib::Request &req, httplib::Response &rsp);
// 日期格式化函数
static std::string TimetoStr(time_t t);
// 展示页面请求处理函数
static void ListShow(const httplib::Request &req, httplib::Response &rsp);
// 获取Etag函数
static std::string GetETag(const BackupInfo &info);
// 文件下载请求处理函数
static void Download(const httplib::Request &req, httplib::Response &rsp);
private:
int _server_port; // 服务器端口
std::string _server_ip; // 服务器IP
std::string _download_prefix; // 文件下载请求前缀
httplib::Server _server; // Server类对象用于搭建服务器
};
#ifndef __MY_SERVICE__
#define __MY_SERVICE__
#include "util.hpp"
#include "config.hpp"
#include "data.hpp"
#include "httplib.h"
extern cloud::DataManager* _data;
namespace cloud
{
class Service
{
public:
Service()
{
Config* config = Config::GetInstance();
_server_port = config->GetServerPort();
_server_ip = config->GetSeverIp();
_download_prefix = config->GetDownloadPrefix();
}
bool RunModule()
{
_server.Post("/upload", Upload);
_server.Get("/listshow", ListShow);
_server.Get("/", ListShow);
_server.Get("/download/(.*)", Download);
_server.listen(_server_ip.c_str(), _server_port);
return true;
}
private:
static void Upload(const httplib::Request &req, httplib::Response &rsp)
{
auto ret = req.has_file("file");
if(ret == false)
{
rsp.status = 400;
return;
}
const auto& file = req.get_file_value("file");
std::string back_dir = Config::GetInstance()->GetBackDir();
std::string realpath = back_dir + FileUtil(file.filename).FileName();
FileUtil fu(realpath);
fu.SetContent(file.content); // 将数据写入文件中
BackupInfo info;
info.NewBackupInfo(realpath); // 组织备份的文件信息
_data->Insert(info); // 向数据管理模块添加备份的文件信息
return;
}
static std::string TimetoStr(time_t t)
{
std::string tmp = std::ctime(&t);
return tmp;
}
static void ListShow(const httplib::Request &req, httplib::Response &rsp)
{
// 1.获取所有文件的备份信息
std::vector<BackupInfo> array;
_data->GetAll(&array);
// 2.根据所有备份信息,组织html文件数据
std::stringstream ss;
ss << "Download ";
ss << "Download
";
for(auto &a : array)
{
ss << "";
std::string filename = FileUtil(a.real_path).FileName();
ss << "" << filename << " ";
ss << "" << TimetoStr(a.mtime) << " ";
ss << "" << a.fsize / 1024 << "k ";
}
ss << "
";
rsp.body = ss.str();
rsp.set_header("Content-Type", "text/html");
rsp.status = 200;
}
static std::string GetETag(const BackupInfo &info)
{
// etag: filename-fsize-mtime
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)
{
// 1.获取客户端请求的路径资源,如果被压缩,要先解压缩
// 2.根据资源路径,获取文件备份信息
BackupInfo info;
_data->GetOneByURL(req.path, &info);
// 3.判断文件是否被压缩,如果被压缩,要先解压缩
if(info.pack_flag == true)
{
FileUtil fu(info.pack_path);
fu.UnCompress(info.real_path); // 将文件解压到备份目录下
// 4.删除压缩包,修改备份信息(已经没有被压缩)
fu.Remove();
info.pack_flag = false;
_data->Updata(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;
}
}
// 如果没有If-Range字段则是正常下载,或者如果有这个字段,但是
// 它的值与当前文件的etag不一致,则必须重新返回全部数据
// 5.读取文件数据,放入rsp.body中
FileUtil fu(info.real_path);
if(retrans == false)
{
fu.GetContent(&rsp.body);
// 6.设置相应头部字段: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_value("Range"); bytes=starts-end
fu.GetContent(&rsp.body);
fu.GetContent(&rsp.body);
rsp.set_header("Accept-Ranges", "bytes");
rsp.set_header("ETag", GetETag(info));
// rsq.set_header("Content-Range", "bytes start-end/fsize");
rsp.status = 206;
}
}
private:
int _server_port;
std::string _server_ip;
std::string _download_prefix;
httplib::Server _server;
};
}
#endif