✨个人主页: 熬夜学编程的小林
💗系列专栏: 【C语言详解】 【数据结构详解】【C++详解】【Linux系统编程】【Linux网络编程】
目录
1、HttpRequest类
1.1、基本结构
1.2、构造析构函数
1.3、反序列化函数
1.4、GetLine()
1.5、打印函数
2、HttpServer类
2.1、HandlerHttpRequest()
3、测试
3.1、测试结果(基本版本)
3.2、代码优化一
3.3、测试结果(优化一)
3.4、代码优化二
3.5、测试结果(优化二)
4、增加路径字段
4.1、HttpRequest类
4.2、解析请求行
4.3、获取url 和 path
4.4、HandlerHttpRequest()
1、HttpRequest类
1.1、基本结构
HttpRequest类基本结构包括请求行,请求报头,正文!
class HttpRequest
{
private:// \r\n// \r\ndata// 获取一行信息std::string GetLine(std::string &reqstr);// 解析请求行void ParseReqLine();// 解析请求报头,以冒号加空格分隔void ParseReqHeader();
public:HttpRequest();// 反序列化void Deserialize(std::string &reqstr) ;// 打印请求信息void Print();~HttpRequest();
private:// 基本的httprequest的格式std::string _req_line; // 请求行std::vector<std::string> _req_headers; // 请求报头std::string _blank_line; // 空行std::string _body_text; // 正文
};
1.2、构造析构函数
构造函数初始化空行即可,因为空行是固定的,析构函数无需处理!
const static std::string base_sep = "\r\n";HttpRequest() : _blank_line(base_sep)
{}~HttpRequest()
{}
1.3、反序列化函数
反序列化即将字符串转化成结构化字段!
// 反序列化
void Deserialize(std::string &reqstr)
{// 基本的反序列化_req_line = GetLine(reqstr); // 读取一行,请求行// 请求报头std::string header;do{header = GetLine(reqstr);if (header.empty())break;else if (header == base_sep)break;_req_headers.push_back(header);} while (true);// 正文if (!reqstr.empty()){_body_text = reqstr;}
}
1.4、GetLine()
获取一行有效信息,没找到分隔符返回空串,找到分隔符但是没有有效信息则返回分隔符!
// \r\n
// \r\ndata
// 获取一行信息
std::string GetLine(std::string &reqstr)
{auto pos = reqstr.find(base_sep);if (pos == std::string::npos) // 没找到分隔符返回空return std::string();std::string line = reqstr.substr(0, pos); // 截取一行有效信息reqstr.erase(0, line.size() + base_sep.size()); // 删除有效信息和分隔符return line.empty() ? base_sep : line; // 有效信息为空则返回分隔符,不为空返回有效信息
}
1.5、打印函数
打印反序列化出来的字符串!
// 打印请求信息
void Print()
{std::cout << "---------------------------" << std::endl;std::cout << "###" << _req_line << std::endl;for (auto &header : _req_headers){std::cout << "@@@" << header << std::endl;}std::cout << "***" << _blank_line;std::cout << ">>>" << _body_text << std::endl;
}
2、HttpServer类
2.1、HandlerHttpRequest()
std::string HandlerHttpRequest(std::string &reqstr)
{
#ifdef TESTstd::cout << "---------------------------------------------" << std::endl;std::cout << reqstr;// return std::string();std::string responsestr = "HTTP/1.1 200 OK\r\n";responsestr += "Content-Type: text/html\r\n";responsestr += "\r\n";responsestr += "<html><h1>hello linux,hello net!<h2></html>";return responsestr;
#elseHttpRequest req; // 构建请求对象req.Deserialize(reqstr); // 反序列化字符串req.Print(); // 打印反序列的字符串return std::string(); // 保证编译通过
#endif
}
3、测试
主函数
// ./httpserver 8888
int main(int argc, char *argv[])
{if (argc != 2){std::cerr << "Usage: " << argv[0] << "local-port" << std::endl;exit(0);}uint16_t port = std::stoi(argv[1]);HttpServer hserver;std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(std::bind(&HttpServer::HandlerHttpRequest, &hserver, std::placeholders::_1),port);tsvr->Loop();return 0;
}
3.1、测试结果(基本版本)
注意:执行的方法都是HandlerHttpRequest(),且代码执行的是#else后的代码!
运行结果
上面是基本的序列化,我们还可以进一步序列化,将请求行的成员都反序列化,因此请求类需要增加成员变量!
3.2、代码优化一
HttpRequest类成员变量
HttpRequest类需要增加请求方法,统一资源定位符,版本三个成员变量!
class HttpRequest
{
private:// 基本的httprequest的格式std::string _req_line; // 请求行std::vector<std::string> _req_headers; // 请求报头std::string _blank_line; // 空行std::string _body_text; // 正文// 更具体的属性字段,需要进一步反序列化std::string _method; // 请求方法std::string _url; // 统一资源定位符std::string _version; // 版本
};
反序列化函数
反序列化函数需要增加解析请求行函数!
// 反序列化
void Deserialize(std::string &reqstr)
{// 基本的反序列化_req_line = GetLine(reqstr); // 读取一行,请求行// 请求报头std::string header;do{header = GetLine(reqstr);if (header.empty())break;else if (header == base_sep)break;_req_headers.push_back(header);} while (true);// 正文if (!reqstr.empty()){_body_text = reqstr;}// 再进一步反序列化ParseReqLine(); // 解析请求行
}
解析请求行函数
请求行的成员之间使用空格分隔,因此可以使用字符串流对象,直接进行流提取!
// 解析请求行
void ParseReqLine()
{std::stringstream ss(_req_line); // 以空格为分隔符 cin >>ss >> _method >> _url >> _version; // 以空格为分隔符依次将请求行的内容赋值给成员变量
}
打印请求信息
// 打印请求信息
void Print()
{std::cout << "---------------------------" << std::endl;std::cout << "###" << _req_line << std::endl;for (auto &header : _req_headers){std::cout << "@@@" << header << std::endl;}std::cout << "***" << _blank_line;std::cout << ">>>" << _body_text << std::endl;std::cout << "Method: " << _method << std::endl;std::cout << "Url: " << _url << std::endl;std::cout << "Version: " << _version << std::endl;
}
3.3、测试结果(优化一)
浏览器输入 42.193.244.117:8888
注意:IP是自己服务器的公网IP,并且需要启动可执行程序!
运行结果
浏览器输入 42.193.244.117:8888/a/b/c/d/e/f/html
运行结果
上面将请求行的成员都反序列化,我们还可以进一步序列化,将请求报头进行反序列化,照样需要增加成员变量,此处我们使用哈希表存储,因为每行报文都是以冒号+空格分隔的值!
3.4、代码优化二
HttpRequest类成员变量
HttpRequest类增加一个KV形式的哈希表即可!
class HttpRequest
{
private:// 基本的httprequest的格式std::string _req_line; // 请求行std::vector<std::string> _req_headers; // 请求报头std::string _blank_line; // 空行std::string _body_text; // 正文// 更具体的属性字段,需要进一步反序列化std::string _method; // 请求方法std::string _url; // 统一资源定位符std::string _version; // 版本std::unordered_map<std::string, std::string> _headers_kv; // 存储每行报文的哈希表
};
反序列化函数
该反序列函数需要加前面的基础上继续解析请求报头!
// 反序列化
void Deserialize(std::string &reqstr)
{// 基本的反序列化_req_line = GetLine(reqstr); // 读取一行,请求行// 请求报头std::string header;do{header = GetLine(reqstr);if (header.empty())break;else if (header == base_sep)break;_req_headers.push_back(header);} while (true);// 正文if (!reqstr.empty()){_body_text = reqstr;}// 再进一步反序列化ParseReqLine(); // 解析请求行ParseReqHeader(); // 解析请求报头
}
解析请求报头函数
遍历请求报头成员变量,以行分隔符查找有效信息,找到且分隔符前的有效信息不为空且分隔符后的有效信息不为空,则将KV值插入到哈希表中!
const static std::string line_sep = ": "; // 行分隔符// 解析请求报头,以冒号加空格分隔
void ParseReqHeader()
{for (auto &header : _req_headers){auto pos = header.find(line_sep);if (pos == std::string::npos)continue;std::string k = header.substr(0, pos); // 截取key值std::string v = header.substr(pos + line_sep.size()); // 截取value值if (k.empty() || v.empty())continue;_headers_kv.insert(std::make_pair(k, v)); // 将对应的kv值插入到哈希表中}
}
打印请求信息函数
// 打印请求信息
void Print()
{std::cout << "---------------------------" << std::endl;std::cout << "###" << _req_line << std::endl;for (auto &header : _req_headers){std::cout << "@@@" << header << std::endl;}std::cout << "***" << _blank_line;std::cout << ">>>" << _body_text << std::endl;std::cout << "Method: " << _method << std::endl;std::cout << "Url: " << _url << std::endl;std::cout << "Version: " << _version << std::endl;for (auto head_kv : _headers_kv){std::cout << ")))" << head_kv.first << "->"<< head_kv.second << std::endl;}
}
3.5、测试结果(优化二)
注意:执行的方法都是HandlerHttpRequest(),且代码执行的是#else后的代码!
运行结果
4、增加路径字段
我们向服务器请求的时候,需要知道资源的路径,因此我们可以增加路径字段(根目录为wwwroot/),当url为 / 时,默认访问wwwroot/index.html!
4.1、HttpRequest类
HttpRequest类增加路径成员,并将路径初始化为web根目录!
const static std::string prefixpath = "wwwroot"; // web根目录class HttpRequest
{
public:HttpRequest() : _blank_line(base_sep), _path(prefixpath){}
private:// 基本的httprequest的格式std::string _req_line; // 请求行std::vector<std::string> _req_headers; // 请求报头std::string _blank_line; // 空行std::string _body_text; // 正文// 更具体的属性字段,需要进一步反序列化std::string _method; // 请求方法std::string _url; // 统一资源定位符std::string _path; // 资源路径std::string _version; // 版本std::unordered_map<std::string, std::string> _headers_kv; // 存储每行报文的哈希表
};
4.2、解析请求行
解析请求行除了解析url之外还需要解析path,path 默认直接使用path + url即可,但是当url 为 / 时,path 还需要加上默认访问文件 index.html !
// 解析请求行
void ParseReqLine()
{std::stringstream ss(_req_line); // 以空格为分隔符 cin >>ss >> _method >> _url >> _version; // 以空格为分隔符依次将请求行的内容赋值给成员变量_path += _url;// 只有web根目录返回index.htmlif (_path[_path.size() - 1] == '/'){_path += homepage;}
}
4.3、获取url 和 path
获取url 和 path 直接返回成员变量即可!
std::string Url()
{LOG(DEBUG, "Client Want url %s\n", _url.c_str());return _url;
}
std::string Path()
{LOG(DEBUG, "Client Want path %s\n", _path.c_str());return _path;
}
4.4、HandlerHttpRequest()
std::string HandlerHttpRequest(std::string &reqstr)
{HttpRequest req; // 构建请求对象req.Deserialize(reqstr); // 反序列化字符串std::string url = req.Url();std::string path = req.Path();return std::string(); // 保证编译通过
}
运行结果