📢博客主頁:https://blog.csdn.net/2301_779549673
📢博客倉庫:https://gitee.com/JohnKingW/linux_test/tree/master/lesson
📢歡迎點贊 👍 收藏 ?留言 📝 如有錯誤敬請指正!
📢本文由 JohnKi 原創,首發于 CSDN🙉
📢未來很長,值得我們全力奔赴更美好的生活?
文章目錄
- 🏳??🌈一、增加請求后綴
- 1.1 HttpRequest 類
- 1.2 HttpHandler 類
- 🏳??🌈二、狀態碼描述 及 自動跳轉404
- 2.1 狀態碼描述
- 2.2 自動跳轉404
- 🏳??🌈三、重定向狀態碼
- 🏳??🌈四、注冊等多功能服務
- 4.1 HttpRequest 類
- 4.2 HttpHandler 類
- 4.3 TcpServer.cpp
- 4.4 測試
- 👥總結
🏳??🌈一、增加請求后綴
我們在瀏覽器上訪問我們自己的服務端時,會遇到客戶端發送來的請求,想要訪問 1.jpg
或者 default.html
因此我們可以將這個后綴給整理一下,通過日志打印告訴我們自己目標想要訪問的資源在當前的哪里。
1.1 HttpRequest 類
我們在這個類里進行如下操作
- 添加成員變量
_suffix
- 在請求行解析方法中,增添一段,通過后綴分隔符
"."
,來找到我們的后綴,沒有找到就返回默認后綴- 添加函數方法,獲取當前請求的后綴名
const static std::string _suffixsep = "."; // 后綴分隔符 class HttpRequest {
private:// 解析請求行void PraseReqLine() {// 以空格為分隔符,不斷讀取std::stringstream ss(_req_line);ss >> _method >> _url >> _version;_path += _url;// 處理url,如果是根目錄,則返回默認路徑if (_url == "/")_path += _default_path;// 獲取后綴auto pos = _path.rfind(_suffixsep);if (pos == std::string::npos)_suffix = ".default";else_suffix = _path.substr(pos);}public:std::string Suffix() {LOG(LogLevel::INFO) << "client want suffix : " << _suffix;return _suffix;}
}
1.2 HttpHandler 類
這里我們需要先知道一個概念 - MIMIE
MIME 是一種 ?互聯網標準,最初設計用于擴展電子郵件協議(如 SMTP),使其能傳輸非文本數據(如圖片、音頻)。后被 HTTP 協議廣泛采用,用于標識網絡資源的 ?數據類型。
- .html → text/html(HTML 文檔)
- .jpg → image/jpeg(JPEG 圖片)
- .json → application/json(JSON 數據)
這個類中我們需要增加一個后綴映射,并將其添加在返回報文的報頭列表中
1, 增加成員變量
后綴
與后綴存儲
的映射
2.`構造函數 時,初始化映射
3. 處理請求時,將映射結果添加到響應報頭中
class HttpHandler {
public:HttpHandler() {_mime_type.insert(std::make_pair(".html", "text/html"));_mime_type.insert(std::make_pair(".jpg", "image/jpg"));_mime_type.insert(std::make_pair(".png", "image/png"));_mime_type.insert(std::make_pair(".default", "text/html"));}std::string HandleRequest(std::string req) {std::cout << "------------------------------------" << std::endl;std::cout << req;HttpRequest req_obj;req_obj.Descrialize(req);std::string content = GetFileContent(req_obj.Path());if (content.empty())return std::string();HttpResponse rsp;rsp.AddCode(200);rsp.AddHeader("Content-Length", std::to_string(content.size()));rsp.AddHeader("Content-type", _mime_type[req_obj.Suffix()]);rsp.AddBodyText(content);return rsp.Serialize();}
private:std::unordered_map<std::string, std::string> _mime_type;
};
🏳??🌈二、狀態碼描述 及 自動跳轉404
前面的文章中我們已經知道了狀態碼描述的存在,但是沒有可利用過,這里再介紹一下這個狀態碼,添加一個狀態碼的映射,并且根據訪問內容,去判斷要不要顯示404界面
2.1 狀態碼描述
這里需要進行兩方面的更改,一個是 HttpResponse,一個是 HttpHandler 的構造和成員變量
HttpHandler 中我們添加
狀態碼
和狀態碼描述
的映射,然后在構造中表示出來
class HttpHandler {
public:HttpHandler() {_mime_type.insert(std::make_pair(".html", "text/html")); // HTML 類型_mime_type.insert(std::make_pair(".jpg", "image/jpeg")); // JPEG 圖片_mime_type.insert(std::make_pair(".png", "image/png")); // PNG 圖片_mime_type.insert(std::make_pair(".default", "text/html")); // 默認文本類型_status_code_desc.insert(std::make_pair(100, "Continue"));_status_code_desc.insert(std::make_pair(200, "OK"));_status_code_desc.insert(std::make_pair(201, "Created"));_status_code_desc.insert(std::make_pair(404, "Not Found"));}
private:std::unordered_map<int, std::string> _status_code_desc;
};
HttpResponse 中的
AddCode
方法,我們之前默認是不論什么都是 OK,現在我們對其進行專屬化處理
// 添加 狀態碼 和 狀態碼描述
void AddCode(int code, std::string desc) {_status_code = code;_desc = desc;
}
2.2 自動跳轉404
我們在 HttpHandler
的 HandleRequest
方法中,當判定訪問的路徑內容為空時,就將這個路徑改成 404.html
std::string HandleRequest(std::string req) {std::cout << "------------------------------------" << std::endl;std::cout << req;HttpRequest req_obj;req_obj.Descrialize(req);std::string content = GetFileContent(req_obj.Path());HttpResponse rsp;if (content.empty()) {content = GetFileContent("wwwroot/404.html");rsp.AddCode(404, _status_code_desc[404]);rsp.AddHeader("Content-Length", std::to_string(content.size()));rsp.AddHeader("Content-type", _mime_type[".default"]);rsp.AddBodyText(content);} else {rsp.AddCode(200, _status_code_desc[200]);rsp.AddHeader("Content-Length", std::to_string(content.size()));rsp.AddHeader("Content-type", _mime_type[req_obj.Suffix()]);rsp.AddBodyText(content);}return rsp.Serialize();
}
🏳??🌈三、重定向狀態碼
HTTP 狀態碼 301(永久重定向)和 302(臨時重定向)都依賴 Location 選項。
無論是 HTTP 301 還是 HTTP 302 重定向,都需要依賴 Location 選項來指定資源的新位置。這個 Location 選項是一個標準的 HTTP 響應頭部,用于告訴瀏覽器應該將請求重定向到哪個新的 URL 地址。
我們現在
default.html
中添加測試重定向
的選項
我們測試永久重定向,將當前的網址重定向到
qq.com
中
std::string HandleRequest(std::string req) {std::cout << "------------------------------------" << std::endl;std::cout << req;HttpRequest req_obj;req_obj.Descrialize(req);HttpResponse rsp;if (req_obj.Url() == "/redirect") {// 重定向處理std::string redirect_path = "https://www.qq.com";rsp.AddCode(302, _status_code_desc[302]);rsp.AddHeader("Location", redirect_path);rsp.AddHeader("Content-Type", "text/plain"); // 添加 Content-Typersp.AddHeader("Content-Length", "0"); // 顯式設置內容長度為 0rsp.AddBodyText(""); // 確保響應體為空} else {std::string content = GetFileContent(req_obj.Path());if (content.empty()) {content = GetFileContent("wwwroot/404.html");rsp.AddCode(404, _status_code_desc[404]);rsp.AddHeader("Content-Length", std::to_string(content.size()));rsp.AddHeader("Content-type", _mime_type[".default"]);rsp.AddBodyText(content);} else {rsp.AddCode(200, _status_code_desc[200]);rsp.AddHeader("Content-Length", std::to_string(content.size()));rsp.AddHeader("Content-type", _mime_type[req_obj.Suffix()]);rsp.AddBodyText(content);}}return rsp.Serialize();
}
🏳??🌈四、注冊等多功能服務
因為存在兩種主要的提交方式,分別是 POST
和 GET
,因此參數的位置會不一樣,
GET
下,位于 網址 的?
后面
POST 下,位于請求正文中
所以我們要根據不同的情況,分出響應的 參數,以及路徑
4.1 HttpRequest 類
需要改的主要有三部分
- 增加新的成員變量,
_args
用來記錄參數,_isexcute
用來記錄是否有參數 - 構造函數中給
_isexcute
默認為false - 更改
Descrialize
細節,使其區分GET
和POST
,并且能夠準確提取出_args
和_path
class HttpRequest{private:public:HttpRequest() : _blank_line(_base_sep), _path(_prefix_path), _isexcute(false) {}void Descrialize(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())_req_body = reqstr;// 進一步反序列化請求行PraseReqLine();// 分割請求報頭,獲取鍵值對PraseHeader(); // 判斷是否需要動態執行if(_method == "POST"){_isexcute = true;_args = _req_body;LOG(LogLevel::INFO) << "POST _path : " << _path;LOG(LogLevel::INFO) << "POST _args : " << _args;} else if(_method == "GET"){auto pos = _path.find("?");if(pos != std::string::npos){_isexcute = true;_args = _path.substr(pos + 1);_path = _path.substr(0, pos);LOG(LogLevel::INFO) << "GET _path : " << _path;LOG(LogLevel::INFO) << "GET _args : " << _args;}}}std::string Args(){LOG(LogLevel::INFO) << "client want _args : " << _args; return _args;}bool Isexecute(){LOG(LogLevel::INFO) << "client want _isexcute : " << _isexcute; return _isexcute;}private:bool _isexcute; // 是否需要動態執行std::string _args; // 動態執行的參數};
4.2 HttpHandler 類
增加功能路由表也就是映射目標路徑和方法的
unoredered_map
。同時也要構建能夠處理相應操作的回調函數類型
using http_handler_t = std::function<HttpResponse(HttpRequest&)>;
std::unordered_map<std::string, http_handler_t> _route; // 功能路由表
增加注冊服務的相關功能
// 注冊服務功能void RegisterHandler(std::string funcname, http_handler_t service) {std::string name = _prefix_path + funcname;_route.insert(std::make_pair(name, service));}// 判斷是否存在該功能bool HasHandler(std::string funcname) {auto iter = _route.find(funcname);if (iter == _route.end())return false;elsereturn true;}
將http請求處理主要分為三塊
- 重定向處理
- 動態操作處理
- 靜態頁面變化處理
std::string HandleRequest(std::string req) {std::cout << "------------------------------------" << std::endl;std::cout << req;HttpRequest req_obj;req_obj.Descrialize(req);HttpResponse rsp;if (req_obj.Url() == "/redirect") {LOG(LogLevel::DEBUG) << "重定向服務";// 重定向處理std::string redirect_path = "https://www.qq.com";rsp.AddCode(302, _status_code_desc[302]);rsp.AddHeader("Location", redirect_path);rsp.AddHeader("Content-Type", "text/plain"); // 添加 Content-Typersp.AddHeader("Content-Length", "0"); // 顯式設置內容長度為 0rsp.AddBodyText(""); // 確保響應體為空} else if (req_obj.Isexecute()) {LOG(LogLevel::DEBUG) << "注冊服務";if (HasHandler(req_obj.Path())) {LOG(LogLevel::DEBUG) << "找到注冊服務";rsp = _route[req_obj.Path()](req_obj);} else {LOG(LogLevel::DEBUG) << "沒有找到注冊服務";std::string content = GetFileContent("wwwroot/404.html");rsp.AddCode(404, _status_code_desc[404]);rsp.AddHeader("Content-Length", std::to_string(content.size()));rsp.AddHeader("Content-type", _mime_type[".default"]);rsp.AddBodyText(content);}} else {LOG(LogLevel::DEBUG) << "靜態頁面服務";std::string content = GetFileContent(req_obj.Path());if (content.empty()) {content = GetFileContent("wwwroot/404.html");rsp.AddCode(404, _status_code_desc[404]);rsp.AddHeader("Content-Length", std::to_string(content.size()));rsp.AddHeader("Content-type", _mime_type[".default"]);rsp.AddBodyText(content);} else {rsp.AddCode(200, _status_code_desc[200]);rsp.AddHeader("Content-Length", std::to_string(content.size()));rsp.AddHeader("Content-type", _mime_type[req_obj.Suffix()]);rsp.AddBodyText(content);}}return rsp.Serialize();
}
4.3 TcpServer.cpp
這里我們需要將注冊服務具體化,并添加到 功能路由表
中
現實生活中,我們還需要對這個傳進來的參數(用戶名、密碼等)進行序列化,到數據庫中查找等操作,這里就不拓展了,簡簡單單地使用
success.html
界面表示我們登錄成功就行了
HttpResponse Login(HttpRequest& req){HttpResponse rsp;LOG(LogLevel::INFO) << "進入登錄模塊" << req.Path() << ", " << req.Args();std::string req_args = req.Args();// 1. 解析參數格式,得到要的參數// 2. 訪問數據庫,驗證對應的用戶是否是合法用戶,以及...// 3. 登錄成功HttpHandler httphandler;std::string content = httphandler.GetFileContent("wwwroot/success.html");rsp.AddCode(200, "OK");rsp.AddHeader("Content-Length", std::to_string(content.size()));rsp.AddHeader("Content-Type", "text/html");rsp.AddHeader("Set-Cokkie", "req_args");rsp.AddBodyText(content);return rsp;
}
4.4 測試
當 ·login
方法是 GET
時
當是 POST
時
👥總結
本篇博文對 【Linux網絡】Http服務優化 - 增加請求后綴、狀態碼描述、重定向、自動跳轉及注冊多功能服務 做了一個較為詳細的介紹,不知道對你有沒有幫助呢
覺得博主寫得還不錯的三連支持下吧!會繼續努力的~