实现服务器接口
我们用一个http
服务器作为底层,但是c++中并没有先成的http服务器,所以我在GitHub上找到一个牛人写的http
服务器,拿来直接用,节省本项目开发的时间
这是服务器的链接地址
上面有详细的使用方法,本文就不再解释具体如何实现,就直接用该cpp-httplib
这个库来进行开发
https://github.com/yhirose/cpp-httplib.git
然后我们就要实现相应的接口了
- 首先我们先要让我们的服务器和数据库连通,所以我们先要进行数据库客户端的初始化和释放
- 第二步我们就要设置一些自定义的路由(此处的路由并不是IP里的路由,而是指的是http中对应的方法+path对应到相应的处理函数上),要实现新增博客,查看博客等在数据库里所对应的相应的功能
- 第三步,我们要设置静态文件目录,把我们将来写好的网页放到这个目录里
和数据库建立链接
在这一步中,连接很简单,调用我们封装好的数据库API就行,但是断开连接就比较麻烦,因为整个服务器一但启动,他会进入到监听状态,所以必须等服务器关闭时,我们才能断开连接,而我们一般在Linux下关闭服务器都是使用ctrl+c
来进行关闭。ctrl+c
是一个信号,所以我们可以当触发这个信号时,就进行断开连接操作。
using namespace httplib;
using namespace blog_system;
//1. 先和数据库建立好链接
mysql = blog_system::MySQLInit();
signal(SIGINT,[](int){blog_system::MySQLRelease(mysql);exit(0);});
//2.创建相关数据库处理对象
BlogTable blog_table(mysql);
TagTable tag_table(mysql);
我们在进行信号处理时,第二个参数,是一个回调函数,当第一个参数触发时,执行后面回调函数里的内容,但是要写一个函数太麻烦,这里使用c++11中提供的lambda表达式。这样可以使代码更简洁,更容易进行维护。
实现新增博客
我们要先接受请求消息中的body,并把它转化成json,也就是把HHTP中的我们看不懂或者是太难理解的请求,转化为j我们能看懂或是看起来比较方便的json格式。
而我们要实现新增博客必须要经历以下几个步骤
- 首先我们肯定要接受http的请求消息,这里就是一个Post请求
- 我们要对这个消息进行校验,但是http的格式并不好让我们进行校验,所以我们要先把http的格式转化为json格式,方便我们进行校验
- 校验完后,我们就可以调用我们封装好的mysql的API对数据进行操作
- 最后我们还要封装正确的返回结果,给用户友好的提示
- 这里我们并不使用异常处理,我们使用错误判定,这样更好,虽然代码比较乱,但是可以很容易理解。而且c++中异常处理比较差,功能比较有限
- 不光是这个接口,我们每个博客接口都必须捕捉到blog_table这个对象
server.Post("/blog", [&blog_table](const Request& req, Response& resp) {
printf("新增博客!\n");
//创建一些我们需要用到的对象
Json::FastWriter writer; //写对象
Json::Reader reader; //读取对象
Json::Value req_json; //请求对象
Json::Value resp_json; //响应对象
//1.获取到请求中的body并解析成json
//parse函数中第一个参数是需要解析的字符串
//第二个参数是把解析后的字符串放到哪个对象中
//第三个参数是注释,默认是true,所以我就不管了
bool ret = reader.parse(req.body,req_json);
if(!ret){
//解析出错,给用户提示
printf("解析请求失败! %s\n",req.body.c_str());
//构造一个相应对象,告诉客户端出错了
resp_json["ok"] = false;
resp_json["reason"] = "提交的数据格式有误!\n";
resp.status = 400;
//返回给客户端,第二个参数设置反回的数据类型
resp.set_content(writer.write(resp_json), "application/json");
return;
}
//2. 进行参数校验
//这些字段如果缺任何一个,都代表有错
if (req_json["title"].empty() || req_json["content"].empty()
|| req_json["tag_id"].empty() || req_json["create_time"].empty()) {
resp_json["ok"] = false;
resp_json["reason"] = "博客的格式出错!\n";
resp.status = 400;
resp.set_content(writer.write(resp_json), "application/json");
return;
}
//3.调用数据库接口进行操作
ret = blog_table.Insert(req_json);
if (!ret) {
printf("插入博客失败!\n");
resp_json["ok"] = false;
resp_json["reason"] = "插入失败!\n";
resp.status = 500;
resp.set_content(writer.write(resp_json), "application/json");
return;
}
//4.封装正确的返回结果
resp_json["ok"] = true;
resp.set_content(writer.write(resp_json), "application/json");
return;
});
查看所有博客
server.Get("/blog", [&blog_table](const Request& req, Response& resp) {
printf("查看所有博客!\n");
Json::Reader reader;
Json::FastWriter writer;
Json::Value resp_json;
//如果没传tag_id,返回的是空字符串
const std::string& tag_id = req.get_param_value("tag_id");
// 对于查看博客来说 API 没有请求参数, 不需要解析参数和校验了, 直接构造结果即可
// 1. 调用数据库接口查询数据
Json::Value blogs;
bool ret = blog_table.SelectAll(&blogs, tag_id);
if (!ret) {
resp_json["ok"] = false;
resp_json["reason"] = "查看博客失败\n";
resp.status = 500;
resp.set_content(writer.write(resp_json), "application/json");
return;
}
// 2. 构造响应结果
resp.set_content(writer.write(blogs), "application/json");
return;
});
查看一篇博客
查看一篇博客内容
查看blog_id,如果这里只写blog_id,这个httplib的库并不能识别,我又仔细看了
这个库的文档,他用了正则表达式,所以我又学习了一些正则表达式的内容
我们可以用\d+
表示匹配一个数字,但是这里又可能会引发c++中的转义字符,所以我们需要c++11中提供的R()使转义字符不生效
学习正则表达式
server.Get(R"(/blog/(\d+))", [&blog_table](const Request& req, Response& resp) {
//1.解析获取到blog_id
int32_t blog_id = std::stoi(req.matches[1].str());
printf("查看id为 %d 的博客!\n",blog_id);
Json::Value resp_json;
Json::FastWriter writer;
//2.直接调用数据库操作
bool ret = blog_table.SelectOne(blog_id, &resp_json);
if (!ret) {
resp_json["ok"] = false;
resp_json["reason"] = "查看指定博客失败!\n";
resp.status = 404;
resp.set_content(writer.write(resp_json), "application/json");
return;
}
//3.包装正确的响应
resp_json["ok"] = true;
resp.set_content(writer.write(resp_json), "application/json");
return;
});
删除博客
server.Delete(R"(/blog/(\d+))", [&blog_table](const Request& req, Response& resp) {
Json::Value resp_json;
Json::FastWriter writer;
// 1. 解析获取 blog_id
//使用 matches[1] 就能获取到 blog_id
int32_t blog_id = std::stoi(req.matches[1].str());
printf("删除 id 为%d 的博客!\n",blog_id);
// 2. 调用数据库接口删除博客
bool ret = blog_table.Delete(blog_id);
if (!ret) {
resp_json["ok"] = false;
resp_json["reason"] = "删除博客失败!\n";
resp.status = 500;
resp.set_content(writer.write(resp_json), "application/json");
return;
}
//3.包装正确的响应
resp_json["ok"] = true;
resp.set_content(writer.write(resp_json), "application/json");
return;
});
修改博客
修改博客就是重新插入新博客,所以我们又要校验博客信息,当然再校验之前还是需要对博客进行解析。
server.Put(R"(/blog/(\d+))", [&blog_table](const Request& req, Response& resp) {
Json::Reader reader;
Json::FastWriter writer;
Json::Value req_json;
Json::Value resp_json;
// 1. 获取到博客 id
int32_t blog_id = std::stoi(req.matches[1].str());
printf("修改 id为 %d的博客!\n",blog_id);
// 2. 解析博客信息
bool ret = reader.parse(req.body, req_json);
if (!ret) {
resp_json["ok"] = false;
resp_json["reason"] = "解析博客失败!\n";
resp.status = 400;
resp.set_content(writer.write(resp_json), "application/json");
return ;
}
//一定要记得补充上 blog_id
req_json["blog_id"] = blog_id; //从path中得到的id设置到json对象中
// 3. 校验博客信息
if (req_json["title"].empty() || req_json["content"].empty()
|| req_json["tag_id"].empty()) {
// 请求解析出错, 返回一个400响应
resp_json["ok"] = false;
resp_json["reason"] = "更新博客格式错误\n";
resp.status = 400;
resp.set_content(writer.write(resp_json), "application/json");
return;
}
// 4. 调用数据库接口进行修改
ret = blog_table.Update(req_json);
if (!ret) {
resp_json["ok"] = false;
resp_json["reason"] = "更新失败!\n";
resp.status = 500;
resp.set_content(writer.write(resp_json), "application/json");
return;
}
// 5. 封装正确的数据
resp_json["ok"] = true;
resp.set_content(writer.write(resp_json), "application/json");
return;
});
新增标签
新增标签跟新增博客是一样的道理,都需要把依赖对象传进来,然后先进行格式校验,校验前先进行解析,校验完后再调用mysqlAPI,最后带着结果返回。
server.Post("/tag", [&tag_table](const Request& req, Response& resp) {
Json::Reader reader;
Json::FastWriter writer;
Json::Value req_json;
Json::Value resp_json;
printf("新增标签!\n");
// 1. 请求解析成 Json 格式
bool ret = reader.parse(req.body, req_json);
if (!ret) {
resp_json["ok"] = false;
resp_json["reason"] = "解析失败\n";
resp.status = 400;
resp.set_content(writer.write(resp_json), "application/json");
return ;
}
// 2. 校验标签格式
if (req_json["tag_name"].empty()) {
resp_json["ok"] = false;
resp_json["reason"] = "标签格式有误!\n";
resp.status = 400;
resp.set_content(writer.write(resp_json), "application/json");
return;
}
// 3. 调用数据库接口, 插入标签
ret = tag_table.Insert(req_json);
if (!ret) {
resp_json["ok"] = false;
resp_json["reason"] = "插入标签失败!\n";
resp.status = 500;
resp.set_content(writer.write(resp_json), "application/json");
return;
}
// 4. 返回正确的结果
resp_json["ok"] = true;
resp.set_content(writer.write(resp_json), "application/json");
});
删除标签
server.Delete(R"(/tag/(\d+))", [&tag_table](const Request& req, Response& resp) {
Json::Value resp_json;
Json::FastWriter writer;
// 1. 解析出 tag_id
int tag_id = std::stoi(req.matches[1].str());
printf("要删除的标签id 为 %d\n",tag_id);
// 2. 执行数据库操作删除标签
bool ret = tag_table.Delete(tag_id);
if (!ret) {
resp_json["ok"] = false;
resp_json["reason"] = "删除所有标签失败\n";
resp.status = 500;
resp.set_content(writer.write(resp_json), "application/json");
return;
}
// 3. 包装正确的结果
resp_json["ok"] = true;
resp.set_content(writer.write(resp_json), "application/json");
return;
});
获取所有标签
server.Get("/tag", [&tag_table](const Request& req, Response& resp) {
printf("获取所有标签\n");
Json::Reader reader;
Json::FastWriter writer;
Json::Value resp_json;
// 1. 调用数据库接口查询数据
Json::Value tags;
bool ret = tag_table.SelectAll(&tags);
if (!ret) {
resp_json["ok"] = false;
resp_json["reason"] = "查找所有标签失败\n";
resp.status = 500;
resp.set_content(writer.write(resp_json), "application/json");
return;
}
// 2. 构造响应结果
resp.set_content(writer.write(tags), "application/json");
return;
});
获取静态文件目录
server.set_base_dir("./wwwroot");