仿Muduo的高并发服务器:Http协议模块

本期我们接着深入项目

相关代码在这里:仿muduo服务器: 本项目致力于实现一个仿造muduo库的简易并发服务器,为个人项目,参考即可

目录

Util工具类模块

源码

HTTP模块系列

HTTPRequest模块

设计思路

源码

HTTPResponse模块

设计思路

源码

HTTPContext模块

设计思路

源码


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正则进行解析后,所提取的数据,比如提取资源路径中的数字...

功能性接口

  1. 将成员变量设置为公有成员,便于直接访问

  2. 提供查询字符串,以及头部字段的单个查询和获取,插入功能

  3. 获取正文长度

  4. 判断长连接&短链接 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响应信息要素,提供简单的功能性接口
响应信息要素

  1. 响应状态码

  2. 头部字段

  3. 响应正文

  4. 重定向信息(是否进行了重定向的标志,重定向的路径)
    功能性接口

  5. 为了便于成员的访问,因此将成员设置为公有成员

  6. 头部字段的新增,查询,获取

  7. 正文的设置

  8. 重定向的设置

  9. 长短连接的判断

源码

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;
    }
}

本期内容到这里就结束了,喜欢请点个赞谢谢

封面图自取:

相关推荐
AI机器学习算法8 小时前
《动手学深度学习PyTorch版》笔记
人工智能·学习·机器学习
milo.qu8 小时前
RockyLinux9.7 docker部署Jisti Meet
linux·docker·容器
贺一航【Niki】8 小时前
【学习笔记】杂乱知识
笔记·学习
GanGanGanGan_8 小时前
CentOS 7.9 glibc 2.17 源码编译升级到 glibc 2.31
linux·运维·centos·glibc
tjl521314_218 小时前
04C++ 名称空间(Namespace)
开发语言·c++
ximu_polaris8 小时前
设计模式(C++)-行为型模式-备忘录模式
c++·设计模式·备忘录模式
charlie1145141918 小时前
嵌入式Linux驱动开发——class 和 device 模型 - 自动创建设备节点的幕后机制
linux·运维·驱动开发
白雪茫茫9 小时前
监督学习、半监督学习、无监督学习算法详解
python·学习·算法·ai
杨云龙UP10 小时前
SQL Server2022部署:Windows Server 2016下安装、SSMS配置、备份还原与1433端口放通全流程_20260508
运维·服务器·数据库·sql·sqlserver·2022