📢博客主頁:https://blog.csdn.net/2301_779549673
📢博客倉庫:https://gitee.com/JohnKingW/linux_test/tree/master/lesson
📢歡迎點贊 👍 收藏 ?留言 📝 如有錯誤敬請指正!
📢本文由 JohnKi 原創,首發于 CSDN🙉
📢未來很長,值得我們全力奔赴更美好的生活?
文章目錄
- 🏳??🌈一、HttpRequest 類
- 1.1 基本結構
- 1.2 構造函數、析構函數
- 1.3 反序列化函數 Descrialize
- 1.4 獲取一行字符串 GetLine()
- 1.5 打印方法 Print
- 1.6 解析請求行 PraseReqLine
- 1.7 解析請求頭 void PraseHeader();
- 1.8 增加路徑字段
- 1.9 測試
- 🏳??🌈二 、整體代碼
- 👥總結
🏳??🌈一、HttpRequest 類
1.1 基本結構
我們結合這張圖,構建出 http請求
的基本結構,并定義一些基本方法
const static std::string _base_sep = "\r\n"; // static 關鍵字使變量具有內部鏈接,僅當前翻譯單元(源文件)可見。class HttpRequest {
private:std::string GetLine(std::string& reqstr); // 獲取一行信息void PraseReqLine(); // 解析請求行void PraseHeader(); // 解析請求頭
public:HttpRequest();void Descrialize(std::string& reqstr);void Print();~HttpRequest();private:std::string _req_line; // 請求行std::vector<std::string> _req_headers; // 請求報頭std::string _blank_line; // 空行std::string _req_body; // 請求體
};
1.2 構造函數、析構函數
構造函數初始化空行即可,因為空行是固定的,析構函數無需處理!
HttpRequest() : _blank_line(_base_sep) {}
~HttpRequest() {}
1.3 反序列化函數 Descrialize
我們這里是需要解析獲取到的請求,所以用的方法自然是 反序列化
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;
}
1.4 獲取一行字符串 GetLine()
// 獲取一行信息
std::string GetLine(std::string& reqstr) {auto pos = reqstr.find(_base_sep);if (pos == std::string::npos)return "";std::string line = reqstr.substr(0, pos); // 截取一行有效信息reqstr.erase(0, pos + _base_sep.length()); // 刪除有效信息和分隔符return line.empty() ? _base_sep: line; // 有效信息為空則返回分隔符,否則返回有效信息
}
1.5 打印方法 Print
void Print() {std::cout << "----------------------------------------" << std::endl;std::cout << "請求行: " << _req_line << std::endl;std::cout << "請求報頭: " << std::endl;for (auto& header : _req_headers) {std::cout << header << std::endl;}std::cout << "空行: " << _blank_line << std::endl;std::cout << "請求體: " << _req_body << std::endl;
}
當我們使用瀏覽器訪問我們的服務器,就能夠成功地將我們需要地所有信息給序列化出來,這里沒有請求,所以請求體為空
1.6 解析請求行 PraseReqLine
我們已經知道請求行的組成如下,所以我們可以進一步細分 HttpRequest
類,增加響應的請求行的成員變量
std::string _method; // 請求方法
std::string _url; // 請求url
std::string _version; // 請求版本
將 _req_line(請求行)封裝為字符串流,按空格分隔讀取方法、路徑、協議版本
// 解析請求行
void PraseReqLine() {// 以空格為分隔符,不斷讀取std::stringstream ss(_req_line);ss >> _method >> _url >> _version;
}
1.7 解析請求頭 void PraseHeader();
我們可以知道請求報頭中存在類似哈希表的 KV 結構,因此我們可以是使用一個 unordered_map
,存儲每一個鍵值對
根據我們之前獲取到的請求報頭,可以知道分隔符是 ": "
,可以根據這個進行解析請求報頭
// 解析請求頭
void PraseHeader() {for (auto& header : _req_headers) {auto pos = header.find(':');if (pos == std::string::npos)continue;std::string k = header.substr(0, pos);std::string v = header.substr(pos + _line_sep.size());if (k.empty() || v.empty())continue;_headers_kv[k] = v;}
}
1.8 增加路徑字段
**我們向服務器請求的時候,需要知道資源的路徑,因此我們可以增加路徑字段
**
- 我們提供一個路徑的前綴
wwwroot
- 并且當這個用戶訪問的路徑為
/
時,提供默認路徑default.html
const static std::string _prefix_path = "wwwroot"; // 默認前綴路勁
const static std::string _default_path = "default.html"; // 默認路徑
我們可以在構造函數時,給路勁添加上默認前綴路徑
HttpRequest() : _blank_line(_base_sep), _path(_prefix_path) {}
我們在解析 請求行
時對 url
進行分析,判斷是否為空,并且給 path
賦值
void PraseReqLine(){ // 以空格為分隔符,不斷讀取std::stringstream ss(_req_line);ss >> _method >> _url >> _version; _path += _url;// 處理url,如果是根目錄,則返回默認路徑if(_url == "/")_path += _default_path;}
提供兩個新方法用來獲取當前的 url
和 路徑
std::string Url() {LOG(LogLevel::INFO) << "client want url : " << _url;return _url;
}
std::string Path() {LOG(LogLevel::INFO) << "client want url : " << _path;return _path;
}
1.9 測試
我們運行服務端后,再用瀏覽器訪問我們的服務器,成功捕捉到了兩次請求,
- ?第一次請求?(端口 3362):
瀏覽器主動請求你輸入的 URL(如 http://119.91.133.45:8080/),服務端返回頁面/default.html - 第二次請求?(端口 3361):
瀏覽器 ?自動請求網站圖標? /favicon.ico,用于在標簽頁、書簽欄顯示小圖標。若服務端未顯式處理該請求,瀏覽器仍會嘗試獲取。
🏳??🌈二 、整體代碼
#pragma once#include <iostream>
#include <string>
#include <vector>
#include <sstream>
#include <unordered_map>
#include "Log.hpp"using namespace LogModule;const static std::string _base_sep = "\r\n"; // static 關鍵字使變量具有內部鏈接,僅當前翻譯單元(源文件)可見。
// const static std::string _base_sep = "\r\n"; // 默認具有外部鏈接,其他文件可通過 extern 引用。
const static std::string _line_sep = ": ";
const static std::string _prefix_path = "/wwwroot"; // 默認前綴路勁
const static std::string _default_path = "default.html"; // 默認路徑namespace HttpServer{class HttpRequest{private:// 獲取一行信息std::string GetLine(std::string& reqstr){auto pos = reqstr.find(_base_sep);if(pos == std::string::npos) return "";std::string line = reqstr.substr(0, pos); // 截取一行有效信息reqstr.erase(0, pos + _base_sep.length()); // 刪除有效信息和分隔符return line.empty() ? _base_sep : line; // 有效信息為空則返回分隔符,否則返回有效信息}// 解析請求行void PraseReqLine(){ // 以空格為分隔符,不斷讀取std::stringstream ss(_req_line);ss >> _method >> _url >> _version; _path += _url;// 處理url,如果是根目錄,則返回默認路徑if(_url == "/")_path = _default_path;}// 解析請求頭void PraseHeader(){for(auto& header : _req_headers){auto pos = header.find(':');if(pos == std::string::npos)continue;std::string k = header.substr(0, pos);std::string v = header.substr(pos + _line_sep.size());if(k.empty() || v.empty()) continue;_headers_kv[k] = v;}}public:HttpRequest() : _blank_line(_base_sep), _path(_prefix_path) {}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(); }void Print(){std::cout << "----------------------------------------" <<std::endl;std::cout << "請求行: ###" << _req_line << std::endl;std::cout << "請求報頭: " << std::endl;for(auto& header : _req_headers){std::cout << "@@@" << header << std::endl;}std::cout << "空行: " << _blank_line << std::endl;std::cout << "請求體: " << _req_body << std::endl;std::cout << "Method: " << _method << std::endl;std::cout << "Url: " << _url << std::endl;std::cout << "Version: " << _version << std::endl;}std::string Url(){LOG(LogLevel::INFO) << "client want url : " << _url; return _url;}std::string Path(){LOG(LogLevel::INFO) << "client want path : " << _path; return _path;}~HttpRequest() {}private:std::string _req_line; // 請求行std::vector<std::string> _req_headers; // 請求報頭std::string _blank_line; // 空行std::string _req_body; // 請求體std::string _method; // 請求方法std::string _path; // 資源路徑std::string _url; // 請求urlstd::string _version; // 請求版本std::unordered_map<std::string, std::string> _headers_kv; // 存儲每行報文的哈希表};class HttpHandler{public:HttpHandler(){}std::string handle(std::string req){std::cout << "------------------------------------" << std::endl;std::cout << req;HttpRequest req_obj;req_obj.Descrialize(req);// req_obj.Print();std::string path = req_obj.Path();std::string url = req_obj.Url();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;}~HttpHandler(){}};
}
👥總結
本篇博文對 【Linux網絡】構建與優化HTTP請求處理 - 從HttpRequest到HttpServer 做了一個較為詳細的介紹,不知道對你有沒有幫助呢
覺得博主寫得還不錯的三連支持下吧!會繼續努力的~