C++ Qt建立一个HTTP服务器

1、下载http模块

上网查询发现在C++中,建立HTTP服务器几乎都是用的是http-parser,然后我也准备直接使用,但是如果要在Qt中使用,这还需要自己再次封装一遍,于是乎我找到了这个示例,非常好用:

legahero/HttpJsonServer: 用QT实现HTTP JSON服务器( http json server by QT)https://github.com/legahero/HttpJsonServer

打开这个项目目录,需要知道的有主窗口的源文件,以及config目录、httpbase目录,分析项目结构后不难发现,config是数据库配置文件所在的目录(你如果不需要,则完全可以不用管),httpbase是封装好了的http和数据库各种类的目录,详细的下面慢慢介绍。

2、引用http模块

首先第一步是引用,上面地址的项目示例下载完成后,直接将httpbase目录copy到我们的项目目录中,然后像示例一样,在.pro文件中引用:

不要忘了加上Qt的网络模块(network)和数据库(sql)模块,如果你觉得每次建立项目都需要加上这么一堆,很麻烦,那就在httpbase目录中建立一个.pri文件,然后引用.pri文件就可以,例如:

bash 复制代码
#.pri文件内容
QT       += network sql

SOURCES += httpbase/qhttprequest.cpp \
    httpbase/qhttpresponse.cpp \
    httpbase/qhttpserver.cpp \
    httpbase/http_parser.c \
    httpbase/threadhandle.cpp \
    httpbase/qasyntcpserver.cpp \
    httpbase/qasynhttpsocket.cpp \
    httpbase/staticfilecontroller.cpp \
    httpbase/httphandler.cpp \
    httpbase/qasyntcpsocket.cpp \
    httpbase/qconnectpool.cpp \
    httpbase/qmultidbmanager.cpp

HEADERS  += httpbase/http_parser.h \
    httpbase/qhttprequest.h \
    httpbase/qhttpresponse.h \
    httpbase/threadhandle.h \
    httpbase/qasyntcpserver.h \
    httpbase/qasynhttpsocket.h \
    httpbase/qhttpserver.h \
    httpbase/staticfilecontroller.h \
    httpbase/httphandler.h \
    httpbase/qasyntcpsocket.h \
    httpbase/qhttpserverfwd.h \
    httpbase/qconnectpool.h \
    httpbase/qmultidbmanager.h

#.pri文件在.pro文件中引用
include(httpbase/httpbase.pri)

3、使用http模块

首先看主窗口的源文件,就可以看到初始化http直接使用QHttpServer类就行了,不需要过多的动作,然后就找http的业务处理在哪里,在QHttpServer类中我们看定义的各种函数,一下就可以看到有个槽函数叫handleRequest,一看就知道是处理业务的函数,然后我们去看看示例中的业务处理代码如下,逐字逐句分析,这里使用注释讲解:

cpp 复制代码
//处理新的http 请求,这里处理业务
void QHttpServer::handleRequest(QHttpRequest *req, QHttpResponse *resp)
{
    qDebug() << "QHttpServer:handleRequest,ThreadId:"<<QThread::currentThreadId()  ;


    qDebug() <<"path:"<< req->path()<<"body:"<<req->body();//打印请求路径和消息内容
    qDebug() <<"headers:"<< req->headers();//打印请求头,是hash表类型的数据

    if(req->path().indexOf('.')>=0)//如果请求的是根目录中的文件,则使用文件转发模块
    {
        QString configFileName=searchConfigFile();
        // Configure static file controller
        QSettings* fileSettings=new QSettings(configFileName,QSettings::IniFormat);
        fileSettings->beginGroup("docroot");
        StaticFileController* staticFileController=new StaticFileController(fileSettings);

        //StaticFileController staticFileController(fileSettings);
        staticFileController->Handler(*req, *resp);
        return ;
    }

    QJsonDocument doc=QJsonDocument::fromBinaryData(req->body());//这里接收到的数据是二进制形式的数据,所以使用fromBinaryData函数,如果是utf8格式使用fromJson
    QJsonObject recv_obj=doc.object();//这是接收到的json对象

    QConnectPool* dbpool=QMultiDbManager::getDb("sql2014");//获取sql2014数据库句柄
    if(dbpool!=NULL)
    {
        QSqlDatabase db=dbpool->openSession();
        QSqlQuery query(db);
        query.exec("SELECT top 1 * FROM tLogin ");//查询用户列表

        QJsonObject resp_obj; //返回json对象
        while (query.next()) {
            //将用户数据打包进返回json数据中
            resp_obj.insert("LoginName",query.value("LoginName").toString());
            resp_obj.insert("Pwd", query.value("LoginName").toString());
        }
        dbpool->closeSession(db);//关闭数据库

        QByteArray data = QJsonDocument(resp_obj).toJson(QJsonDocument::Compact);

        resp->setHeader("Content-Type", "text/html");//设置请求头,返回数据类型
        resp->setHeader("Content-Length", QString::number(data.length()));//返回消息内容大小
        resp->writeHead(200);//写入请求头,并带有返回码200表示请求成功
        resp->end(data);//写入完毕,顺便带入最后的消息。可以去定义处看,一般写入使用函数write
    }else
    {
        qDebug() <<"get  QMultiDbManager::getDb fail";
        //resp->setHeader("Content-Type", "text/html");
        resp->writeHead(403);//这里数据库打开失败了,返回403
    }
    resp->flush();//开始回收资源

    req->deleteLater();
    resp->deleteLater();
    qDebug() <<"handleRequest end";

}

上述就是大概的使用流程了,关于请求业务类型的区分,查看QHttpRequest类的定义可以看到枚举类型HttpMethod,就可以知道有哪些业务请求类型了,然后两个函数获取当前请求的类型method()和methodString()。

4、使用过程中会遇到的问题

(1)消息主体为空

打印req->body()没有消息,看程序输出日志readall有,说明解析后的消息内容没有添加到QHttpRequest类中,在哪里断层了,跟踪解析逻辑后来到QAsynHttpSocket类,这里是进行消息各个值赋值的地方,分析各个私有变量后发现当前临时的消息主体QByteArray m_currentBody,然后查看QHttpRequest中实例化了QAsynHttpSocket类同样是私有成员,所以为了直接能够在业务处理中打印m_currentBody,所以定义两个公有的函数:

cpp 复制代码
//QAsynHttpSocket类中,这个直接复制在头文件的公有定义中
    const QByteArray &getCurTempbody() const
    {
        return m_currentBody;
    }

//QHttpRequest类中
QByteArray QHttpRequest::getCurTempbody()
{
    if(m_connection == nullptr){
        return "";
    }
    return m_connection->getCurTempbody();
}

然后调试打印,发现m_currentBody就是最新的那个消息内容,准确无误了,再次分析QAsynHttpSocket类,发现函数doMessageComplete()的功能就是构建QHttpRequest类,并传递出去,那我们就在这里添加消息主体内容,问题解决:

cpp 复制代码
Q_EMIT request->data(theConnection->m_currentBody);//分析后发现该信号就是添加内容的,我们直接将当前临时的消息发送过去就行了

Q_EMIT theConnection->newRequest(request, response);//这是发送构建完成的信号,在这里之前添加内容

(2)请求文件资源一直失败

测试访问文件资源一直失败,打印发现一直提示termiteHttpServer.ini文件未找到,这个时候也可以看到根目录为"./etc",那我们就直接在这里创建termiteHttpServer.ini文件,因为跟踪查看,发现其实什么内容都不用添加,再访问仍旧失败,业务处理函数中,直接看staticFileController->Handler(*req, *resp);一句,这里转发文件,分析函数Handler后可以知道这里缺少请求头,消息内容的长度以及应答状态码,我们找到下面的第一行代码,然后将后续两行直接添加即可:

cpp 复制代码
response.setHeader("Cache-Control","max-age=" + QByteArray::number(maxAge/1000));
response.setHeader("Content-Length", QString::number(file.size()));//文件大小
response.writeHead(200);//状态码
相关推荐
软件开发技术局39 分钟前
撕碎QT面具(8):对控件采用自动增加函数(转到槽)的方式,发现函数不能被调用的解决方案
开发语言·qt
OopspoO2 小时前
QT事件循环
qt·事件循环
小金的学习笔记2 小时前
如何在本地和服务器新建mysql用户和密码
运维·服务器·mysql
s_fox_2 小时前
nginx ngx_http_module(7) 指令详解
运维·nginx·http
周杰伦fans2 小时前
C#中修饰符
开发语言·c#
荔枝荷包蛋6662 小时前
【Linux】HTTP:Cookie 和 Session 详解
网络·网络协议·http
EasyNVR2 小时前
EasyRTC智能硬件:实时畅联、沉浸互动、消音护航
运维·服务器·网络·安全·音视频·webrtc·p2p
CAir22 小时前
HTTP SSE 实现
http·libhv·sse
yngsqq2 小时前
c# —— StringBuilder 类
java·开发语言
赔罪2 小时前
Python 高级特性-切片
开发语言·python