响应式网站的费用,移动商城信息费,搭建网站视频教程,湖南常德市对于生命#xff0c;你不妨大胆一点#xff0c; 因为我们始终要失去它。 --- 尼采 --- ✨✨✨项目地址在这里 ✨✨✨ ✨✨✨https://gitee.com/penggli_2_0/TcpServer✨✨✨ 仿mudou的高并发服务器 1 前言2 Util工具类3 HTTP协议3.1 HTTP请求3.2 HTTP应答 4 上下文解析模块… 对于生命你不妨大胆一点 因为我们始终要失去它。 --- 尼采 --- ✨✨✨项目地址在这里 ✨✨✨ ✨✨✨https://gitee.com/penggli_2_0/TcpServer✨✨✨ 仿mudou的高并发服务器 1 前言2 Util工具类3 HTTP协议3.1 HTTP请求3.2 HTTP应答 4 上下文解析模块5 HTTP服务器对象 1 前言
上一篇文章我们基本实现了高并发服务器所需的基础模块通过TcpServer类可以快速搭建一个TCP服务器。我们的最终目的是使用这个高并发服务器去实现一些业务那么在网络通信中我们就可以来实现一下HTTP服务。让浏览器可以访问获取数据。
为了实现HTTP服务器首要的工作就是实现HTTP协议协议是网络通信的基础只有确定了协议我们才能正常解析请求报文并组织应答报文可以让浏览器成功获取数据。
完成HTTP协议之后就是设计一种报文解析模块可以从缓冲区中获取数据进行解析数据得到完整请求。
最终将这些整合为一个HTTP服务器模块设计回调函数实现HTTP服务器的功能
2 Util工具类
在HTTP服务器处理中经常需要一些常用操作比如切分字符串编码转换通过状态码找到对应状态解析… Util工具类就是用来实现这些功能的类
SplitStr 功能根据指定的分隔符 sep 将字符串 src 切分成多个子字符串并将这些子字符串存储在 sub 向量中。返回值返回切分后的子字符串数量。 ReadFile 功能以二进制方式读取文件 filename 的内容到字符串 buf 中。返回值如果文件打开和读取成功返回 true否则返回 false。 WriteFile 功能以二进制方式将字符串 buf 的内容写入到文件 filename 中如果文件已存在则覆盖。返回值如果文件打开和写入成功返回 true否则返回 false。 UrlEncode 功能对字符串 url 进行 URL 编码可以选择是否将空格编码为 。返回值返回编码后的字符串。 HexToC 功能将十六进制字符转换为对应的整数值。返回值返回转换后的整数值。 UrlDecode 功能对字符串 url 进行 URL 解码可以选择是否将 解码为空格。返回值返回解码后的字符串。 StatuDesc 功能根据给定的状态码 code 返回对应的状态描述。返回值返回状态描述字符串如果状态码未知则返回 “Unkonw”。 ExtMime 功能根据 URL 的扩展名返回对应的 MIME 类型。返回值返回 MIME 类型字符串如果扩展名未知则返回 “application/octet-stream”。 IsLegPath 功能检查字符串 path 是否是合法的路径主要检查是否存在非法的 “…” 使用。返回值如果路径合法返回 true否则返回 false。 IsDir 功能检查给定的路径 dir 是否是一个目录。返回值如果是目录返回 true否则返回 false。 IsRegular 功能检查给定的路径 dir 是否是一个常规文件。返回值如果是常规文件返回 true否则返回 false。
// 公共方法类
class Util
{
public:static ssize_t SplitStr(const std::string src, const std::string sep, std::vectorstd::string sub){// 根据sep分隔符切分字符串int offset 0; // 偏移量while (offset src.size()){size_t pos src.find(sep, offset);// 没有找到sepif (pos std::string::npos){// 直接将offset后的字符串当成子串sub.push_back(src.substr(offset));break;}// 找到了sepelse{size_t len pos - offset;if (len 0){offset;continue;}sub.push_back(src.substr(offset, len));offset len; // 偏移量向后移动}}return sub.size();}static bool ReadFile(const std::string filename, std::string *buf){std::ifstream ifs(filename, std::ios::binary); // 以读方式打开文件采取二进制读取方式if (ifs.is_open() false){LOG(ERROR, Open %s Failed!\n, filename.c_str());return false;}// 获取文件大小ifs.seekg(0, ifs.end); // 将读取位置移动到文件末尾size_t n ifs.tellg(); // 此时的偏移量即为文件大小ifs.seekg(0, ifs.beg); // 将读取位置移动到到文件开头buf-resize(n); // 将缓冲区大小设置为文件大小// 进行写入ifs.read((*buf)[0], n);// 关闭文件ifs.close();return true;}static bool WriteFile(const std::string filename, const std::string buf){std::ofstream ofs(filename, std::ios::binary | std::ios::trunc); // 使用写方式打开进行二进制覆盖写if (ofs.is_open() false){LOG(ERROR, Open %s Failed!\n, filename.c_str());return false;}// 进行写入ofs.write(buf[0], buf.size());if (ofs.good() false){LOG(ERROR, Write %s Failed!\n, filename.c_str());return false;}ofs.close();return true;}static std::string UrlEncode(const std::string url, bool is_space_encode){std::string ret;// 进行编码for (auto ch : url){//. - _ ~ 四个字符绝对不编码// 字母与数字不见编码if (ch . || ch - || ch _ || ch ~ || isalnum(ch)){ret ch;continue;}// 空格编码为 if (ch is_space_encode){ret ;continue;}// 其余字符进行编码char buf[4]; // 编码格式 %___snprintf(buf, 4, %%%02X, ch);ret buf;}return ret;}// URL解码static char HexToC(char c){if (c 0 c 9){return c - 0;}else if (c a c z){return c - a 10;}else if (c A c Z){return c - A 10;}return -1;}static std::string UrlDecode(const std::string url, bool is_space_decode){std::string res;// 遍历字符串 遇到%就进行解码for (int i 0; i url.size(); i){if (url[i] %){char v1 HexToC(url[i 1]);char v2 HexToC(url[i 2]);char c (v1 4) v2;res c;i 2;continue;}else if (url[i] is_space_decode){res ;continue;}else{res url[i];}}return res;}// 返回状态码static std::string StatuDesc(int code){auto ret _statu_msg.find(code);if (ret _statu_msg.end()){return Unkonw;}return ret-second;}// 解析文件后缀static std::string ExtMime(const std::string url){size_t pos url.rfind(.);// 没有找到返回if (pos std::string::npos){LOG(DEBUG, 没有找到.\n);return applicantion/octet-stream;}std::string str url.substr(pos);LOG(DEBUG, 文件类型:%s\n, str.c_str());auto it _mime_msg.find(str);if (it _mime_msg.end()){return applicantion/octet-stream;}return it-second;}// 检查是否是合法路径static bool IsLegPath(const std::string path){// 采用计数法int level 0;std::vectorstd::string subdir;int ret SplitStr(path, .., subdir);if (ret 0)return false;for (auto s : subdir){if (s ..){level--;if (level 0)return false;continue;}elselevel;}return true;}static bool IsDir(const std::string dir){struct stat st;int n ::stat(dir.c_str(), st);if (n 0)return false;return S_ISDIR(st.st_mode);}static bool IsRegular(const std::string dir){struct stat st;int n ::stat(dir.c_str(), st);if (n 0)return false;return S_ISREG(st.st_mode);}
};3 HTTP协议
3.1 HTTP请求
http协议的请求格式是这样的
请求行包含请求方法资源路径URLHTTP版本请求报头以键值对的形式储存必要信息空行用于识别正文请求正文储存本次请求的正文 针对这个结构我们可以搭建一个HTTP请求的基础框架
class
{
public:std::string _method; // 请求方法std::string _path; // 查询路径std::string _version; // 协议版本std::string _body; // 请求正文std::smatch _matches; // 资源路径的正则提取解析std::unordered_mapstd::string, std::string _headers; // 请求报头std::unordered_mapstd::string, std::string _params; // 查询字符串};然后继续设置一些接口
插入头部字段的接口检查请求中是否有该头部字段插入查询字符串检查请求中是否有该查询字符串获取查询字符串获取正文长度是否为长连接
class HttpRequest
{
public:std::string _method; // 请求方法std::string _path; // 查询路径std::string _version; // 协议版本std::string _body; // 请求正文std::smatch _matches; // 资源路径的正则提取解析std::unordered_mapstd::string, std::string _headers; // 请求报头std::unordered_mapstd::string, std::string _params; // 查询字符串
public:// 重置请求void Reset(){_method.clear();_path.clear();_version.clear();_body.clear();std::smatch tmp;_matches.swap(tmp);_headers.clear();_params.clear();}// 插入头部字段void SetHeader(const std::string key, const std::string val){_headers.insert(std::make_pair(key, val));}// 判断是否有该头部字段bool HasHeader(const std::string key) const{auto it _headers.find(key);if (it _headers.end()){return false;}return true;}// 获取头部字段std::string GetHeader(const std::string key) const{auto it _headers.find(key);if (it _headers.end()){return ;}return it-second;}// 插入查询字符串void SetParam(const std::string key, const std::string val){_params.insert(std::make_pair(key, val));}// 判断是否有该查询字符串bool HasParam(const std::string key){auto it _params.find(key);if (it _params.end()){return false;}return true;}// 获取查询字符串std::string GetParam(const std::string key){auto it _params.find(key);if (it _params.end()){return ;}return it-second;}// 获取正文长度size_t ContentLength(){bool ret HasHeader(Content-Length);if (ret){// 转换为长整形return std::stol(GetHeader(Content-Length));}return 0;}bool Close() const{// 没有Connection字段或者Connection字段是close 就是短连接if (HasHeader(Connection) true GetHeader(Connection) close){return true;}return false;}
};
这样一个基础的HTTP请求结构就设计好了
3.2 HTTP应答
http协议的应答格式是这样的
状态行包含HTTP版本状态码状态码描述应答报头储存必要信息换行符用于识别正文正文储存应答的正文结构 根据应答结构我们可以搭建其应答框架
设置头部字段获取头部字段设置正文设置应答状态是否是长连接
class HttpResponse
{
public:int _statu; // 状态码bool _rediect_flag; // 重定向标志std::string _rediect_url; // 重定向的路径std::string _body; // 响应正文std::unordered_mapstd::string, std::string _headers; // 响应报头public:HttpResponse(int statu) : _statu(statu) {}// 重置响应void Reset(){}// 插入头部字段void SetHeader(const std::string key, const std::string val){_headers.insert(std::make_pair(key, val));}// 判断是否有该头部字段bool HasHeader(const std::string key){auto it _headers.find(key);if (it _headers.end()){return false;}return true;}// 获取头部字段std::string GetHeader(const std::string key){auto it _headers.find(key);if (it _headers.end()){return ;}return it-second;}void SetContent(const std::string body, const std::string type text/html){_body body;SetHeader(Content-Type, type);}void SetRediret(const std::string url, int statu 302){_statu statu;_rediect_flag true;_rediect_url url;}bool Close(){// 没有Connection字段或者Connection字段是close 就是短连接if (HasHeader(Connection) true GetHeader(Connection) close){return true;}return false;}
};
这样HTTP协议的请求与应答我们就完成了可以进一步进行请求与应答的解析工作了
4 上下文解析模块
针对应答的反序列化我们不在协议模块中直接进行设置因为我们无法保证连接一次就可以获取完整的报文结构所以在一个连接中要维护一个上下文结构可以在多次处理时知道本次处理应该从何处进行
在这个上下文中首先我们就需要一个状态变量可以标识当前应该处理什么字段 RECV_HTTP_ERROR --- 处理出错RECV_HTTP_LINE --- 处理请求行RECV_HTTP_HEAD --- 处理头部字段RECV_HTTP_BODY --- 处理正文RECV_HTTP_OVER --- 处理完成每一个上下文都匹配一个请求对象将解析好的字段储存到这个请求对象中
处理请求行处理请求行时使用正则表达式快速进行处理注意URL编码的转换请求方法的大小写以及拆分出查询字符串处理头部字段一行一行的进行处即可直到遇到空行处理正文从缓冲区读取出正文长度的数据不够继续等待够了就返回。
需要注意的是获取数据时不一定会获取到预期的数据一定要做好情况分类保证正常读取 避免出现数据过长数据不足等情况
上下文每次解析都将数据及时储存到该上下文中对应的请求对象中
typedef enum
{RECV_HTTP_ERROR,RECV_HTTP_LINE,RECV_HTTP_HEAD,RECV_HTTP_BODY,RECV_HTTP_OVER
} HttpRecvStatu;static const int MAX_SIZE 8192;class HttpContext
{
private:int _resp_statu; // 响应状态码HttpRequest _request; // 请求信息HttpRecvStatu _recv_statu; // 解析状态
private:bool ParseHttpLine(const std::string line){// 对请求行进行正则表达式解析// 设置解析方法 忽略大小写// std::regex re((GET|HEAD|POST|PUT|DELETE) ([^?])\\?(.*) (HTTP/1\\.[01])(?:\n|\r\n)?, std::regex::icase);std::regex re((GET|HEAD|POST|PUT|DELETE) ([^?]*)(?:\\?(.*))? (HTTP/1\\.[01])(?:\n|\r\n)?, std::regex::icase);//(GET|HEAD|POST|PUT|DELETE) 获取GET...请求方法//([^?]) 匹配若干个 非字符 直到 --- 获取资源路径//\\?(.*) \\?表示匹配原始字符 (.*)访问到空格 ---获取请求参数//(HTTP/1\\.[01]) 匹配HTTP/1. 01任意一个字符//(?:\n|\r\n)? 匹配\n或者\r\n (?: ...)表示匹配摸个格式字符串但是不提取 .结尾的表示前面的表达式0次或1次std::smatch matches;bool ret std::regex_match(line, matches, re);if (ret false){LOG(ERROR, regex_match failed\n);_resp_statu 400; // Bad Reauest!return false;}// 0:GET /a/b/c/search?qkeywordlangen HTTP/1.1// 1:GET// 2:/a/b/c/search// 3:qkeywordlangen// 4:HTTP/1.1_request._method matches[1];// 请求方法统一转换为大写std::transform(_request._method.begin(), _request._method.end(), _request._method.begin(), ::toupper);_request._path Util::UrlDecode(matches[2], false);_request._version matches[4];// 对查询字符串进行解析std::string str matches[3];std::vectorstd::string substr;// 进行切分字符串Util::SplitStr(str, , substr);// 遍历容器for (auto s : substr){// 寻找size_t pos s.find();if (pos std::string::npos){LOG(ERROR, ParseHttpLine Failed\n);_recv_statu RECV_HTTP_ERROR;_resp_statu 400; // BAD Resquestreturn false;}// 找到了 ‘’std::string key Util::UrlDecode(s.substr(0, pos), true);std::string value Util::UrlDecode(s.substr(pos 1), true);LOG(INFO, 查询字符串%s: %s\n, key.c_str(), value.c_str());_request.SetParam(key, value);}return true;}// 解析请求行bool RecvHttpLine(Buffer *buf){if (_recv_statu ! RECV_HTTP_LINE)return false;// 获取一行数据 带有\r\nstd::string line buf-GetLineAndPop();if (line.size() 0){// 缓存区中没有完整的一行数据 进行分类讨论// 如果缓冲区数据大于极限值if (buf-ReadAbleSize() MAX_SIZE){_resp_statu 414; // URL TOO LONG_recv_statu RECV_HTTP_ERROR;return false;}// 反之不处理return true;}// 一行的数据过长if (line.size() MAX_SIZE){_resp_statu 414; // URL TOO LONG_recv_statu RECV_HTTP_ERROR;return false;}// 进行解析bool ret ParseHttpLine(line);if (ret false)return false;// 请求行解析完毕 开始解析请求报头_recv_statu RECV_HTTP_HEAD;return true;}// 解析报头bool RecvHttpHead(Buffer *buf){if (_recv_statu ! RECV_HTTP_HEAD)return false;// 解析请求报头直到遇到空行while (1){std::string line buf-GetLineAndPop();// LOG(DEBUG, line:%s\n, line.c_str());if (line.size() 0){// 缓存区中没有完整的一行数据 进行分类讨论// 如果缓冲区数据大于极限值if (buf-ReadAbleSize() MAX_SIZE){// LOG(ERROR, line too long\n);_resp_statu 414; // URL TOO LONG_recv_statu RECV_HTTP_ERROR;return false;}// 反之不处理 等待新数据到来// LOG(ERROR, wait new buffer\n);return true;}// 一行的数据过长if (line.size() MAX_SIZE){// LOG(ERROR, line too long\n);_resp_statu 414; // URL TOO LONG_recv_statu RECV_HTTP_ERROR;return false;}if (line \n || line \r\n){// LOG(ERROR, line is empty\n);break;}// LOG(INFO, line正常 进行解析处理);// 去除换行 \r \nif (line.back() \n)line.pop_back();if (line.back() \r)line.pop_back();// 进行解析bool ret ParseHttpHead(line);if (ret false)return false;}// 头部解析完成 继续解析正文_recv_statu RECV_HTTP_BODY;return true;}bool ParseHttpHead(const std::string line){// 每一行都是key: val\r\n 格式// LOG(DEBUG, ParseHttpHead:%s\n, line.c_str());// 进行解析即可size_t pos line.find(: );if (pos std::string::npos){LOG(ERROR, ParseHttpLine Failed\n);_recv_statu RECV_HTTP_ERROR;_resp_statu 400; // BAD Resquestreturn false;}std::string key line.substr(0, pos);std::string val line.substr(pos 2);// LOG(DEBUG, %s: %s\n, key.c_str(), val.c_str());_request.SetHeader(key, val);return true;}bool RecvHttpBody(Buffer *buf){if (_recv_statu ! RECV_HTTP_BODY)return false;// 获取正文长度size_t len _request.ContentLength();// 没有正文 直接读取完毕if (len 0){_recv_statu RECV_HTTP_OVER;return true;}// 当前已经接受了多少数据 _request._bodysize_t relen len - _request._body.size();// 接收正文放到body中 但是要考虑当前缓冲区中的数据是否是全部的报文// 缓冲区数据包含所有正文if (relen buf-ReadAbleSize()){// 加到_request.body的后面_request._body.append(buf-ReadPos(), relen);buf-MoveReadOffset(relen);_recv_statu RECV_HTTP_OVER;return true;}// 缓冲区无法满足正文_request._body.append(buf-ReadPos(), buf-ReadAbleSize());buf-MoveReadOffset(buf-ReadAbleSize());return true;}public:HttpContext() : _resp_statu(200), _recv_statu(RECV_HTTP_LINE) {}int RespStatu() { return _resp_statu; }HttpRequest Request() { return _request; }HttpRecvStatu RecvStatu() { return _recv_statu; }// 重置上下文void Reset(){_resp_statu 200;_recv_statu RECV_HTTP_LINE;_request.Reset();}void RecvhttpRequest(Buffer *buf){// 根据不同的状态 处理不同情况// 处理完不要break 因为处理完 可以继续进行处理下面的数据 而不是直接退出等待新数据switch (_recv_statu){case RECV_HTTP_LINE:RecvHttpLine(buf);case RECV_HTTP_HEAD:RecvHttpHead(buf);case RECV_HTTP_BODY:RecvHttpBody(buf);}return;}
};
5 HTTP服务器对象
现在HTTP协议我们实现了可以通过协议进行通信如何通过缓冲区获取请求的上下文方法我们也实现了可以在缓冲区中读取数据即使一次没有发送全下一次可以继续在原有进度上继续进行解析
那么接下来我们对这些功能进行一个整合封装实现HTTP服务器的功能
首先这个模块中有请求方法/资源路径 与 函数指针的映射关系表可以根据http请求的url找到对应的资源
表中记录了对于哪个请求应该使用哪一个函数来进行业务处理当服务器收到一个请求就要在请求路由表中查找是否存在对应的处理函数没有就返回404 Not Found这样做的好处是用户只需要实现业务处理函数然后将请求与函数的对应关系添加到服务器中服务器只需要接收数据解析数据查找路由表映射关系执行业务处理函数
要实现简便的搭建Http服务器所需的要素和提供的功能有以下几项
GET请求的路由映射表 — 功能性请求的处理POST请求的路由映射表PUT请求的路由映射表DELETE请求的路由映射表高性能TCP服务器 — 进行连接的IO操作静态资源相对根目录 — 实现静态资源的处理
再来看服务器的处理流程只有熟悉了服务器处理流程才能明白代码逻辑然后进行功能实现
从Socket接收数据。放到接收缓冲区调用OnMessage回调函数进行业务处理对请求进行解析,得到一个HttpRequest结构包含所有的请求要素进行请求的路由查找 — 找到对应请求的处理方法 静态资源请求 — 一些实体文件资源的请求功能性请求 — 在请求中根据路由映射表查找处理函数 对静态资源请求/功能性请求进行处理完毕后得到了一个填充了响应信息的HttpReaponse对象组成http格式报文 成员变量 GET请求的路由映射表 _get_route — 通过正则表达式映射处理函数POST请求的路由映射表 _post_routePUT请求的路由映射表 _put_routeDELETE请求的路由映射表 _delete_route静态资源根目录 _basedirTcpServer服务器 _server 私有成员函数 设置上下文 OnConnect(const PtrConn conn)给连接设置空的上下文 缓冲区数据解析处理 OnMessage(const PtrConn conn , Buffer *buf)只要缓冲区里有数据就持续处理首先先获取上下文通过上下文对缓冲区数据进行处理得到HttpRequest对象根据状态码 400判断解析结果 如果解析出错 直接回复出错响应 ErrorHandler(req , rsp) 并关闭连接 请求解析不完整 直接return 等待下一次处理。直到解析完毕 才去进行数据处理。然后进行请求路由Route(req ,rsp) 在路由中进行数据处理业务处理处理后得到应答报文对HttpResponse 进行组织发送 WriteResponse(const PtrConn conn , req , rsp)此时重置连接的上下文根据长短连接判断是否要关闭连接或者继续保持连接 路由查找 Route对请求进行判断是请求静态资源还是功能性请求 静态资源请求 判断是否是静态资源请求然后进行静态资源的处理功能性请求 通过req的请求方法判断使用哪一个路由表使用Dispatch进行任务派发既不是静态资源一般是功能性请求 就返回404 判断是否是静态资源请求 IsFileHandler首先必须设置了静态资源根目录请求方法必须是GET / HEAD 请求的资源路径必须是合法路径请求的资源必须存在 当请求路径是/要补全一个初始页面 index.html注意合并_basedir得到真正的路径 静态资源的请求处理 FileHandler将静态资源的数据读取出来放到rsp的正文中直接读取路径上的文件放到正文中获取mime文件类型添加到头部字段Content-Type 功能性请求的任务分发 Dispatcher在对应路由表中寻找是否有对应请求的处理函数有就直接进行调用 没有就返回404。路由表中储存的是 正则表达式-处理函数 的键值对。使用正则表达式进行匹配 匹配成功就进行执行函数 发送应答WriteResponse将HttpReaponse应答按照http应答格式进行组织发送 首先完善头部字段 然后将rsp的元素按照http协议的格式进行组织最终发送数据 处理错误应答ErrorHandler 提供一个错误展示页面将页面数据当作响应正文放入rsp中 公有成员函数 构造函数插入关系映射到GET路由表、POST路由表、PUT路由表、DELETE路由表。设置静态资源根目录设置线程数量启动Http服务器
class HttpServer
{
private:using Handler std::functionvoid(const HttpRequest , HttpResponse *);using Handlers std::vectorstd::pairstd::regex, Handler;Handlers _get_route; // GET方法处理函数映射表Handlers _post_route; // POST方法处理函数映射表Handlers _delete_route; // DELETE方法处理函数映射表Handlers _put_route; // PUT方法处理函数映射表std::string _basedir;TcpServer _server;public:// 设置空白上下文void OnConnect(const PtrConn conn){conn-SetContext(HttpContext());LOG(INFO, NEW CONNECTION :%p\n, this);}void ErrorHandler(const HttpRequest req, HttpResponse *rsp){// 提供一个错误展示页面std::string body;body !DOCTYPE html;body html langen;body head;body meta charsetUTF-8;body meta nameviewport contentwidthdevice-width, initial-scale1.0;body titleError std::to_string(rsp-_statu) - Server Error/title;body style;body body { background-color: #f2f2f2; color: #333; font-family: Arial, sans-serif; };body h1 { color: #d8000c; background-color: #ffbaba; border: 1px solid #d8d8d8; padding: 10px; text-align: center; };body div.container { max-width: 600px; margin: 50px auto; padding: 20px; background-color: #fff; border-radius: 8px; box-shadow: 0 0 10px rgba(0,0,0,0.1); };body /style;body /head;body body;body div classcontainer;body h1;body Error std::to_string(rsp-_statu) - Util::StatuDesc(rsp-_statu);body /h1;body pWere sorry, but something went wrong./p;body /div;body /body;body /html;// 将页面数据当作响应正文放入rsp中rsp-SetContent(body, text/html);}// 缓冲区数据解析处理void OnMessage(const PtrConn conn, Buffer *buf){while (buf-ReadAbleSize() 0){// 从连接中获取上下文HttpContext *context conn-GetContext()-GetHttpContext();// 从缓冲区中获取数据 处理后得到Requestcontext-RecvhttpRequest(buf);HttpRequest req context-Request();// 根据请求构建应答HttpResponse rsp(context-RespStatu());// 根据状态码判断处理结果// LOG(DEBUG, res-statu :%d\n, rsp._statu);// 状态码大于400说明解析出错 直接退出if (context-RespStatu() 400){// 重置上下文context-Reset();// 清空缓冲区buf-MoveReadOffset(buf-ReadAbleSize());// 获取错误响应ErrorHandler(req, rsp);// 发送错误请求WriteResponse(conn, req, rsp);// 关闭连接conn-Shutdown();return;}// 如果解析没有完成就等待下一次处理if (context-RecvStatu() ! RECV_HTTP_OVER){// 退出等待新数据到来 重新进行处理return;}// 请求解析完成进行处理Route(req, rsp);LOG(INFO, %s\n, rsp._body.c_str());if (rsp._statu 400){// 获取错误响应ErrorHandler(req, rsp);// 发送错误请求WriteResponse(conn, req, rsp);// 重置上下文context-Reset();// 关闭连接conn-Shutdown();return;}// 获取应答WriteResponse(conn, req, rsp);// 重置上下文context-Reset();// 根据长短连接判断是否需要关闭连接if (rsp.Close() true)conn-Shutdown();}return;}bool Route(HttpRequest req, HttpResponse *rsp){// 判断是否是静态资源处理if (IsFileHandler(req) true)return FileHandler(req, rsp);// 判断是否实际功能性请求if (req._method GET || req._method HEAD)return Dispatcher(req, rsp, _get_route);else if (req._method POST)return Dispatcher(req, rsp, _post_route);else if (req._method PUT)return Dispatcher(req, rsp, _put_route);else if (req._method DELETE)return Dispatcher(req, rsp, _delete_route);// 不是静态请求也不是功能性请求else{rsp-_statu 405; // Method Not Allowedreturn false;}}// 判断是否是静态资源bool IsFileHandler(HttpRequest req){// 首先_basedir必须存在if (_basedir.empty() true)return false;// 请求方法必须是 GET / HEADif (req._method ! GET req._method ! HEAD)return false;// 请求路径必须是合法路径if (Util::IsLegPath(req._path) false)return false;// 请求的资源必须存在std::string req_path _basedir req._path;// 如果直接请求的网络根目录要补全一个初始页面if (req_path.back() /)req_path index.html;if (Util::IsRegular(req_path) false)return false;// req请求路径的真正路径req._path req_path;return true;}// 静态资源的请求处理bool FileHandler(HttpRequest req, HttpResponse *rsp){LOG(INFO, 静态资源请求:%s\n, req._path.c_str());// 将请求资源读取到应答正文中bool ret Util::ReadFile(req._path, rsp-_body);if (ret false){// 数据读取失败LOG(ERROR, 数据读取失败\n);return false;}// 获取文件类型mimestd::string mime Util::ExtMime(req._path);LOG(DEBUG, Content-Type:%s\n, mime.c_str());// 添加到应答报头rsp-SetHeader(Content-Type, mime);return true;}// 功能性请求的任务分发bool Dispatcher(HttpRequest req, HttpResponse *rsp, Handlers handlers){// LOG(INFO, %s 功能性请求:%s\n, req._method.c_str(), req._path.c_str());// 首先根据路由表找到目标for (auto handler : handlers){const std::regex re handler.first;// 根据这个正则表达式进行解析bool ret std::regex_match(req._path, req._matches, re);if (ret false)continue;// 找到了就进行执行函数Handler Functor handler.second;Functor(req, rsp);return true;}// 没有找到目标LOG(DEBUG, 404 Not Found\n);rsp-_statu 404; // 设置为Not Foundreturn false;}// 将HttpReaponse应答按照http应答格式进行组织发送void WriteResponse(const PtrConn conn, const HttpRequest req, HttpResponse rsp){// 首先先完善头部字段if (req.Close() true)rsp.SetHeader(Connection, close);elsersp.SetHeader(Connection, keep-alive);if (rsp._body.empty() true rsp.HasHeader(Content-Length) false)rsp.SetHeader(Content-Length, std::to_string(rsp._body.size()));if (rsp._body.empty() true rsp.HasHeader(Content-Type) false)rsp.SetHeader(Content-Type, application/octet-stream);if (rsp._rediect_flag true)rsp.SetHeader(Location, rsp._rediect_url);// 将rsp组织成http格式的应答报文std::stringstream rsp_str;rsp_str req._version std::to_string(rsp._statu) Util::StatuDesc(rsp._statu) \r\n;for (auto it : rsp._headers){rsp_str it.first : it.second \r\n;}rsp_str \r\n;rsp_str rsp._body \r\n;// 进行发送// LOG(INFO, WriteResponse Send :%s \n, rsp_str.str().c_str());conn-Send(rsp_str.str().c_str(), rsp_str.str().size());}public:HttpServer(int port, int timeout DEFALT_TIMEOUT) : _server(port){_server.SetConnectCB(std::bind(HttpServer::OnConnect, this, std::placeholders::_1));_server.SetMessageCB(std::bind(HttpServer::OnMessage, this, std::placeholders::_1, std::placeholders::_2));_server.EnableActiveRelease(timeout); // 设置超时时间}// 插入关系映射到GET路由表void GET(const std::string pattern, const Handler func) { _get_route.push_back(std::make_pair(std::regex(pattern), func)); }void POST(const std::string pattern, const Handler func) { _post_route.push_back(std::make_pair(std::regex(pattern), func)); }void PUT(const std::string pattern, const Handler func) { _put_route.push_back(std::make_pair(std::regex(pattern), func)); }void DELETE(const std::string pattern, const Handler func) { _delete_route.push_back(std::make_pair(std::regex(pattern), func)); }void SetBaseDir(const std::string dir){assert(Util::IsDir(dir) true);_basedir dir;}// 设置服务器线程数量void SetThreadSize(size_t size){_server.SetThreadSize(size);}// 启动服务器void Start(){_server.Start();}
};