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

news2024/12/24 21:06:25

文章目录

  • 前言
  • 正文
    • 1. Util
      • 1.1 File
      • 1.2 Url
      • 1.3 Str
      • 1.4 Infor
    • 2. Http
      • 2.1 Request
      • 2.2 Response
      • 2.3 Context
      • 2.4 Server
  • 尾序

前言

在上一篇文章当中,博主从代码的层面介绍了服务器模块的实现,最终封装出了一个传输层的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协议就是将包子包得再紧一些,那么最后就是将包子放到蒸笼里面蒸熟看包子最终的样子是好是坏,即让组件用起来看看怎么样,就要看最终测试环节了,不过这是下篇的内容,我们下篇再续。最后,我是舜华,期待与你的每一次相遇!下篇文章再见了!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2051300.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

中国篆刻艺术孙溟㠭凿木《未来之门》

孙溟㠭凿木《未来之门》 每人都有着自己对未来的期盼&#xff0c;未来并不遥远&#xff0c;下一秒就是未来&#xff0c;当下的一切好和一切的恶是暂时的&#xff0c;都会随着时间一秒一秒过去&#xff0c;走向未来&#xff0c;希望每人都能尽早打开未来之门&#xff0c;到达自己…

Arduino开源四足蜘蛛机器人制作教程

视频教程&#xff1a;手把手叫你做四足蜘蛛机器人——1零件介绍_哔哩哔哩_bilibili 一、项目介绍 1.1 项目介绍 Arduino主控&#xff0c;图形化编程&#xff0c;趣味学习 Arduino nano开发板舵机扩展底板 4.8V可充电电池&#xff0c;支持Arduino C语言编程和米思齐图形化编程…

Linux os下借助Qt+libvlc是实现多路拉取摄像头rtsp数据流并实时显示

前言 应客户方的一个实际项目需求&#xff0c;需要在Linux操作系统下拉取多路摄像头的RTSP数据流并实时显示。 该项目的硬件平台基于飞腾2000四核处理器与景嘉微显卡&#xff0c;搭载了Kylin V10操作系统。 当前景嘉微GPU最多支持同时连接16路摄像头&#xff0c;拉取1920x108…

C++中二叉搜索树的底层原理及实现

小编在学习完二叉搜索树(SearchBinaryTree)之后觉得虽然二叉搜索树不是很难&#xff0c;但是它对于后面学习C中的AVL树和红黑树及map和set的封装都有重要的作用&#xff0c;因此小编今天带给大家二叉搜索树的原理及实现&#xff0c;话不多说&#xff0c;开始学习&#xff01;~~…

<Linux> git

在使用git之前&#xff0c;要先在linux中安装git yum list | grep git yum install -y 文件名 在第一次安装git时&#xff0c;需要进行下面的操作 git config --global user.email "你的邮箱名" git config --global user.name "你想要的名字" 1. git clon…

LeetCode 热题 HOT 100 (038/100)【宇宙最简单版】

【动态规划】No. 0337 打家劫舍III【中等】&#x1f449;力扣对应题目指路 希望对你有帮助呀&#xff01;&#xff01;&#x1f49c;&#x1f49c; 如有更好理解的思路&#xff0c;欢迎大家留言补充 ~ 一起加油叭 &#x1f4a6; 欢迎关注、订阅专栏 【力扣详解】谢谢你的支持&a…

NGINX 常用内置变量

目录 $remote_addr 变量 $args 变量 $is_args 变量 $document_root 变量 $document_uri 变量 $host 变量 $limit_rate 变量 $remote_port 变量 $remote_port --显示客户端端口 $request_method 变量 --返回请求方式 $request_filename 变量 --返回请求实际路径 $request_uri…

eNSP 华为ACL配置

华为ACL配置 需求&#xff1a;公司保证财务部数据安全&#xff0c;禁止研发部门和互联网访问财务服务器&#xff0c;但总裁办不受影响 R1&#xff1a; <Huawei>sys [Huawei]sys Router1 [Router1]undo info-center enable [Router1]int g1/0/0 [Router1-GigabitEthern…

AI 工程应用 建筑表面检测及修复

文章目录 1 项目概述&#xff08;必写&#xff09;&#xff1a;2 技术方案与实施步骤2.1 模型选择&#xff08;必写&#xff09;&#xff1a;2.2 数据的构建&#xff1a;2.3 功能整合&#xff08;进阶&#xff09;&#xff1a; 3 实施步骤&#xff1a;3.1 环境搭建&#xff08;…

【Nginx】nginx的核心配置

1.nginx的文件启动 [rootNginx ~]# vim /lib/systemd/system/nginx.service [Unit] DescriptionThe NGINX HTTP and reverse proxy server Aftersyslog.target network-online.target remote-fs.target nss-lookup.target Wantsnetwork-online.target [Service] Typeforking P…

Python -- GUI图形界面编程—GUI编程实例 博主也在持续学习中[ 持续更新中!!! 欢迎白嫖 也求粉啊啊啊~ ]

本文介绍了GUI的图形界面编程&#xff08;相关视频是哔站上的应该搜这个题目就能找到&#xff09;&#xff0c;文章还是很基础的&#xff0c;反正我是小白从0开始&#xff0c;主要的结构tinkter库、重要组件简介&#xff08;这个不用死记硬背 用的时候再说&#xff09;、Label&…

诊断知识:DTC Status中pending位的使用

文章目录 前言OCC6的定义pending位的定义pending位的使用总结 前言 上一篇文章介绍了ConfirmedDTCLimit的使用&#xff0c;诊断知识&#xff1a;ConfirmedDTCLimit的使用&#xff0c;后面发现理解还是有问题的&#xff0c;其实原来的图画的没有问题&#xff0c;之前对OCC6理解…

【业余玩儿AI】Day 1

【业余玩儿AI】Day 1 实际是昨天的事儿了&#xff0c;记录以下 魔法 不管三七二十一&#xff0c;重新启用魔法&#xff0c;没有魔法这些事情肯定是不行滴 种子任务 把收藏了两个星期的短视频都看了一遍&#xff0c;挑了个种子任务&#xff0c;《本地部署Llama3.1》&#x…

【Web IDE】WebContainer容器在浏览器中启动运行nodejs并使用vite启动项目

参考了文章WebContainer/api 基础&#xff08;Web IDE 技术探索 一&#xff09; 在浏览器中运行vite的vue3项目 示例站点 最终效果 主要流程 加载WebContainer》加载代码压缩包>解压代码压缩包》生成文件树》挂载文件树》pnpm安装依赖》启动项目 代码 <script setup…

Unity动画模块 之 3D模型导入基础设置Model页签

本文仅作笔记学习和分享&#xff0c;不用做任何商业用途 本文包括但不限于unity官方手册&#xff0c;unity唐老狮等教程知识&#xff0c;如有不足还请斧正​ 创建模型&#xff1a;在 Unity 外部创建模型 - Unity 手册 导入模型&#xff1a;将模型导入 Unity - Unity 手册 1.…

算法的学习笔记—二叉树的镜像(牛客JZ27)

&#x1f600;前言 在二叉树相关的问题中&#xff0c;镜像操作是一个非常经典且常见的题目。本文将通过一道具体的题目&#xff0c;详细讲解如何将一棵二叉树转换为它的镜像&#xff0c;并提供实现该操作的Java代码示例。 &#x1f3e0;个人主页&#xff1a;尘觉主页 文章目录 …

【LVGL9学习笔记-2.添加百问网demo至工程模板】

添加百问网demo至工程模板 上一节使用codeBlocks运行了LVGL ,以此作为模板&#xff0c;将百问网的一些demo添加至该工程中 拷贝文件与配置文件至该目录下 打开工程包含文件 –>add files recusively –>add files 编译一次出现如下情况&#xff08;后面学到了再做解决…

[机器学习]全景指南:从基础概念到实战流程的全面解析

文章目录 1.引言1.1机器学习的重要性1.2机器学习的应用范围1.3本文的内容结构 2. 机器学习的基本概念与分类2.1 机器学习的定义2.2 机器学习的分类 4. 强化学习&#xff08;Reinforcement Learning&#xff09; 3. 机器学习的工作流程3.1 数据收集与准备1. 数据源与类型2. 数据…

Windows SDK 消息类型详解

消息结构体 如下是消息的结构体 typedef struct tagMSG {HWND hwnd; // 消息所属窗口的句柄UINT message; // 消息的标识符&#xff0c;表示什么类型的消息&#xff0c;如WM_PAINT、WM_QUIT等。WPARAM wParam; // 与消息相关的附加信息。具体含义取决于消息的类型。L…

模型驱动设计(MODEL-DRIVEN DESIGN)

前言 为了保证软件实现得简洁并且与模型保持一致&#xff0c;不管实际情况如何复杂&#xff0c;必须运用建模和设计的最佳实践。 本书中的软件设计风格主要遵循"职责驱动设计"的原则&#xff0c;这个原则是Wirfs-Brock等人在1990年中提出的&#xff0c;并在2003年进…