文章目录
- 前言
- 正文
- 1. Util
- 1.1 File
- 1.2 Url
- 1.3 Str
- 1.4 Infor
- 2. Http
- 2.1 Request
- 2.2 Response
- 2.3 Context
- 2.4 Server
- 尾序
前言
在上一篇文章当中,博主从代码的层面介绍了服务器模块的实现,最终封装出了一个传输层的TcpServer模块,那么在本篇将进一步向上拓展出一个应用层——Http服务器,用以进行Http连接的业务处理。话不多说,但强调一点,需将本专栏的前两篇文章消化完毕之后,再看本篇。
正文
1. Util
说明:Util可以是个类,里面封装各种静态接口,也可以使用命名空间,目的都是为了使用作用域防止命名污染。如下介绍接口时默认是在作用域范围内部的,不再赘述,有兴趣可通过上篇文章的源码连接查看。
1.1 File
读写
- C++IO流获取文件大小
std::ifstream ifs(filename,std::ios::binary);//二进制的方式打开
ifs.seekg(0,ifs.end);//到文件的末尾
size_t fsz = ifs.tellg();//从开始到末尾的大小
ifs.seekg(0,ifs.beg);//调整到文件开始处
static bool ReadFile(const std::string filename,std::string* str)
{
//1.打开文件
std::ifstream ifs(filename,std::ios::binary);
if(false == ifs.is_open())
{
return false;
}
//2.获取文件大小
ifs.seekg(0,ifs.end);
size_t fsz = ifs.tellg();
str->resize(fsz);//将str扩容至文件的大小
ifs.seekg(0,ifs.beg);
//3.读取文件内容
ifs.read(&(*str)[0],fsz);
if(false == ifs.good())
{
return false;
}
return true;
}
static bool WriteFile(const std::string filename,const std::string& str)
{
std::ofstream ofs(filename,std::ios::binary | std::ios::trunc);//以二进制且以清空的方式打开
if(false == ofs.is_open())
{
return false;
}
else
{
ofs.write(&str[0],str.size());
if(false == ofs.good())
{
return false;
}
}
return true;
}
类型判断
- 接口
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int stat(const char *pathname, struct stat *statbuf);
/*
参数:
1.pathname,输入型参数,文件名。
2.statbuf,输出型参数,用于存放文件的属性信息。stat.st_mode,类型为mode_t,此处用于判断文件的类型。
类型判断的宏:S_ISDIR(mode_t)用于判断是否为目录,S_ISREG(mode_t)用于判断是否为普通文件。
返回值:
1.成功返回0。
2.失败返回-1,并设置合适的错误码。
*/
bool IsRegular(const std::string& filename)
{
return S_ISREG(Kind(filename));
}
bool IsDir(const std::string& filename)
{
return S_ISDIR(Kind(filename));
}
mode_t Kind(const std::string& filename)
{
struct stat st;
int ret = stat(filename.c_str(),&st);
if(ret < 0)
{
//错误
}
return st.st_mode;
}
1.2 Url
编码与解码
编码规则:
URL,即统一资源定位符,也就是所谓的网址,比如说https://www.google.com/search?q=C%2B%2B
,其中q=C%2B%2B,就是我们要搜索的参数,我本来搜索的是C++,但是其中的++与网址的标准格式的字符冲突被编码,可能会使处理出错,因此需要编码。
Http的标准格式:
解释完为啥需要编码,再来谈谈如何进行编码:
.
,-
,_
,~
,字母
,数字
为绝对不编码字符,因此不用进行转换。- 空格根据不同的标准可能进行转化,W3cb标准必须被编码为+,RFC2396要转换为%%HH格式。
- 其它的字符统一转换成%%HH格式,即百分号 + 两位十六进制。
- 接口:
- 用于判断是否字母或者字符
#include<ctype.h>
int isalpha(int c)
/*
参数:字符
返回值:如果是则为非0值,不是则为0值.
*/
- 用于将10进制(int)转换为16进制(string),以字符串的形式输出。
std::stringstream ssm;
ssm << std::uppercase << std::setw(2) << std::setfill('0') << std::hex << dec;
//ssm.str()获取转换的string
实现:
static std::string UrlIncode(const std::string& iurl, bool is_w3c)
{
//3.判断为字母和数字的接口:#include<ctype.h> int isalpha(int c)
std::string res;
for(auto c : iurl)
{
//[. - _ ~ 字母 数字]不属于绝对不编码字符
if(c == '.' || c == '-' || c == '_' || c == '~' || isalpha(c))
{
res += c;
}
//空格W3c必须被编码为+
else if(c == ' ' && is_w3c)
{
res += '+';
}
//其余要转换为%%HH格式
else
{
int dec = c;
std::stringstream ssm;
ssm << std::uppercase << std::setw(2) << std::setfill('0') << std::hex << dec;
res += ("%" + ssm.str());
}
}
return res;
}
static std::string UrlDecode(const std::string& durl,bool is_w3c)
{
std::string res;
for(int i = 0; i < durl.size();)
{
if(durl[i] == '%')
{
std::string hnum = durl.substr(i + 1,2);
int c = stoi(hnum,nullptr,16);
res += c;
i += 3;
}
else
{
if(durl[i] == '+' && is_w3c)
res += ' ';
else
res += durl[i];
i += 1;
}
}
return res;
}
资源路径是否有效
- 思路:资源路径不能跑到资源的根目录上面,比如https://blog.csdn.net/…/就会被判定为无效,CSDN网站处理的是返回网站的首页,但是本项目的实现强硬了一些,直接非法,最终会返回错误界面。
static bool Valid(const std::string& filename)
{
std::vector<std::string> dirs;
//下文会提及的,这里是将字符串按照"/"切分存储到dirs中目的是将路径进行切分。
Split(filename,"/",&dirs);
int level = 0;
for(auto& d : dirs)
{
if(d == "..")
{
if(--level < 0)
{
return false;
}
}
else
{
level++;
}
}
return true;
}
1.3 Str
分割字符串,本项目主要是在解析请求行用到,此函数是仿照Boost库中的split函数实现的,也可以使用其提供的函数。
- boost接口
//说明:需按照Boost库才可使用
#include<boost/algorithm/string/string.hpp>
template<typename SequenceSequenceT, typename RangeT, typename PredicateT>
SequenceSequenceT & split(SequenceSequenceT & Result, RangeT & Input, PredicateT Pred,
token_compress_mode_type eCompress = token_compress_off);
/*
参数
1:vector<type>类型的,用于存放切割后的内容。
2:切割的内容。
3:分割符。
4:切割的模式,一般设置为token_compress_on,意为将连续的分割符看成一个。
返回值
vector<type>类型的引用
*/
- 接口
size_t find(string str,int pos); //从pos位置开始找str
std::string substr (int pos,int len);//从pos位置切割len长度的字符串
- 实现
static int Split(const std::string src,const std::string sep,std::vector<std::string> *arr)
{
int pos = 0;
arr->resize(0);
while (pos < src.size())
{
size_t offset = src.find(sep,pos);
if(offset == std::string::npos)
{
arr->push_back(src.substr(pos));
break;
}
else if(offset != pos)
{
arr->push_back(src.substr(pos,offset - pos));
}
pos = offset + sep.size();
}
return arr->size();
}
1.4 Infor
响应行是这样的——HTTP/1.1 200 OK,此处的响应状态码和响应信息是对应的关系,因此可以使用哈希表的形式进行存储。
说明:响应状态码和与之对应的响应信息可在RFC文档 查看,在这里偷偷告诉你一个小技巧,即将对应信息喂给AI助手,让其帮你生成一个对应格式的数据,你可以告诉举个例子告诉AI生成{100, "Continue"},
的数据然后让其输出,可以少一些Cv。
Status
static std::string Statu(int stat)
{
auto it = _stu.find(stat);
if(it != _stu.end())
{
return it->second;
}
return "Unknow";
}
std::unordered_map<int, std::string> Util::_stu =
{
{100, "Continue"},
{101, "Switching Protocols"},
{200, "OK"},
{201, "Created"},
{202, "Accepted"},
{203, "Non-Authoritative Information"},
{204, "No Content"},
{205, "Reset Content"},
{206, "Partial Content"},
{300, "Multiple Choices"},
{301, "Moved Permanently"},
{302, "Found"},
{303, "See Other"},
{304, "Not Modified"},
{305, "Use Proxy"},
{307, "Temporary Redirect"},
{308, "Permanent Redirect"},
{400, "Bad Request"},
{401, "Unauthorized"},
{402, "Payment Required"},
{403, "Forbidden"},
{404, "Not Found"},
{405, "Method Not Allowed"},
{406, "Not Acceptable"},
{407, "Proxy Authentication Required"},
{408, "Request Timeout"},
{409, "Conflict"},
{410, "Gone"},
{411, "Length Required"},
{412, "Precondition Failed"},
{413, "Payload Too Large"},
{414, "URI Too Long"},
{415, "Unsupported Media Type"},
{416, "Range Not Satisfiable"},
{417, "Expectation Failed"},
{426, "Upgrade Required"},
{451, "Unavailable For Legal Reasons"},
{500, "Internal Server Error"},
{501, "Not Implemented"},
{502, "Bad Gateway"},
{503, "Service Unavailable"},
{504, "Gateway Timeout"},
{505, "HTTP Version Not Supported"}
};
Mine
在Http协议格式中,需要说明响应正文是什么类型的,因此需要消息头中的Content-Type字段,而Mine类型则用于表示响应的格式,比如如果是.png类型的,则为Content-Type: image/png,而image/png即为Mine类型对于文件的描述形式。
说明:Mine类型可在RFC官方文档 进行查看,或者从网上翻一翻常见的或者让AI帮你生成一份常见的也行。
static std::string Mine(const std::string& filename)
{
int pos = filename.find(".");
if(pos == std::string::npos)
{
//二进制数据,通常用于不知道文件类型时使用。
return "application/octet-stream";
}
auto it = _mine.find(filename.substr(pos));
if(it == _mine.end())
{
return "application/octet-stream";
}
return it->second;
}
std::unordered_map<std::string, std::string> Util::_mine =
{
{".txt", "text/plain"},
{".pdf", "application/pdf"},
{".doc", "application/msword"},
{".docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"},
{".xls", "application/vnd.ms-excel"},
{".xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"},
{".ppt", "application/vnd.ms-powerpoint"},
{".pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation"},
{".jpg", "image/jpeg"},
{".jpeg", "image/jpeg"},
{".png", "image/png"},
{".gif", "image/gif"},
{".bmp", "image/bmp"},
{".svg", "image/svg+xml"},
{".html", "text/html"},
{".css", "text/css"},
{".js", "application/javascript"},
{".xml", "application/xml"},
{".json", "application/json"},
{".zip", "application/zip"},
{".rar", "application/x-rar-compressed"},
{".tar", "application/x-tar"},
{".gz", "application/gzip"},
{".mp3", "audio/mpeg"},
{".wav", "audio/wav"},
{".mp4", "video/mp4"},
{".avi", "video/x-msvideo"},
{".mkv", "video/x-matroska"},
{".exe", "application/octet-stream"},
{".dll", "application/octet-stream"},
{".bat", "application/x-bat"},
{".sh", "application/x-shellscript"},
{".py", "text/x-python"},
{".java", "text/x-java-source"},
{".c", "text/x-c"},
{".cpp", "text/x-c++"},
{".h", "text/x-c-header"},
{".sql", "application/x-sql"},
{".csv", "text/csv"},
{".log", "text/plain"},
{".md", "text/markdown"},
{".psd", "image/vnd.adobe.photoshop"},
{".indd", "application/vnd.adobe.indesign"}
};
2. Http
2.1 Request
请求协议格式:
此类所做的只是将协议打散为类的成员元素,方便进行管理,做一些增删改查的功能。
struct HttpRequest
{
//查找,修改,获取头部字段。
bool HasHeader(const std::string& hdr)
{
auto it = _headers.find(hdr);
if(it != _headers.end()) return true;
return false;
}
void SetHeader(const std::string& key,const std::string& val)
{
auto it = _headers.find(key);
if(it != _headers.end()) return;
_headers.insert({key,val});
}
std::string GetHeader(const std::string& hdr)
{
auto it = _headers.find(hdr);
if(it != _headers.end()) return it->second;
return "Unknow Header";
}
//查找,修改,获取消息头,或者说请求报头也可以字段。
bool HasParm(const std::string& par)
{
auto it = _parameter.find(par);
if(it != _parameter.end()) return true;
return false;
}
void SetParm(const std::string& key,const std::string& val)
{
auto it = _parameter.find(key);
if(it != _parameter.end()) return;
_parameter.insert({key,val});
}
std::string GetParm(const std::string& par)
{
auto it = _parameter.find(par);
if(it != _parameter.end()) return it->second;
return "Unknow Parameter";
}
//判断是否为短连接
bool Close()
{
const std::string& hdr = "Connection";
if(true == HasHeader(hdr) && GetHeader(hdr) == "keep-alive")
{
return false;
}
return true;
}
//获取正文长度
size_t GetBodyLen()
{
std::string slen = GetHeader("Content-Length");
if(slen == "Unknow Header")
{
return 0;
}
return stoi(slen);
}
//重置清除信息
void Reset()
{
std::smatch tmp;
_sch.swap(tmp);
_method.clear();
_version.clear();
_source_dir.clear();
_parameter.clear();
_headers.clear();
_body.clear();
}
std::smatch _sch; //解析头部字段,动态请求资源路径处理的参数
std::string _method;//请求方法
std::string _version; //版本
std::string _source_dir; //资源路径
std::unordered_map<std::string,std::string> _parameter;//查询参数
std::unordered_map<std::string,std::string> _headers;//消息头
std::string _body;//消息体
};
2.2 Response
响应协议格式:
此类所做的与Request相同,是对连接响应信息的管理,特殊地消息头为Location:
即服务器的url可能会重定向,因此需要进行特殊判断。
struct HttpResponse
{
HttpResponse(int stat = 200):_stau(stat),_is_redir(false){};
//查找,修改,获取头部字段。
bool HasHeader(const std::string& hdr)
{
auto it = _headers.find(hdr);
if(it != _headers.end()) return true;
return false;
}
void SetHeader(const std::string& key,const std::string& val)
{
auto it = _headers.find(key);
if(it != _headers.end()) return;
_headers.insert({key,val});
}
std::string GetHeader(const std::string& hdr)
{
auto it = _headers.find(hdr);
if(it != _headers.end()) return it->second;
return "Unknow Header";
}
//判断是否为短连接
bool Close()
{
const std::string& hdr = "Connection";
if(true == HasHeader(hdr) && GetHeader(hdr) == "keep-alive")
{
return false;
}
return true;
}
//设置重定向的URL
void SetReRirUrl(const std::string& url,int stat)
{
_stau = stat;
_is_redir = true;
_redir_url = url;
}
//设置Body正文
void SetContent(const std::string& content,const std::string& type = "text/html")
{
_body = content;
SetHeader("Content-Type",type);
}
int _stau;
std::unordered_map<std::string,std::string> _headers;//消息头
bool _is_redir;
std::string _redir_url;
std::string _body;
};
2.3 Context
在上下文模块中进行请求行分析主要是通过正则表达式进行处理获取请求方法,资源路径,参数,HTTP版本。
demo:
#include <iostream>
#include <sstream>
#include <string>
#include <regex>
#include <iterator>
using namespace std;
int main()
{
//全文匹配,使用子表达式存放解析结果,只提取出请求方法,域名信息,查询字段(可能有或者没有)。
smatch res;
string str = "GET /blog.csdn.net/Shun_Hua?user=xiaoming&pass=123123 HTTP/1.1\r\n";
string pattern("(GET|HEAD|POST|PUT|DELETE) ([^?]*)(?:\\?(.*))? (HTTP/1\\.[01])(?:\r\n|\n)?");
//(GET|HEAD|POST|PUT|DELETE)请求方法的匹配。
//([^?]*)表示匹配非问号字符串,用于获取域名。
//\\?,首先?是特殊字符,要想正常匹配得使用\进行转义,\也是特殊转义字符,要想使用得再使用\进行转义。
//(.*),.*是对任意字符串进行匹配。
//?:\\?,表示不提取?到res中
//(\\?(.*))?,表示子表达式出现0次或者1次。
//(?:\\?(.*))?,子表达式出现0次或者1次,不提取?,提取(.*)到res中。
//HTTP/1\\.[01],匹配版本号。
//(?:\r\n|\n)?,同理表示匹配\r\n或者\n,0次或者1次,且不提取。
regex r(pattern);
bool ret = regex_match(str, res, r);
if (ret)
{
cout << "匹配成功" << endl;
for (auto e : res)
{
stringstream ssm;
ssm << e;
string s = e.str();
cout << s << endl;
}
}
else
{
cout << "匹配失败" << endl;
}
return 0;
}
说明:对正则表达式不太熟悉的C友可查看【C++进阶之路】C++11——正则表达式进行快速地入门和学习C++容器提供的对应接口。
涉及接口:
#include <algorithm>
std::stransform(oldbegin,oldend(),newposbegin,newposend,hder);
//将[begin,end)中的数据通过hder函数处理,放到newposbengin的位置,此处用于对请求方法统一转大写处理,因为可能发送的可能为小写。
- 实现
//上下文处理的状态基,错误,初始,请求行处理完毕,消息头处理完毕,全部处理完毕。
typedef enum
{
Error,
Init,
RevLine_Over,
RevHeader_Over,
Over,
}CxtStau;
class HttpContext
{
public:
HttpContext(int reponse = 200)
:_rep_stu(200),_stage(Init)
{};
CxtStau GetCxtStu()
{
return _stage;
}
int GetRepStu()
{
return _rep_stu;
}
HttpRequest GetRequest()
{
return _req;
}
void Recv_Parse(Buffer* buf)
{
switch (_stage)
{
case Init: if(!RevLine(buf)) break;
case RevLine_Over: if(!RevHeader(buf)) break;
case RevHeader_Over: if(!RevBody(buf)) break;
}
}
void Reset()
{
_stage = Init;
_rep_stu = 200;
_req.Reset();
}
private:
//从接收缓存区中读取并解析请求行
bool RevLine(Buffer* buf)
{
//从buf获取一行数据
std::string line = buf->GetLine();
//不到一行的数据大于MAX_LEN
if(line.size() == 0 && buf->ReadableSize() > MAX_LEN)
{
_stage = Error;
_rep_stu = 414; //URL太长了。
return false;
}
//读取出来的一行数据大于MAX_LEN
else if(line.size() > 0)
{
if(line.size() > MAX_LEN)
{
_stage = Error;
_rep_stu = 414; //URL太长了。
return false;
}
//处理数据
else
{
//对请求行进行解析
if(ParseLine(line))
{
_stage = RevLine_Over;
return true;
}
else
{
//解析出错
_stage = Error;
_rep_stu = 400;
return false;
}
}
}
//等待数据。
return true;
}
bool ParseLine(std::string& line)
{
//1、使用正则表达式进行分析处理获取——请求方法 URL 请求参数 Http版本
//2、对URL进行解码操作
//3、对请求参数按照"&"分割,按照"="处理,获取key,val。
std::smatch res;
std::string pattern("(GET|HEAD|POST|PUT|DELETE) ([^?]*)(?:\\?(.*))? (HTTP/1\\.[01])(?:\r\n|\n)?");
std::regex rx(pattern);
bool ret = regex_match(line,res,rx);
//第一个元素为line中的内容,剩余四个元素分别为请求方法,资源路径,参数,HTTP版本
if(false == ret || res.size() != 5)//如果不为四个元素说明传输的数据有误。
{
return false;
}
else
{
//请求方法
_req._method = res[1];
std::string& str = _req._method;
std::transform(str.begin(),str.end(),str.begin(),::toupper);//GET可能为小写的需要转换为大写。
//版本和资源路径
_req._version = res[4];
_req._source_dir = Util::UrlDecode(res[2],false);//资源路径不需要转空格
//参数解析
std::vector<std::string> arr;
Util::Split(res[3],"&",&arr);//对参数行按照 & 进行分割
for(auto& str : arr)
{
size_t pos = str.find("=");
if(pos == std::string::npos)
{
return false;
}
else
{
//参数需要转空格
std::string key = Util::UrlDecode(str.substr(0,pos),true);
std::string val = Util::UrlDecode(str.substr(pos + 1),true);;
_req.SetParm(key,val);
}
}
}
return true;
}
bool RevHeader(Buffer* buf)
{
if(_stage != RevLine_Over)
{
return false;
}
for(std::string line = buf->GetLine(); line != "\r\n" && line != "\n"; line = buf->GetLine())
{
if(line.size() == 0 && buf->ReadableSize() > MAX_LEN)
{
_stage = Error;
_rep_stu = 414; //URL太长了。
return false;
}
else if(line.size() > 0)
{
if(line.size() > MAX_LEN)
{
_stage = Error;
_rep_stu = 414; //URL太长了。
return false;
}
else if(!ParseHeader(line))
{
_stage = Error;
_rep_stu = 400;
return false;
}
}
}
_stage = RevHeader_Over;
return true;
}
bool ParseHeader(std::string& line)
{
while(line.back() == '\r' || line.back() == '\n') line.pop_back();
std::vector<std::string> kv;
Util::Split(line, ": ",&kv);
if(kv.size() != 2)
{
return false;
}
_req.SetHeader(kv[0],kv[1]);
return true;
}
bool RevBody(Buffer* buf)
{
if(_stage != RevHeader_Over) return false;
//1.获取正文长度。
//2.判断数据是否足够读取。
size_t len = _req.GetBodyLen();
if(len > 0)
{
int rsz = _req._body.size();
if(buf->Size() + rsz >= len)
{
_req._body.append(buf->Read(len - rsz));
_stage = Over;
return true;
}
else
{
_req._body.append(buf->Read());
return true;
}
}
else if(len == 0)
{
_stage = Over;
}
return true;
}
private:
CxtStau _stage;
int _rep_stu;
HttpRequest _req;
enum{MAX_LEN = 8192};//一行的最大长度。
};
- 从Buffer中获取除了正文以外的数据都是通过以行为单位进行获取和分析的,因此大于或者小于一行的数据不能过长,应有一个最长的长度,本项目中设置我8192长度,转化为单位为4KB。如果超过了此长度则认为数据非法,返回对应414响应码。
- 在解析头部时需要将后面的尾巴——"\r\n"处理干净,以及在解析正文时需要根据头部提供的Content-Length获取正文长度,如果Buffer中的缓存区数据不够,将其所有数据放到上下文中,等待数据的到来。
2.4 Server
class HttpServer
{
public:
using hder_t = std::function<void(const HttpRequest&,HttpResponse*)>;
using router_t = std::vector<pair<std::string,hder_t>>;
HttpServer(int port = 8000,int timout = 30):_svr(port)
{
_svr.EnRseTimout(timout);
_svr.SetConCb(std::bind(&HttpServer::SetCxt,this,std::placeholders::_1));
_svr.SetMsgCb(std::bind(&HttpServer::MsgHder,this,std::placeholders::_1,std::placeholders::_2));
SetBaseDir();
}
private:
//通过ConPtr设置上下文
void SetCxt(const ConPtr& con)
{
con->SetAnyCb(HttpContext());
}
//来自连接消息的解析和处理
void MsgHder(const ConPtr& con,Buffer* buf)
{
while(buf->Size())
{
//获取上下文
HttpContext* cxt = con->GetTxt()->get<HttpContext>();
//接着进行处理获取更加完整的请求
cxt->Recv_Parse(buf);
HttpRequest req = cxt->GetRequest();
HttpResponse rsp(cxt->GetRepStu());
//判断
if(cxt->GetCxtStu() == Error)
{
WriteResponse(con,req,&rsp);
//出错时要清空Buffer,之后不用再发数据了。
buf->Clear();
con->ShutDown();
return;
}
else if(cxt->GetCxtStu() != Over)
{
return;
}
//路由处理
Route(req,&rsp);
//组装请求进行发送
WriteResponse(con,req,&rsp);
//重置上下文
cxt->Reset();
//判断是否要断开连接
if(req.Close())
{
con->ShutDown();
}
}
}
public:
void SetBaseDir(std::string dir = "./wwwroot")
{
_base_dir = dir;
}
void SetHander(std::string method,std::string par,const hder_t& hder)
{
if(method == "HEAD" || method == "GET")
{
_get_rter.push_back({par,hder});
}
else if(method == "POST")
{
_post_rter.push_back({par,hder});
}
else if(method == "PUT")
{
_put_rter.push_back({par,hder});
}
else if(method == "DELETE")
{
_delete_rter.push_back({par,hder});
}
}
void Start(int thread_cnt = 4)
{
_svr.Start(thread_cnt);
}
private:
//对请求处理方法进行分类
void Route(HttpRequest& req,HttpResponse* rsp)
{
//判断是否是静态资源
if(IsFileSource(req,rsp))
{
return FileHdr(req,rsp);
}
//分类进行动态的处理
std::string method = req._method;
if(method == "GET" || method == "HEAD")
{
return Dispacher(req,rsp,_get_rter);
}
else if(method == "PUT")
{
return Dispacher(req,rsp,_put_rter);
}
else if(method == "POST")
{
return Dispacher(req,rsp,_post_rter);
}
else if(method == "DELETE")
{
return Dispacher(req,rsp,_delete_rter);
}
else
{
rsp->_stau = 405; //Method Not Allowed
}
}
//静态处理
void FileHdr(HttpRequest& req,HttpResponse* rsp)
{
bool ret = Util::ReadFile(req._source_dir,&(rsp->_body));
if(ret == true)
{
rsp->SetHeader("Content-Type",Util::Mine(req._source_dir));
}
else
{
ERROR_LOG("read file failed!!");
}
}
bool IsFileSource(HttpRequest& req,HttpResponse* rsp)
{
std::string& sor_dir = req._source_dir;
if(_base_dir != "" && (req._method == "GET" || req._method == "HEAD")&& Util::Valid(sor_dir))
{
std::string path = _base_dir + sor_dir;
if(sor_dir == "/")
{
path += "index.html";
}
if(Util::IsRegular(path))
{
sor_dir = path;
return true;
}
}
return false;
}
//动态处理
void Dispacher(HttpRequest& req,HttpResponse* rsp,router_t& hders)
{
for(auto &kv : hders)
{
std::regex regx(kv.first);
auto& hder = kv.second;
bool ret = regex_match(req._source_dir,req._sch,regx);
if(ret == true)
{
return hder(req,rsp);
}
}
rsp->_stau = 404;//Not Found
}
//填充错误页面
void ErrorHder(const HttpRequest& req,HttpResponse* rsp)
{
std::string filename = _base_dir + "/error.html";
std::string content;
Util::ReadFile(filename,&content);
//错误页面放到响应中
rsp->SetContent(content);
return;
}
//对请求组织处理形成响应。
void WriteResponse(const ConPtr& con,HttpRequest& req,HttpResponse* rsp)
{
//判断是否出错填充错误页面
if(rsp->_stau > 400)
{
ErrorHder(req,rsp);
}
//完善头部字段: Connection Content-Length Content-Type Location
if(req.Close())
{
rsp->SetHeader("Connection","close");
}
else
{
rsp->SetHeader("Connection","keep-alive");
}
if(!rsp->_body.empty())
{
if(!rsp->HasHeader("Content-Length"))
{
rsp->SetHeader("Content-Length",std::to_string(rsp->_body.size()));
}
if(!rsp->HasHeader("Content-Type"))
{
rsp->SetHeader("Content-Type","application/octet-stream");
}
}
if(rsp->_is_redir){rsp->SetHeader("Location",rsp->_redir_url);}
//组装Http协议响应信息
//版本号 状态码 状态表示\r\n
std::stringstream stm;
stm << req._version << " " << rsp->_stau << " " << Util::Statu(rsp->_stau) << "\r\n";
//头部信息
for(auto& kv : rsp->_headers)
{
stm << kv.first << ": " << kv.second << "\r\n";
}
stm << "\r\n";
//头部信息
stm << rsp->_body;
con->Send(stm.str());//不进行实际的发送只打开写事件监控
}
private:
router_t _put_rter;
router_t _post_rter;
router_t _get_rter;
router_t _delete_rter;
TcpServer _svr;
std::string _base_dir;
};
- HttpServer是一个最终的综合模块,通过服务器模块封装出来的TcpServer组件完成高效的IO处理,在初始化时将连接初始化和消息处理回调设置到其内部。上层只需关系资源的设置以及业务处理函数的管理。
- 此模块内部通过设置上下文完成对连接的初始化,通过消息处理函数完成对连接业务地处理,业务处理主要包含静态的业务处理,即通过GET方法获取服务器的资源文件,即文件的读取,也可以通过路由表,即一个vector<string,函数回调>,动态地添加函数回调最终根据资源路径进行动态匹配,将结果放到HttpRequest的smath中,如果匹配成功则执行对应的回调,如果失败则接着继续匹配,直到遍历完为止。
业务处理图解:
尾序
如果说上篇文章是包包子的话,那这篇文章就是用Http协议就是将包子包得再紧一些,那么最后就是将包子放到蒸笼里面蒸熟看包子最终的样子是好是坏,即让组件用起来看看怎么样,就要看最终测试环节了,不过这是下篇的内容,我们下篇再续。最后,我是舜华,期待与你的每一次相遇!
下篇文章再见了!