本文主要记录在 Qt 中实现http服务的示例,包括解析json协议数据。
很早之前,对自己写的一个工程测试,需对接一个C++写的web服务,但局域网中尚未有,于是部署之,web服务使用了cgi技术,于是找了fastcgi等库和程序,最终和nginx一道合力完成部署。当时觉得比较麻烦,C++应该有更好的方法。后因故没有继续研究。
最后,机缘之下,需要在Qt环境中搭建web服务,于是找了相应的资料并实现。
首先找到的是qthttpserver
,,看上去是官方出品的,看了demo里的使用方法,十分符合习惯,但对Qt版本有要求,故放弃之。
后来找到cpphttplib
(),其使用方法亦符合习惯,且只有一头文件,于是用之。
创建简单的窗体工程,上置一button,并设置槽以响应之。主体完整代码如下:
#include "dialog.h"
#include "ui_dialog.h"
#include <thread>
#include <httplib.h>
#include <json.hpp>
//using namespace httplib;
//using json = nlohmann::json;
/*
json解析注意事项:
解析的类型和传递的须一致,否则无法继续,可能抛出异常。
如字段本应为int,但传入字符串,则会异常。
*/
std::string gVersion = "1.0";
void handlePostFoobar(const httplib::Request &req, httplib::Response &res)
{
qDebug("start!!!!!\n");
qDebug("req: %d\n", req.body.size());
qDebug("req: [%s]\n", req.body.data());
if (req.has_header("Content-Type")) {
auto val = req.get_header_value("Content-Type");
qDebug("value: %s", val.data());
if (val.compare("application/json") != 0) {
nlohmann::json outJson;
outJson["err"] = "not json";
std::string jsonStr = outJson.dump();
res.set_content(jsonStr, "application/json");
return;
}
}
nlohmann::json myjson;
try {
myjson = nlohmann::json::parse(req.body);
} catch(std::exception& e) {
qDebug("json parse failed %s", e.what());
}
if (myjson == nullptr)
{
nlohmann::json outJson;
outJson["err"] = "not json";
std::string jsonStr = outJson.dump();
res.set_content(jsonStr, "application/json");
return;
}
std::string name;
int age;
std::string version;
std::string timestamp;
bool isSigned = false;
int myType = 0;
try {
myjson.at("name").get_to(name);
myjson.at("age").get_to(age);
myjson.at("version").get_to(version);
// this is ok
// myjson.at("timestamp").get_to(info.timestamp);
// this is also ok
timestamp = myjson.at("timestamp").get<std::string>();
// for bool
isSigned = myjson["sign"].get<bool>();
// data字段也是一个json对象
nlohmann::json datajson = myjson["data"];
myType = datajson["type"].get<int>();
} catch(std::exception& e) // 异常
{
qDebug("json parse failed %s", e.what());
}
qDebug("result: %s %d %s %s %d %d\n", name.data(), age, version.data(), timestamp.data(), isSigned, myType);
// 输出
nlohmann::json outJson;
outJson["a"] = name;
outJson["b"] = version;
outJson["nums"] = {1, 2, 3, 4, 5};
// outJson["ids"] = {"id1", "id2", "id3", "id4", "id5"};
// for vector
std::vector<std::string> vec;
vec.push_back("id1");
vec.push_back("id2");
vec.push_back("id100");
outJson["ids"] = vec;
// for map
std::map<int, std::string> m;
m.insert({1, "南宁"});
m.insert({2, "梧州"});
m.insert({3, "岑溪"});
outJson["cities"] = m;
// 子对象
nlohmann::json outData;
outData["age"] = age;
outJson["data"] = outData;
std::string jsonStr = outJson.dump();
qDebug("end: %s", jsonStr.data());
res.set_content(jsonStr, "application/json");
}
void startHttpServer()
{
httplib::Server svr;
svr.Get("/version", [](const httplib::Request& /*req*/, httplib::Response& res) {
res.set_content(gVersion, "text/plain");
});
svr.Post("/foobar", handlePostFoobar);
int port = 8080;
qDebug("start listen at %d\n", port);
svr.listen("0.0.0.0", port);
}
Dialog::Dialog(QWidget *parent)
: QDialog(parent)
, ui(new Ui::Dialog)
{
ui->setupUi(this);
setWindowTitle(tr("MainWindow Demo"));
setMinimumSize(480, 320);
Qt::WindowFlags winFlags = Qt::Dialog;
winFlags = winFlags | Qt::WindowMinMaxButtonsHint | Qt::WindowCloseButtonHint;
//winFlags = Qt::WindowFullscreenButtonHint;
setWindowFlags(winFlags);
}
Dialog::~Dialog()
{
delete ui;
}
void Dialog::on_btnStart_clicked()
{
ui->btnStart->setEnabled(false);
std::thread thttp(startHttpServer);
thttp.detach();
}
使用curl
发送请求:
curl -s -X POST http://127.0.0.1:8080/foobar -H "Content-Type:application/json" -d '{"name":"Late Lee","timestamp":"2023-01-01 00:12:34","version":"250","age":44,"sign":false,"data":{"type":100}}' | json
请求命令后使用了json(需要额外安装,可不用)格式化,返回信息如下:
{
"a": "Late Lee",
"b": "250",
"cities": [
[
1,
"南宁"
],
[
2,
"梧州"
],
[
3,
"岑溪"
]
],
"data": {
"age": 44
},
"ids": [
"id1",
"id2",
"id100"
],
"nums": [
1,
2,
3,
4,
5
]
}
发送含中文的请求:
$ curl -s -X POST http://127.0.0.1:8080/foobar -H "Content-Type:application/json" -d '{"name":"李迟","timestamp":"2023-01-01 00:12:34","version":"250","age":44,"sign":false,"data":{"type":100}}' | json
{
"err": "not json"
}
服务器输出:
json parse failed [json.exception.parse_error.101] parse error at line 1, column 10: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '"?'
相同请求,但使用postman
工具发送,服务器解析正常。输出:
req: [{"name":"李迟","timestamp":"2023-01-01 00:12:34","version":"250","age":44,"sign":false,"data":{"type":100}}]
value: application/json
result: 李迟 44 250 2023-01-01 00:12:34 0 100
end: {"a":"李迟","b":"250","cities":[[1,"南宁"],[2,"梧州"],[3,"岑溪"]],"data":{"age":44},"ids":["id1","id2","id100"],"nums":[1,2,3,4,5]}
httplib
、nlohmann/json
实现上只有一个头文件,工程集成比较方便,只是编译时会慢一些。
httplib
、nlohmann::json
均可设置命名空间,可用之减少代码编写。笔者习惯不使用命名空间。
httplib
在windows上需使用vs2015及以上版本编译。后面用了vs2015编译,出现很多错误,于是改成minGW编译。
httplib
需在配置文件中添加socket库,否则编译失败,示例:
QT += core gui network
win32: LIBS += -lSetupAPI -luser32 -lws2_32
看httplib
官方issue
,有提到无法处理中文情况。经测试,如果用命令行curl
方式请求,中文字符会出现乱码。但是,相同请求,使用postman
工具发送,能正常解析。
目前没有对httplib
做大规模的测试, 不确定其是否满足生产环境要求。
本文所述,能应付一般的demo需求,算是一种轻盈的实现优雅。