本期我们接着深入项目
相关代码在这里:仿muduo服务器: 本项目致力于实现一个仿造muduo库的简易并发服务器,为个人项目,参考即可
目录
Util工具类模块
Util是一个针对于HTTP协议处理的相关工具函数,其主要的功能如下:
1、读取文件内容
2、向文件写入内容
3、URL编码
4、URL解码
5、HTTP状态码&描述信息
6、根据文件后缀名获取mime
7、判断一个文件是否是目录
8、判断一个文件是否是一个普通文件
9、HTTP资源路径的有效性判断
源码
Util.hpp
cpp
#pragma once
#include <string>
#include <vector>
#include <unordered_map>
namespace ImMuduo
{
std::unordered_map<int, std::string> _statu_msg = {
{100, "Continue"},
{101, "Switching Protocol"},
{102, "Processing"},
{103, "Early Hints"},
{200, "OK"},
{201, "Created"},
{202, "Accepted"},
{203, "Non-Authoritative Information"},
{204, "No Content"},
{205, "Reset Content"},
{206, "Partial Content"},
{207, "Multi-Status"},
{208, "Already Reported"},
{226, "IM Used"},
{300, "Multiple Choice"},
{301, "Moved Permanently"},
{302, "Found"},
{303, "See Other"},
{304, "Not Modified"},
{305, "Use Proxy"},
{306, "unused"},
{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"},
{418, "I'm a teapot"},
{421, "Misdirected Request"},
{422, "Unprocessable Entity"},
{423, "Locked"},
{424, "Failed Dependency"},
{425, "Too Early"},
{426, "Upgrade Required"},
{428, "Precondition Required"},
{429, "Too Many Requests"},
{431, "Request Header Fields Too Large"},
{451, "Unavailable For Legal Reasons"},
{501, "Not Implemented"},
{502, "Bad Gateway"},
{503, "Service Unavailable"},
{504, "Gateway Timeout"},
{505, "HTTP Version Not Supported"},
{506, "Variant Also Negotiates"},
{507, "Insufficient Storage"},
{508, "Loop Detected"},
{510, "Not Extended"},
{511, "Network Authentication Required"}
};
std::unordered_map<std::string, std::string> _mime_msg = {
{".aac", "audio/aac"},
{".abw", "application/x-abiword"},
{".arc", "application/x-freearc"},
{".avi", "video/x-msvideo"},
{".azw", "application/vnd.amazon.ebook"},
{".bin", "application/octet-stream"},
{".bmp", "image/bmp"},
{".bz", "application/x-bzip"},
{".bz2", "application/x-bzip2"},
{".csh", "application/x-csh"},
{".css", "text/css"},
{".csv", "text/csv"},
{".doc", "application/msword"},
{".docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"},
{".eot", "application/vnd.ms-fontobject"},
{".epub", "application/epub+zip"},
{".gif", "image/gif"},
{".htm", "text/html"},
{".html", "text/html"},
{".ico", "image/vnd.microsoft.icon"},
{".ics", "text/calendar"},
{".jar", "application/java-archive"},
{".jpeg", "image/jpeg"},
{".jpg", "image/jpeg"},
{".js", "text/javascript"},
{".json", "application/json"},
{".jsonld", "application/ld+json"},
{".mid", "audio/midi"},
{".midi", "audio/x-midi"},
{".mjs", "text/javascript"},
{".mp3", "audio/mpeg"},
{".mpeg", "video/mpeg"},
{".mpkg", "application/vnd.apple.installer+xml"},
{".odp", "application/vnd.oasis.opendocument.presentation"},
{".ods", "application/vnd.oasis.opendocument.spreadsheet"},
{".odt", "application/vnd.oasis.opendocument.text"},
{".oga", "audio/ogg"},
{".ogv", "video/ogg"},
{".ogx", "application/ogg"},
{".otf", "font/otf"},
{".png", "image/png"},
{".pdf", "application/pdf"},
{".ppt", "application/vnd.ms-powerpoint"},
{".pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation"},
{".rar", "application/x-rar-compressed"},
{".rtf", "application/rtf"},
{".sh", "application/x-sh"},
{".svg", "image/svg+xml"},
{".swf", "application/x-shockwave-flash"},
{".tar", "application/x-tar"},
{".tif", "image/tiff"},
{".tiff", "image/tiff"},
{".ttf", "font/ttf"},
{".txt", "text/plain"},
{".vsd", "application/vnd.visio"},
{".wav", "audio/wav"},
{".weba", "audio/webm"},
{".webm", "video/webm"},
{".webp", "image/webp"},
{".woff", "font/woff"},
{".woff2", "font/woff2"},
{".xhtml", "application/xhtml+xml"},
{".xls", "application/vnd.ms-excel"},
{".xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"},
{".xml", "application/xml"},
{".xul", "application/vnd.mozilla.xul+xml"},
{".zip", "application/zip"},
{".3gp", "video/3gpp"},
{".3g2", "video/3gpp2"},
{".7z", "application/x-7z-compressed"}
};
class Util
{
public:
//字符串分割函数,将src字符串按照sep字符进行分割,得到的各个字串放到arry中,最终返回字串的数量
static size_t Split(const std::string &src, const std::string &sep, std::vector<std::string> *arry);
//读取文件的所有内容,将读取的内容放到一个Buffer中
static bool ReadFile(const std::string &filename, std::string *buf);
//向文件写入数据
static bool WriteFile(const std::string &filename, const std::string &buf);
//URL编码,避免URL中资源路径与查询字符串中的特殊字符与HTTP请求中特殊字符产生歧义
//编码格式:将特殊字符的ascii值,转换为两个16进制字符,前缀% C++ -> C%2B%2B
// 不编码的特殊字符: RFC3986文档规定 . - _ ~ 字母,数字属于绝对不编码字符
//RFC3986文档规定,编码格式 %HH
//W3C标准中规定,查询字符串中的空格,需要编码为+, 解码则是+转空格
static std::string UrlEncode(const std::string url, bool convert_space_to_plus);
static std::string UrlDecode(const std::string url, bool convert_plus_to_space);
//HTTP状态码描述
//响应状态码的描述信息获取
static std::string StatDesc(int statusCode);
//根据文件后缀名获取文件mime
static std::string ExtMime(const std::string &filename);
//判断一个文件是否是一个目录
static bool IsDirectory(const std::string &filename);
//判断一个文件是否是一个普通文件
static bool IsRegular(const std::string &filename);
//http请求的资源路径有效性判断
// /index.html --- 前边的/叫做相对根目录 映射的是某个服务器上的子目录
// 想表达的意思就是,客户端只能请求相对根目录中的资源,其他地方的资源都不予理会
// /../login, 这个路径中的..会让路径的查找跑到相对根目录之外,这是不合理的,不安全的
static bool ValidPath(const std::string &path);
};
}
Util.cpp
cpp
#include "Util.hpp"
#include <fstream>
#include <sstream>
#include <iomanip>
#include <filesystem>
#include <cctype>
namespace ImMuduo
{
// ========== 字符串分割 ==========
size_t Util::Split(const std::string& src, const std::string& sep,
std::vector<std::string>* arry)
{
if (arry == nullptr || src.empty()) return 0;
arry->clear();
size_t pos = 0, found;
while ((found = src.find(sep, pos)) != std::string::npos)
{
arry->emplace_back(src.substr(pos, found - pos));
pos = found + sep.size();
}
arry->emplace_back(src.substr(pos));
return arry->size();
}
// ========== 文件操作 ==========
bool Util::ReadFile(const std::string& filename, std::string* buf)
{
if (buf == nullptr) return false;
std::ifstream ifs(filename, std::ios::in | std::ios::binary);
if (!ifs.is_open()) return false;
std::ostringstream oss;
oss << ifs.rdbuf();
*buf = oss.str();
return true;
}
bool Util::WriteFile(const std::string& filename, const std::string& buf)
{
std::ofstream ofs(filename, std::ios::out | std::ios::binary | std::ios::trunc);
if (!ofs.is_open()) return false;
ofs.write(buf.data(), static_cast<std::streamsize>(buf.size()));
return ofs.good();
}
// ========== URL 编解码 ==========
namespace
{
int HexToInt(char c)
{
if (c >= '0' && c <= '9') return c - '0';
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
return 0;
}
}
std::string Util::UrlEncode(const std::string url, bool convSpaceToPlus)
{
std::ostringstream oss;
oss << std::hex << std::uppercase << std::setfill('0');
for (unsigned char c : url)
{
if (std::isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~')
{
oss << c;
}
else if (c == ' ' && convSpaceToPlus)
{
oss << '+';
}
else
{
oss << '%' << std::setw(2) << static_cast<int>(c);
}
}
return oss.str();
}
std::string Util::UrlDecode(const std::string url, bool convPlusToSpace)
{
std::string result;
result.reserve(url.size());
for (size_t i = 0; i < url.size(); ++i)
{
if (url[i] == '%' && i + 2 < url.size() &&
std::isxdigit(static_cast<unsigned char>(url[i + 1])) &&
std::isxdigit(static_cast<unsigned char>(url[i + 2])))
{
result += static_cast<char>(
HexToInt(url[i + 1]) * 16 + HexToInt(url[i + 2]));
i += 2;
}
else if (url[i] == '+' && convPlusToSpace)
{
result += ' ';
}
else
{
result += url[i];
}
}
return result;
}
// ========== HTTP 工具(使用头文件中定义的全局 map) ==========
std::string Util::StatDesc(int statusCode)
{
auto it = _statu_msg.find(statusCode);
return it != _statu_msg.end() ? it->second : "Unknown";
}
std::string Util::ExtMime(const std::string& filename)
{
auto pos = filename.rfind('.');
if (pos == std::string::npos) return "application/octet-stream";
std::string ext = filename.substr(pos);
auto it = _mime_msg.find(ext);
return it != _mime_msg.end() ? it->second : "application/octet-stream";
}
// ========== 文件系统(C++17) ==========
bool Util::IsDirectory(const std::string& filename)
{
std::error_code ec;
return std::filesystem::is_directory(filename, ec);
}
bool Util::IsRegular(const std::string& filename)
{
std::error_code ec;
return std::filesystem::is_regular_file(filename, ec);
}
bool Util::ValidPath(const std::string& path)
{
if (path.find("..") != std::string::npos) return false;
std::error_code ec;
return std::filesystem::exists(path, ec);
}
}
HTTP模块系列
HTTPRequest模块
设计思路
http请求信息模块 :存储HTTP请求信息要素,提供简单的功能性接口
请求信息要素 :
1、请求行:请求方法,URL,协议版本
2、URL:资源路径,查询字符串
GET /search/1234?word=C++&en=utf8 HTTP/1.1
3、请求头部:key: val\r\nkey: val\r\n......
Content-Length: 0\r\n
4、正文
要素 :请求方法,资源路径,查询字符串,头部字段,正文,协议版本
std::smatch 保存首行使用regex正则进行解析后,所提取的数据,比如提取资源路径中的数字...
功能性接口 :
-
将成员变量设置为公有成员,便于直接访问
-
提供查询字符串,以及头部字段的单个查询和获取,插入功能
-
获取正文长度
-
判断长连接&短链接 Connection: close / keep-alive
源码
HttpRequest.hpp
cpp
#pragma once
#include "Util.hpp"
#include <unordered_map>
#include <regex>
namespace ImMuduo
{
class HttpRequest
{
public:
std::string method_;
std::string path_;
std::string version_;
std::string body_;
std::smatch matches_;
std::unordered_map<std::string, std::string> headers_;
std::unordered_map<std::string, std::string> params_;
public:
HttpRequest();
~HttpRequest() = default;
void Reset();
void SetHeader(const std::string& key, const std::string& val);
bool HasHeader(const std::string& key) const;
std::string GetHeader(const std::string& key) const;
void SetParam(const std::string& key, const std::string& val);
bool HasParam(const std::string& key) const;
std::string GetParam(const std::string& key) const;
size_t ContentLength() const;
bool IsShortLinkConnection() const;
};
}
HttpRequest.cpp
cpp
#include "HttpRequest.hpp"
namespace ImMuduo
{
HttpRequest::HttpRequest() {}
void HttpRequest::Reset()
{
method_.clear();
path_.clear();
version_.clear();
body_.clear();
std::smatch m;
matches_.swap(m);
headers_.clear();
params_.clear();
}
void HttpRequest::SetHeader(const std::string& key, const std::string& val)
{
headers_[key] = val;
}
bool HttpRequest::HasHeader(const std::string& key) const
{
return headers_.find(key) != headers_.end();
}
std::string HttpRequest::GetHeader(const std::string& key) const
{
auto it = headers_.find(key);
return it != headers_.end() ? it->second : std::string{};
}
void HttpRequest::SetParam(const std::string& key, const std::string& val)
{
params_[key] = val;
}
bool HttpRequest::HasParam(const std::string& key) const
{
return params_.find(key) != params_.end();
}
std::string HttpRequest::GetParam(const std::string& key) const
{
auto it = params_.find(key);
return it != params_.end() ? it->second : std::string{};
}
size_t HttpRequest::ContentLength() const
{
// 优先从 Content-Length 头获取
auto it = headers_.find("Content-Length");
if (it != headers_.end())
return static_cast<size_t>(std::stoul(it->second));
return body_.size();
}
bool HttpRequest::IsShortLinkConnection() const
{
auto it = headers_.find("Connection");
if (it == headers_.end()) return true;
// 大小写不敏感比较
std::string val = it->second;
for (auto& c : val) c = static_cast<char>(std::tolower(c));
return val == "close";
}
}
HTTPResponse模块
设计思路
功能 :存储HTTP响应信息要素,提供简单的功能性接口
响应信息要素 :
-
响应状态码
-
头部字段
-
响应正文
-
重定向信息(是否进行了重定向的标志,重定向的路径)
功能性接口 : -
为了便于成员的访问,因此将成员设置为公有成员
-
头部字段的新增,查询,获取
-
正文的设置
-
重定向的设置
-
长短连接的判断
源码
HttpReponse.hpp
cpp
#pragma once
#include "HttpRequest.hpp"
#include <unordered_map>
namespace ImMuduo
{
class HttpResponse
{
private:
int statu_;
bool redirect_flag_;
std::string body_;
std::string redirect_url_;
std::unordered_map<std::string, std::string> headers_;
public:
HttpResponse();
explicit HttpResponse(int statu);
~HttpResponse() = default;
void Reset();
void SetHeader(const std::string& key, const std::string& val);
bool HasHeader(const std::string& key) const;
std::string GetHeader(const std::string& key) const;
void SetContent(const std::string& body,
const std::string& type = "text/html");
void SetRedirect(const std::string& url, int statu = 302);
bool IsShortLinkConnection() const;
int GetStatus() const { return statu_; }
bool IsRedirect() const { return redirect_flag_; }
const std::string& GetBody() const { return body_; }
const std::string& GetRedirectUrl() const { return redirect_url_; }
};
}
HttpReponse.cpp
cpp
#include "HttpResponse.hpp"
namespace ImMuduo
{
HttpResponse::HttpResponse()
: statu_(200), redirect_flag_(false), body_(), redirect_url_(), headers_()
{}
HttpResponse::HttpResponse(int statu)
: statu_(statu), redirect_flag_(false), body_(), redirect_url_(), headers_()
{}
void HttpResponse::Reset()
{
statu_ = 200;
redirect_flag_ = false;
body_.clear();
redirect_url_.clear();
headers_.clear();
}
void HttpResponse::SetHeader(const std::string& key, const std::string& val)
{
headers_[key] = val;
}
bool HttpResponse::HasHeader(const std::string& key) const
{
return headers_.find(key) != headers_.end();
}
std::string HttpResponse::GetHeader(const std::string& key) const
{
auto it = headers_.find(key);
return it != headers_.end() ? it->second : std::string{};
}
void HttpResponse::SetContent(const std::string& body, const std::string& type)
{
body_ = body;
headers_["Content-Type"] = type;
headers_["Content-Length"] = std::to_string(body.size());
}
void HttpResponse::SetRedirect(const std::string& url, int statu)
{
redirect_flag_ = true;
redirect_url_ = url;
statu_ = statu;
}
bool HttpResponse::IsShortLinkConnection() const
{
auto it = headers_.find("Connection");
if (it == headers_.end()) return true;
std::string val = it->second;
for (auto& c : val) c = static_cast<char>(std::tolower(c));
return val == "close";
}
}
HTTPContext模块
设计思路
功能 :
这是一个请求接收上下文模块,记录HTTP请求的接收和处理进度
意义 :
有可能出现接收的数据并不是一条完整的HTTP请求数据,也就是请求的处理需要在多次收到数据后才能处理完成,因此在每次处理的时候,就需要将处理进度记录下来,以便于下次从当前进度继续向下处理
实现要素 :
1、接收状态
2、接收请求行:当前处于接收并处理请求行的阶段
3、接收请求头部:表示请求头部的接收还没有完毕
4、接收正文:表示还有正文没有接收完毕
5、接收数据完毕:个接收完毕,可以对请求进行处理的阶段
6、接收处理请求出错
7、响应状态码:注意:在请求的接收并处理中,有可能会出现各种不同的问题,解析出错,访问的资源不对,没有权限......而这些错误的响应状态码都是不一样的。
8、已经接收并被处理的信息
接口:
接收并处理请求数据:
1、接收请求行
2、解析请求行
3、接收头部
4、解析头部
5、接收正文
返回解析完毕的请求信息
返回响应状态码
返回接收解析状态
源码
HttpContext.hpp
cpp
#pragma once
#include "HttpRequest.hpp"
#include "Buffer.hpp"
namespace ImMuduo
{
class HttpContext
{
public:
enum ParseState
{
kExpectRequestLine,
kExpectHeaders,
kExpectBody,
kGotAll
};
HttpContext();
// 解析缓冲区,返回 true 表示请求完整
bool Parse(Buffer* buf);
bool IsComplete() const { return state_ == kGotAll; }
const HttpRequest& GetRequest() const { return request_; }
HttpRequest& GetRequest() { return request_; }
void Reset();
private:
bool ParseRequestLine(const std::string& line);
bool ParseHeaderLine(const std::string& line);
ParseState state_;
HttpRequest request_;
};
}
HttpContext.cpp
cpp
#include "HttpContext.hpp"
#include "Log.hpp"
#include <sstream>
#include <algorithm>
#include <cctype>
namespace ImMuduo
{
HttpContext::HttpContext()
: state_(kExpectRequestLine)
{}
void HttpContext::Reset()
{
state_ = kExpectRequestLine;
request_.Reset();
}
bool HttpContext::Parse(Buffer* buf)
{
if (buf == nullptr || state_ == kGotAll) return IsComplete();
bool hasMore = true;
while (hasMore && state_ != kGotAll)
{
switch (state_)
{
case kExpectRequestLine:
case kExpectHeaders:
{
const char* crlf = buf->FindCRLF();
if (crlf == nullptr) { hasMore = false; break; }
std::string line(buf->ReadPos(), crlf);
buf->MoveReadPos(crlf - buf->ReadPos() + 2);
if (state_ == kExpectRequestLine)
{
if (!ParseRequestLine(line)) return false;
state_ = kExpectHeaders;
}
else if (line.empty())
{
if (request_.ContentLength() > 0)
state_ = kExpectBody;
else
state_ = kGotAll;
}
else
{
ParseHeaderLine(line);
}
break;
}
case kExpectBody:
{
size_t need = request_.ContentLength();
if (buf->ReadableSize() >= need)
{
request_.body_.assign(buf->ReadPos(), need);
buf->MoveReadPos(need);
state_ = kGotAll;
}
else
{
hasMore = false;
}
break;
}
case kGotAll:
break;
}
}
return IsComplete();
}
bool HttpContext::ParseRequestLine(const std::string& line)
{
std::istringstream iss(line);
std::string method, path, version;
if (!(iss >> method >> path >> version)) return false;
for (auto& c : method) c = static_cast<char>(std::toupper(c));
if (method != "GET" && method != "HEAD" && method != "POST") return false;
request_.method_ = std::move(method);
request_.path_ = std::move(path);
request_.version_ = std::move(version);
return true;
}
bool HttpContext::ParseHeaderLine(const std::string& line)
{
auto pos = line.find(':');
if (pos == std::string::npos) return false;
std::string key = line.substr(0, pos);
std::string val = line.substr(pos + 1);
size_t start = val.find_first_not_of(" \t");
if (start != std::string::npos) val = val.substr(start);
request_.headers_[std::move(key)] = std::move(val);
return true;
}
}
本期内容到这里就结束了,喜欢请点个赞谢谢
封面图自取:
