您的当前位置:首页正文

Qt实践录:实现http服务并解析json协议

2024-11-06 来源:个人技术集锦

本文主要记录在 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]}

经验

httplibnlohmann/json实现上只有一个头文件,工程集成比较方便,只是编译时会慢一些。

httplibnlohmann::json均可设置命名空间,可用之减少代码编写。笔者习惯不使用命名空间。

httplib在windows上需使用vs2015及以上版本编译。后面用了vs2015编译,出现很多错误,于是改成minGW编译。

httplib需在配置文件中添加socket库,否则编译失败,示例:

QT       += core gui network
win32: LIBS += -lSetupAPI -luser32 -lws2_32

httplib官方issue,有提到无法处理中文情况。经测试,如果用命令行curl方式请求,中文字符会出现乱码。但是,相同请求,使用postman工具发送,能正常解析。

目前没有对httplib做大规模的测试, 不确定其是否满足生产环境要求。

小结

本文所述,能应付一般的demo需求,算是一种轻盈的实现优雅。

Top