HTTP协议(课外拓展)HTTP的基本概念HTTPHyperText Transfer Protocol 是用于分布式、协作式和超媒体信息系统的应用层协议。它是万维网的基础定义了浏览器与服务器之间传输数据的规则。HTTP是基于tcp实现的短链接服务器收到客户端处理后将消息推送给客户端然后关闭单方连接客户端收到后也关闭单方连接。HTTP协议的工作原理1客户端发起请求浏览器或其他客户端向服务器发送HTTP请求。2服务器处理请求服务器接收到请求后进行相应的处理如读取文件、执行脚本等。3服务器返回响应处理完成后服务器返回HTTP响应给客户端。4客户端处理响应客户端接收到响应后渲染页面或进行其他操作。常见的HTTP方法和状态码●HTTP方法○GET请求指定的资源。○POST向指定资源提交数据。○PUT更新指定资源。○DELETE删除指定资源。○HEAD获取资源的头部信息。○OPTIONS获取服务器支持的HTTP方法。●HTTP状态码○200 OK请求成功。○201 Created资源创建成功。○400 Bad Request客户端请求有语法错误。○401 Unauthorized未经授权。○404 Not Found资源未找到。○500 Internal Server Error服务器内部错误。HTTP 头部的基本格式1请求/响应行Request/Response Line○请求行Request Line包含请求方法、请求 URI 和 HTTP 版本。■例子GET /index.html HTTP/1.1○响应行Response Line包含 HTTP 版本、状态码和状态消息。■例子HTTP/1.1 200 OK2头部字段Header Fields○每个头部字段由一个字段名和一个字段值组成中间用冒号 : 分隔。字段名和字段值之间可以有空格。○头部字段的格式如下Field-Name: Field-Value头部字段可以有多行通常使用换行符CRLF来分隔。空行Empty Line请求/响应行和头部字段之间用一个空行分隔表示头部的结束。HTTP 请求示例GET /index.html HTTP/1.1 Host: www.example.com User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3 Accept: text/html,application/xhtmlxml,application/xml;q0.9,image/webp,*/*;q0.8 Accept-Language: en-US,en;q0.5 Connection: keep-aliveHTTP 响应示例HTTP/1.1 200 OK Date: Wed, 21 Oct 2015 07:28:00 GMT Server: Apache/2.4.1 (Unix) Last-Modified: Wed, 21 Oct 2015 07:28:00 GMT Content-Type: text/html; charsetUTF-8 Content-Length: 1234 Connection: close html headtitleExample/title/head bodyh1Hello, World!/h1/body /html常用 HTTP 头部字段请求头部字段Host: 指定请求的主机名和端口号。User-Agent: 指定发起请求的客户端软件信息。Accept: 指定客户端能够接收的内容类型。Authorization: 用于提供身份验证凭据。Content-Type: 指定请求体的媒体类型通常在 POST 请求中使用。响应头部字段Content-Type: 指定响应体的媒体类型。Content-Length: 指定响应体的字节长度。Set-Cookie: 用于设置 HTTP cookies。Cache-Control: 指定缓存机制。Location: 用于重定向指定新的 URL。以下是一些常见的 HTTP 请求内容类型及其用途application/json用于发送 JSON 格式的数据。广泛用于 RESTful API。示例Content-Type: application/jsonapplication/x-www-form-urlencoded用于表单提交时数据以 URL 编码的形式发送。每个键值对通过符号连接键和值通过符号连接。示例Content-Type: application/x-www-form-urlencodedmultipart/form-data用于表单提交时尤其是当表单包含文件上传时。它允许在同一请求中发送多种类型的数据。示例Content-Type: multipart/form-data; boundary---011000010111000001110001text/plain用于发送纯文本数据不带格式。示例Content-Type: text/plainapplication/xml用于发送 XML 格式的数据。示例Content-Type: application/xmltext/html用于发送 HTML 格式的数据。示例Content-Type: text/htmlapplication/octet-stream用于发送二进制数据通常用于文件下载或未指定类型的内容。示例Content-Type: application/octet-streamQt5与Qt6的差异在Qt6中引入了更高级的HTTP服务器类如QHttpServer简化了HTTP服务器的实现过程。而在Qt5.12中需要使用QTcpServer和QTcpSocket结合手动解析HTTP请求和构建响应。这不仅增加了实现的复杂度但也提供了更多的学习和理解HTTP协议的机会。跨域问题跨域问题CORSCross-Origin Resource Sharing是指浏览器的同源策略限制了一个网页能够请求不同源域名、协议或端口不同的资源。由于安全原因浏览器默认不允许跨域请求以防止恶意网站窃取用户数据或进行其他恶意操作。什么是同源策略同源策略是指在一个网页中脚本只能访问与其同源的资源。所谓同源是指协议相同http、https域名相同example.com、sub.example.com端口相同80、443等例如以下两个 URL 是同源的http://example.com/page1http://example.com/page2而以下两个 URL 则是不同源的http://example.com/page1http://another.com/page2跨域请求的场景跨域请求通常发生在以下几种场景中AJAX 请求当网页通过 JavaScript 使用XMLHttpRequest或fetchAPI 发起请求时如果请求的 URL 不同于当前网页的 URL就会发生跨域请求。Web 字体如果网页使用了来自不同源的字体文件浏览器会进行跨域请求。图片、视频等媒体资源如果网页中引用了来自不同源的图片或视频也会涉及跨域请求。如何解决跨域问题CORS跨源资源共享例如以下响应头允许来自任意源的 GET 和 POST 请求Access-Control-Allow-Origin: * Access-Control-Allow-Methods: GET, POST服务器可以通过设置 HTTP 响应头来允许特定的源访问资源。常用的 CORS 头包括Access-Control-Allow-Origin: 指定允许哪些源进行访问可以是具体的域名或*表示允许所有源。Access-Control-Allow-Methods: 指定允许的 HTTP 方法GET, POST, OPTIONS等。Access-Control-Allow-Headers: 指定允许的请求头。JSONPJSON with Padding通过script标签加载数据利用 JavaScript 的动态脚本加载特性来实现跨域请求。虽然这种方法可以解决跨域问题但它只支持 GET 请求并且存在安全隐患。代理服务器在同源的服务器上设置一个代理客户端请求代理服务器代理服务器再去请求目标资源。这样可以绕过浏览器的同源策略。使用 WebSocketWebSocket 协议不受同源策略的限制可以用于实现跨域通信。实现HttpServerHttpServer接下来我们基于QTcpServer和QTcpSocket实现一个http服务#ifndef HTTPSERVER_H #define HTTPSERVER_H #include QObject #include QTcpServer #include QTcpSocket class HttpServer : public QObject { Q_OBJECT public: explicit HttpServer(quint16 port, QObject *parent nullptr); bool start(); private slots: // 新连接建立触发 void onNewConnection(); // 读就绪触发的槽函数 void onReadyRead(); // 断开连接触发的槽函数 void onDisconnected(); private: QTcpServer *tcpServer; quint16 listenPort; // 简单的请求处理方法 void handleRequest(QTcpSocket *socket, const QString request); // 处理post请求 void handlePostRequest(QTcpSocket *socket, const QString request); }; #endif // HTTPSERVER_H添加实现构造函数里创建QTcpServer对象并且连接信号HttpServer::HttpServer(quint16 port, QObject *parent) : QObject(parent), listenPort(port) { tcpServer new QTcpServer(this); connect(tcpServer, QTcpServer::newConnection, this, HttpServer::onNewConnection); }新连接触发的槽函数void HttpServer::onNewConnection() { while (tcpServer-hasPendingConnections()) { QTcpSocket *clientSocket tcpServer-nextPendingConnection(); qDebug() 新的连接来自 clientSocket-peerAddress().toString() : clientSocket-peerPort(); connect(clientSocket, QTcpSocket::readyRead, this, []() { onReadyRead(); }); connect(clientSocket, QTcpSocket::disconnected, this, []() { onDisconnected(); }); } }开始启动bool HttpServer::start() { if (!tcpServer-listen(QHostAddress::Any, listenPort)) { qCritical() 无法启动服务器: tcpServer-errorString(); return false; } qDebug() HTTP服务器已启动监听端口 listenPort; return true; }读取数据和断开连接//读取数据 void HttpServer::onReadyRead() { QTcpSocket *clientSocket qobject_castQTcpSocket*(sender()); if (!clientSocket) return; while (clientSocket-canReadLine()) { QByteArray requestData clientSocket-readAll(); QString requestString(requestData); qDebug() 收到请求 requestString; handleRequest(clientSocket, requestString); } } //断开连接 void HttpServer::onDisconnected() { QTcpSocket *clientSocket qobject_castQTcpSocket*(sender()); if (clientSocket) { qDebug() 连接断开 clientSocket-peerAddress().toString() : clientSocket-peerPort(); clientSocket-deleteLater(); } }处理get和post请求// 修改 handleRequest 方法以调用 handlePostRequest void HttpServer::handleRequest(QTcpSocket *socket, const QString request) { // 解析请求行 QStringList requestLines request.split(\r\n); if (requestLines.isEmpty()) { // 无效请求 return; } QString requestLine requestLines.at(0); QStringList requestParts requestLine.split( ); if (requestParts.size() 3) { // 无效请求行 return; } QString method requestParts.at(0); QString path requestParts.at(1); QString httpVersion requestParts.at(2); // 添加 CORS 头, 支持跨域访问 QString corsHeaders Access-Control-Allow-Origin: *\r\n // 允许所有来源 Access-Control-Allow-Methods: GET, POST, OPTIONS\r\n // 允许的方法 Access-Control-Allow-Headers: Content-Type\r\n; // 允许的请求头 if (method OPTIONS) { // 处理预检请求 QString response HTTP/1.1 204 No Content\r\n Connection: close\r\n corsHeaders \r\n; socket-write(response.toUtf8()); socket-flush(); socket-disconnectFromHost(); return; } if (method GET) { // 处理GET请求之前的逻辑 if (path /) { QString body 欢迎使用Qt 5.12 创建的简单HTTP服务器\n; QString response QString(HTTP/1.1 200 OK\r\n Content-Type: text/plain; charsetutf-8\r\n Content-Length: %1\r\n Connection: close\r\n corsHeaders \r\n%2) .arg(body.length()) .arg(body); socket-write(response.toUtf8()); socket-flush(); socket-disconnectFromHost(); return; } else if (path /api/hello) { QString body {\message\: \Hello, World!\}\n; QString response QString(HTTP/1.1 200 OK\r\n Content-Type: application/json\r\n Content-Length: %1\r\n Connection: close\r\n corsHeaders \r\n%2) .arg(body.length()) .arg(body); socket-write(response.toUtf8()); socket-flush(); socket-disconnectFromHost(); return; } else { // 处理404 QString body 404 - Not Found\n; QString response QString(HTTP/1.1 404 Not Found\r\n Content-Type: text/plain\r\n Content-Length: %1\r\n Connection: close\r\n corsHeaders \r\n%2) .arg(body.length()) .arg(body); socket-write(response.toUtf8()); socket-flush(); socket-disconnectFromHost(); return; } } else if (method POST) { // 处理POST请求 handlePostRequest(socket, request); } else { // 不支持的方法 QString response HTTP/1.1 501 Not Implemented\r\n Content-Length: 0\r\n Connection: close\r\n corsHeaders \r\n; socket-write(response.toUtf8()); socket-flush(); socket-disconnectFromHost(); return; } }处理post请求//处理post请求 void HttpServer::handlePostRequest(QTcpSocket *socket, const QString request) { qDebug() hello world!; // 简化示例假设请求体是JSON格式并且以空行结束 QStringList requestLines request.split(\r\n\r\n); if (requestLines.size() 2) { // 无效请求 return; } QString headersPart requestLines.at(0); QString bodyPart requestLines.at(1); // 处理请求行 QStringList headerLines headersPart.split(\r\n); if (headerLines.isEmpty()) { return; } QString requestLine headerLines.at(0); QStringList requestParts requestLine.split( ); if (requestParts.size() 3) { return; } QString method requestParts.at(0); QString path requestParts.at(1); QString httpVersion requestParts.at(2); // 添加 CORS 头 QString corsHeaders Access-Control-Allow-Origin: *\r\n // 允许所有来源 Access-Control-Allow-Methods: GET, POST, OPTIONS\r\n // 允许的方法 Access-Control-Allow-Headers: Content-Type\r\n; // 允许的请求头 if (method ! POST) { // 仅处理POST请求 return; } // 解析JSON体 QJsonDocument jsonDoc QJsonDocument::fromJson(bodyPart.toUtf8()); if (!jsonDoc.isObject()) { QString response HTTP/1.1 400 Bad Request\r\n Content-Type: text/plain\r\n Content-Length: 0\r\n Connection: close\r\n corsHeaders\r\n; socket-write(response.toUtf8()); socket-flush(); socket-disconnectFromHost(); return; } if(path ! /api/post){ QString responseBody Not Found; QString response QString(HTTP/1.1 404 Not Found\r\n Content-Type: text/plain\r\n Content-Length: %1\r\n Connection: close\r\n corsHeaders\r\n%2) .arg(responseBody.length()) .arg(responseBody); socket-write(response.toUtf8()); socket-flush(); socket-disconnectFromHost(); return; } QJsonObject jsonObj jsonDoc.object(); QString name jsonObj.value(name).toString(Guest); // 构建响应 QString responseBody QString({\message\: \Hello, %1!\}).arg(name); QString response QString(HTTP/1.1 200 OK\r\n Content-Type: application/json\r\n Content-Length: %1\r\n Connection: close\r\n corsHeaders\r\n%2) .arg(responseBody.length()) .arg(responseBody); // 发送响应 socket-write(response.toUtf8()); socket-flush(); socket-disconnectFromHost(); }main函数中启动#include mainwindow.h #include QApplication #include httpserver.h int main(int argc, char *argv[]) { QApplication a(argc, argv); quint16 port 8080; // 定义监听端口 HttpServer server(port); if (!server.start()) { return -1; } MainWindow mw; mw.show(); return a.exec(); }我们在浏览器中分别输入不同的路由发送get请求给服务器比如发送localhost:8080/api/hello或者访问根目录localhost:8080点击教案文件夹内的index.html输入姓名点击发送按钮能收到服务器回馈的json格式的数据HttpServer优化多线程模式我们之前实现的httpserver是单线程模式同一时刻只能处理一个请求为了提升并发能力我们可以改为多线程模式在收到连接后我们为新的连接创建一个新的线程处理读写httpserver简化#ifndef HTTPSERVER_H #define HTTPSERVER_H #include QObject #include QTcpServer #include QTcpSocket #include QJsonDocument class HttpServer : public QObject { Q_OBJECT public: explicit HttpServer(quint16 port, QObject *parent nullptr); bool start(); private slots: // 新连接建立触发 void onNewConnection(); // 断开连接触发的槽函数 void onDisconnected(); private: QTcpServer *tcpServer; quint16 listenPort; }; #endif // HTTPSERVER_HHttpServer具体实现#include httpserver.h #include QDebug #include QJsonDocument #include QJsonObject #include QThread #include clienthandler.h HttpServer::HttpServer(quint16 port, QObject *parent) : QObject(parent), listenPort(port) { tcpServer new QTcpServer(this); connect(tcpServer, QTcpServer::newConnection, this, HttpServer::onNewConnection); } bool HttpServer::start() { if (!tcpServer-listen(QHostAddress::Any, listenPort)) { qCritical() 无法启动服务器: tcpServer-errorString(); return false; } qDebug() HTTP服务器已启动监听端口 listenPort; return true; } void HttpServer::onNewConnection() { while (tcpServer-hasPendingConnections()) { QTcpSocket *clientSocket tcpServer-nextPendingConnection(); qDebug() 新的连接来自 clientSocket-peerAddress().toString() : clientSocket-peerPort(); // 创建一个新的线程 QThread *thread new QThread; ClientHandler *handler new ClientHandler(clientSocket); //没有父节点的对象才可移动到其他线程 clientSocket-setParent(nullptr); //需要将对象移动到子线程中 clientSocket-moveToThread(thread); handler-moveToThread(thread); connect(thread, QThread::started, handler, ClientHandler::process); connect(handler, ClientHandler::finished, thread, QThread::quit); connect(handler, ClientHandler::finished, handler, ClientHandler::deleteLater); connect(thread, QThread::finished, thread, QThread::deleteLater); thread-start(); } } //断开连接 void HttpServer::onDisconnected() { QTcpSocket *clientSocket qobject_castQTcpSocket*(sender()); if (clientSocket) { qDebug() 连接断开 clientSocket-peerAddress().toString() : clientSocket-peerPort(); clientSocket-deleteLater(); } }优化clienthandler#ifndef CLIENTHANDLER_H #define CLIENTHANDLER_H #include QObject #include QTcpSocket class HttpServer; class ClientHandler : public QObject { Q_OBJECT public: explicit ClientHandler(QTcpSocket *socket, QObject *parent nullptr); ~ ClientHandler(); void process(); signals: void finished(); private slots: void onReadyRead(); void onDisconnected(); private: QTcpSocket *clientSocket; HttpServer* _hs; // 简单的请求处理方法 void handleRequest(const QString request); //处理post请求 void handlePostRequest(const QString path, const QString bodyPart); }; #endif // CLIENTHANDLER_Hclienthandler具体实现#include clienthandler.h #include QDebug #include QHostAddress #include httpserver.h #include QJsonObject #include global.h ClientHandler::ClientHandler(QTcpSocket *socket, QObject *parent) : QObject(parent), clientSocket(socket) { connect(clientSocket, QTcpSocket::readyRead, this, ClientHandler::onReadyRead); connect(clientSocket, QTcpSocket::disconnected, this, ClientHandler::onDisconnected); registerRoutes(); } ClientHandler::~ClientHandler() { } void ClientHandler::process() { // 可以在此处进行初始化操作 } void ClientHandler::onReadyRead() { QByteArray requestData clientSocket-readAll(); QString requestString(requestData); qDebug() 收到请求 requestString; handleRequest(requestString); } void ClientHandler::onDisconnected() { qDebug() 连接断开 clientSocket-peerAddress().toString() : clientSocket-peerPort(); clientSocket-deleteLater(); emit finished(); } void ClientHandler::handlePostRequest(const QString path, const QString bodyPart) { // 添加 CORS 头 QString corsHeaders Access-Control-Allow-Origin: *\r\n // 允许所有来源 Access-Control-Allow-Methods: GET, POST, OPTIONS\r\n // 允许的方法 Access-Control-Allow-Headers: Content-Type\r\n; // 允许的请求头 // 解析JSON体 QJsonDocument jsonDoc QJsonDocument::fromJson(bodyPart.toUtf8()); if (!jsonDoc.isObject()) { QString response HTTP/1.1 400 Bad Request\r\n Content-Type: text/plain\r\n Content-Length: 0\r\n Connection: close\r\n corsHeaders\r\n; clientSocket-write(response.toUtf8()); clientSocket-flush(); clientSocket-disconnectFromHost(); return; } //判断路由是否存在 if(!routesPost.contains(path)){ QString responseBody Not Found; QString response QString(HTTP/1.1 404 Not Found\r\n Content-Type: text/plain\r\n Content-Length: %1\r\n Connection: close\r\n corsHeaders\r\n%2) .arg(responseBody.length()) .arg(responseBody); clientSocket-write(response.toUtf8()); clientSocket-flush(); clientSocket-disconnectFromHost(); return; } //处理请求 routesPost[path](clientSocket,jsonDoc); } // 修改 handleRequest 方法以调用 handlePostRequest void ClientHandler::handleRequest(const QString request) { // 解析请求行 QStringList requestLines request.split(\r\n); if (requestLines.isEmpty()) { // 无效请求 return; } QString requestLine requestLines.at(0); QStringList requestParts requestLine.split( ); if (requestParts.size() 3) { // 无效请求行 return; } QString method requestParts.at(0); QString path requestParts.at(1); QString httpVersion requestParts.at(2); // 添加 CORS 头, 支持跨域访问 QString corsHeaders Access-Control-Allow-Origin: *\r\n // 允许所有来源 Access-Control-Allow-Methods: GET, POST, OPTIONS\r\n // 允许的方法 Access-Control-Allow-Headers: Content-Type\r\n; // 允许的请求头 if (method OPTIONS) { // 处理预检请求 QString response HTTP/1.1 204 No Content\r\n Connection: close\r\n corsHeaders \r\n; clientSocket-write(response.toUtf8()); clientSocket-flush(); clientSocket-disconnectFromHost(); return; } if (method GET) { if(!routesGet.contains(path)){ // 处理404 QString body 404 - Not Found\n; QString response QString(HTTP/1.1 404 Not Found\r\n Content-Type: text/plain\r\n Content-Length: %1\r\n Connection: close\r\n corsHeaders \r\n%2) .arg(body.length()) .arg(body); clientSocket-write(response.toUtf8()); clientSocket-flush(); clientSocket-disconnectFromHost(); return; } routesGet[path](clientSocket,request); } else if (method POST) { QString bodyPart requestLines.at(requestLines.length()-1); // 处理POST请求 handlePostRequest(path, bodyPart); } else { // 不支持的方法 QString response HTTP/1.1 501 Not Implemented\r\n Content-Length: 0\r\n Connection: close\r\n corsHeaders \r\n; clientSocket-write(response.toUtf8()); clientSocket-flush(); clientSocket-disconnectFromHost(); return; } }新增global.h和global.cpp#ifndef GLOBAL_H #define GLOBAL_H #include QObject #include functional #include QTcpSocket //注册路由 extern void registerRoutes(); //存储get和post路由 extern QMapQString, std::functionvoid(QTcpSocket*, const QString) routesGet; extern QMapQString, std::functionvoid(QTcpSocket*, const QJsonDocument ) routesPost; #endif // GLOBAL_Hgloal.cpp#include global.h #include QJsonDocument #include QJsonObject //注册路由 void registerRoutes(){ // 添加 CORS 头, 支持跨域访问 QString corsHeaders Access-Control-Allow-Origin: *\r\n // 允许所有来源 Access-Control-Allow-Methods: GET, POST, OPTIONS\r\n // 允许的方法 Access-Control-Allow-Headers: Content-Type\r\n; // 允许的请求头 //注册路由和回调函数写入map routesGet[/] [](QTcpSocket *socket, const QString request){ Q_UNUSED(request); QString body 欢迎使用Qt 5.12 创建的简单HTTP服务器\n; QString response QString(HTTP/1.1 200 OK\r\n Content-Type: text/plain; charsetutf-8\r\n Content-Length: %1\r\n Connection: close\r\n corsHeaders \r\n%2) .arg(body.length()) .arg(body); socket-write(response.toUtf8()); socket-flush(); socket-disconnectFromHost(); }; //注册路由和回调函数 routesGet[/api/hello] [](QTcpSocket *socket, const QString request){ Q_UNUSED(request); QString body {\message\: \Hello, World!\}\n; QString response QString(HTTP/1.1 200 OK\r\n Content-Type: application/json\r\n Content-Length: %1\r\n Connection: close\r\n corsHeaders \r\n%2) .arg(body.length()) .arg(body); socket-write(response.toUtf8()); socket-flush(); socket-disconnectFromHost(); }; //注册post请求 routesPost[/api/post] [](QTcpSocket *socket, const QJsonDocument jsonDoc){ //解析document QJsonObject jsonObj jsonDoc.object(); QString name jsonObj.value(name).toString(Guest); // 构建响应 QString responseBody QString({\message\: \Hello, %1!\}).arg(name); QString response QString(HTTP/1.1 200 OK\r\n Content-Type: application/json\r\n Content-Length: %1\r\n Connection: close\r\n corsHeaders\r\n%2) .arg(responseBody.length()) .arg(responseBody); // 发送响应 socket-write(response.toUtf8()); socket-flush(); socket-disconnectFromHost(); }; } //存储get和post路由 QMapQString, std::functionvoid(QTcpSocket*, const QString) routesGet; QMapQString, std::functionvoid(QTcpSocket*, const QJsonDocument ) routesPost;main函数中添加调用#include mainwindow.h #include QApplication #include httpserver.h int main(int argc, char *argv[]) { QApplication a(argc, argv); quint16 port 8080; // 定义监听端口 HttpServer server(port); if (!server.start()) { return -1; } MainWindow mw; mw.show(); return a.exec(); }发送http请求Qt 的网络模块 (QtNetwork) 提供了一组高级 API用于处理网络通信包括 HTTP、FTP 等协议。对于 HTTP 客户端功能QNetworkAccessManager是核心类配合QNetworkRequest和QNetworkReply使用可以方便地发送各种 HTTP 请求并处理响应。关键特点异步操作所有网络请求都是异步的不会阻塞主线程。信号与槽机制使用 Qt 的信号与槽机制轻松处理请求完成、错误等事件。支持多种协议不仅支持 HTTP还支持 HTTPS、FTP 等。主要类介绍QNetworkAccessManager负责管理网络请求可以发送 GET、POST、PUT、DELETE 等 HTTP 请求。常用方法get(const QNetworkRequest request)post(const QNetworkRequest request, const QByteArray data)put(const QNetworkRequest request, const QByteArray data)deleteResource(const QNetworkRequest request)QNetworkRequest表示网络请求主要包含请求的 URL、请求头等信息。常用方法setHeader(QNetworkRequest::KnownHeaders header, const QVariant value)setRawHeader(const QByteArray headerName, const QByteArray headerValue)QNetworkReply表示网络响应包含响应数据、状态码等信息。通常与信号finished()、readyRead()、errorOccurred()等配合使用。发送 GET 请求#include mainwindow.h #include QApplication #include QNetworkAccessManager #include QNetworkReply // 创建网络访问管理器 QNetworkAccessManager manager; void get_req(QString url_req){ // 创建请求对象 QUrl url(url_req); QNetworkRequest request(url); // 发送 GET 请求 QNetworkReply *reply manager.get(request); // 连接请求完成的信号 QObject::connect(reply, QNetworkReply::finished, []() { if (reply-error() QNetworkReply::NoError) { // 读取响应数据 QByteArray responseData reply-readAll(); qDebug() GET 响应数据: QString::fromUtf8(responseData); } else { // 输出错误信息 qDebug() 错误: reply-errorString(); } reply-deleteLater(); }); } int main(int argc, char *argv[]) { QApplication a(argc, argv); get_req(http://localhost:8080); MainWindow mw; mw.show(); return a.exec(); }解释创建QNetworkAccessManager实例用于管理网络请求。创建QNetworkRequest对象指定请求的 URL。发送 GET 请求调用manager.get(request)发送请求返回一个QNetworkReply对象。连接信号通过连接finished信号处理响应或错误。读取响应数据如果没有错误读取并输出响应数据。GET 请求用于从服务器获取数据通常不会带有请求体。发送 POST 请求POST 请求用于向服务器发送数据通常包含请求体例如 JSON 数据。#include QCoreApplication #include QNetworkAccessManager #include QNetworkRequest #include QNetworkReply #include QUrl #include QDebug #include QJsonDocument #include QJsonObject void post_req(QString url_req, QJsonObject json){ // 创建请求对象 QUrl url(url_req); QNetworkRequest request(url); // 设置请求头例如内容类型为 JSON request.setHeader(QNetworkRequest::ContentTypeHeader, application/json); // 转为json对象为json文档 QJsonDocument jsonDoc(json); // 转成字节流数组 QByteArray jsonData jsonDoc.toJson(); // 发送 POST 请求 QNetworkReply *reply manager.post(request, jsonData); // 连接请求完成的信号 QObject::connect(reply, QNetworkReply::finished, []() { if (reply-error() QNetworkReply::NoError) { // 读取响应数据 QByteArray responseData reply-readAll(); qDebug() POST 响应数据: responseData; } else { // 输出错误信息 qDebug() 错误: reply-errorString(); } reply-deleteLater(); }); } int main(int argc, char *argv[]) { QApplication a(argc, argv); //构造json数据 QJsonObject jsonobj; jsonobj[name] zack; post_req(http://localhost:8080/api/post,jsonobj); MainWindow mw; mw.show(); return a.exec(); }解释设置请求头指定内容类型为application/json。构建 JSON 数据使用QJsonObject和QJsonDocument构建 JSON 格式的请求体。发送 POST 请求调用manager.post(request, jsonData)将 JSON 数据作为请求体发送。处理响应与 GET 请求类似通过信号处理响应或错误。处理响应与错误在上述示例中通过连接finished信号来处理响应。除了finished信号外还可以连接其他信号来实时处理数据或错误。常用信号readyRead()当有新的数据可读时触发可以用来逐步读取响应数据。errorOccurred(QNetworkReply::NetworkError code)当发生错误时触发提供错误类型。void get_req_error(QString url_req){ // 创建请求对象 QUrl url(url_req); QNetworkRequest request(url); // 发送 GET 请求 QNetworkReply *reply manager.get(request); // 连接请求完成的信号 QObject::connect(reply, QNetworkReply::finished, []() { if (reply-error() QNetworkReply::NoError) { // 读取响应数据 QByteArray responseData reply-readAll(); qDebug() GET 响应数据: QString::fromUtf8(responseData); } else { // 输出错误信息 qDebug() 错误: reply-errorString(); } reply-deleteLater(); }); // 连接错误信号 QObject::connect(reply, QOverloadQNetworkReply::NetworkError::of(QNetworkReply::error), [](QNetworkReply::NetworkError code){ qDebug() 发生错误错误码: code 错误信息: reply-errorString(); }); }说明连接errorOccurred信号当发生网络错误时输出错误码和错误信息。故意使用无效 URL触发错误演示错误处理。
66、HTTP协议(课外拓展)---------网络编程
发布时间:2026/6/16 2:55:27
HTTP协议(课外拓展)HTTP的基本概念HTTPHyperText Transfer Protocol 是用于分布式、协作式和超媒体信息系统的应用层协议。它是万维网的基础定义了浏览器与服务器之间传输数据的规则。HTTP是基于tcp实现的短链接服务器收到客户端处理后将消息推送给客户端然后关闭单方连接客户端收到后也关闭单方连接。HTTP协议的工作原理1客户端发起请求浏览器或其他客户端向服务器发送HTTP请求。2服务器处理请求服务器接收到请求后进行相应的处理如读取文件、执行脚本等。3服务器返回响应处理完成后服务器返回HTTP响应给客户端。4客户端处理响应客户端接收到响应后渲染页面或进行其他操作。常见的HTTP方法和状态码●HTTP方法○GET请求指定的资源。○POST向指定资源提交数据。○PUT更新指定资源。○DELETE删除指定资源。○HEAD获取资源的头部信息。○OPTIONS获取服务器支持的HTTP方法。●HTTP状态码○200 OK请求成功。○201 Created资源创建成功。○400 Bad Request客户端请求有语法错误。○401 Unauthorized未经授权。○404 Not Found资源未找到。○500 Internal Server Error服务器内部错误。HTTP 头部的基本格式1请求/响应行Request/Response Line○请求行Request Line包含请求方法、请求 URI 和 HTTP 版本。■例子GET /index.html HTTP/1.1○响应行Response Line包含 HTTP 版本、状态码和状态消息。■例子HTTP/1.1 200 OK2头部字段Header Fields○每个头部字段由一个字段名和一个字段值组成中间用冒号 : 分隔。字段名和字段值之间可以有空格。○头部字段的格式如下Field-Name: Field-Value头部字段可以有多行通常使用换行符CRLF来分隔。空行Empty Line请求/响应行和头部字段之间用一个空行分隔表示头部的结束。HTTP 请求示例GET /index.html HTTP/1.1 Host: www.example.com User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3 Accept: text/html,application/xhtmlxml,application/xml;q0.9,image/webp,*/*;q0.8 Accept-Language: en-US,en;q0.5 Connection: keep-aliveHTTP 响应示例HTTP/1.1 200 OK Date: Wed, 21 Oct 2015 07:28:00 GMT Server: Apache/2.4.1 (Unix) Last-Modified: Wed, 21 Oct 2015 07:28:00 GMT Content-Type: text/html; charsetUTF-8 Content-Length: 1234 Connection: close html headtitleExample/title/head bodyh1Hello, World!/h1/body /html常用 HTTP 头部字段请求头部字段Host: 指定请求的主机名和端口号。User-Agent: 指定发起请求的客户端软件信息。Accept: 指定客户端能够接收的内容类型。Authorization: 用于提供身份验证凭据。Content-Type: 指定请求体的媒体类型通常在 POST 请求中使用。响应头部字段Content-Type: 指定响应体的媒体类型。Content-Length: 指定响应体的字节长度。Set-Cookie: 用于设置 HTTP cookies。Cache-Control: 指定缓存机制。Location: 用于重定向指定新的 URL。以下是一些常见的 HTTP 请求内容类型及其用途application/json用于发送 JSON 格式的数据。广泛用于 RESTful API。示例Content-Type: application/jsonapplication/x-www-form-urlencoded用于表单提交时数据以 URL 编码的形式发送。每个键值对通过符号连接键和值通过符号连接。示例Content-Type: application/x-www-form-urlencodedmultipart/form-data用于表单提交时尤其是当表单包含文件上传时。它允许在同一请求中发送多种类型的数据。示例Content-Type: multipart/form-data; boundary---011000010111000001110001text/plain用于发送纯文本数据不带格式。示例Content-Type: text/plainapplication/xml用于发送 XML 格式的数据。示例Content-Type: application/xmltext/html用于发送 HTML 格式的数据。示例Content-Type: text/htmlapplication/octet-stream用于发送二进制数据通常用于文件下载或未指定类型的内容。示例Content-Type: application/octet-streamQt5与Qt6的差异在Qt6中引入了更高级的HTTP服务器类如QHttpServer简化了HTTP服务器的实现过程。而在Qt5.12中需要使用QTcpServer和QTcpSocket结合手动解析HTTP请求和构建响应。这不仅增加了实现的复杂度但也提供了更多的学习和理解HTTP协议的机会。跨域问题跨域问题CORSCross-Origin Resource Sharing是指浏览器的同源策略限制了一个网页能够请求不同源域名、协议或端口不同的资源。由于安全原因浏览器默认不允许跨域请求以防止恶意网站窃取用户数据或进行其他恶意操作。什么是同源策略同源策略是指在一个网页中脚本只能访问与其同源的资源。所谓同源是指协议相同http、https域名相同example.com、sub.example.com端口相同80、443等例如以下两个 URL 是同源的http://example.com/page1http://example.com/page2而以下两个 URL 则是不同源的http://example.com/page1http://another.com/page2跨域请求的场景跨域请求通常发生在以下几种场景中AJAX 请求当网页通过 JavaScript 使用XMLHttpRequest或fetchAPI 发起请求时如果请求的 URL 不同于当前网页的 URL就会发生跨域请求。Web 字体如果网页使用了来自不同源的字体文件浏览器会进行跨域请求。图片、视频等媒体资源如果网页中引用了来自不同源的图片或视频也会涉及跨域请求。如何解决跨域问题CORS跨源资源共享例如以下响应头允许来自任意源的 GET 和 POST 请求Access-Control-Allow-Origin: * Access-Control-Allow-Methods: GET, POST服务器可以通过设置 HTTP 响应头来允许特定的源访问资源。常用的 CORS 头包括Access-Control-Allow-Origin: 指定允许哪些源进行访问可以是具体的域名或*表示允许所有源。Access-Control-Allow-Methods: 指定允许的 HTTP 方法GET, POST, OPTIONS等。Access-Control-Allow-Headers: 指定允许的请求头。JSONPJSON with Padding通过script标签加载数据利用 JavaScript 的动态脚本加载特性来实现跨域请求。虽然这种方法可以解决跨域问题但它只支持 GET 请求并且存在安全隐患。代理服务器在同源的服务器上设置一个代理客户端请求代理服务器代理服务器再去请求目标资源。这样可以绕过浏览器的同源策略。使用 WebSocketWebSocket 协议不受同源策略的限制可以用于实现跨域通信。实现HttpServerHttpServer接下来我们基于QTcpServer和QTcpSocket实现一个http服务#ifndef HTTPSERVER_H #define HTTPSERVER_H #include QObject #include QTcpServer #include QTcpSocket class HttpServer : public QObject { Q_OBJECT public: explicit HttpServer(quint16 port, QObject *parent nullptr); bool start(); private slots: // 新连接建立触发 void onNewConnection(); // 读就绪触发的槽函数 void onReadyRead(); // 断开连接触发的槽函数 void onDisconnected(); private: QTcpServer *tcpServer; quint16 listenPort; // 简单的请求处理方法 void handleRequest(QTcpSocket *socket, const QString request); // 处理post请求 void handlePostRequest(QTcpSocket *socket, const QString request); }; #endif // HTTPSERVER_H添加实现构造函数里创建QTcpServer对象并且连接信号HttpServer::HttpServer(quint16 port, QObject *parent) : QObject(parent), listenPort(port) { tcpServer new QTcpServer(this); connect(tcpServer, QTcpServer::newConnection, this, HttpServer::onNewConnection); }新连接触发的槽函数void HttpServer::onNewConnection() { while (tcpServer-hasPendingConnections()) { QTcpSocket *clientSocket tcpServer-nextPendingConnection(); qDebug() 新的连接来自 clientSocket-peerAddress().toString() : clientSocket-peerPort(); connect(clientSocket, QTcpSocket::readyRead, this, []() { onReadyRead(); }); connect(clientSocket, QTcpSocket::disconnected, this, []() { onDisconnected(); }); } }开始启动bool HttpServer::start() { if (!tcpServer-listen(QHostAddress::Any, listenPort)) { qCritical() 无法启动服务器: tcpServer-errorString(); return false; } qDebug() HTTP服务器已启动监听端口 listenPort; return true; }读取数据和断开连接//读取数据 void HttpServer::onReadyRead() { QTcpSocket *clientSocket qobject_castQTcpSocket*(sender()); if (!clientSocket) return; while (clientSocket-canReadLine()) { QByteArray requestData clientSocket-readAll(); QString requestString(requestData); qDebug() 收到请求 requestString; handleRequest(clientSocket, requestString); } } //断开连接 void HttpServer::onDisconnected() { QTcpSocket *clientSocket qobject_castQTcpSocket*(sender()); if (clientSocket) { qDebug() 连接断开 clientSocket-peerAddress().toString() : clientSocket-peerPort(); clientSocket-deleteLater(); } }处理get和post请求// 修改 handleRequest 方法以调用 handlePostRequest void HttpServer::handleRequest(QTcpSocket *socket, const QString request) { // 解析请求行 QStringList requestLines request.split(\r\n); if (requestLines.isEmpty()) { // 无效请求 return; } QString requestLine requestLines.at(0); QStringList requestParts requestLine.split( ); if (requestParts.size() 3) { // 无效请求行 return; } QString method requestParts.at(0); QString path requestParts.at(1); QString httpVersion requestParts.at(2); // 添加 CORS 头, 支持跨域访问 QString corsHeaders Access-Control-Allow-Origin: *\r\n // 允许所有来源 Access-Control-Allow-Methods: GET, POST, OPTIONS\r\n // 允许的方法 Access-Control-Allow-Headers: Content-Type\r\n; // 允许的请求头 if (method OPTIONS) { // 处理预检请求 QString response HTTP/1.1 204 No Content\r\n Connection: close\r\n corsHeaders \r\n; socket-write(response.toUtf8()); socket-flush(); socket-disconnectFromHost(); return; } if (method GET) { // 处理GET请求之前的逻辑 if (path /) { QString body 欢迎使用Qt 5.12 创建的简单HTTP服务器\n; QString response QString(HTTP/1.1 200 OK\r\n Content-Type: text/plain; charsetutf-8\r\n Content-Length: %1\r\n Connection: close\r\n corsHeaders \r\n%2) .arg(body.length()) .arg(body); socket-write(response.toUtf8()); socket-flush(); socket-disconnectFromHost(); return; } else if (path /api/hello) { QString body {\message\: \Hello, World!\}\n; QString response QString(HTTP/1.1 200 OK\r\n Content-Type: application/json\r\n Content-Length: %1\r\n Connection: close\r\n corsHeaders \r\n%2) .arg(body.length()) .arg(body); socket-write(response.toUtf8()); socket-flush(); socket-disconnectFromHost(); return; } else { // 处理404 QString body 404 - Not Found\n; QString response QString(HTTP/1.1 404 Not Found\r\n Content-Type: text/plain\r\n Content-Length: %1\r\n Connection: close\r\n corsHeaders \r\n%2) .arg(body.length()) .arg(body); socket-write(response.toUtf8()); socket-flush(); socket-disconnectFromHost(); return; } } else if (method POST) { // 处理POST请求 handlePostRequest(socket, request); } else { // 不支持的方法 QString response HTTP/1.1 501 Not Implemented\r\n Content-Length: 0\r\n Connection: close\r\n corsHeaders \r\n; socket-write(response.toUtf8()); socket-flush(); socket-disconnectFromHost(); return; } }处理post请求//处理post请求 void HttpServer::handlePostRequest(QTcpSocket *socket, const QString request) { qDebug() hello world!; // 简化示例假设请求体是JSON格式并且以空行结束 QStringList requestLines request.split(\r\n\r\n); if (requestLines.size() 2) { // 无效请求 return; } QString headersPart requestLines.at(0); QString bodyPart requestLines.at(1); // 处理请求行 QStringList headerLines headersPart.split(\r\n); if (headerLines.isEmpty()) { return; } QString requestLine headerLines.at(0); QStringList requestParts requestLine.split( ); if (requestParts.size() 3) { return; } QString method requestParts.at(0); QString path requestParts.at(1); QString httpVersion requestParts.at(2); // 添加 CORS 头 QString corsHeaders Access-Control-Allow-Origin: *\r\n // 允许所有来源 Access-Control-Allow-Methods: GET, POST, OPTIONS\r\n // 允许的方法 Access-Control-Allow-Headers: Content-Type\r\n; // 允许的请求头 if (method ! POST) { // 仅处理POST请求 return; } // 解析JSON体 QJsonDocument jsonDoc QJsonDocument::fromJson(bodyPart.toUtf8()); if (!jsonDoc.isObject()) { QString response HTTP/1.1 400 Bad Request\r\n Content-Type: text/plain\r\n Content-Length: 0\r\n Connection: close\r\n corsHeaders\r\n; socket-write(response.toUtf8()); socket-flush(); socket-disconnectFromHost(); return; } if(path ! /api/post){ QString responseBody Not Found; QString response QString(HTTP/1.1 404 Not Found\r\n Content-Type: text/plain\r\n Content-Length: %1\r\n Connection: close\r\n corsHeaders\r\n%2) .arg(responseBody.length()) .arg(responseBody); socket-write(response.toUtf8()); socket-flush(); socket-disconnectFromHost(); return; } QJsonObject jsonObj jsonDoc.object(); QString name jsonObj.value(name).toString(Guest); // 构建响应 QString responseBody QString({\message\: \Hello, %1!\}).arg(name); QString response QString(HTTP/1.1 200 OK\r\n Content-Type: application/json\r\n Content-Length: %1\r\n Connection: close\r\n corsHeaders\r\n%2) .arg(responseBody.length()) .arg(responseBody); // 发送响应 socket-write(response.toUtf8()); socket-flush(); socket-disconnectFromHost(); }main函数中启动#include mainwindow.h #include QApplication #include httpserver.h int main(int argc, char *argv[]) { QApplication a(argc, argv); quint16 port 8080; // 定义监听端口 HttpServer server(port); if (!server.start()) { return -1; } MainWindow mw; mw.show(); return a.exec(); }我们在浏览器中分别输入不同的路由发送get请求给服务器比如发送localhost:8080/api/hello或者访问根目录localhost:8080点击教案文件夹内的index.html输入姓名点击发送按钮能收到服务器回馈的json格式的数据HttpServer优化多线程模式我们之前实现的httpserver是单线程模式同一时刻只能处理一个请求为了提升并发能力我们可以改为多线程模式在收到连接后我们为新的连接创建一个新的线程处理读写httpserver简化#ifndef HTTPSERVER_H #define HTTPSERVER_H #include QObject #include QTcpServer #include QTcpSocket #include QJsonDocument class HttpServer : public QObject { Q_OBJECT public: explicit HttpServer(quint16 port, QObject *parent nullptr); bool start(); private slots: // 新连接建立触发 void onNewConnection(); // 断开连接触发的槽函数 void onDisconnected(); private: QTcpServer *tcpServer; quint16 listenPort; }; #endif // HTTPSERVER_HHttpServer具体实现#include httpserver.h #include QDebug #include QJsonDocument #include QJsonObject #include QThread #include clienthandler.h HttpServer::HttpServer(quint16 port, QObject *parent) : QObject(parent), listenPort(port) { tcpServer new QTcpServer(this); connect(tcpServer, QTcpServer::newConnection, this, HttpServer::onNewConnection); } bool HttpServer::start() { if (!tcpServer-listen(QHostAddress::Any, listenPort)) { qCritical() 无法启动服务器: tcpServer-errorString(); return false; } qDebug() HTTP服务器已启动监听端口 listenPort; return true; } void HttpServer::onNewConnection() { while (tcpServer-hasPendingConnections()) { QTcpSocket *clientSocket tcpServer-nextPendingConnection(); qDebug() 新的连接来自 clientSocket-peerAddress().toString() : clientSocket-peerPort(); // 创建一个新的线程 QThread *thread new QThread; ClientHandler *handler new ClientHandler(clientSocket); //没有父节点的对象才可移动到其他线程 clientSocket-setParent(nullptr); //需要将对象移动到子线程中 clientSocket-moveToThread(thread); handler-moveToThread(thread); connect(thread, QThread::started, handler, ClientHandler::process); connect(handler, ClientHandler::finished, thread, QThread::quit); connect(handler, ClientHandler::finished, handler, ClientHandler::deleteLater); connect(thread, QThread::finished, thread, QThread::deleteLater); thread-start(); } } //断开连接 void HttpServer::onDisconnected() { QTcpSocket *clientSocket qobject_castQTcpSocket*(sender()); if (clientSocket) { qDebug() 连接断开 clientSocket-peerAddress().toString() : clientSocket-peerPort(); clientSocket-deleteLater(); } }优化clienthandler#ifndef CLIENTHANDLER_H #define CLIENTHANDLER_H #include QObject #include QTcpSocket class HttpServer; class ClientHandler : public QObject { Q_OBJECT public: explicit ClientHandler(QTcpSocket *socket, QObject *parent nullptr); ~ ClientHandler(); void process(); signals: void finished(); private slots: void onReadyRead(); void onDisconnected(); private: QTcpSocket *clientSocket; HttpServer* _hs; // 简单的请求处理方法 void handleRequest(const QString request); //处理post请求 void handlePostRequest(const QString path, const QString bodyPart); }; #endif // CLIENTHANDLER_Hclienthandler具体实现#include clienthandler.h #include QDebug #include QHostAddress #include httpserver.h #include QJsonObject #include global.h ClientHandler::ClientHandler(QTcpSocket *socket, QObject *parent) : QObject(parent), clientSocket(socket) { connect(clientSocket, QTcpSocket::readyRead, this, ClientHandler::onReadyRead); connect(clientSocket, QTcpSocket::disconnected, this, ClientHandler::onDisconnected); registerRoutes(); } ClientHandler::~ClientHandler() { } void ClientHandler::process() { // 可以在此处进行初始化操作 } void ClientHandler::onReadyRead() { QByteArray requestData clientSocket-readAll(); QString requestString(requestData); qDebug() 收到请求 requestString; handleRequest(requestString); } void ClientHandler::onDisconnected() { qDebug() 连接断开 clientSocket-peerAddress().toString() : clientSocket-peerPort(); clientSocket-deleteLater(); emit finished(); } void ClientHandler::handlePostRequest(const QString path, const QString bodyPart) { // 添加 CORS 头 QString corsHeaders Access-Control-Allow-Origin: *\r\n // 允许所有来源 Access-Control-Allow-Methods: GET, POST, OPTIONS\r\n // 允许的方法 Access-Control-Allow-Headers: Content-Type\r\n; // 允许的请求头 // 解析JSON体 QJsonDocument jsonDoc QJsonDocument::fromJson(bodyPart.toUtf8()); if (!jsonDoc.isObject()) { QString response HTTP/1.1 400 Bad Request\r\n Content-Type: text/plain\r\n Content-Length: 0\r\n Connection: close\r\n corsHeaders\r\n; clientSocket-write(response.toUtf8()); clientSocket-flush(); clientSocket-disconnectFromHost(); return; } //判断路由是否存在 if(!routesPost.contains(path)){ QString responseBody Not Found; QString response QString(HTTP/1.1 404 Not Found\r\n Content-Type: text/plain\r\n Content-Length: %1\r\n Connection: close\r\n corsHeaders\r\n%2) .arg(responseBody.length()) .arg(responseBody); clientSocket-write(response.toUtf8()); clientSocket-flush(); clientSocket-disconnectFromHost(); return; } //处理请求 routesPost[path](clientSocket,jsonDoc); } // 修改 handleRequest 方法以调用 handlePostRequest void ClientHandler::handleRequest(const QString request) { // 解析请求行 QStringList requestLines request.split(\r\n); if (requestLines.isEmpty()) { // 无效请求 return; } QString requestLine requestLines.at(0); QStringList requestParts requestLine.split( ); if (requestParts.size() 3) { // 无效请求行 return; } QString method requestParts.at(0); QString path requestParts.at(1); QString httpVersion requestParts.at(2); // 添加 CORS 头, 支持跨域访问 QString corsHeaders Access-Control-Allow-Origin: *\r\n // 允许所有来源 Access-Control-Allow-Methods: GET, POST, OPTIONS\r\n // 允许的方法 Access-Control-Allow-Headers: Content-Type\r\n; // 允许的请求头 if (method OPTIONS) { // 处理预检请求 QString response HTTP/1.1 204 No Content\r\n Connection: close\r\n corsHeaders \r\n; clientSocket-write(response.toUtf8()); clientSocket-flush(); clientSocket-disconnectFromHost(); return; } if (method GET) { if(!routesGet.contains(path)){ // 处理404 QString body 404 - Not Found\n; QString response QString(HTTP/1.1 404 Not Found\r\n Content-Type: text/plain\r\n Content-Length: %1\r\n Connection: close\r\n corsHeaders \r\n%2) .arg(body.length()) .arg(body); clientSocket-write(response.toUtf8()); clientSocket-flush(); clientSocket-disconnectFromHost(); return; } routesGet[path](clientSocket,request); } else if (method POST) { QString bodyPart requestLines.at(requestLines.length()-1); // 处理POST请求 handlePostRequest(path, bodyPart); } else { // 不支持的方法 QString response HTTP/1.1 501 Not Implemented\r\n Content-Length: 0\r\n Connection: close\r\n corsHeaders \r\n; clientSocket-write(response.toUtf8()); clientSocket-flush(); clientSocket-disconnectFromHost(); return; } }新增global.h和global.cpp#ifndef GLOBAL_H #define GLOBAL_H #include QObject #include functional #include QTcpSocket //注册路由 extern void registerRoutes(); //存储get和post路由 extern QMapQString, std::functionvoid(QTcpSocket*, const QString) routesGet; extern QMapQString, std::functionvoid(QTcpSocket*, const QJsonDocument ) routesPost; #endif // GLOBAL_Hgloal.cpp#include global.h #include QJsonDocument #include QJsonObject //注册路由 void registerRoutes(){ // 添加 CORS 头, 支持跨域访问 QString corsHeaders Access-Control-Allow-Origin: *\r\n // 允许所有来源 Access-Control-Allow-Methods: GET, POST, OPTIONS\r\n // 允许的方法 Access-Control-Allow-Headers: Content-Type\r\n; // 允许的请求头 //注册路由和回调函数写入map routesGet[/] [](QTcpSocket *socket, const QString request){ Q_UNUSED(request); QString body 欢迎使用Qt 5.12 创建的简单HTTP服务器\n; QString response QString(HTTP/1.1 200 OK\r\n Content-Type: text/plain; charsetutf-8\r\n Content-Length: %1\r\n Connection: close\r\n corsHeaders \r\n%2) .arg(body.length()) .arg(body); socket-write(response.toUtf8()); socket-flush(); socket-disconnectFromHost(); }; //注册路由和回调函数 routesGet[/api/hello] [](QTcpSocket *socket, const QString request){ Q_UNUSED(request); QString body {\message\: \Hello, World!\}\n; QString response QString(HTTP/1.1 200 OK\r\n Content-Type: application/json\r\n Content-Length: %1\r\n Connection: close\r\n corsHeaders \r\n%2) .arg(body.length()) .arg(body); socket-write(response.toUtf8()); socket-flush(); socket-disconnectFromHost(); }; //注册post请求 routesPost[/api/post] [](QTcpSocket *socket, const QJsonDocument jsonDoc){ //解析document QJsonObject jsonObj jsonDoc.object(); QString name jsonObj.value(name).toString(Guest); // 构建响应 QString responseBody QString({\message\: \Hello, %1!\}).arg(name); QString response QString(HTTP/1.1 200 OK\r\n Content-Type: application/json\r\n Content-Length: %1\r\n Connection: close\r\n corsHeaders\r\n%2) .arg(responseBody.length()) .arg(responseBody); // 发送响应 socket-write(response.toUtf8()); socket-flush(); socket-disconnectFromHost(); }; } //存储get和post路由 QMapQString, std::functionvoid(QTcpSocket*, const QString) routesGet; QMapQString, std::functionvoid(QTcpSocket*, const QJsonDocument ) routesPost;main函数中添加调用#include mainwindow.h #include QApplication #include httpserver.h int main(int argc, char *argv[]) { QApplication a(argc, argv); quint16 port 8080; // 定义监听端口 HttpServer server(port); if (!server.start()) { return -1; } MainWindow mw; mw.show(); return a.exec(); }发送http请求Qt 的网络模块 (QtNetwork) 提供了一组高级 API用于处理网络通信包括 HTTP、FTP 等协议。对于 HTTP 客户端功能QNetworkAccessManager是核心类配合QNetworkRequest和QNetworkReply使用可以方便地发送各种 HTTP 请求并处理响应。关键特点异步操作所有网络请求都是异步的不会阻塞主线程。信号与槽机制使用 Qt 的信号与槽机制轻松处理请求完成、错误等事件。支持多种协议不仅支持 HTTP还支持 HTTPS、FTP 等。主要类介绍QNetworkAccessManager负责管理网络请求可以发送 GET、POST、PUT、DELETE 等 HTTP 请求。常用方法get(const QNetworkRequest request)post(const QNetworkRequest request, const QByteArray data)put(const QNetworkRequest request, const QByteArray data)deleteResource(const QNetworkRequest request)QNetworkRequest表示网络请求主要包含请求的 URL、请求头等信息。常用方法setHeader(QNetworkRequest::KnownHeaders header, const QVariant value)setRawHeader(const QByteArray headerName, const QByteArray headerValue)QNetworkReply表示网络响应包含响应数据、状态码等信息。通常与信号finished()、readyRead()、errorOccurred()等配合使用。发送 GET 请求#include mainwindow.h #include QApplication #include QNetworkAccessManager #include QNetworkReply // 创建网络访问管理器 QNetworkAccessManager manager; void get_req(QString url_req){ // 创建请求对象 QUrl url(url_req); QNetworkRequest request(url); // 发送 GET 请求 QNetworkReply *reply manager.get(request); // 连接请求完成的信号 QObject::connect(reply, QNetworkReply::finished, []() { if (reply-error() QNetworkReply::NoError) { // 读取响应数据 QByteArray responseData reply-readAll(); qDebug() GET 响应数据: QString::fromUtf8(responseData); } else { // 输出错误信息 qDebug() 错误: reply-errorString(); } reply-deleteLater(); }); } int main(int argc, char *argv[]) { QApplication a(argc, argv); get_req(http://localhost:8080); MainWindow mw; mw.show(); return a.exec(); }解释创建QNetworkAccessManager实例用于管理网络请求。创建QNetworkRequest对象指定请求的 URL。发送 GET 请求调用manager.get(request)发送请求返回一个QNetworkReply对象。连接信号通过连接finished信号处理响应或错误。读取响应数据如果没有错误读取并输出响应数据。GET 请求用于从服务器获取数据通常不会带有请求体。发送 POST 请求POST 请求用于向服务器发送数据通常包含请求体例如 JSON 数据。#include QCoreApplication #include QNetworkAccessManager #include QNetworkRequest #include QNetworkReply #include QUrl #include QDebug #include QJsonDocument #include QJsonObject void post_req(QString url_req, QJsonObject json){ // 创建请求对象 QUrl url(url_req); QNetworkRequest request(url); // 设置请求头例如内容类型为 JSON request.setHeader(QNetworkRequest::ContentTypeHeader, application/json); // 转为json对象为json文档 QJsonDocument jsonDoc(json); // 转成字节流数组 QByteArray jsonData jsonDoc.toJson(); // 发送 POST 请求 QNetworkReply *reply manager.post(request, jsonData); // 连接请求完成的信号 QObject::connect(reply, QNetworkReply::finished, []() { if (reply-error() QNetworkReply::NoError) { // 读取响应数据 QByteArray responseData reply-readAll(); qDebug() POST 响应数据: responseData; } else { // 输出错误信息 qDebug() 错误: reply-errorString(); } reply-deleteLater(); }); } int main(int argc, char *argv[]) { QApplication a(argc, argv); //构造json数据 QJsonObject jsonobj; jsonobj[name] zack; post_req(http://localhost:8080/api/post,jsonobj); MainWindow mw; mw.show(); return a.exec(); }解释设置请求头指定内容类型为application/json。构建 JSON 数据使用QJsonObject和QJsonDocument构建 JSON 格式的请求体。发送 POST 请求调用manager.post(request, jsonData)将 JSON 数据作为请求体发送。处理响应与 GET 请求类似通过信号处理响应或错误。处理响应与错误在上述示例中通过连接finished信号来处理响应。除了finished信号外还可以连接其他信号来实时处理数据或错误。常用信号readyRead()当有新的数据可读时触发可以用来逐步读取响应数据。errorOccurred(QNetworkReply::NetworkError code)当发生错误时触发提供错误类型。void get_req_error(QString url_req){ // 创建请求对象 QUrl url(url_req); QNetworkRequest request(url); // 发送 GET 请求 QNetworkReply *reply manager.get(request); // 连接请求完成的信号 QObject::connect(reply, QNetworkReply::finished, []() { if (reply-error() QNetworkReply::NoError) { // 读取响应数据 QByteArray responseData reply-readAll(); qDebug() GET 响应数据: QString::fromUtf8(responseData); } else { // 输出错误信息 qDebug() 错误: reply-errorString(); } reply-deleteLater(); }); // 连接错误信号 QObject::connect(reply, QOverloadQNetworkReply::NetworkError::of(QNetworkReply::error), [](QNetworkReply::NetworkError code){ qDebug() 发生错误错误码: code 错误信息: reply-errorString(); }); }说明连接errorOccurred信号当发生网络错误时输出错误码和错误信息。故意使用无效 URL触发错误演示错误处理。