一、HttpContext模塊
要實(shí)現(xiàn)漸變的搭建HTTP服務(wù)器,所需要提供的要素和功能!
要素:
1. GET請(qǐng)求的路由映射表
2. POST請(qǐng)求的路由映射表
3. PUT請(qǐng)求的路由映射表
4. DELETE請(qǐng)求的路由映射表 —— 路由映射表記錄對(duì)應(yīng)請(qǐng)求方法的請(qǐng)求的處理函數(shù)映射關(guān)系
5. 高性能TCP服務(wù)器—— 進(jìn)行連接的IO操作
6. 靜態(tài)資源相對(duì)根目錄 —— 實(shí)現(xiàn)靜態(tài)資源的處理
接口:
服務(wù)器處理流程:
1. 從socket接受數(shù)據(jù)放到接受緩沖區(qū)
2. 調(diào)用onmessage回調(diào)函數(shù)進(jìn)行業(yè)務(wù)處理
3. 對(duì)請(qǐng)求進(jìn)行介意,得到了一個(gè)HTTPREQUEST結(jié)構(gòu),包含了所有的請(qǐng)求要素!
4. 進(jìn)行請(qǐng)求的路由映射 —— 找到對(duì)應(yīng)請(qǐng)求的處理方法
1. 靜態(tài)資源請(qǐng)求 —— 一些實(shí)體文件資源的請(qǐng)求 html,image
將靜態(tài)資源文件的數(shù)據(jù)讀取出來(lái),填充到HTTPresponse結(jié)構(gòu)中
2. 功能性請(qǐng)求 —— 在請(qǐng)求路由映射表中查找處理函數(shù),找到了則執(zhí)行函數(shù)
具體的業(yè)務(wù)請(qǐng)求,并進(jìn)行HTTPREsponse結(jié)構(gòu)的數(shù)據(jù)填充
5. 對(duì)靜態(tài)資源請(qǐng)求——功能性請(qǐng)求處理完畢后,得到一個(gè)填充了相應(yīng)信息的httpResponse 的對(duì)象,組織http響應(yīng)格式進(jìn)行發(fā)送!
接口:
- 添加請(qǐng)求—— 處理函數(shù)映射信息(GET/POST/PUT/DELETE)
- 設(shè)置靜態(tài)資源根目錄
- 設(shè)置是否啟動(dòng)超時(shí)連接關(guān)閉
- 設(shè)置線程池中線程數(shù)量
- 啟動(dòng)服務(wù)器
- OnConnected - 用于給TcpServer設(shè)置協(xié)議上下文
- OnMessage - 用于進(jìn)行緩沖區(qū)數(shù)據(jù)解析處理
- 獲取上下文,進(jìn)行緩沖區(qū)數(shù)據(jù)對(duì)象
- 請(qǐng)求的路由查
找 - 靜態(tài)資源請(qǐng)求查找和處理
- 功能性請(qǐng)求查找和處理組織響應(yīng)進(jìn)程回復(fù)
二、HttpServer模塊
三、HttpContext模塊實(shí)現(xiàn)思想
(一)功能
記錄HTTP請(qǐng)求的接受和處理進(jìn)度。
(二)意義
有可能出現(xiàn)接收的數(shù)據(jù)并不是一條完整的HTTP請(qǐng)求數(shù)據(jù),也就是請(qǐng)求的處理需要在多次受到數(shù)據(jù)之后才能處理完成,因此在每次處理的時(shí)候,就需要將進(jìn)度處理記錄下來(lái),以便于下次從當(dāng)前進(jìn)度繼續(xù)向下處理。
(三)接口
- 接收并且處理數(shù)據(jù)
- 接受請(qǐng)求行
- 解析請(qǐng)求行
- 接收頭部
- 解析頭部
- 接受正文
- 返回解析完成的請(qǐng)求信息
四、HttpServer模塊實(shí)現(xiàn)思想
(一)功能
對(duì)于HTTP協(xié)議支持所有模塊的整合
(二)意義
讓HTTP服務(wù)器的搭建變得更加簡(jiǎn)單
(三)分析
HttpServer模塊:用于實(shí)現(xiàn)HTTP服務(wù)器的搭建文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-722990.html
表中記錄了針對(duì)哪個(gè)請(qǐng)求,應(yīng)該使用哪個(gè)函數(shù)來(lái)進(jìn)行業(yè)務(wù)處理的映射關(guān)系
當(dāng)服務(wù)器收到了一一個(gè)請(qǐng)求,就在請(qǐng)求路由表中,查找有沒有對(duì)應(yīng)請(qǐng)求的處理函數(shù),如果有,則執(zhí)行對(duì)應(yīng)的處理函數(shù)即可說白了,什么請(qǐng)求,怎么處理,由用戶來(lái)設(shè)定,服務(wù)器收到了請(qǐng)求只需要執(zhí)行函數(shù)即可,這樣做的好處:用戶只需要實(shí)現(xiàn)業(yè)務(wù)處理函數(shù),然后將請(qǐng)求與處理函數(shù)的映射關(guān)系,添加到服務(wù)器中而服務(wù)器只需要接收數(shù)據(jù),解析數(shù)據(jù),查找路由表映射關(guān)系,執(zhí)行業(yè)務(wù)處理函數(shù)。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-722990.html
五、代碼
- HttpResponse
class HttpResponse {
public:
int _statu;
bool _redirect_flag;
std::string _body;
std::string _redirect_url;
std::unordered_map<std::string, std::string> _headers;
public:
HttpResponse():_redirect_flag(false), _statu(200) {}
HttpResponse(int statu):_redirect_flag(false), _statu(statu) {}
void ReSet() {
_statu = 200;
_redirect_flag = false;
_body.clear();
_redirect_url.clear();
_headers.clear();
}
//插入頭部字段
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 SetRedirect(const std::string &url, int statu = 302) {
_statu = statu;
_redirect_flag = true;
_redirect_url = url;
}
//判斷是否是短鏈接
bool Close() {
// 沒有Connection字段,或者有Connection但是值是close,則都是短鏈接,否則就是長(zhǎng)連接
if (HasHeader("Connection") == true && GetHeader("Connection") == "keep-alive") {
return false;
}
return true;
}
};
- HttpServer模塊
class HttpServer {
private:
using Handler = std::function<void(const HttpRequest &, HttpResponse *)>;
using Handlers = std::vector<std::pair<std::regex, Handler>>;
Handlers _get_route;
Handlers _post_route;
Handlers _put_route;
Handlers _delete_route;
std::string _basedir; //靜態(tài)資源根目錄
TcpServer _server;
private:
void ErrorHandler(const HttpRequest &req, HttpResponse *rsp) {
//1. 組織一個(gè)錯(cuò)誤展示頁(yè)面
std::string body;
body += "<html>";
body += "<head>";
body += "<meta http-equiv='Content-Type' content='text/html;charset=utf-8'>";
body += "</head>";
body += "<body>";
body += "<h1>";
body += std::to_string(rsp->_statu);
body += " ";
body += Util::StatuDesc(rsp->_statu);
body += "</h1>";
body += "</body>";
body += "</html>";
//2. 將頁(yè)面數(shù)據(jù),當(dāng)作響應(yīng)正文,放入rsp中
rsp->SetContent(body, "text/html");
}
//將HttpResponse中的要素按照http協(xié)議格式進(jìn)行組織,發(fā)送
void WriteReponse(const PtrConnection &conn, const HttpRequest &req, HttpResponse &rsp) {
//1. 先完善頭部字段
if (req.Close() == true) {
rsp.SetHeader("Connection", "close");
}else {
rsp.SetHeader("Connection", "keep-alive");
}
if (rsp._body.empty() == false && rsp.HasHeader("Content-Length") == false) {
rsp.SetHeader("Content-Length", std::to_string(rsp._body.size()));
}
if (rsp._body.empty() == false && rsp.HasHeader("Content-Type") == false) {
rsp.SetHeader("Content-Type", "application/octet-stream");
}
if (rsp._redirect_flag == true) {
rsp.SetHeader("Location", rsp._redirect_url);
}
//2. 將rsp中的要素,按照http協(xié)議格式進(jìn)行組織
std::stringstream rsp_str;
rsp_str << req._version << " " << std::to_string(rsp._statu) << " " << Util::StatuDesc(rsp._statu) << "\r\n";
for (auto &head : rsp._headers) {
rsp_str << head.first << ": " << head.second << "\r\n";
}
rsp_str << "\r\n";
rsp_str << rsp._body;
//3. 發(fā)送數(shù)據(jù)
conn->Send(rsp_str.str().c_str(), rsp_str.str().size());
}
bool IsFileHandler(const HttpRequest &req) {
// 1. 必須設(shè)置了靜態(tài)資源根目錄
if (_basedir.empty()) {
return false;
}
// 2. 請(qǐng)求方法,必須是GET / HEAD請(qǐng)求方法
if (req._method != "GET" && req._method != "HEAD") {
return false;
}
// 3. 請(qǐng)求的資源路徑必須是一個(gè)合法路徑
if (Util::ValidPath(req._path) == false) {
return false;
}
// 4. 請(qǐng)求的資源必須存在,且是一個(gè)普通文件
// 有一種請(qǐng)求比較特殊 -- 目錄:/, /image/, 這種情況給后邊默認(rèn)追加一個(gè) index.html
// index.html /image/a.png
// 不要忘了前綴的相對(duì)根目錄,也就是將請(qǐng)求路徑轉(zhuǎn)換為實(shí)際存在的路徑 /image/a.png -> ./wwwroot/image/a.png
std::string req_path = _basedir + req._path;//為了避免直接修改請(qǐng)求的資源路徑,因此定義一個(gè)臨時(shí)對(duì)象
if (req._path.back() == '/') {
req_path += "index.html";
}
if (Util::IsRegular(req_path) == false) {
return false;
}
return true;
}
//靜態(tài)資源的請(qǐng)求處理 --- 將靜態(tài)資源文件的數(shù)據(jù)讀取出來(lái),放到rsp的_body中, 并設(shè)置mime
void FileHandler(const HttpRequest &req, HttpResponse *rsp) {
std::string req_path = _basedir + req._path;
if (req._path.back() == '/') {
req_path += "index.html";
}
bool ret = Util::ReadFile(req_path, &rsp->_body);
if (ret == false) {
return;
}
std::string mime = Util::ExtMime(req_path);
rsp->SetHeader("Content-Type", mime);
return;
}
//功能性請(qǐng)求的分類處理
void Dispatcher(HttpRequest &req, HttpResponse *rsp, Handlers &handlers) {
//在對(duì)應(yīng)請(qǐng)求方法的路由表中,查找是否含有對(duì)應(yīng)資源請(qǐng)求的處理函數(shù),有則調(diào)用,沒有則發(fā)揮404
//思想:路由表存儲(chǔ)的時(shí)鍵值對(duì) -- 正則表達(dá)式 & 處理函數(shù)
//使用正則表達(dá)式,對(duì)請(qǐng)求的資源路徑進(jìn)行正則匹配,匹配成功就使用對(duì)應(yīng)函數(shù)進(jìn)行處理
// /numbers/(\d+) /numbers/12345
for (auto &handler : handlers) {
const std::regex &re = handler.first;
const Handler &functor = handler.second;
bool ret = std::regex_match(req._path, req._matches, re);
if (ret == false) {
continue;
}
return functor(req, rsp);//傳入請(qǐng)求信息,和空的rsp,執(zhí)行處理函數(shù)
}
rsp->_statu = 404;
}
void Route(HttpRequest &req, HttpResponse *rsp) {
//1. 對(duì)請(qǐng)求進(jìn)行分辨,是一個(gè)靜態(tài)資源請(qǐng)求,還是一個(gè)功能性請(qǐng)求
// 靜態(tài)資源請(qǐng)求,則進(jìn)行靜態(tài)資源的處理
// 功能性請(qǐng)求,則需要通過幾個(gè)請(qǐng)求路由表來(lái)確定是否有處理函數(shù)
// 既不是靜態(tài)資源請(qǐng)求,也沒有設(shè)置對(duì)應(yīng)的功能性請(qǐng)求處理函數(shù),就返回405
if (IsFileHandler(req) == true) {
//是一個(gè)靜態(tài)資源請(qǐng)求, 則進(jìn)行靜態(tài)資源請(qǐng)求的處理
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);
}
rsp->_statu = 405;// Method Not Allowed
return ;
}
//設(shè)置上下文
void OnConnected(const PtrConnection &conn) {
conn->SetContext(HttpContext());
DBG_LOG("NEW CONNECTION %p", conn.get());
}
//緩沖區(qū)數(shù)據(jù)解析+處理
void OnMessage(const PtrConnection &conn, Buffer *buffer) {
while(buffer->ReadAbleSize() > 0){
//1. 獲取上下文
HttpContext *context = conn->GetContext()->get<HttpContext>();
//2. 通過上下文對(duì)緩沖區(qū)數(shù)據(jù)進(jìn)行解析,得到HttpRequest對(duì)象
// 1. 如果緩沖區(qū)的數(shù)據(jù)解析出錯(cuò),就直接回復(fù)出錯(cuò)響應(yīng)
// 2. 如果解析正常,且請(qǐng)求已經(jīng)獲取完畢,才開始去進(jìn)行處理
context->RecvHttpRequest(buffer);
HttpRequest &req = context->Request();
HttpResponse rsp(context->RespStatu());
if (context->RespStatu() >= 400) {
//進(jìn)行錯(cuò)誤響應(yīng),關(guān)閉連接
ErrorHandler(req, &rsp);//填充一個(gè)錯(cuò)誤顯示頁(yè)面數(shù)據(jù)到rsp中
WriteReponse(conn, req, rsp);//組織響應(yīng)發(fā)送給客戶端
context->ReSet();
buffer->MoveReadOffset(buffer->ReadAbleSize());//出錯(cuò)了就把緩沖區(qū)數(shù)據(jù)清空
conn->Shutdown();//關(guān)閉連接
return;
}
if (context->RecvStatu() != RECV_HTTP_OVER) {
//當(dāng)前請(qǐng)求還沒有接收完整,則退出,等新數(shù)據(jù)到來(lái)再重新繼續(xù)處理
return;
}
//3. 請(qǐng)求路由 + 業(yè)務(wù)處理
Route(req, &rsp);
//4. 對(duì)HttpResponse進(jìn)行組織發(fā)送
WriteReponse(conn, req, rsp);
//5. 重置上下文
context->ReSet();
//6. 根據(jù)長(zhǎng)短連接判斷是否關(guān)閉連接或者繼續(xù)處理
if (rsp.Close() == true) conn->Shutdown();//短鏈接則直接關(guān)閉
}
return;
}
public:
HttpServer(int port, int timeout = DEFALT_TIMEOUT):_server(port) {
_server.EnableInactiveRelease(timeout);
_server.SetConnectedCallback(std::bind(&HttpServer::OnConnected, this, std::placeholders::_1));
_server.SetMessageCallback(std::bind(&HttpServer::OnMessage, this, std::placeholders::_1, std::placeholders::_2));
}
void SetBaseDir(const std::string &path) {
assert(Util::IsDirectory(path) == true);
_basedir = path;
}
/*設(shè)置/添加,請(qǐng)求(請(qǐng)求的正則表達(dá))與處理函數(shù)的映射關(guān)系*/
void Get(const std::string &pattern, const Handler &handler) {
_get_route.push_back(std::make_pair(std::regex(pattern), handler));
}
void Post(const std::string &pattern, const Handler &handler) {
_post_route.push_back(std::make_pair(std::regex(pattern), handler));
}
void Put(const std::string &pattern, const Handler &handler) {
_put_route.push_back(std::make_pair(std::regex(pattern), handler));
}
void Delete(const std::string &pattern, const Handler &handler) {
_delete_route.push_back(std::make_pair(std::regex(pattern), handler));
}
void SetThreadCount(int count) {
_server.SetThreadCount(count);
}
void Listen() {
_server.Start();
}
};
到了這里,關(guān)于1.16.C++項(xiàng)目:仿muduo庫(kù)實(shí)現(xiàn)并發(fā)服務(wù)器之HttpContext以及HttpServer模塊的設(shè)計(jì)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!