当前位置: 首页 > news >正文

Linux与HTTP报头属性和请求方式

HTTP报头属性、请求方式

本篇介绍

在上一节深入HTTP序列化和反序列化已经详细讲解了HTTP是如何进行序列化和反序列化的,但是上一节对请求报头和响应报头的具体内容并没有做出具体的说明,本节就会基于这个问题继续探讨HttpServer;另外在介绍HTTP协议基本结构与基本实现HTTPServer一节提到,HTTP请求的方式有很多种,而最常见的就是GETPOST,那么什么是请求方式,GETPOST这两者又有什么区别也是本节需要探讨的话题。所以综上本节主要就解决两个问题:

  1. 何为报头属性
  2. 何为请求方式,具体的请求方式又有什么区别

HTTP报头属性

HTTP报头一共有两种,分别是请求报头和响应报头。虽然有两种报头,但是二者的报头属性是一样的,所以接下来会以HTTP请求报头为例对报头进行介绍,再以HTTP响应报头演示HTTP报头如何进行设置

认识HTTP报头属性

在HTTP报头中有很多属性,每一个属性都是以键值对的方式表示,例如在深入HTTP序列化和反序列化第一阶段结果中就有一些报头属性,如图所示:

在这里插入图片描述

上面的每个字段和值的解释如下:

图片中的请求报头包含了多个字段,每个字段都有其特定的含义。以下是对每个字段和值的解释:

  1. Host: localhost:8080 - 指定服务器的主机名和端口号。客户端通过这个字段告诉服务器它想要访问的主机
  2. Connection: keep-alive - 表示客户端希望与服务器保持连接,以便在同一连接上发送多个请求
  3. sec-ch-ua: “Not;A=Brand”;v=“24”, “Chromium”;v=“128” - 表示客户端的用户代理品牌和版本信息。sec-ch-ua 是一个客户端提示头,用于提供用户代理的品牌和版本信息
  4. sec-ch-ua-mobile: ?0 - 表示客户端是否为移动设备。?0 表示不是移动设备
  5. sec-ch-ua-platform: “Linux” - 表示客户端操作系统平台。这里是 Linux
  6. DNT: 1 - 表示客户端不希望被追踪。1 表示启用了“请勿追踪”功能
  7. Upgrade-Insecure-Requests: 1 - 表示客户端希望服务器将不安全的HTTP请求升级为 HTTPS请求
  8. User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 - 表示客户端的用户代理字符串,包含了浏览器和操作系统的信息。这里表示使用的是Chrome浏览器,运行在Linux x86_64平台上
  9. Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,/;q=0.8,application/signed-exchange;v=b3;q=0.7 - 表示客户端可以接受的MIME类型(鼠标悬浮/手指点击时显示更多信息)。这里表示客户端可以接受HTML、XHTML、XML、AVIF、WebP、APNG等格式的内容
  10. Sec-Fetch-Site: none - 表示请求的上下文。none 表示请求不是从其他站点发起的
  11. Sec-Fetch-Mode: navigate - 表示请求的模式。navigate 表示这是一个导航请求
  12. Sec-Fetch-User: ?1 - 表示请求是否由用户触发。?1 表示是由用户触发的请求
  13. Sec-Fetch-Dest: document - 表示请求的目的。document 表示请求的目的是获取一个文档
  14. Accept-Encoding: gzip, deflate, br, zstd - 表示客户端可以接受的内容编码。这里表示客户端可以接受gzip、deflate、br和zstd编码的内容
  15. Accept-Language: en,zh-CN;q=0.9,zh;q=0.8,en-US;q=0.7 - 表示客户端可以接受的语言。这里表示客户端可以接受英语(en)、简体中文(zh-CN)、中文(zh)和美式英语(en-US)

上面的解释只需要了解即可,下面针对常见的报头属性进行说明:

  • Host: 客户端告知服务器,所请求的资源是在哪个主机的哪个端口上
  • User-Agent: 声明用户的操作系统和浏览器版本信息
  • Referer: 当前页面是从哪个页面跳转过来的
  • Location: 搭配3xx状态码使用,告诉客户端接下来要去哪里访问
  • Content-Type: 数据类型
  • Content-Length: Body的长度
  • Cookie: 用于在客户端存少量信息。通常用于实现会话(session)的功能,关于Cookie和Session会在后面的章节讲解,此处不具体说明

在上面常见的报头属性中存在三类:

=== “只出现在请求报头中”

  • Host: 这是HTTP/1.1规范中唯一必须包含在请求中的字段,用于指定服务器的主机名和端口号
  • User-Agent: 只出现在请求中,表示客户端应用程序的信息
  • Referer: 只出现在请求中,表示用户从哪个页面链接过来的
  • Cookie: 只出现在请求中,客户端发送之前服务器存储的Cookie信息

=== “只出现在响应报头中”

  • Location: 只出现在响应中,主要配合3xx重定向状态码使用
  • Set-Cookie: 只出现在响应中,用于服务器指示客户端保存Cookie,这个字段需要搭配客户端的Cookie使用,这一点会在后面的Cookie中详细介绍

=== “可能同时出现在请求或者响应报头中”

  • Content-Type: 在请求和响应中都可以出现,表示实体的媒体类型
  • Content-Length: 在请求和响应中都可以出现,表示实体主体的大小

在HTTP响应报头中演示

上面认识到了常见的报头属性,但是也是文字上的了解,具体怎么做还并不知道,所以接下来就是在HTTP响应中使用这些报头属性

需要注意,因为是在HTTP报头中演示,所以只会演示上面可以出现在响应报头中的属性

Content-TypeContent-Length

在前面客户端和服务端通信时,都是将内容读取然后直接发给客户端,但是这里存在一个问题,服务端知道文件有多大,也知道文件的结尾在哪里,而因为HTTP是基于TCP的,有可能客户端收到的数据并不是完整的,却被客户端误认为读到了文件结尾,这时就会出现客户端显示的内容并不一定是正确且完整的,所以为了尽可能避免这个问题,在服务端给客户端响应数据时通常响应报头需要携带Content-Length,通过这个属性,客户端就可以知道自己是否读取到了完整的数据

但是,客户端有文件的大小还不够,因为HTTP协议不仅可以传递文本信息,还可以传递一些媒体信息,例如图片、视频等,如果客户端只知道文件大小而不知道文件类型,那么就可能出现二进制文件被当成文本文件进行解析从而导致显示的内容异常,所以服务端给客户端响应数据时除了需要响应Content-Length外,还需要给客户端响应文件类型,即Content-Type

虽然正确设置这些HTTP头部是最佳实践,但在之前的例子中没有使用这些头部,主要是因为浏览器会自动推断文本文件的类型(通常默认为text/plain或根据内容判断为text/html),且早期HTTP实现中服务器可通过直接关闭连接来表示传输结束。然而,在实际生产环境中,应该正确设置Content-TypeContent-Length,以确保客户端正确解析内容类型、确认完整接收数据并支持持久连接的正常运行

现在目录下有4张图片:

在这里插入图片描述

在HTML中引入这4张图片:

<!DOCTYPE html>
<html lang="zh-CN"><head><!-- ... --><title>商城</title><!-- ... -->
</head><body><!-- ... --><div class="container"><h2 class="section-title">热卖推荐</h2><div class="product-grid"><div class="product-card"><div class="product-img"><img src="../assets/public/images/1.png" /></div><!-- ... --></div><div class="product-card"><div class="product-img"><img src="../assets/public/images/2.png" /></div><!-- ... --></div><div class="product-card"><div class="product-img"><img src="../assets/public/images/3.png" /></div><!-- ... --></div><div class="product-card"><div class="product-img"><img src="../assets/public/images/4.png" /></div><!-- ... --></div></div></div>
</body></html>

正常显示结果如下:

在这里插入图片描述

接下来,让服务器读取这个文件并发送给客户端,不过为了更好得展现出客户端正在向服务端请求的资源,可以在客户端获取资源时打印出URI,即:

void buildHttpResponse(HttpRequest &req)
{// 获取uristd::string req_uri = req.getReqUri();LOG(LogLevel::INFO) << "客户端正在请求:" << req_uri;// ...
}

运行服务器和客户端,查看结果:

在这里插入图片描述

可以看到图片并没有显示,但是这里可能有两种可能:

  1. 客户端没有请求图片
  2. 图片发送失败

为了验证是第二种结果,接下来看是否存在刚才加的一行打印,结果如下:
在这里插入图片描述

可以发现,客户端的确请求了四张图片,说明第一种可能不存在,但是这些图片并没有正常显示,说明没有客户端没有正常获取到这些图片,如果查看一下浏览器调试就可以看到原因:
在这里插入图片描述

这里,Sec-Fetch-Dest表示客户端需要一张图片,接着再看Response一栏结果:

在这里插入图片描述

可以发现,服务端发送给客户端的数据被当成了文本进行解析,但是因为图片是二进制文件,导致直接解析成文本也没有结果

从上面的例子可以看出,如果没有图片,那么解析都是正常的,因为默认识别为文本,但是一旦是媒体文件就会出现错误

为了可以正常显示图片和文本,原来的文本方式读取就不再可用,所以接下来就要对getFileContent函数进行修改,使其可以读取二进制文件(包括文本文件),修改思路如下:

前面提到客户端需要知道文件的大小以便正确解析文件,所以需要设置文件大小,这里可以考虑添加一个成员单独保存这个长度值,也可以考虑将存储文件内容的容器规定为文件的大小,这样存储文件内容的容器的有效数据大小就是文件大小,本次以后者为例

接下来就是考虑如何获取到文件大小,其中一种方式就是移动读取光标,然后获取光标的偏移量,第二种方式就是利用C++17的filesystem中提供的库函数file_size快速获取文件的大小:

=== “移动光标读取偏移量”

// 使用光标偏移量获取文件大小
size_t getFileSize(std::string& file)
{// 以二进制、光标在文件结尾的方式打开文件std::fstream f(file, std::ios::binary | std::ios::ate);if(!f.is_open())return 0;return static_cast<size_t>(f.tellg());
}

=== “使用file_size库函数”

// 使用file_size函数获取文件大小
size_t getFileSize(std::filesystem::path filepath)
{return static_cast<size_t>(std::filesystem::file_size(filepath));
}

接着,完善getFileContent函数:

// 获取文件内容
std::string getFileContent(std::string &uri)
{// 默认访问index.html文件if (uri.back() == '/')uri = "wwwroot/src/index.html";// 当前uri中即为用户需要的文件,使用二进制方式打开文件std::fstream f(uri, std::ios::in | std::ios::binary);// 如果文件为空,直接返回空字符串if (!f.is_open())return std::string();// 否则就读取文件内容std::string content;std::string line;// 先获取文件大小size_t filesize = getFileSize(uri);// 调整容器容量content.resize(filesize);f.read(const_cast<char *>(content.c_str()), filesize);LOG(LogLevel::INFO) << "读取到的文件大小为:" << filesize;f.close();return content;
}

修改了读取文件的方式后,代表文件可以被正常读取,接着就是设置Content-TypeContent-Length

对于Content-Length来说,只需要获取一下容器的有效数据大小即可,但是Content-Type就没那么容易了,那么可以通过什么方式获取文件类型?

实际上,Content-Type的值为MIME类型,在一些网站(例如MDN)中提供了可用文件的后缀对应的MIME类型

本次只需要用到三种类型:

  1. 后缀.mp4:对应的MIME类型为application/mp4
  2. 后缀.png:对应的MIME类型为image/png
  3. 后缀.html:对应的MIME类型为text/html

接下来,就需要处理两件事:

  1. 获取文件后缀:处理方式为反向查找.,从该位置开始到结尾即为文件后缀
  2. 根据文件后缀选择对应的MIME类型字符串:简单的字符串比较

=== “获取文件后缀”

 // 获取文件后缀std::string getFileSuffix(std::string& file){// 反向查找.auto pos = file.rfind(default_file_suffix_flag);if(pos == std::string::npos)return std::string();// 截取文件后缀字符串return file.substr(pos);}

=== “获取MIME类型”

 // 根据文件后缀获取MIME类型字符串std::string getFileMimeType(const std::string& suffix){if(suffix == ".mp4")return "application/mp4";else if (suffix == ".png")return "image/png";else if (suffix == ".html")return "text/html";return std::string();}

需要注意,不推荐正向查找文件后缀。因为操作系统的逻辑一般都是以最后一个后缀为标识,例如script.min.js,正向查找会找到.min,而不是真正的扩展名.js以及data.backup.2023.csv,正向查找会找到.backup,而不是.csv

接着就是完善buildHttpResponse函数,因为媒体文件的位置与HTML文件的位置不同,所以在设置实际URI时也需要根据后缀判断选择哪一个拼接方式,并且还需要在设置请求体之前先设置响应报头:

void buildHttpResponse(HttpRequest &req)
{// 获取uristd::string req_uri = req.getReqUri();// 拼接HTML文件路径std::string real_uri;if(getFileSuffix(req_uri) == ".html")real_uri = default_webapp_dir + default_html_dir + req_uri;else if(getFileSuffix(req_uri) == ".png")real_uri = default_webapp_dir + req_uri;// ...// 构建响应报头// 设置文件大小insertRespHead("Content-Length", std::to_string(content.size()));// 设置文件类型insertRespHead("Content-Type", getFileMimeType(getFileSuffix(real_uri)));// ...
}

需要注意,在上面的代码中会发现媒体资源文件的路径和HTML文件拼接方式不同,这是因为在HTML文件中,使用的是../assets/public/images/1.png这种引用方式,而因为../是返回上级目录,也就是相对HTML文件来说是Web应用根目录,所以实际上请求资源时路径为/assets/public/images/1.png,所以只需要在前面拼接上wwwroot即可

接着,还需要处理一种特殊情况,即客户端的默认请求/,对于这一点,如果按照上面的逻辑,就会让real_uri是一个空字符串传递给getFileContent(),此时就会出现问题,所以为了避免这种情况,还需要将getFileContent中单独处理/的逻辑移动到buildHttpResponse中:

void buildHttpResponse(HttpRequest &req)
{// ...// 默认访问index.html文件std::string real_uri;if (req_uri.back() == '/')real_uri = "wwwroot/src/index.html";else if(getFileSuffix(req_uri) == ".html")real_uri = default_webapp_dir + default_html_dir + req_uri;else if(getFileSuffix(req_uri) == ".png")real_uri = default_webapp_dir + req_uri;// ...
}

最后,编译运行上面的代码,观察结果:

在这里插入图片描述

可以发现图片都可以正常显示了,再查看调试器可以看到在上面设置的两个报头属性:

在这里插入图片描述

另外,为了保证响应格式的确没有问题,可以使用Postman向服务器发起请求:

在这里插入图片描述

可以看到正常接收到结果且结果正常

重定向与Location

上面已经介绍了两个基本的属性,这两个属性涉及到网页能否正常显示出了文本以外的内容。接下来还存在一个属性,就是Location,这个属性只出现在响应报头,表示服务器需要客户端重定向到一个具体的网址,重定向对应的状态码种类有下面三类:

=== “临时重定向”

  • 302 Found - 最常用的临时重定向状态码
  • 303 See Other - 特别用于将POST请求重定向到GET请求
  • 307 Temporary Redirect - 临时重定向,但严格保持原始请求方法不变

=== “永久重定向”

  • 301 Moved Permanently - 资源已永久移动到新位置
  • 308 Permanent Redirect - 永久重定向,但严格保持原始请求方法不变

=== “其他重定向”

  • 300 Multiple Choices - 表示请求有多个可能的响应
  • 304 Not Modified - 缓存重定向,表示资源未修改

一旦服务器设置了Location响应头,客户端(浏览器)就会自动向Location的值对应的URL发起请求访问新的页面

下面以临时重定向为例,实现这一个功能,以一个场景为例:当前主页并不是index.html而是index1.html,再不改变原来默认请求index.html代码的情况下,通过Location自动跳转到index1.html

为了实现这个功能,首先需要一个index1.html

接着,在buildHttpResponse函数中设置重定向属性,注意,因为是直接重定向,所以可以直接在请求/就直接返回一个完整的HTTP响应结构,因为此处还需要设置对应的状态码和状态码描述,所以还需要对获取状态码描述的函数进行修改:

=== “buildHttpResponse函数”

 void buildHttpResponse(HttpRequest &req){// ...if (req_uri.back() == '/'){// ...// 请求旧主页时重定向// 设置重定向位置insertRespHead("Location", "http://127.0.0.1:8080/index1.html");// 设置响应状态码和状态码描述// 设置重定向状态码和描述_status_code = 302; // 临时重定向_status_code_desc = setStatusCodeDesc(_status_code);// 设置一个简单的响应体_resp_body = "Redirecting to new version...";// 设置响应头部insertRespHead("Content-Type", "text/html");insertRespHead("Content-Length", std::to_string(_resp_body.size()));// 构建响应行_resp_line = _http_ver + " " + std::to_string(_status_code) + " " + _status_code_desc;return;}// ...}

=== “setStatusCodeDesc函数”

 // 根据状态码得到状态码描述std::string setStatusCodeDesc(int status_code){switch (status_code){case 200:return "OK";case 301:return "Moved Permanently";case 302:return "Found";case 404:return "Not Found";default:break;}return std::string();}

再次启动服务器并运行就可以发现原来显示的是index.html的页面内容,现在默认显示的是index1.html中的内容:

在这里插入图片描述

HTTP请求参数

在实际生活中,有的时候显示的网页不只有给用户看的内容,还需要有与用户交互的内容,例如登录、注册等,当用户向登录或者注册中一些输入框输入一些数据后点击提交时,这些数据会被携带着一起发送给服务器,这些由用户输入并发送给服务器的数据就是HTTP请求参数

在HTTP中,由下面几种请求方式:

  1. GET
  2. POST
  3. PUT
  4. DELETE
  5. HEAD
  6. PATCH
  7. OPTIONS
  8. TRACE
  9. CONNECT

但是,虽然上面列举了9种请求方式,实际上最常用的就只有前两种,即GETPOST,下面针对二者做一下区分:

  1. GET参数通过URL传递,POST通过请求体传递
  2. GET请求有长度限制,POST没有严格限制
  3. GET请求可被缓存,POST通常不缓存
  4. GET相对不安全,POST相对更安全
  5. GET具有幂等性,POST通常非幂等

基于上面的概念,下面为了演示出GETPOST的区别,需要先准备一个登录页面

下面重点关注表单部分:

<form method="get"><!-- ... -->
</form>

当前默认是GET请求,运行服务器请求该页面,输入内容并点击提交观察结果:

在这里插入图片描述

再将请求方式修改为POST请求,观察结果:

<form method="post"><!-- ... -->
</form>

在这里插入图片描述

可以看到,如果是GET请求,参数就是直接放在URI的后方,而如果是POST,参数则在请求体中,这也证明了前面GETPOST区别的第一点

当服务端需要客户端发送数据时,这个数据肯定是需要被服务端拿去使用的,例如比对账号是否存在于数据库,但是因为本次并不存在数据库以及访问数据库的操作,所以本次演示就只是从HTTP请求中获取到对应的值即可

因为GETPOST对请求参数的处理是不同的,所以需要分为两种情况处理,一般对于一个请求来说,如果是GET请求,那么请求的参数会放在URI之后,以?开头,每一个属性都是key=value的形式,多个属性之间用&进行连接,但是如果是POST请求,那么请求的参数就放在请求正文部分,同样每一个属性都是key=value的形式,默认情况下,多个属性之间用&进行连接

这里可以通过一个函数包装分情况处理的逻辑,而因为需要存储响应体,所以这里考虑使用一个哈希表分别存储每一个键值对,所以基本结构如下:

// HTTP请求
class HttpRequest
{
public:// ...// 获取请求参数void getReqParams(){if(_req_method == "GET"){// 处理GET请求中的参数}else if(_req_method == "POST"){// 处理POST请求中的参数}}// ...private:// ...std::unordered_map<std::string, std::string> _param_kv; // 请求参数// ...
};

首先处理GET请求中的参数,前面提到,GET请求的参数位于URI的后方中,所以需要从URI中截取出参数,因为参数起始的字符是?,所以从?开始查找,该字符的下一个位置就是第一个参数键值对,当找到第一个&就代表找到了一个完整的参数键值对,现在就只需要将这个参数字符串提取出来存储到哈希表中即可:

=== “获取GET中的参数”

 // 获取GET参数void getReqParamsFromReqLine(){// 找到?的位置auto pos = _req_uri.find(default_get_param_start_flag);if (pos == std::string::npos)return;// ?username=123&password=123while (true){// 找到&auto pos1 = _req_uri.find(default_param_sep, pos + 1);if (pos1 == std::string::npos){// 最后一个参数键值对auto pos_t = _req_uri.find(default_kv_sep, pos + 1);if (pos_t == std::string::npos)break;std::string key = _req_uri.substr(pos + default_get_param_start_flag.size(), pos_t - pos - 1);std::string value = _req_uri.substr(pos_t + 1);_param_kv.insert({key, value});break;}// 找到=,pos表示查找起始位置auto pos2 = _req_uri.find(default_kv_sep, pos + 1);// pos2 - pos - 1表示截取的最后一个字符,不包括std::string key = _req_uri.substr(pos + default_get_param_start_flag.size(), pos2 - pos - 1);std::string value = _req_uri.substr(pos2 + default_kv_sep.size(), pos1 - pos2 - 1);_param_kv.insert({key, value});// 修改起始位置pos = pos1;}// 恢复pospos = start_param;// 从URI中移除参数_req_uri.erase(pos);for (auto kv : _param_kv){std::cout << kv.first << ":" << kv.second << std::endl;}}

=== “获取POST中的参数”

// 处理POST请求参数
void getReqParamsFromBody()
{if (_req_body.empty())return;// username=123&password=123auto pos = size_t(-1); // 初始位置设为-1,因为POST参数没有?前缀while (true){// 找到&auto pos1 = _req_body.find(default_param_sep, pos + 1);if (pos1 == std::string::npos){// 最后一个参数键值对auto pos_t = _req_body.find(default_kv_sep, pos + 1);if (pos_t == std::string::npos)break;std::string key = _req_body.substr(pos + 1, pos_t - pos - 1);std::string value = _req_body.substr(pos_t + 1);_param_kv.insert({key, value});break;}// 找到=auto pos2 = _req_body.find(default_kv_sep, pos + 1);if (pos2 == std::string::npos || pos2 > pos1)break;std::string key = _req_body.substr(pos + 1, pos2 - pos - 1);std::string value = _req_body.substr(pos2 + 1, pos1 - pos2 - 1);_param_kv.insert({key, value});// 修改起始位置pos = pos1;}// 打印解析结果for (const auto &kv : _param_kv){std::cout << kv.first << ":" << kv.second << std::endl;}
}

=== “处理参数函数”

// 获取请求参数
void getReqParams()
{if(_req_method == "GET"){// 处理GET请求中的参数getReqParamsFromReqLine();}else if(_req_method == "POST"){// 处理POST请求中的参数getReqParamsFromBody();}
}

需要注意,在上面的getReqParamsFromReqLine函数中,在最后要处理一下从URI中移除参数,否则获取完参数后URI还携带参数影响后续其他逻辑执行

最后,在反序列化函数中调用处理参数函数:

// 反序列化
bool deserialize(std::string &in_str)
{// ...// 获取参数内容getReqParams();return true;
}

接下来,分别在GET请求和POST请求下测试:

=== “GET请求”

在这里插入图片描述

=== “POST请求”

在这里插入图片描述

静态资源和动态资源

介绍与准备

在上面以及之前两节中,服务器都是向客户端直接响应字符串或者一个静态资源,但是有的时候不只有静态资源,还有很多的动态资源,例如在表单提交时form标签内的action字段的值就是对应的一个动态资源

以一个实例表单为例:

<form action="/login" method="get"><!-- ... -->
</form>

在这个表单中,action的值即为一个动态资源,这个动态资源一般是一个函数,有HTTP服务器调用上层的函数去完成,这里依旧是以登录为例:

<!-- ... -->
<body><!-- ... --><div class="container"><div class="login-container"><h2 class="login-title">账号登录</h2><form action="/login" method="get"><!-- ... --></form></div></div>
</body><!-- ... -->

因为动态资源一般都是为了处理请求中的参数,所以可以对前面获取到的参数进行处理,同样考虑简单的处理。这里设计一个布尔类型的成员变量_hasArgs,标记是否有参数,如果_hasArgs为真,那么就需要上层去执行任务,否则就返回静态资源。而因为判断是否有参数的函数在HttpRequest中,所以这里将_hasArgs放在HttpRequest中:

// HTTP请求
class HttpRequest
{
public:HttpRequest():_hasArgs(false){}// ...private:// ...bool _hasArgs; // 是否有参数
};

接着,提供一个函数获取_hasArgs的值方便上层调用:

// 获取_hasArgs
bool getHasArgs()
{return _hasArgs;
}

最后,在获取到参数时更改_hasArgs的值,但是这里不可以直接修改_hasArgs,而应该是成功获取了GET或者POST请求中的参数才可以修改,所以还需要为getReqParamsFromReqLinegetReqParamsFromBody设计一个返回值,用于判断是否执行成功:

=== “getReqParamsFromReqLine函数添加布尔返回值”

 // 获取GET参数bool getReqParamsFromReqLine(){LOG(LogLevel::INFO) << "进入获取GET参数" << _req_uri;// 找到?的位置auto pos = _req_uri.find(default_get_param_start_flag);auto start_param = pos;// 找不到返回falseif (pos == std::string::npos)return false;// ?username=123&password=123while (true){// 找到&auto pos1 = _req_uri.find(default_param_sep, pos + 1);if (pos1 == std::string::npos){// 最后一个参数键值对auto pos_t = _req_uri.find(default_kv_sep, pos + 1);// 找不到返回falseif (pos_t == std::string::npos)return false;std::string key = _req_uri.substr(pos + default_get_param_start_flag.size(), pos_t - pos - 1);std::string value = _req_uri.substr(pos_t + 1);_param_kv.insert({key, value});break;}// 找到=,pos表示查找起始位置auto pos2 = _req_uri.find(default_kv_sep, pos + 1);// pos2 - pos - 1表示截取的最后一个字符,不包括std::string key = _req_uri.substr(pos + default_get_param_start_flag.size(), pos2 - pos - 1);std::string value = _req_uri.substr(pos2 + default_kv_sep.size(), pos1 - pos2 - 1);_param_kv.insert({key, value});// 修改起始位置pos = pos1;}// 恢复pospos = start_param;// 从URI中移除参数_req_uri.erase(pos);LOG(LogLevel::INFO) << "离开获取GET参数" << _req_uri;// 找到返回truereturn true;}

=== “getReqParamsFromBody函数添加布尔返回值”

 // 处理POST请求参数bool getReqParamsFromBody(){// 找不到返回falseif (_req_body.empty())return false;// username=123&password=123auto pos = size_t(-1); // 初始位置设为-1,因为POST参数没有?前缀while (true){// 找到&auto pos1 = _req_body.find(default_param_sep, pos + 1);if (pos1 == std::string::npos){// 最后一个参数键值对auto pos_t = _req_body.find(default_kv_sep, pos + 1);// 找不到返回falseif (pos_t == std::string::npos) return false;std::string key = _req_body.substr(pos + 1, pos_t - pos - 1);std::string value = _req_body.substr(pos_t + 1);_param_kv.insert({key, value});break;}// 找到=auto pos2 = _req_body.find(default_kv_sep, pos + 1);// 找不到返回falseif (pos2 == std::string::npos || pos2 > pos1)return false;std::string key = _req_body.substr(pos + 1, pos2 - pos - 1);std::string value = _req_body.substr(pos2 + 1, pos1 - pos2 - 1);_param_kv.insert({key, value});// 修改起始位置pos = pos1;}// 找到返回truereturn true;}

=== “获取请求参数函数”

 void getReqParams(){if (_req_method == "GET"){_hasArgs = true;// 处理GET请求中的参数getReqParamsFromReqLine();}else if (_req_method == "POST"){_hasArgs = true;// 处理POST请求中的参数getReqParamsFromBody();}}

所谓的上层调用,就是让HttpServer去调用上层传递的函数,所以接下来设计HttpServer

修改HttpServer

既然HttpServer需要调用上层函数,那么就需要HttpServer提供一个入口,因为执行的函数可能不止一个,所以这里考虑建立一张动态资源和函数映射的哈希表,本次简单处理,函数都是void(HttpRequest& req, HttpResponse& resp)的函数:

using handler_t = std::function<void()>;class HttpServer
{
public:// ...private:// ...std::unordered_map<std::string, handler_t> _dynamic_func;
};

接着,需要向上层提供一个添加处理动态资源函数的接口:

void pushDynamicTask(std::string name, handler_t handler)
{_dynamic_func[name] = handler;
}

另外,为了保证执行正常,可以提供一个判断接口,用于判断需要执行的函数是否存在:

bool hasFunc(std::string name)
{auto pos = _dynamic_func.find(name);return pos != _dynamic_func.end();
}

最后就是对handleHttpRequest进行处理,只需要根据getHasArgs函数的返回值即可判断是否执行动态资源处理函数:

void handleHttpRequest(SockAddrIn sock_addr_in, int ac_socketfd)
{LOG(LogLevel::INFO) << "收到来自:" << sock_addr_in.getIp() << ":" << sock_addr_in.getPort() << "的连接";// 获取客户端传来的HTTP请求base_socket_ptr bs = _tp->getSocketPtr();std::string in_str;bs->recvData(in_str, ac_socketfd);// 反序列化HttpRequest req;req.deserialize(in_str);HttpResponse resp;if(req.getHasArgs()){// 处理动态资源// 动态资源即在URI中std::string service = req.getReqUri();// 根据service查找哈希表执行对应的函数if(hasFunc(service))_dynamic_func[service](req, resp);}else{// 处理静态资源// 构建HTTP响应resp.buildHttpResponse(req);}// ...
}

测试

为了在调用动态资源处理函数时可以看到一些数据,这里考虑在HttpRequestHttpResponse中提供获取参数的函数:

void getParamKv()
{std::for_each(_param_kv.begin(), _param_kv.end(), [&](std::pair<std::string, std::string> kv){std::cout << kv.first << ":" << kv.second << std::endl;});
}

接着,提供一个处理/login动态资源的函数:

void login(HttpRequest &req, HttpResponse &resp)
{LOG(LogLevel::DEBUG) << "进入登录模块";req.getParamKv();
}

修改主函数如下:

int main(int argc, char* argv[])
{uint16_t port = std::stoi(argv[1]);std::shared_ptr<HttpServer> hs = std::make_shared<HttpServer>(port);// 注册处理动态资源的函数hs->pushDynamicTask("/login", login);hs->start();return 0;
}

编译上面的代码并打开login.html,输入内容并点击提交按钮,观察结果:

=== “GET请求”

在这里插入图片描述

=== “POST请求”

在这里插入图片描述

可以看到不论是GET还是POST都执行了动态资源

相关文章:

Linux与HTTP报头属性和请求方式

HTTP报头属性、请求方式 本篇介绍 在上一节深入HTTP序列化和反序列化已经详细讲解了HTTP是如何进行序列化和反序列化的&#xff0c;但是上一节对请求报头和响应报头的具体内容并没有做出具体的说明&#xff0c;本节就会基于这个问题继续探讨HttpServer&#xff1b;另外在介绍…...

WordPress漏洞

一&#xff0c;后台修改模板拿WebShell 1&#xff0c;安装好靶场后访问 2&#xff0c;在如图所示的位置选择一个php文件写入一句话木马&#xff0c;我们这里选择在404.php中写入 3&#xff0c;访问404.php 二&#xff0c;上传主题拿WebShell 1&#xff0c;找到如图所示的页面…...

go命令使用

查看配置信息 go env配置go国内源 export GO111MODULEon export GOPROXYhttps://goproxy.cn测试 go install github.com/jesseduffield/lazydockerlatesthttps://github.com/jesseduffield/lazydocker...

uniapp vue3使用uniapp的生命周期

使用uniapp的onLoad等生命周期 // 需要引入 import { onLoad , onShow } from dcloudio/uni-app; // 箭头函数 onLoad(()>{//内容 })使用vue生命周期 vue官方文档&#xff1a;https://cn.vuejs.org/api/options-lifecycle.html import { ref,onMounted } from vue; onMou…...

nginx vue history模式 try_files

server {listen 80;server_name localhost chat.test.com;#配置根目录location / {root /temp/test;#index index.html index.htm;try_files $uri $uri/ /index.html;add_header Content-Security-Policy upgrade-insecure-requests;}} https://blog.csdn.net/xutongbao/…...

【css酷炫效果】纯CSS实现悬浮弹性按钮

【css酷炫效果】纯CSS实现悬浮弹性按钮 缘创作背景html结构css样式完整代码效果图 想直接拿走的老板&#xff0c;链接放在这里&#xff1a;https://download.csdn.net/download/u011561335/90492020 缘 创作随缘&#xff0c;不定时更新。 创作背景 刚看到csdn出活动了&…...

MySQL自动化配置工具开发:探索如何用脚本实现MySQL一键安装与配置,提升运维效率

引言 MySQL作为最流行的开源关系型数据库之一,广泛应用于各类业务场景。然而,手动安装和配置MySQL不仅耗时,还容易出错。为了提高运维效率,开发一款MySQL自动化配置工具显得尤为重要。本文将探索如何通过脚本实现MySQL的一键安装与配置,并提供具体的配置脚本文档和关键参…...

如何查看安卓版本号的方法(例如查看是13、12、11、10...)

开发过程中需要了解到安卓版本号是多少&#xff0c;那么以下有三种方法可以知晓安卓手机的Android版本号。 方法1&#xff1a;手机设置直接查看 1.打开【设置】 --> 滑动到手机最底部 --> 点击【关于手机】或 【系统】--> 选择【Android版本】 2.直接查看版本号&am…...

ubuntu 解挂载时提示 “umount: /home/xx/Applications/yy: target is busy.”

问题如题所示&#xff0c;我挂载一个squanfs文件系统到指定目录&#xff0c;当我使用完后&#xff0c;准备解挂载时&#xff0c;提示umount: /home/xx/Applications/yy: target is busy.&#xff0c;具体的如图所示&#xff0c; 这种提示通常是表明这个路径的内容正在被某些进…...

Java XML与JSON相互转换详解

目录 一、为什么需要XML与JSON转换二、使用Jackson库进行转换1. 添加依赖2. XML转JSON3. JSON转XML三、注意事项在现代软件开发中,数据格式的转换是一项常见的任务,特别是在处理不同系统或服务之间的数据交换时。XML(可扩展标记语言)和JSON(JavaScript对象表示法)是两种广…...

[AI速读]用持续集成(CI)优化芯片验证环境:Jenkins与EDA工具的实战指南

在芯片验证中,回归测试(Regression Test)是确保设计稳定性的关键步骤。但随着设计复杂度增加,手动管理海量测试用例、分析日志和覆盖率数据变得异常耗时。本文将介绍如何利用持续集成(CI)工具Jenkins,结合EDA验证环境(如Cadence vManager),实现自动化测试与结果分析,…...

Java-SpringBootWeb入门、Spring官方脚手架连接不上解决方法

一. Spring 官网&#xff1a;Spring | Home Spring发展到今天已经形成了一种开发生态圈&#xff0c;Spring提供了若干个子项目&#xff0c;每个项目用于完成特定的功能(Spring全家桶) Spring Boot可以帮助我们非常快速的构建应用程序、简化开发、提高效率 。 二. Spring Boot入…...

WEB攻防-PHP反序列化-字符串逃逸

目录 前置知识 字符串逃逸-减少 字符串逃逸-增多 前置知识 1.PHP 在反序列化时&#xff0c;语法是以 ; 作为字段的分隔&#xff0c;以 } 作为结尾&#xff0c;在结束符}之后的任何内容不会影响反序列化的后的结果 class people{ public $namelili; public $age20; } var_du…...

Android第五次面试总结(网络篇)

一、引言&#xff1a;为什么需要 DNS&#xff1f; 在浏览器输入www.example.com的瞬间&#xff0c;一场跨越全球的数字接力赛悄然启动。DNS&#xff08;域名系统&#xff09;如同互联网的 “电话号码簿”&#xff0c;将人类可读的域名转化为机器可识别的 IP 地址。本文将以工程…...

全网首创/纯Qt/C++实现国标GB28181服务/实时视频/云台控制/预置位/录像回放和下载/事件订阅/语音对讲

一、前言说明 用纯Qt来实现这个GB28181的想法很久了&#xff0c;具体可以追溯到2014年&#xff0c;一晃十年都过去了&#xff0c;总算是整体的框架和逻辑都打通了&#xff0c;总归还是杂七杂八的事情多&#xff0c;无法静下心来研究具体的协议&#xff0c;最开始初步了解协议后…...

详解MySQL的事务实现机制

MySQL事务实现机制 1. 锁机制2. Redo Log&#xff08;重做日志&#xff09;3. Undo Log&#xff08;撤销日志&#xff09;4. MVCC&#xff08;多版本并发控制&#xff09;综合事务处理流程 在MySQL中&#xff08;主要以InnoDB为例&#xff09;&#xff0c;事务的实现依赖于多个…...

什么是 BA ?BA怎么样?BA和BI是什么关系?

前几天有朋友在评论区提到了BA这个角色&#xff0c;具体是干什么的&#xff0c;我大概来说一下。 什么是BA BA 英文的全称是Business Analyst&#xff0c;从字面上意思就是商业分析师&#xff0c;做过商业智能BI项目的应该比较了解。实际上以我个人的经验&#xff0c;BA 的角…...

Junit在测试过程中的使用方式,具体使用在项目测试中的重点说明

JUnit 是一个广泛使用的 Java 单元测试框架,主要用于编写和运行可重复的测试。以下是 JUnit 在项目测试中的使用方式和重点说明: 1. 基本使用 场景:测试一个简单的 Java 类。 示例: import org.junit.Test; import static org.junit.Assert.*;public class CalculatorTe…...

网络安全之前端学习(HTML篇)

前言&#xff1a;网络安全中有一个漏洞叫xss漏洞&#xff0c;就是利用网页引发弹窗&#xff0c;这就要求我们看得懂源码&#xff0c;所以我会持续更新前端学习&#xff0c;可以不精通&#xff0c;但是一定要会&#xff0c;主要掌握HTML&#xff0c;css&#xff0c;js这三项技术…...

Lineageos 22.1(Android 15)实现负一屏

一、前言 方案是参考的这位大佬的&#xff0c;大家可以去付费订阅支持一波。我大概理一下Android15的修改。 大佬的方案代码 二、Android15适配调整 1.bp调整&#xff0c;加入aidl引入&#xff0c;这样make之后就可以索引代码了 filegroup {name: "launcher-src"…...

Redisson分布式锁(超时释放及锁续期)

&#x1f353; 简介&#xff1a;java系列技术分享(&#x1f449;持续更新中…&#x1f525;) &#x1f353; 初衷:一起学习、一起进步、坚持不懈 &#x1f353; 如果文章内容有误与您的想法不一致,欢迎大家在评论区指正&#x1f64f; &#x1f353; 希望这篇文章对你有所帮助,欢…...

SQL授予用户查询某个模式或者具体某个表

例如&#xff1a;需要给sjzt授权查询这个模式下的这个表的操作 数据库&#xff1a;DB_HZ_HYZHFZ 模式&#xff1a;hn_proxy 服务注册表&#xff1a;hn_proxy."serverrg" 账号:sjzt -- 登录到目标数据库 DB_HZ_HYZHFZ -- 授予权限给账号 sjzt 在模式 hn_proxy 中访问…...

Burp Suite 代理配置与网络通信

目录 1. 引言 2. Burp 代理基础配置 2.1 浏览器代理设置 2.2 Burp 监听端口配置 2.3 常见错误排查 3. 网络问题解决 3.1 端口占用检查 3.2 防火墙配置 3.3 证书信任问题 4. 虚拟机环境配置 4.1 NAT 模式与端口转发 4.2 桥接模式配置 4.3 跨设备访问测试 5. 技术概…...

PyCharm安装redis,python安装redis,PyCharm使用失败问题

报错信息 Usage: D:\wb2\wbrj_pys\venv\Scripts\python.exe -m pip install [options] [package-index-options] … D:\wb2\wbrj_pys\venv\Scripts\python.exe -m pip install [options] -r [package-index-options] … D:\wb2\wbrj_pys\venv\Scripts\python.exe -m pip instal…...

使用命令行工具控制wireshark对抓包文件进行针对性处理的总结

近日&#xff0c;工作中有开发对抓包文件进行针对性过滤的小程序的需求&#xff0c;兜兜转转踩了很多坑后还是绕回了wireshark。 作为最出名的开源软件之一&#xff0c;wireshark也具有使用命令行进行操作的功能&#xff0c;这就是我们今天会总结到的“tshark.exe”。 通常&a…...

【STM32实物】基于STM32的太阳能充电宝设计

基于STM32的太阳能充电宝设计 演示视频: 基于STM32的太阳能充电宝设计 硬件组成: 系统硬件包括主控 STM32F103C8T6、0.96 OLED 显示屏、蜂鸣器、电源自锁开关、温度传感器 DS18B20、继电器、5 V DC 升压模块 、TB4056、18650锂电池、9 V太阳能板、稳压降压 5 V三极管。 功能…...

NLP 与常见的nlp应用

自然语言处理&#xff08;NLP&#xff09;是一个广泛的领域&#xff0c;它不仅包括自然语言理解&#xff08;NLU&#xff09;&#xff0c;还涉及一系列其他任务和子领域。以下是NLP领域中的主要组成部分及其相关任务&#xff1a; 1. 自然语言理解&#xff08;NLU&#xff09; …...

基于javaweb的SSM+Maven宠物领养宠物商城流浪动物管理系统与实现(源码+文档+部署讲解)

技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;免费功能设计、开题报告、任务书、中期检查PPT、系统功能实现、代码编写、论文编写和辅导、论…...

Java数据类型 Arrays VS ArraysList VS LikedList 解析

在学习Java过程中&#xff0c;在刷题时总是搞不清楚这三种数据结构的区别&#xff0c;打算写篇文章记录一下 Arrays VS ArrayList ArrayList VS LinkedList 总结 Arrays VS ArrayList ArraysArrayList类型Java的基本数据类型Java集合框架中的一个类&#xff0c;实现了List接…...

C++进阶(一)

个人主页&#xff1a;PingdiGuo_guo 收录专栏&#xff1a;C干货专栏 前言 本篇博客是讲解函数的重载以及引用的知识点的。 文章目录 前言 1.函数重载 1.1何为函数重载 1.2函数重载的作用 1.3函数重载的实现 2.引用 2.1何为引用 2.2定义引用 2.3引用特性 2.4常引用 2…...

Elasticsearch 在航空行业:数据管理的游戏规则改变者

作者&#xff1a;来自 Elastic Adam La Roche 数字化客户体验不再是奢侈品&#xff0c;而是欧洲航空公司必不可少的需求。它推动了客户满意度&#xff0c;提升了运营效率&#xff0c;并创造了可持续的竞争优势。随着行业的不断发展&#xff0c;优先投资前沿数字技术和平台的航空…...

Powershell WSL部署ubuntu22.04.5子系统

前提条件WSL 安装 wsl 安装参考1wsl 安装csdn参考2wsl 百度网盘离线下载 本地目录安装ubuntu22.04.5 子系统 powershell 管理员打开执行(实现,下载安装ubuntu子系统,用户创建,远程ssh登录设置,防火墙端口开放)子系统IP 查看方法wsl...

华为网路设备学习-16 虚拟路由器冗余协议(VRRP)

VRRP是针对干线上三层网络设备&#xff08;如&#xff1a;路由器、防火墙等&#xff09;的网络虚拟化技术&#xff0c;提供冗余和状态监测等功能。确保在网络中的单点故障发生时&#xff0c;能够快速切换到备份设备&#xff0c;从而保证网络通信的连续性和可靠性。‌ VRRP通过…...

SQLark中如何进行数据筛选与排序

本文将为你介绍在 SQLark 中如何进行数据筛选与排序&#xff0c;掌握这些操作能够极大提升你的工作效率。 SQLark官网链接:www.sqlark.com 数据筛选 在数据库操作中&#xff0c;数据筛选是一项关键功能&#xff0c;它依据特定条件对数据进行过滤&#xff0c;帮助用户从海量数据…...

slq-labs日志

上次写了第五关的布尔盲注&#xff0c;和双查询报错注入&#xff0c;还有其他报错注入的方法 一.extractvalue函数 extractvalue函数 原理 extractvalue 是一个在 XML 数据中提取值的 MySQL 内置函数。它的语法为&#xff1a; EXTRACTVALUE(xml, xpath_expr)xml 是 XML 字符…...

Webrtc编译官方示例实现视频通话

Webrtc编译官方示例实现视频通话 前言 webrtc官网demo中给了一个供我们学习和应用webrtc的一个很好的例子&#xff1a;peerconnection&#xff0c;这期我们就来编译和运行下这个程序看看视频通话的效果以。 1、打开源码工程 继上期源码编译完成后&#xff0c;我们使用vs打开…...

对接SaToken @SaCheckEL 鉴权注解

对接SaToken SaCheckEL 鉴权注解 文章目录 对接SaToken SaCheckEL 鉴权注解前言一、引入插件和配置SaToken属性配置二、配置1.自定义SaTokenInterceptor并注入Bean2.WebMvcConfig配置SaTokenInterceptor&#xff0c;拦截所有请求路径 三、重载SaToken权限接口和方法注入SaCheck…...

【数据结构】顺序表(附源码)

数据结构之顺序表 1、线性表2、顺序表2.1 概念与结构2.2 顺序表的分类 3、动态顺序表3.1 结构3.2 初始化3.3 容量检查3.4 尾部插入数据3.5 删除尾部数据3.6 头部插入数据3.7 头部删除数据3.8 任意位置pos插入数据3.9 任意位置pos删除数据3.10 查找 4、动态顺序表实现的源码 1、…...

java使用Apache POI 操作word文档

项目背景&#xff1a; 当我们对一些word文档&#xff08;该文档包含很多的标题比如 1.1 &#xff0c;1.2 &#xff0c; 1.2.1.1&#xff0c; 1.2.2.3&#xff09;当我们删除其中一项或者几项时&#xff0c;需要手动的对后续的进行补充。该功能主要是对标题进行自动的补充。 具…...

【Linux网络编程】网络基础

目录 1、OSI分层思想2、数据的封装3、ip地址ipv4的ip地址分类&#xff1a;为什么这样划分&#xff1f;举例 4、端口号5、tcp三次握手为什么需要三次握手&#xff1f;tcp连接的标识为什么 IP 地址不能单独标识一条连接&#xff1f; 6、tcp四次挥手为什么需要四次挥手为什么需要三…...

国内首家,百度智能云千帆AppBuilder全面兼容MCP协议

百度智能云千帆 AppBuilder 已兼容 MCP 协议&#xff01;作为国内首家支持 MCP 协议的大模型应用开发平台&#xff08;Claude、LangGraph、Cursor、Cline、N8N等海外平台已支持&#xff09;&#xff0c;千帆 AppBuilder 完成兼容后&#xff0c;用户可通过千帆 AppBuilder 轻松调…...

eclipse [jvm memory monitor] SHOW_MEMORY_MONITOR=true

eclipse虚拟机内存监控设置SHOW_MEMORY_MONITORtrue D:\eclipse-jee-oxygen-2-win32-x86_64\workspace\.metadata\.plugins\org.eclipse.core.runtime\.settings org.eclipse.ui.prefs (文件比较多&#xff0c;别找错了&#xff09; SHOW_MEMORY_MONITORtrue 重启 -xms 1024…...

k8s中的组件

1.namespace Namespace 用于将集群资源划分为不同的逻辑组&#xff0c;方便管理和隔离 kubectl get namespace 查看所有逻辑组 kubectl describe namespace <namespace-name> 查看某个逻辑组信息详情 kubectl create namespace ... 创建逻辑组 kubectl delete names…...

单目3d detection算法记录

1、centernet object as points 这篇文章的核心单目3d检测主要是利用中心点直接回归出3d模型的所有属性&#xff0c;head共享整个backbone&#xff0c;其中3d属性包括&#xff1a;2d目标中心点、2dw和h、2d offsets、3doffsets、3d dimmession、rot还有depth。 其中对应的dep…...

vue3二次封装tooltip实现el-table中的show-overflow-tooltip效果

开发过程中遇到需要根据后端返回的数据长度来判断是否需要使用el-tooltip的情况&#xff0c;想到el-table里面就有这种交互效果&#xff0c;如果不论文字是否超出容器长度都展示tooltip的话&#xff0c;交互效果难免会差很多&#xff0c;所以二次封装了这个组件&#xff1a; 给…...

STM32八股【1】-----启动流程和startup文件理解

启动流程 知识点 MCU 上电复位。MSP从向量表第0个地址读取一个32位&#xff08;2字节&#xff09;的值并保存&#xff0c;该值为栈顶地址。PC计数器从第1个地址读取一个两字节的值并保存&#xff0c;该值为程序入口&#xff0c;一般是Reset_Handler。想了解FLASH地址映射可以…...

Go语言中package的使用规则《二》

在 Go 语言中&#xff0c;包&#xff08;Package&#xff09; 是代码组织和复用的核心单元。以下是其定义、引用规则及使用习惯的详细说明&#xff1a; 一、包的定义规则 目录与包名 一个包对应一个目录&#xff08;文件夹&#xff09;&#xff0c;目录名通常与包名一致。 包名…...

初级:控制流程面试题精讲

一、引言 在Java开发中&#xff0c;控制流程语句是构建程序逻辑的基础。面试官通过相关问题考察候选人对if-else、switch、循环等语句的理解和运用能力&#xff0c;以及在复杂业务场景下合理选择控制流程语句的水平。本文将深入剖析常见的控制流程面试题&#xff0c;结合实际开…...

HTTP 失败重试(重发)方案

在 Qt 网络开发中&#xff0c;使用 QNetworkAccessManager 进行 HTTP 请求时&#xff0c;可能会遇到网络超时、服务器错误等情况。为了提高请求的可靠性&#xff0c;可以实现 HTTP 失败重试&#xff08;重发&#xff09; 机制。下面介绍几种常见的 失败重发方案&#xff1a; 单…...

TNNLS 2024 | 基于残差超密集网络的高光谱图像空间光谱融合方法

A Spatio-Spectral Fusion Method for Hyperspectral Images Using Residual Hyper-Dense Network IEEE Transactions on Neural Networks and Learning Systems 2024 这篇文章的研究背景是针对高光谱图像&#xff08;HS&#xff09;与全色图像&#xff08;PAN&#xff09;的…...