小工作室做网站/关键词优化排名网站

小工作室做网站,关键词优化排名网站,网站标题图片怎么做,做网站的多钱深入HTTP序列化和反序列化 本篇介绍 在上一节已经完成了客户端和服务端基本的HTTP通信,但是前面的传递并没有完全体现出HTTP的序列化和反序列化,为了更好得理解其工作流程,在本节会以更加具体的方式分析到HTTP序列化和反序列化 本节会在介绍…

深入HTTP序列化和反序列化

本篇介绍

在上一节已经完成了客户端和服务端基本的HTTP通信,但是前面的传递并没有完全体现出HTTP的序列化和反序列化,为了更好得理解其工作流程,在本节会以更加具体的方式分析到HTTP序列化和反序列化

本节会在介绍HTTP协议基本结构与基本实现HTTPServer的基础之上继续完善HTTP服务器,所以需要有对应的知识作为铺垫才可以开始本节

基本实现思路

本次实现的HttpServer类主要完成接收客户端发送的HTTP请求,也就是说,服务器需要根据客户端的HTTP请求回复一个HTTP响应,所以必须要有的方法就是处理请求方法,但是前面已经提到过,HTTP的请求属于结构化的数据,并且这个数据在传递给服务器时就已经做了序列化,服务器需要处理的就是将结构化的数据进行反序列化;同样,服务器处理完毕后还需要发给客户端,所以此处就需要服务器对处理的结果填充到HTTP响应结构对象中再返回给客户端,此处就需要进行序列化

基于上面的原因,与前面序列化和反序列化与网络计算器一样,需要实现一个协议,包含HttpRequestHttpResponse类,用于处理序列化和反序列化

本次为了更好得理解序列化和反序列化,以HTTP请求为例,首先以请求行、请求报头和请求体三个整体做序列化和反序列化,接着再深入请求行、请求报头和请求体中的字段

根据这个两个阶段,需要实现的目标如下:

  1. 第一阶段:打印出反序列化和序列化的结果
  2. 第二阶段:向客户端返回具体的静态HTML文件

第一阶段

创建HttpRequest

根据前面的基本思路,实现HttpRequest类就需要实现对应的反序列化。因为HTTP请求中带有三种数据:

  1. 请求行
  2. 请求报头
  3. 请求体

所以需要定义三个成员分别存放从请求获取到的内容,所以基本结构如下:

class HttpRequest
{
public:HttpRequest(){}// 反序列化bool deserialize(std::string& in_str){}~HttpRequest(){}
private:std::string _req_line;              // 请求行std::vector<std::string> _req_head; // 请求报头std::string _req_body;              // 请求体
};

实现HttpRequest反序列化接口

在HTTP中的反序列化本质就是根据基本的格式去除掉多余的部分,从而提取出有效的数据放在相应的字段中,所以根据这个思路依次进行提取

注意,前面提到过,HTTP是基于TCP的,而TCP是面向字节流的,这就导致可能服务器接收到的HTTP请求不完整,对此还需要对接收到的HTTP请求进行完整性判断,但是本次不考虑这一步

截取请求行

首先提取请求行中的数据,根据前面对HTTP请求结构的描述可以知道HTTP请求的请求行以\r\n结尾,所以只需要找到第一个\r\n,就说明找到了请求行,这里定义一个成员函数用来处理这个逻辑:

// 获取请求行
bool parseReqLineFromTotal(std::string& in_str)
{auto pos = in_str.find(default_sep);if(pos == std::string::npos){LOG(LogLevel::WARNING) << "未找到请求行";return false;}// 获取到请求行_req_line = in_str.substr(0, pos);// 从原始字符串中移除请求头和第一个分隔符in_str.erase(0, pos + default_sep.size());LOG(LogLevel::DEBUG) << "请求行处理后:" << in_str;return true;
}

这里考虑到后面的请求体也是以\r\n结尾,所以考虑将该函数更改为更通用的版本:

// 截取以\r\n结尾的数据
bool parseOneLineFromTotal(std::string& in_str, std::string& out_str)
{auto pos = in_str.find(default_sep);if(pos == std::string::npos)return false;// 获取到截取数据out_str = in_str.substr(0, pos);// 从原始字符串中移除截取的字符串和对应的分隔符in_str.erase(0, pos + default_sep.size());return true;
}

接着完善反序列化接口:

// 反序列化
bool deserialize(std::string& in_str)
{bool getReqLineFlag = parseOneLineFromTotal(in_str, _req_line);if(!getReqLineFlag){LOG(LogLevel::WARNING) << "反序列化获取请求行失败";return false;}LOG(LogLevel::INFO) << "截取的请求行为:" << _req_line;// 未完...
}
截取请求报头

截取请求报头的方式与请求行非常类似,无非就是需要多次截取,但是需要考虑截取何时结束。根据HTTP请求体的特点,最后一行就是一个空行,即\r\n,所以可以考虑利用这个空行进行处理,具体思路如下:

因为每一次截取都会截取到分隔符之前的内容,所以可以考虑定义一个变量用于接收请求报头的结果,那么根据截取一行的函数的逻辑,只有成功找到了\r\n时才会进行截取,而截取的结果不会包含\r\n,那么一旦截取的结果是空且找到了\r\n,就说明找到了最后一行

根据这个思路,将获取请求报头数据的逻辑放在一个单独的函数中,如下:

// 获取请求报头
bool getReqHeadFromTotal(std::string &in_str)
{// 保存以\r\n结尾的一行数据std::string oneLine;while (true){bool getOneLineFlag = parseOneLineFromTotal(in_str, oneLine);// 如果getOneLineFlag为true并且oneLine不为空,说明当前行有效,否则代表已经找到了结尾if(getOneLineFlag && !oneLine.empty()){_req_head.push_back(oneLine);}else if(getOneLineFlag && oneLine.empty()){break;}else{return false;}}return true;
}

继续完善反序列化接口:

// 反序列化
bool deserialize(std::string &in_str)
{// ...bool getReqHeadLine = getReqHeadFromTotal(in_str);if (!getReqHeadLine){LOG(LogLevel::WARNING) << "反序列化获取请求报头失败";return false;}LOG(LogLevel::INFO) << "获取到的请求行为:";std::for_each(_req_head.begin(), _req_head.end(), [&](int i){std::cout << _req_head[i] << std::endl;});// 未完...
}
截取请求体

因为在前面截取请求行和截取请求报头时已经修改了HTTP请求字符串,所以剩下的就是请求体,直接赋值即可:

// 反序列化
bool deserialize(std::string &in_str)
{// ...// 获取请求体_req_body = in_str;return true;
}

创建HttpResponse

服务器需要给客户端返回内容,所以在这之前必须对整个HttpResponse结构进行序列化。同样,HTTP响应也有对应的三种数据:

  1. 请求行
  2. 请求报头
  3. 请求体

所以需要定义三个成员分别存放从请求获取到的内容,所以基本结构如下:

// HTTP响应
class HttpResponse
{
public: HttpResponse(std::string &rl, std::vector<std::string> &rq, std::string &rb): _resp_line(rl), _resp_head(rq), _resp_body(rb){}HttpResponse(std::string &rl, std::string &rb): _resp_line(rl), _resp_body(rb){}// 序列化bool serialize(std::string &out_str){}~HttpResponse(){}
private:std::string _resp_line;              // 响应头std::vector<std::string> _resp_head; // 响应报头std::string _resp_body;             // 响应体
};

实现HttpResponse序列化接口

实现serialize就会比实现deserialize接口简单,只需要根据对应的字段加上\r\n即可,所以基本代码如下:

// 序列化
bool serialize(std::string &out_str)
{// 给请求行添加\r\n_resp_line += default_sep;out_str += _resp_line;// 给请求报头的每一个字段加上\r\nstd::for_each(_resp_head.begin(), _resp_head.end(), [&](std::string &str){str += default_sep;out_str += str; });// 添加空行out_str += default_sep;out_str += _resp_body;return true;
}

修改HttpServer

只需要改变HttpServer类中的请求处理函数,但是如果要打印反序列的结果就必须提供对应的接口或者在HttpRequest类内提供打印函数,本次考虑后者:

void print()
{// 请求行LOG(LogLevel::INFO) << "请求行:" << _req_line;// 请求报头std::for_each(_req_head.begin(), _req_head.end(), [&](std::string& str){ LOG(LogLevel::INFO) << "请求报头:" << str; });// 请求体LOG(LogLevel::INFO) << "请求体:" << _req_body;
}

接着修改HttpServerhandleHttpRequest函数:

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);// 打印反序列结果req.print();// 构建HttpResponse返回std::string line = "HTTP 1.1 200 OK";std::string body = "<h1>Build HttpResponse success</h1>";HttpResponse resp(line, body);std::string out_str;// 序列化resp.serialize(out_str);LOG(LogLevel::INFO) << out_str;bs->sendData(out_str, ac_socketfd);
}

测试

主函数与上一节一样,测试结果如下:

在这里插入图片描述

在这里插入图片描述

从上图可以看到可以成功获取到HTTP请求结果并且正常回复HTTP响应,第一阶段目标完成

第二阶段

在第一阶段的基础之上,现在需要对HTTP请求和HTTP响应的每一个字段进行细化,本次不考虑某个字段或者属性是什么含义,只需要将其进行提取即可

修改HttpRequest

提取HTTP请求行中的字段

因为HTTP请求行中的每个字段是根据空格进行分隔的,回忆C/C++的输入和输出,默认也是以空白字符进行分隔,所以就可以利用这一点,可以使用C语言的sscanf()进行读取,也可以考虑使用C++的stringstream进行

因为需要读取到每个字段,所以需要对应的成员进行接收,这里就使用三个成员_req_method_req_uri_req_ver作为补充成员:

class HttpRequest
{
public:// ...private:std::string _req_method;            // 请求方法std::string _req_uri;               // 请求资源路径std::string _req_ver;               // HTTP请求版本// ...
};

接着就是实现一个函数用于从req_line中提取对应的字段填充_req_method_req_uri_req_ver三个成员:

=== “sscanf版本”

// sscanf版本
bool getContentFromReqLine()
{char method[1024] = {0};char uri[1024] = {0};char ver[1024] = {0};sscanf(_req_line.c_str(), "%s%s%s", method, uri, ver);LOG(LogLevel::INFO) << "请求行:" << method << "-" << uri << "-" << ver;_req_method = method;_req_uri = uri;_req_ver = ver;return true;
}

=== “stringstream版本”

 // stringstream版本bool getContentFromReqLine(){std::stringstream ss;// 读取到stringstream中ss << _req_line;// 输出到成员中ss >> _req_method >> _req_uri >> _req_ver;LOG(LogLevel::INFO) << "请求行:" << _req_method << "-" << _req_uri << "-" << _req_ver;return true;}

接下来修改deserialize的逻辑:

bool deserialize(std::string &in_str)
{// 截取请求行bool getReqLineFlag = parseOneLineFromTotal(in_str, _req_line);if (!getReqLineFlag){LOG(LogLevel::WARNING) << "反序列化获取请求行失败";return false;}LOG(LogLevel::INFO) << "截取的请求头为:" << _req_line;// 填充请求行的字段getContentFromReqLine();// ...return true;
}
提取HTTP请求报头中的字段

前面完成了获取到HTTP请求报头中的每一条数据,但是请求报头实际上是key-value结构的数据,服务器需要拿到其中的key以及value进行后续的处理,所以这里就需要分别取出key和对应的value

为了存储对应的keyvalue,可以考虑使用一个哈希表。这里,因为处理每一个每一条报头数据不需要经过_req_head过渡,所以可以考虑直接将分割出的字符串传递给处理分隔的函数,在该函数中直接将对应的键值对添加到哈希表即可

每一个键值对字符串以: 分隔,而不是:

首先完成分割逻辑:

bool getPairFromReqHead(std::string& oneLine)
{// 找到分隔符auto pos = oneLine.find(default_head_sep);// 左侧即为keystd::string key = oneLine.substr(0, pos);// 右侧即为valuestd::string value = oneLine.substr(pos + default_head_sep.size());// 插入到哈希表中_kv.insert({key, value});return true;
}

接下来修改deserializegetReqHeadFromTotal的逻辑:

=== “getReqHeadFromTotal

// 获取请求报头
bool getReqHeadFromTotal(std::string &in_str)
{// 保存以\r\n结尾的一行数据std::string oneLine;while (true){bool getOneLineFlag = parseOneLineFromTotal(in_str, oneLine);// 如果getOneLineFlag为true并且oneLine不为空,说明当前行有效,否则代表已经找到了结尾if (getOneLineFlag && !oneLine.empty()){getPairFromReqHead(oneLine);}// ...}return true;
}

=== “deserialize

// 反序列化
bool deserialize(std::string &in_str)
{// ...// 截取请求报头bool getReqHeadLine = getReqHeadFromTotal(in_str);if (!getReqHeadLine){LOG(LogLevel::WARNING) << "反序列化获取请求报头失败";return false;}LOG(LogLevel::INFO) << "获取到的请求报头为:";std::for_each(_kv.begin(), _kv.end(), [&](std::pair<std::string, std::string> kv){LOG(LogLevel::INFO) << kv.first << "-" << kv.second;});// ...return true;
}
提取HTTP请求体中的字段

保持和第一阶段的处理方式一样

修改HttpResponse

第一阶段的HttpResponse类只是使用一个固定的字符串进行序列化再发送给客户端,这个做法明显是不妥的。实际上,对于HTTP响应来说,应该处理下面几点:

  1. 允许根据请求内容是否合法自动构建响应状态码,并且根据状态码自动生成状态码描述
  2. 允许外部添加响应报头中的属性
  3. 根据客户端请求的内容读取服务端存在的对应文件并返回给客户端,没有时返回404页面
HTTP状态码

根据上面的思路,首先需要处理的就是状态码,在介绍HTTP协议基本结构与基本实现HTTPServer一节提到,HTTP协议规定任何客户端的请求都必须得到响应,而区分响应情况就是通过状态码

在HTTP中,状态码有以下几种:

类别原因短语
1XXInformational(信息性状态码)接收的请求正在处理
2XXSuccess(成功状态码)请求正常处理完毕
3XXRedirection(重定向状态码)需要进行附加操作以完成请求
4XXClient Error(客户端错误状态码)服务器无法处理请求
5XXServer Error(服务器错误状态码)服务器处理请求出错

但是,仅有开头还不足以说明具体的问题,所以每一种类别下还有具体的状态码和对应描述,因为状态码太多,所以下面仅仅展示常见的状态码:

状态码类别描述示例场景
100信息响应请求已接收,客户端应继续发送请求客户端询问服务器是否支持某些功能
101信息响应切换协议客户端请求切换到WebSocket协议
200成功响应请求成功页面加载成功
201成功响应资源创建成功创建新用户或上传文件
204成功响应请求成功但无内容返回删除操作后不返回任何内容
301重定向永久重定向网站迁移至新域名
302重定向临时重定向用户登录后跳转到主页
304重定向资源未修改,使用缓存浏览器缓存的资源未过期
400客户端错误请求无效或无法被服务器理解请求参数缺失或格式错误
401客户端错误未授权访问用户未提供身份验证
403客户端错误禁止访问用户权限不足
404客户端错误资源未找到请求的页面或API不存在
405客户端错误方法不允许使用了不支持的HTTP方法(如POST代替GET)
429客户端错误请求过多超过API速率限制
500服务器错误内部服务器错误服务器代码逻辑出错
502服务器错误错误网关服务器作为网关时收到无效响应
503服务器错误服务不可用服务器过载或维护中
504服务器错误网关超时服务器作为网关时未能及时从上游获取响应

其中,更为常见的就是200(OK)、404(Not Found)、403(Forbidden)、302(Redirect)和504(Bad Gateway)

本次优先考虑200(OK)和404(Not Found),对于重定向将在后面的章节介绍,此处不具体描述

处理响应行

首先是HTTP响应版本,这个字段可以设置为一个固定值,因为一般情况下只会在升级的时候更改,所以可以考虑使用一个字符串指定

接着是HTTP响应状态码和状态码描述,因为本次只考虑200和404,所以只有两种情况:

  1. 用户请求的资源存在
  2. 用户请求的资源不存在

根据这两种情况,需要考虑的问题就是如何判断用户请求的资源是否存在。前面提到,URI就是资源路径,也就是说,用户想要拿到的资源就在URI中。根据这一点得出「判断用户请求的资源是否存在」只需要「在当前服务器的资源目录中找到对应的文件是否存在」即可。现在的问题就转变为「如何判断一个文件是否存在」,这里可以使用文件流读取对应的文件,如果文件不存在就给用户返回一个空字符串,否则就将读取到的文件添加到结果字符串即可

根据上面的思路,首先就是要获取到URI,这一点其实在HttpRequest中已经做到了,所以在HttpResponse中需要获取一下即可,这里可以考虑让读取内容的函数接收一个URI字符串,所以获取文件内容函数的基本逻辑如下:

// 获取文件内容
std::string getFileContent(std::string& uri)
{// 当前uri中即为用户需要的文件,使用文件流打开文件std::fstream f(uri);// 如果文件为空,直接返回空字符串if(!f.is_open())return std::string();// 否则就读取文件内容std::string content;std::string line;while (std::getline(f, line))content += line;f.close();return content;
}

接着,在创建一个函数用于处理获取URI以及构建文件内容,前面提到可以使用HttpResponse对象获取到对应的URI,所以当前函数需要接收一个HttpRequest对象,并且在HttpRequest类中需要提供获取URI的函数:

=== “获取URI函数”

 // 获取URIstd::string getReqUri(){return _req_uri;}

=== “处理URI以及构建文件内容函数”

void buildHttpResponse(HttpRequest& req)
{// 获取uristd::string req_uri = req.getReqUri();// 根据uri获取文件内容std::string content = getFileContent(req_uri);
}

现在已经解决了文件问题,也就是说现在可以根据文件是否存在决定状态码的值和描述,根据前面的思路可以得出文件存在会返回空字符串,那么此时就说明状态码应该是404,否则就是200,所以这里就可以通过是否为空设置对应的状态码,而状态码描述可以通过状态码进行匹配,例如:

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

接下来继续完善构建函数buildHttpResponse,因为要设置状态码和状态码描述,所以需要两个成员接收这两个值,便于后面构建HTTP响应结构:

void buildHttpResponse(HttpRequest &req)
{// 获取uristd::string req_uri = req.getReqUri();// 根据uri获取文件内容std::string content = getFileContent(req_uri);if (content.empty()){// 如果为真,说明文件不存在// 设置状态码为404并设置状态码描述_status_code = 404;_status_code_desc = setStatusCodeDesc(_status_code);}else{// 文件存在_status_code = 200;_status_code_desc = setStatusCodeDesc(_status_code);}
}

处理完状态码和状态码描述之后,接下来就是将HTTP版本、状态码和状态码描述构建出一个HTTP响应行,首先修改原有的构造函数,删除不需要的字段:

// 默认HTTP版本
const std::string default_http_ver = "HTTP/1.1";
class HttpResponse
{
public:HttpResponse(): _http_ver(default_http_ver){}private:std::string _http_ver;               // HTTP版本// ...
};

接着,在buildHttpResponse函数中添加构建请求行的逻辑:

void buildHttpResponse(HttpRequest &req)
{// ...// 构建请求行_resp_line = _http_ver + std::to_string(_status_code) + _status_code_desc;
}
处理响应行

根据前面的要求「允许外部添加响应报头中的属性」,需要提供一个哈希表存储keyvalue,所以首先需要创建一个类成员,接着添加一个函数用于执行添加逻辑:

class HttpResponse
{
public:// ...void insertRespHead(std::string& key, std::string& value){_kv[key] = value;}// ...private:// ...std::unordered_map<std::string, std::string> _kv;// ...
};
处理响应体

根据客户端请求的内容读取服务端存在的对应文件并返回给客户端,没有时返回404页面,所以只需要在buildHttpResponse中根据是否有文件内容给定具体文件即可,对于存在对应的文件的,因为getFileContent返回的就是读取到的文件内容,所以直接将结果给响应头即可,但是对于404页面,并没有读取到一个实际的文件内容,这里可以考虑直接给getFileContent写入一个固定文件,即404文件,这样该函数获取到的文件内容就是404文件

有了基本思路,现在就是缺少这类文件。在添加文件之前,先仔细了解一下不带参数的URI,在介绍HTTP协议基本结构与基本实现HTTPServer中提到了/开始不一定是系统根目录,而是Web应用根目录,这个Web应用根目录实际上就是当前服务器程序所在目录下的一个文件夹,这个文件夹下放着一些静态资源,例如HTML、图片、视频、CSS、JavaScript等,所以客户端想访问资源本质就是让服务器在这个文件夹中找到对应的文件并将其中的内容返回给客户端

有了上面的概念,下面就是在当前服务器程序所在的目录创建一个Web应用根目录,基本目录结构如下:

主程序目录- Web应用根目录- src- HTML文件- assets- stylesheets- CSS文件- JavaScripts- JavaScript文件- public- images- 图片文件- videos- 视频文件- HttpServer程序

上面的目录只是一个参考目录,并不是固定的,可以根据自己或者其他地方的规定进行调整,下面将以这个目录结构为例演示,当前创建的目录结构如下:

HttpServer- wwwroot- src- assets- js- style- public- images- videos- HttpServer_main

接着,为了演示出客户端正常接收到服务器存在的文件以及404文件,需要在src目录下创建两个HTML文件,此处不给出具体的HTML文件代码

接着,修改buildHttpResponse逻辑,确保可以读取到文件并将文件内容存储到响应体中:

// 404页面固定路径
std::string default_404_page = "wwwroot/src/404.html";void buildHttpResponse(HttpRequest &req)
{// 获取uristd::string req_uri = req.getReqUri();// 根据uri获取文件内容std::string content = getFileContent(req_uri);if (content.empty()){// 如果为真,说明文件不存在// 设置状态码为404并设置状态码描述_status_code = 404;_status_code_desc = setStatusCodeDesc(_status_code);// 读取404页面并设置响应体_resp_body = getFileContent(default_404_page);}else{// 文件存在_status_code = 200;_status_code_desc = setStatusCodeDesc(_status_code);// 设置响应体_resp_body = content;}// 构建请求行_resp_line = _http_ver + std::to_string(_status_code) + _status_code_desc;
}

修改HttpServer

因为HttpServer需要调用sendData函数,该函数需要传入一个字符串作为发送数据,而HTTP响应中存在一个序列化函数,可以调用这个函数可以传入一个字符串,并将序列化后的字符串存储到参数的字符串中,这样就可以实现发送。所以整体handleHttpRequest逻辑修改如下:

void handleHttpRequest(SockAddrIn sock_addr_in, int ac_socketfd)
{// ...// 构建HTTP响应HttpResponse resq;resq.buildHttpResponse(req);// 序列化std::string out_str;resq.serialize(out_str);// 发送给客户端bs->sendData(out_str, ac_socketfd);
}

测试

主函数与上一节一样,测试结果如下:

在这里插入图片描述

从上面的测试结果可以发现,的确可以接收到数据,如果将地址栏的内容修改为localhost:8080/index.html,结果如下:

在这里插入图片描述

这个测试结果并不是像预期的那样显示主页的内容,而是显示404页面。那么明明存在index.html文件,为什么会出现无法读取到index.html?这是因为在解析URI时并没有考虑到URI起始的/,实际上getFileContent函数得到的uri字符串内容是/index.html,而已有的index.html文件路径为wwwroot/src/index.html,所以还需要在已有的uri上还需要加入默认的Web应用目录wwwroot/src,修改如下:

// Web应用路径
std::string default_webapp_dir = "wwwroot/";// HTML文件路径
std::string default_html_dir = "src";// 404页面固定路径
std::string default_404_page = default_webapp_dir + default_html_dir + "/404.html";void buildHttpResponse(HttpRequest &req)
{// 获取uri// ...// 补充uristd::string real_uri = default_webapp_dir + default_html_dir + req_uri;// 根据uri获取文件内容std::string content = getFileContent(real_uri);// ...
}

再次测试,结果如下:

在这里插入图片描述

优化

从上面的测试可以发现,如果想要访问index.html文件还需要手动加上/index.html,但是访问一个实际的网站时,尽管没有携带/index.html,依旧可以访问到网站的index.html文件,例如访问百度的首页:

默认访问:

在这里插入图片描述

在网址后添加/index.html

在这里插入图片描述

因为直接输入网址,浏览器请求的默认就是Web应用根目录下的某一个文件,只是默认情况下隐藏了IP地址+端口号后的/,实现这个效果的方式很简单,只需要在getFileContent函数的开始判断是否是/,如果是就直接返回index.html的内容即可。思路的确如此,但是在上面先处理了传递给getFileContent的参数为添加了wwwroot/src的字符串,所以实际上如果只有一个/,那么传递给getFileContent函数的参数为wwwroot/src/,所以修改如下:

// 获取文件内容
std::string getFileContent(std::string &uri)
{// 默认访问index.html文件if(uri == "wwwroot/src/")uri = "wwwroot/src/index.html";// ...
}

再次测试,观察结果:

在这里插入图片描述

可以发现已经默认到了index.html的内容

虽然上面的思路的确可以实现问题,但是如果默认以/结尾,那么只要判断最后字符串是否是/即可,这样不论前面的内容是什么,只要是以/结尾,都可以访问到主页,所以还可以修改为:

// 获取文件内容
std::string getFileContent(std::string &uri)
{// 默认访问index.html文件if(uri.back() == '/')uri = "wwwroot/src/index.html";// ...
}

现在,不论前面是什么都可以访问到index.html,哪怕是不存在的目录:

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/72387.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于Python+SQLite实现(Web)验室设备管理系统

实验室设备管理系统 应用背景 为方便实验室进行设备管理&#xff0c;某大学拟开发实验室设备管理系统 来管理所有实验室里的各种设备。系统可实现管理员登录&#xff0c;查看现有的所有设备&#xff0c; 增加设备等功能。 开发环境 Mac OSPyCharm IDEPython3Flask&#xff…

深拷贝and浅拷贝!

一、什么是拷贝&#xff1f;什么是深拷贝和浅拷贝&#xff1f; &#xff08;1&#xff09;拷贝&#xff1a;拷贝就是为了复用原对象的部分or全部数据&#xff0c;在原对象的基础上通过复制的方式创建一个新的对象。 拷贝对象可以分为三种类型&#xff1a;直接赋值、浅拷贝和深拷…

高频面试题(含笔试高频算法整理)基本总结回顾43

干货分享&#xff0c;感谢您的阅读&#xff01; &#xff08;暂存篇---后续会删除&#xff0c;完整版和持续更新见高频面试题基本总结回顾&#xff08;含笔试高频算法整理&#xff09;&#xff09; 备注&#xff1a;引用请标注出处&#xff0c;同时存在的问题请在相关博客留言…

网络爬虫【简介】

我叫补三补四&#xff0c;很高兴见到大家&#xff0c;欢迎一起学习交流和进步 今天来讲一讲视图 一、网络爬虫的定义 网络爬虫&#xff08;Web Crawler&#xff09;&#xff0c;又称为网络蜘蛛、网络机器人等&#xff0c;是一种按照一定规则自动抓取互联网信息的程序或脚本。它…

​AI时代到来,对电商来说是效率跃升,还是温水煮青蛙

​凌晨三点的义乌商贸城&#xff0c;95后创业者小王&#xff0c;静静地盯着屏幕上的AI工具&#xff0c;竟露出了笑容。这个月他的跨境玩具店销量提升了不少&#xff0c;从之前的状态翻了3倍&#xff1b;而且团队人数有所变化&#xff0c;从5人缩减到了2人&#xff08;其中包括他…

PDF文件密码保护破解:安全解密的步骤与技巧

PDF文件加密后&#xff0c;需要特定的密码才能访问内容。以下是一些常见的方法来解密PDF文件&#xff1a; 方法一&#xff1a;使用Adobe Acrobat 如果你有Adobe Acrobat Pro&#xff0c;可以使用它来解密PDF文件。 打开Adobe Acrobat Pro&#xff1a; 启动Adobe Acrobat Pro…

qt 自带虚拟键盘的编译使用记录

一、windows 下编译 使用vs 命令窗口&#xff0c;分别执行&#xff1a; qmake CONFIG"lang-en_GB lang-zh_CN" nmake nmake install 如果事先没有 指定需要使用的输入法语言就进行过编译&#xff0c;则需要先 执行 nmake distclean 清理后执行 qmake 才能生效。 …

C++刷题(二):栈 + 队列

&#x1f4dd;前言说明&#xff1a; 本专栏主要记录本人的基础算法学习以及刷题记录&#xff0c;使用语言为C。 每道题我会给出LeetCode上的题号&#xff08;如果有题号&#xff09;&#xff0c;题目&#xff0c;以及最后通过的代码。没有题号的题目大多来自牛客网。对于题目的…

精通游戏测试笔记(持续更新)

第一章、游戏测试的两条规则 不要恐慌 不要将这次发布当作最后一次发布 不要相信任何人 把每次发布当作最后一次发布 第二章&#xff1a;成为一名游戏测试工程师

Windows功能之FTP服务器搭建

一、创作背景 之前有用linux系统搭建过ftp服务器&#xff0c;最近想着用windows系统也顺便搭建一个&#xff0c;看网上有第三方服务软件一键部署&#xff0c;记得windows可以不借助第三方软件就可以搭建&#xff0c;就想顺便操作试试&#xff0c;结果老是连接不上&#xff0c;费…

星型组网模块的两种交互方式优缺点解析

星型组网模块简介 星型组网模块工作在433MHz频段&#xff1b;星型组网模块集主机&#xff08;协调器&#xff09;、终端为一体&#xff0c;星型组网模块具有长距离、高速率两种传输模式&#xff0c;一个主机&#xff08;协调器&#xff09;支持多达200个节点与其通讯&#xff0…

【2025.3.13】记一次双系统笔记本加装固态硬盘记录 linux扩容 linux更换/home和/opt所在硬盘 windows无法调整亮度

文章目录 &#x1f315;事情经过&#x1f315;更换/home和/opt的挂载硬盘&#x1f319;目的&#x1f319;初始化1t固态硬盘&#x1f319;打开Linux查看硬盘信息&#x1f319;给新1t固态硬盘分区&#x1f319;格式化分区&#x1f319;把新1t固态硬盘先挂载到/mnt/ssd_1t 用于后续…

【测试语言基础篇】Python基础之List列表

一、Python 列表(List) 序列是Python中最基本的数据结构。序列中的每个元素都分配一个数字 - 它的位置&#xff0c;或索引&#xff0c;第一个索引是0&#xff0c;第二个索引是1&#xff0c;依此类推。 Python有6个序列的内置类型&#xff0c;但最常见的是列表和元组。序列都可…

软件测试之使用Requests库进行接口测试

文章目录 前言Requests库是什么为什么要用Requests库进行接口测试安装Requests库Requests库使用发送GET请求发送带查询参数的GET请求响应内容格式添加请求头信息发送一个POST请求查看响应内容断言请求超时Cookie与Session模拟登录 参考目录 前言 阅读本文前请注意最后编辑时间…

AttributeError: module ‘backend_interagg‘ has no attribute ‘FigureCanvas‘

AttributeError: module backend_interagg has no attribute FigureCanvas 这个错误通常是由于 Matplotlib 的后端配置问题引起的。具体来说&#xff0c;Matplotlib 在尝试加载某个后端时&#xff0c;发现该后端模块中缺少必要的属性&#xff08;如 FigureCanvas&#xff09;&a…

iWebOffice2015 中间件如何在Chrome107及之后的高版本中加载

iWebOffice2015是江西金格科技有限公司开发的一款智能文档中间件&#xff0c;和一些知名OA及ERP公司曾经达成OEM合作&#xff0c;所以用户一度比较多&#xff0c;但不幸的是Chromium内核浏览器在2022年10月份发布的107版本中永久取消了对PPAPI插件的加载支持&#xff0c;导致使…

OpnenHarmony 开源鸿蒙北向开发——1.开发环境搭建(DevEco Studio 5.03)

我这边是基于window下对OpenHarmony开源鸿蒙进行北向开发。 一、安装DevEco Studio 1、下载 下载中心 | 华为开发者联盟-HarmonyOS开发者官网&#xff0c;共建鸿蒙生态 2、安装 下载完成之后进行解压 双击进行安装 按照我的步骤进行 选择安装目录&#xff0c;全部配置完成后…

linux(ubuntu)中Conda、CUDA安装Xinference报错ERROR: Failed to build (llama-cpp-python)

文章目录 一、常规办法二、继续三、继续四、缺少 libgomp库&#xff08;最终解决&#xff09;在 Conda 环境中安装 libgomp 如果符合标题情况 执行的&#xff1a; pip install "xinference[all]"大概率是最终解决的情况。 一、常规办法 llama-cpp-python 依赖 CMak…

OpenGL中绘制图形元素的实现(使用visual studio(C++)绘制一个矩形)

目标&#xff1a;使用OpenGL提供的函数绘制矩形、线段、三角形等基本图形元素 所需效果 实验步骤 1、配置OpenGL&#xff08;详情参见OpenGL的配置&#xff09; 2、头文件引入 #include <gl/glut.h> 3、编写方法体 1>矩形实现 //绘制矩形 void DisplayRectangl…

面试高频#LeetCode#Hot100-字母异位词分组

题号链接 49. 字母异位词分组 - 力扣&#xff08;LeetCode&#xff09; 1首先定义map集合一个String对应一个String[]集合&#xff0c;遍历字符串数组 2对其先进行拆分&#xff0c;拆分为字符数组&#xff0c;再进行排序&#xff0c;再转为字符串 3如果key值没有就创建一个字符…