首页 > 其他分享 >【Mudo】实战项目之应用层模块

【Mudo】实战项目之应用层模块

时间:2024-08-18 22:51:18浏览次数:14  
标签:std return string req 应用层 模块 const rsp Mudo

文章目录

前言

在上一篇文章当中,博主从代码的层面介绍了服务器模块的实现,最终封装出了一个传输层的TcpServer模块,那么在本篇将进一步向上拓展出一个应用层——Http服务器,用以进行Http连接的业务处理。话不多说,但强调一点,需将本专栏的前两篇文章消化完毕之后,再看本篇。

正文

1. Util

说明:Util可以是个类,里面封装各种静态接口,也可以使用命名空间,目的都是为了使用作用域防止命名污染。如下介绍接口时默认是在作用域范围内部的,不再赘述,有兴趣可通过上篇文章的源码连接查看。

1.1 File

读写

  • C++IO流获取文件大小
 std::ifstream ifs(filename,std::ios::binary);//二进制的方式打开
 ifs.seekg(0,ifs.end);//到文件的末尾
 size_t fsz = ifs.tellg();//从开始到末尾的大小
 ifs.seekg(0,ifs.beg);//调整到文件开始处
    static bool ReadFile(const std::string filename,std::string* str)
    {
        //1.打开文件
        std::ifstream ifs(filename,std::ios::binary);
        if(false == ifs.is_open())
        {
            return false;
        }
        //2.获取文件大小
        ifs.seekg(0,ifs.end);
        size_t fsz = ifs.tellg();
        str->resize(fsz);//将str扩容至文件的大小
        ifs.seekg(0,ifs.beg);
        //3.读取文件内容
        ifs.read(&(*str)[0],fsz);
        if(false == ifs.good())
        {
            return false;
        }
        return true;
    }
    static bool WriteFile(const std::string filename,const std::string& str)
    {
        std::ofstream ofs(filename,std::ios::binary | std::ios::trunc);//以二进制且以清空的方式打开
        if(false == ofs.is_open())
        {
            return false;
        }
        else
        {
            ofs.write(&str[0],str.size());
            if(false == ofs.good())
            {
                return false;
            }
        }
        return true;
    }

类型判断

  • 接口
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int stat(const char *pathname, struct stat *statbuf);
/*
参数:
	1.pathname,输入型参数,文件名。
	2.statbuf,输出型参数,用于存放文件的属性信息。stat.st_mode,类型为mode_t,此处用于判断文件的类型。
	类型判断的宏:S_ISDIR(mode_t)用于判断是否为目录,S_ISREG(mode_t)用于判断是否为普通文件。
返回值:
	1.成功返回0。
	2.失败返回-1,并设置合适的错误码。
*/

bool IsRegular(const std::string& filename)
{
    return S_ISREG(Kind(filename));
}
bool IsDir(const std::string& filename)
{
    return S_ISDIR(Kind(filename));
}
mode_t Kind(const std::string& filename)
{
    struct stat st;
    int ret = stat(filename.c_str(),&st);
    if(ret < 0)
    {
    	//错误
    }
    return st.st_mode;
}

1.2 Url

编码与解码

编码规则:

URL,即统一资源定位符,也就是所谓的网址,比如说https://www.google.com/search?q=C%2B%2B,其中q=C%2B%2B,就是我们要搜索的参数,我本来搜索的是C++,但是其中的++与网址的标准格式的字符冲突被编码,可能会使处理出错,因此需要编码。

Http的标准格式:
在这里插入图片描述

解释完为啥需要编码,再来谈谈如何进行编码:

  1. .- _~字母数字为绝对不编码字符,因此不用进行转换。
  2. 空格根据不同的标准可能进行转化,W3cb标准必须被编码为+,RFC2396要转换为%%HH格式。
  3. 其它的字符统一转换成%%HH格式,即百分号 + 两位十六进制。
  • 接口:
  1. 用于判断是否字母或者字符
#include<ctype.h>  
int isalpha(int c)
/*
参数:字符
返回值:如果是则为非0值,不是则为0值.
*/
  1. 用于将10进制(int)转换为16进制(string),以字符串的形式输出。
 std::stringstream ssm;
 ssm << std::uppercase << std::setw(2) << std::setfill('0') << std::hex << dec;
 //ssm.str()获取转换的string

实现:

    static std::string UrlIncode(const std::string& iurl, bool is_w3c) 
    {

        
        //3.判断为字母和数字的接口:#include<ctype.h>  int isalpha(int c)
        std::string res;
        for(auto c : iurl)
        {
        	//[. - _ ~ 字母 数字]不属于绝对不编码字符
            if(c == '.' || c == '-' || c == '_' || c == '~' || isalpha(c))
            {
                res += c;
            }
            //空格W3c必须被编码为+
            else if(c == ' ' && is_w3c)
            {
                res += '+';
            }
            //其余要转换为%%HH格式
            else
            {
                int dec = c;
                std::stringstream ssm;
                ssm << std::uppercase << std::setw(2) << std::setfill('0') << std::hex << dec;
                res += ("%" + ssm.str());
            }
        }
        return res;
    }
    static std::string UrlDecode(const std::string& durl,bool is_w3c)
    {
        std::string res;
        for(int i = 0; i < durl.size();)
        {
            if(durl[i] == '%')
            {
                std::string hnum = durl.substr(i + 1,2);
                int c = stoi(hnum,nullptr,16);
                res += c;
                i += 3; 
            }
            else 
            {
                if(durl[i] == '+' && is_w3c)
                    res += ' ';
                else
                    res += durl[i];
                
                i += 1;
            }
        }
        return res; 
    }

资源路径是否有效

  • 思路:资源路径不能跑到资源的根目录上面,比如https://blog.csdn.net/…/就会被判定为无效,CSDN网站处理的是返回网站的首页,但是本项目的实现强硬了一些,直接非法,最终会返回错误界面。
    static bool Valid(const std::string& filename)
    {
    
        std::vector<std::string> dirs;
        //下文会提及的,这里是将字符串按照"/"切分存储到dirs中目的是将路径进行切分。
        Split(filename,"/",&dirs); 
        int level = 0;
        for(auto& d : dirs)
        {
            if(d == "..")
            {
                if(--level < 0) 
                {
                    return false;
                }
            }
            else
            {
                level++;
            } 
        }
        return true;
    }

1.3 Str

分割字符串,本项目主要是在解析请求行用到,此函数是仿照Boost库中的split函数实现的,也可以使用其提供的函数。

  • boost接口
//说明:需按照Boost库才可使用
#include<boost/algorithm/string/string.hpp>
template<typename SequenceSequenceT, typename RangeT, typename PredicateT> 
  SequenceSequenceT & split(SequenceSequenceT & Result, RangeT & Input, PredicateT Pred, 
        token_compress_mode_type eCompress = token_compress_off);
/*
参数
	1:vector<type>类型的,用于存放切割后的内容。
	2:切割的内容。
	3:分割符。
	4:切割的模式,一般设置为token_compress_on,意为将连续的分割符看成一个。
返回值 
	vector<type>类型的引用
*/

  • 接口
 size_t find(string str,int pos); //从pos位置开始找str
 std::string substr (int pos,int len);//从pos位置切割len长度的字符串
  • 实现
    static int Split(const std::string src,const std::string sep,std::vector<std::string> *arr)
    {
        
        int pos = 0;
        arr->resize(0);
        while (pos < src.size())
        {
            size_t offset = src.find(sep,pos);
            if(offset == std::string::npos)
            {
                arr->push_back(src.substr(pos));
                break;
            }
            else if(offset != pos)
            {
                arr->push_back(src.substr(pos,offset - pos));
            }
            pos = offset + sep.size();
        }
        return arr->size();
    }

1.4 Infor

响应行是这样的——HTTP/1.1 200 OK,此处的响应状态码和响应信息是对应的关系,因此可以使用哈希表的形式进行存储。

说明:响应状态码和与之对应的响应信息可在RFC文档 查看,在这里偷偷告诉你一个小技巧,即将对应信息喂给AI助手,让其帮你生成一个对应格式的数据,你可以告诉举个例子告诉AI生成{100, "Continue"},的数据然后让其输出,可以少一些Cv。

Status

 static std::string Statu(int stat)
 {
     auto it = _stu.find(stat);
     if(it != _stu.end())
     {
         return it->second;
     }
     return "Unknow";
 }
std::unordered_map<int, std::string> Util::_stu = 
{
    {100, "Continue"},
    {101, "Switching Protocols"},
    {200, "OK"},
    {201, "Created"},
    {202, "Accepted"},
    {203, "Non-Authoritative Information"},
    {204, "No Content"},
    {205, "Reset Content"},
    {206, "Partial Content"},
    {300, "Multiple Choices"},
    {301, "Moved Permanently"},
    {302, "Found"},
    {303, "See Other"},
    {304, "Not Modified"},
    {305, "Use Proxy"},
    {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"},
    {426, "Upgrade Required"},
    {451, "Unavailable For Legal Reasons"},
    {500, "Internal Server Error"},
    {501, "Not Implemented"},
    {502, "Bad Gateway"},
    {503, "Service Unavailable"},
    {504, "Gateway Timeout"},
    {505, "HTTP Version Not Supported"}
};

Mine

在Http协议格式中,需要说明响应正文是什么类型的,因此需要消息头中的Content-Type字段,而Mine类型则用于表示响应的格式,比如如果是.png类型的,则为Content-Type: image/png,而image/png即为Mine类型对于文件的描述形式。

说明:Mine类型可在RFC官方文档 进行查看,或者从网上翻一翻常见的或者让AI帮你生成一份常见的也行。

static std::string Mine(const std::string& filename)
{
    int pos = filename.find(".");
    if(pos == std::string::npos)
    {
    	//二进制数据,通常用于不知道文件类型时使用。
        return "application/octet-stream";
    }
    auto it = _mine.find(filename.substr(pos));
    if(it == _mine.end())
    {
        return "application/octet-stream";
    }
    return it->second;
}
std::unordered_map<std::string, std::string> Util::_mine = 
{
    {".txt", "text/plain"},
    {".pdf", "application/pdf"},
    {".doc", "application/msword"},
    {".docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"},
    {".xls", "application/vnd.ms-excel"},
    {".xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"},
    {".ppt", "application/vnd.ms-powerpoint"},
    {".pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation"},
    {".jpg", "image/jpeg"},
    {".jpeg", "image/jpeg"},
    {".png", "image/png"},
    {".gif", "image/gif"},
    {".bmp", "image/bmp"},
    {".svg", "image/svg+xml"},
    {".html", "text/html"},
    {".css", "text/css"},
    {".js", "application/javascript"},
    {".xml", "application/xml"},
    {".json", "application/json"},
    {".zip", "application/zip"},
    {".rar", "application/x-rar-compressed"},
    {".tar", "application/x-tar"},
    {".gz", "application/gzip"},
    {".mp3", "audio/mpeg"},
    {".wav", "audio/wav"},
    {".mp4", "video/mp4"},
    {".avi", "video/x-msvideo"},
    {".mkv", "video/x-matroska"},
    {".exe", "application/octet-stream"},
    {".dll", "application/octet-stream"},
    {".bat", "application/x-bat"},
    {".sh", "application/x-shellscript"},
    {".py", "text/x-python"},
    {".java", "text/x-java-source"},
    {".c", "text/x-c"},
    {".cpp", "text/x-c++"},
    {".h", "text/x-c-header"},
    {".sql", "application/x-sql"},
    {".csv", "text/csv"},
    {".log", "text/plain"},
    {".md", "text/markdown"},
    {".psd", "image/vnd.adobe.photoshop"},
    {".indd", "application/vnd.adobe.indesign"}
};

2. Http

2.1 Request

请求协议格式:
在这里插入图片描述
此类所做的只是将协议打散为类的成员元素,方便进行管理,做一些增删改查的功能。

struct HttpRequest
{
    //查找,修改,获取头部字段。
    bool HasHeader(const std::string& hdr)
    {
        auto it = _headers.find(hdr);
        if(it != _headers.end()) return true;
        return false;
    }
    void SetHeader(const std::string& key,const std::string& val)
    {
        auto it = _headers.find(key);
        if(it != _headers.end()) return;
        _headers.insert({key,val});
    }
    std::string GetHeader(const std::string& hdr)
    {
        auto it = _headers.find(hdr);
        if(it != _headers.end()) return it->second;
        return "Unknow Header";
    }
    //查找,修改,获取消息头,或者说请求报头也可以字段。
    bool HasParm(const std::string& par)
    {
        auto it = _parameter.find(par);
        if(it != _parameter.end()) return true;
        return false;
    }
    void SetParm(const std::string& key,const std::string& val)
    {
        auto it = _parameter.find(key);
        if(it != _parameter.end()) return;
        _parameter.insert({key,val});        
    }
    std::string GetParm(const std::string& par)
    {
        auto it = _parameter.find(par);
        if(it != _parameter.end()) return it->second;
        return "Unknow Parameter";
    }
    //判断是否为短连接
    bool Close()
    {
        const std::string& hdr = "Connection";
        if(true == HasHeader(hdr) && GetHeader(hdr) == "keep-alive")
        {
            return false;
        }
        return true;
    }
    //获取正文长度
    size_t GetBodyLen()
    {
        std::string slen = GetHeader("Content-Length");
        if(slen == "Unknow Header")
        {
            return 0;
        }
        return stoi(slen);
    }
    //重置清除信息
    void Reset()
    {
        std::smatch tmp;
        _sch.swap(tmp);
        _method.clear();
        _version.clear();
        _source_dir.clear();
        _parameter.clear();
        _headers.clear();
        _body.clear();
    }
    std::smatch _sch; //解析头部字段,动态请求资源路径处理的参数
    std::string _method;//请求方法
    std::string _version; //版本
    std::string _source_dir; //资源路径
    std::unordered_map<std::string,std::string> _parameter;//查询参数
    std::unordered_map<std::string,std::string> _headers;//消息头
    std::string _body;//消息体
};

2.2 Response

响应协议格式:
在这里插入图片描述
此类所做的与Request相同,是对连接响应信息的管理,特殊地消息头为Location: 即服务器的url可能会重定向,因此需要进行特殊判断。

struct HttpResponse
{
    HttpResponse(int stat = 200):_stau(stat),_is_redir(false){};
    //查找,修改,获取头部字段。
    bool HasHeader(const std::string& hdr)
    {
        auto it = _headers.find(hdr);
        if(it != _headers.end()) return true;
        return false;
    }
    void SetHeader(const std::string& key,const std::string& val)
    {
        auto it = _headers.find(key);
        if(it != _headers.end()) return;
        _headers.insert({key,val});
    }
    std::string GetHeader(const std::string& hdr)
    {
        auto it = _headers.find(hdr);
        if(it != _headers.end()) return it->second;
        return "Unknow Header";
    }    
    //判断是否为短连接
    bool Close()
    {
        const std::string& hdr = "Connection";
        if(true == HasHeader(hdr) && GetHeader(hdr) == "keep-alive")
        {
            return false;
        }
        return true;
    }
    //设置重定向的URL
    void SetReRirUrl(const std::string& url,int stat)
    {
        _stau = stat;
        _is_redir = true;
        _redir_url = url;
    }    
    //设置Body正文
    void SetContent(const std::string& content,const std::string& type = "text/html")
    {
        _body = content;
        SetHeader("Content-Type",type);
    }
    int _stau;
    std::unordered_map<std::string,std::string> _headers;//消息头
    bool _is_redir;
    std::string _redir_url;
    std::string _body;
};

2.3 Context

在上下文模块中进行请求行分析主要是通过正则表达式进行处理获取请求方法,资源路径,参数,HTTP版本。
demo:

#include <iostream>
#include <sstream>
#include <string>
#include <regex>
#include <iterator>
using namespace std;
int main() 
{
	//全文匹配,使用子表达式存放解析结果,只提取出请求方法,域名信息,查询字段(可能有或者没有)。
	smatch res;
	string str = "GET /blog.csdn.net/Shun_Hua?user=xiaoming&pass=123123 HTTP/1.1\r\n";
	string pattern("(GET|HEAD|POST|PUT|DELETE) ([^?]*)(?:\\?(.*))? (HTTP/1\\.[01])(?:\r\n|\n)?");
	//(GET|HEAD|POST|PUT|DELETE)请求方法的匹配。
	//([^?]*)表示匹配非问号字符串,用于获取域名。
	//\\?,首先?是特殊字符,要想正常匹配得使用\进行转义,\也是特殊转义字符,要想使用得再使用\进行转义。
	//(.*),.*是对任意字符串进行匹配。
	//?:\\?,表示不提取?到res中
	//(\\?(.*))?,表示子表达式出现0次或者1次。
	//(?:\\?(.*))?,子表达式出现0次或者1次,不提取?,提取(.*)到res中。
	//HTTP/1\\.[01],匹配版本号。
	//(?:\r\n|\n)?,同理表示匹配\r\n或者\n,0次或者1次,且不提取。
	regex r(pattern);
	bool ret = regex_match(str, res, r);
	if (ret)
	{
		cout << "匹配成功" << endl;
		for (auto e : res)
		{
            stringstream ssm;
            ssm << e;
            string s = e.str();
            cout << s << endl;
		}
	}
	else
	{
		cout << "匹配失败" << endl;
	}
	return 0;
}

说明:对正则表达式不太熟悉的C友可查看【C++进阶之路】C++11——正则表达式进行快速地入门和学习C++容器提供的对应接口。

涉及接口:

#include <algorithm>
std::stransform(oldbegin,oldend(),newposbegin,newposend,hder);
//将[begin,end)中的数据通过hder函数处理,放到newposbengin的位置,此处用于对请求方法统一转大写处理,因为可能发送的可能为小写。
  • 实现
//上下文处理的状态基,错误,初始,请求行处理完毕,消息头处理完毕,全部处理完毕。
typedef enum
{
    Error,
    Init,
    RevLine_Over,
    RevHeader_Over,
    Over,
}CxtStau;

class HttpContext
{
public:
    HttpContext(int reponse = 200)
    :_rep_stu(200),_stage(Init)
    {};
    CxtStau GetCxtStu()
    {
        return _stage;
    }
    int GetRepStu()
    {
        return _rep_stu;
    }
    HttpRequest GetRequest()
    {
        return _req;
    }
    void Recv_Parse(Buffer* buf)
    {
        switch (_stage)
        {
            case Init: if(!RevLine(buf)) break;
            case RevLine_Over: if(!RevHeader(buf)) break;
            case RevHeader_Over: if(!RevBody(buf)) break;
        }
    }
    void Reset()
    {
        _stage = Init;
        _rep_stu = 200;
        _req.Reset();
    }
private:
    //从接收缓存区中读取并解析请求行
    bool RevLine(Buffer* buf)
    {
        //从buf获取一行数据
        std::string line = buf->GetLine();
        //不到一行的数据大于MAX_LEN
        if(line.size() == 0 && buf->ReadableSize() > MAX_LEN)
        {
            _stage = Error;
            _rep_stu = 414; //URL太长了。
            return false;
        }
        //读取出来的一行数据大于MAX_LEN
        else if(line.size() > 0)
        {
            if(line.size() > MAX_LEN) 
            {
                _stage = Error;
                _rep_stu = 414; //URL太长了。
                return false;                
            }
            //处理数据
            else
            {
                //对请求行进行解析
                if(ParseLine(line))
                {
                    _stage = RevLine_Over;
                    return true;
                }
                else
                {
                    //解析出错
                    _stage = Error;
                    _rep_stu = 400;
                    return false;
                }
            }
        }
        //等待数据。
        return true;
    }
    bool ParseLine(std::string& line)
    {
        //1、使用正则表达式进行分析处理获取——请求方法 URL 请求参数 Http版本
        //2、对URL进行解码操作
        //3、对请求参数按照"&"分割,按照"="处理,获取key,val。
        std::smatch res;
        std::string pattern("(GET|HEAD|POST|PUT|DELETE) ([^?]*)(?:\\?(.*))? (HTTP/1\\.[01])(?:\r\n|\n)?");
        std::regex rx(pattern);
        bool ret = regex_match(line,res,rx);
        //第一个元素为line中的内容,剩余四个元素分别为请求方法,资源路径,参数,HTTP版本
        if(false == ret || res.size() != 5)//如果不为四个元素说明传输的数据有误。
        {
            return false;
        }
        else
        {	
        	//请求方法
            _req._method = res[1];    
            std::string& str = _req._method;
            std::transform(str.begin(),str.end(),str.begin(),::toupper);//GET可能为小写的需要转换为大写。
            //版本和资源路径
            _req._version = res[4];
            _req._source_dir = Util::UrlDecode(res[2],false);//资源路径不需要转空格
            //参数解析
            std::vector<std::string> arr;
            Util::Split(res[3],"&",&arr);//对参数行按照 & 进行分割
            for(auto& str : arr)
            {
                size_t pos = str.find("=");
                if(pos == std::string::npos)
                {
                    return false;                    
                }
                else
                {
                    //参数需要转空格
                    std::string key = Util::UrlDecode(str.substr(0,pos),true);
                    std::string val = Util::UrlDecode(str.substr(pos + 1),true);;
                    _req.SetParm(key,val);
                }
            }
        }
        return true;            
    }
    bool RevHeader(Buffer* buf)
    {
        if(_stage != RevLine_Over)
        {
            return false;
        }
        for(std::string line = buf->GetLine(); line != "\r\n" && line != "\n"; line = buf->GetLine())
        {
            if(line.size() == 0 && buf->ReadableSize() > MAX_LEN)
            {
                _stage = Error;
                _rep_stu = 414; //URL太长了。
                return false;
            }
            else if(line.size() > 0)
            {
                if(line.size() > MAX_LEN) 
                {
                    _stage = Error;
                    _rep_stu = 414; //URL太长了。
                    return false;                
                }
                else if(!ParseHeader(line))
                {
                    _stage = Error;
                    _rep_stu = 400;
                    return false;
                }
            }
        }
        _stage = RevHeader_Over;
        return true;
    }
    bool ParseHeader(std::string& line)
    {
        while(line.back() == '\r' || line.back() == '\n') line.pop_back();
        std::vector<std::string> kv;
        Util::Split(line, ": ",&kv);
        if(kv.size() != 2)
        {
            return false;
        }
        _req.SetHeader(kv[0],kv[1]);
        return true;
    }
    bool RevBody(Buffer* buf)
    {
        if(_stage != RevHeader_Over) return false;
        //1.获取正文长度。
        //2.判断数据是否足够读取。
        size_t len = _req.GetBodyLen();
        if(len > 0)
        {
            int rsz = _req._body.size();
            if(buf->Size() + rsz >= len)
            {
                _req._body.append(buf->Read(len - rsz));
                _stage = Over;
                return true;
            }
            else
            {
                _req._body.append(buf->Read());
                return true;
            }
        }
        else if(len == 0)
        {
            _stage = Over;
        }
        return true;
    }
private:
    CxtStau _stage;
    int _rep_stu;
    HttpRequest _req;
    enum{MAX_LEN = 8192};//一行的最大长度。
};
  • 从Buffer中获取除了正文以外的数据都是通过以行为单位进行获取和分析的,因此大于或者小于一行的数据不能过长,应有一个最长的长度,本项目中设置我8192长度,转化为单位为4KB。如果超过了此长度则认为数据非法,返回对应414响应码。
  • 在解析头部时需要将后面的尾巴——"\r\n"处理干净,以及在解析正文时需要根据头部提供的Content-Length获取正文长度,如果Buffer中的缓存区数据不够,将其所有数据放到上下文中,等待数据的到来。

2.4 Server

class HttpServer
{
public:
    using hder_t = std::function<void(const HttpRequest&,HttpResponse*)>;
    using router_t = std::vector<pair<std::string,hder_t>>;
    HttpServer(int port = 8000,int timout = 30):_svr(port)
    {
        _svr.EnRseTimout(timout);
        _svr.SetConCb(std::bind(&HttpServer::SetCxt,this,std::placeholders::_1));
        _svr.SetMsgCb(std::bind(&HttpServer::MsgHder,this,std::placeholders::_1,std::placeholders::_2));
        SetBaseDir();
    }
private:
    //通过ConPtr设置上下文
    void SetCxt(const ConPtr& con)
    {
        con->SetAnyCb(HttpContext());
    }
    //来自连接消息的解析和处理
    void MsgHder(const ConPtr& con,Buffer* buf)
    {
        while(buf->Size())
        {
            //获取上下文
            HttpContext* cxt = con->GetTxt()->get<HttpContext>();
            //接着进行处理获取更加完整的请求
            cxt->Recv_Parse(buf);
            HttpRequest req = cxt->GetRequest();
            HttpResponse rsp(cxt->GetRepStu());            
            //判断
            if(cxt->GetCxtStu() == Error)
            {
                WriteResponse(con,req,&rsp);
                //出错时要清空Buffer,之后不用再发数据了。
                buf->Clear();
                con->ShutDown();
                return;
            }
            else if(cxt->GetCxtStu() != Over)
            {
                return;
            }
            //路由处理
            Route(req,&rsp);
            //组装请求进行发送
            WriteResponse(con,req,&rsp);
            //重置上下文
            cxt->Reset();
            //判断是否要断开连接
            if(req.Close())
            {
                con->ShutDown();
            }
        }
    }
public:
    void SetBaseDir(std::string dir = "./wwwroot")
    {
        _base_dir = dir;
    }
    void SetHander(std::string method,std::string par,const hder_t& hder)
    {
        if(method == "HEAD" || method == "GET")
        {
            _get_rter.push_back({par,hder});
        }
        else if(method == "POST")
        {
            _post_rter.push_back({par,hder});
        }
        else if(method == "PUT")
        {
            _put_rter.push_back({par,hder});
        }
        else if(method == "DELETE")
        {
            _delete_rter.push_back({par,hder});
        }
    }
    void Start(int thread_cnt = 4)
    {
        _svr.Start(thread_cnt);
    }
private:
    //对请求处理方法进行分类
    void Route(HttpRequest& req,HttpResponse* rsp)
    {
        //判断是否是静态资源
        if(IsFileSource(req,rsp))
        {
            return FileHdr(req,rsp);
        }
        //分类进行动态的处理
        std::string method = req._method;
        if(method == "GET" || method == "HEAD")
        {
            return Dispacher(req,rsp,_get_rter);
        }
        else if(method == "PUT")
        {
            return Dispacher(req,rsp,_put_rter);
        }
        else if(method == "POST")
        {
            return Dispacher(req,rsp,_post_rter);
        }
        else if(method == "DELETE")
        {
            return Dispacher(req,rsp,_delete_rter);
        }
        else
        {
            rsp->_stau = 405; //Method Not Allowed
        }
    }
    //静态处理
    void FileHdr(HttpRequest& req,HttpResponse* rsp)
    {
        bool ret = Util::ReadFile(req._source_dir,&(rsp->_body));
        if(ret == true)
        {
            rsp->SetHeader("Content-Type",Util::Mine(req._source_dir));
        }
        else
        {
            ERROR_LOG("read file failed!!");
        }
    }
    bool IsFileSource(HttpRequest& req,HttpResponse* rsp)
    {
        std::string& sor_dir = req._source_dir;
        if(_base_dir != "" && (req._method == "GET" || req._method == "HEAD")&& Util::Valid(sor_dir))
        {
            std::string path = _base_dir + sor_dir;
            if(sor_dir == "/")
            {
                path += "index.html"; 
            }
            if(Util::IsRegular(path))
            {
                sor_dir = path;
                return true;
            } 
        }
        return false;
    }
    //动态处理
    void Dispacher(HttpRequest& req,HttpResponse* rsp,router_t& hders)
    {
        for(auto &kv : hders)
        {
            std::regex regx(kv.first);
            auto& hder = kv.second;
            bool ret = regex_match(req._source_dir,req._sch,regx);
            if(ret == true)
            {
                return hder(req,rsp);
            }
        }
        rsp->_stau = 404;//Not Found
    }
    //填充错误页面
    void ErrorHder(const HttpRequest& req,HttpResponse* rsp)
    {
        std::string filename = _base_dir + "/error.html";
        std::string content;
        Util::ReadFile(filename,&content);
        //错误页面放到响应中
        rsp->SetContent(content);
        return;
    }
    //对请求组织处理形成响应。
    void WriteResponse(const ConPtr& con,HttpRequest& req,HttpResponse* rsp)
    {
        //判断是否出错填充错误页面
        if(rsp->_stau > 400)
        {
            ErrorHder(req,rsp);
        }
        //完善头部字段: Connection Content-Length Content-Type Location
        if(req.Close())
        {
            rsp->SetHeader("Connection","close");
        }
        else
        {
            rsp->SetHeader("Connection","keep-alive");
        }
        if(!rsp->_body.empty())
        {
            if(!rsp->HasHeader("Content-Length"))
            {
                rsp->SetHeader("Content-Length",std::to_string(rsp->_body.size()));
            }
            if(!rsp->HasHeader("Content-Type"))
            {
                rsp->SetHeader("Content-Type","application/octet-stream");
            }
        }
        if(rsp->_is_redir){rsp->SetHeader("Location",rsp->_redir_url);}
        //组装Http协议响应信息

        //版本号 状态码 状态表示\r\n
        std::stringstream stm;
        stm << req._version << " " << rsp->_stau << " " << Util::Statu(rsp->_stau) << "\r\n";
        //头部信息
        for(auto& kv : rsp->_headers)
        {
            stm << kv.first << ": " << kv.second << "\r\n";
        }
        stm << "\r\n";
        //头部信息
        stm << rsp->_body;
        con->Send(stm.str());//不进行实际的发送只打开写事件监控
    }
private:
    router_t _put_rter;
    router_t _post_rter;
    router_t _get_rter;
    router_t _delete_rter;
    TcpServer _svr;
    std::string _base_dir;
};
  • HttpServer是一个最终的综合模块,通过服务器模块封装出来的TcpServer组件完成高效的IO处理,在初始化时将连接初始化和消息处理回调设置到其内部。上层只需关系资源的设置以及业务处理函数的管理。
  • 此模块内部通过设置上下文完成对连接的初始化,通过消息处理函数完成对连接业务地处理,业务处理主要包含静态的业务处理,即通过GET方法获取服务器的资源文件,即文件的读取,也可以通过路由表,即一个vector<string,函数回调>,动态地添加函数回调最终根据资源路径进行动态匹配,将结果放到HttpRequest的smath中,如果匹配成功则执行对应的回调,如果失败则接着继续匹配,直到遍历完为止。

业务处理图解:
在这里插入图片描述

尾序

 如果说上篇文章是包包子的话,那这篇文章就是用Http协议就是将包子包得再紧一些,那么最后就是将包子放到蒸笼里面蒸熟看包子最终的样子是好是坏,即让组件用起来看看怎么样,就要看最终测试环节了,不过这是下篇的内容,我们下篇再续。最后,我是舜华,期待与你的每一次相遇!下篇文章再见了!

标签:std,return,string,req,应用层,模块,const,rsp,Mudo
From: https://blog.csdn.net/Shun_Hua/article/details/141298212

相关文章

  • 基于VSC的MVDC微电网(±10kV)转换器的互连通过等效RL电缆模块实现,此外,在电缆侧引入了
     ......
  • 光模块 -- 光模块类型
    eSFP光模块        eSFP(EnhancedSmallFormFactorPluggableModule):带监控功能的小型化热插拔的低速光模块。监控功能包括:监控发送和接收光功率。        SFP光模块不支持该监控功能,目前较少使用。SFP+/XFP光模块        SFP+(Enhanced8.......
  • Linux系统的Apache2如何启动cgi模块(Ubuntu)
    欢迎诸位来阅读在下的博文~在这里,在下会不定期发表一些浅薄的知识和经验,望诸位能与在下多多交流,共同努力!江山如画,客心如若,欢迎到访,一展风采文章目录环境1.**启用CGI模块**2.**配置CGI脚本目录**3.**配置CGI脚本**4.**测试CGI脚本**5.**访问CGI脚本**6.**调整S......
  • Unity 麦扣 x 勇士传说 全解析 之 怪物基类与野猪(附各模块知识的链接,零基础也包学会的
    通过一阵子的学习,我是这么认为的,因为该教程是难度两星的教程 ,也就是适合学了一阵子基础组件以后的学习者(什么都不会的学习者要是学这套课程会困难重重,如果你什么都不会那么需要学习一星教程)所以该课程没有那么多manger,代码也不那么面向对象,但是从怪物类之后就开始有相关的内......
  • VSCode中解决python模块导入问题
    原因环境变量中不包含当前路径解决方法解决方法有很多种,包括在环境变量文件夹添加pth文件,在导入模块前使用动态添加当前模块的路径到环境变量中。这里介绍一种比较优雅的方案,修改VSCode配置文件在设置中选择“工作区”,搜索terminal.integrated.env在settings.json中编辑......
  • Swift编译加速:模块化策略与实践
    标题:Swift编译加速:模块化策略与实践在Swift语言的生态中,编译速度是衡量开发效率的关键指标之一。Swift编译器的模块化部署策略,作为提升编译速度的重要手段,其核心在于将大型项目分解为更小的、可管理的模块,从而减少编译时需要处理的代码量。本文将深入探讨Swift编译器的模块......
  • 【超详细】Node.js搭建服务器之路由基础与实践并实现模块化
    Node.js路由基础与实践简介在Web开发中,路由是处理客户端请求并将其映射到服务器端资源的一种机制。Node.js提供了灵活的方式来处理HTTP请求,并通过路由来响应不同的URL。环境搭建在开始之前,请确保您的开发环境已经安装了Node.js。接着,创建一个新的项目文件夹,并在其中创建......
  • vault加密模块
    目录vault加密模块ansible-vault简单使用与playbook一起使用vault加密模块Ansible自带的Vault加密功能,通过AES-256加密算法,极为安全,Vault可以将经过加密的密码和敏感数据同Playbook存储在一起ansible-vault简单使用ansible-vault具有简单加解密功能,且对于同一密码同一文......
  • QT设置回调函数给python调用——内置模块法
    1.QT相关函数定义和 QT设置回调函数给python调用——参数法中的定义相同如下://实际的回调函数voidprintValue(intvalue){qDebug()<<"printValuevalue:"<<value;}intgetValue(intvalue){qDebug()<<"getValuevalue:"<<value;......
  • 打包nginx镜像,添加flv_live模块
    因为工作需要而打包一个包含nginx_http_flv_live模块,记录下编译中遇到的问题,和解决方式编译nginx的最大的一个坑就是基础镜像使用的debian:latest,导致各种编译后能通过rtmp推流但是无法通过http拉取http-flv视频流版本alpine==3.8.5nginx==1.25.5https://nginx.org/down......