【Linux网络】Http服务优化 - 增加请求后缀、状态码描述、重定向、自动跳转及注册多功能服务

📢博客主页:https://blog.csdn.net/2301_779549673
📢博客仓库:https://gitee.com/JohnKingW/linux_test/tree/master/lesson
📢欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正!
📢本文由 JohnKi 原创,首发于 CSDN🙉
📢未来很长,值得我们全力奔赴更美好的生活✨

在这里插入图片描述

在这里插入图片描述


🏳️‍🌈一、增加请求后缀

我们在浏览器上访问我们自己的服务端时,会遇到客户端发送来的请求,想要访问 1.jpg 或者 default.html 因此我们可以将这个后缀给整理一下,通过日志打印告诉我们自己目标想要访问的资源在当前的哪里。

1.1 HttpRequest 类

我们在这个类里进行如下操作

  1. 添加成员变量 _suffix
  2. 在请求行解析方法中,增添一段,通过后缀分隔符 "." ,来找到我们的后缀,没有找到就返回默认后缀
  3. 添加函数方法,获取当前请求的后缀名
const static std::string _suffixsep = ".";                      // 后缀分隔符    

class HttpRequest {
private:
    // 解析请求行
    void PraseReqLine() {
        // 以空格为分隔符,不断读取
        std::stringstream ss(_req_line);
        ss >> _method >> _url >> _version;

        _path += _url;
        // 处理url,如果是根目录,则返回默认路径
        if (_url == "/")
            _path += _default_path;

        // 获取后缀
        auto pos = _path.rfind(_suffixsep);
        if (pos == std::string::npos)
            _suffix = ".default";
        else
            _suffix = _path.substr(pos);
    }

public:
    std::string Suffix() {
        LOG(LogLevel::INFO) << "client want suffix : " << _suffix;
        return _suffix;
    }
}

1.2 HttpHandler 类

这里我们需要先知道一个概念 - MIMIE

MIME 是一种 ​互联网标准,最初设计用于扩展电子邮件协议(如 SMTP),使其能传输非文本数据(如图片、音频)。后被 HTTP 协议广泛采用,用于标识网络资源的 ​数据类型。

  • .html → text/html(HTML 文档)
  • .jpg → image/jpeg(JPEG 图片)
  • .json → application/json(JSON 数据)

这个类中我们需要增加一个后缀映射,并将其添加在返回报文的报头列表中

1, 增加成员变量 后缀后缀存储 的映射
2.`构造函数 时,初始化映射
3. 处理请求时,将映射结果添加到响应报头中

class HttpHandler {
public:
    HttpHandler() {
        _mime_type.insert(std::make_pair(".html", "text/html"));
        _mime_type.insert(std::make_pair(".jpg", "image/jpg"));
        _mime_type.insert(std::make_pair(".png", "image/png"));
        _mime_type.insert(std::make_pair(".default", "text/html"));
    }
    std::string HandleRequest(std::string req) {
        std::cout << "------------------------------------" << std::endl;
        std::cout << req;

        HttpRequest req_obj;
        req_obj.Descrialize(req);

        std::string content = GetFileContent(req_obj.Path());
        if (content.empty())
            return std::string();

        HttpResponse rsp;
        rsp.AddCode(200);
        rsp.AddHeader("Content-Length", std::to_string(content.size()));
        rsp.AddHeader("Content-type", _mime_type[req_obj.Suffix()]);
        rsp.AddBodyText(content);

        return rsp.Serialize();
    }
private:
    std::unordered_map<std::string, std::string> _mime_type;
};

🏳️‍🌈二、状态码描述 及 自动跳转404

前面的文章中我们已经知道了状态码描述的存在,但是没有可利用过,这里再介绍一下这个状态码,添加一个状态码的映射并且根据访问内容,去判断要不要显示404界面

2.1 状态码描述

这里需要进行两方面的更改,一个是 HttpResponse一个是 HttpHandler 的构造和成员变量

HttpHandler 中我们添加 状态码状态码描述映射,然后在构造中表示出来

class HttpHandler {
public:
    HttpHandler() {
        _mime_type.insert(std::make_pair(".html", "text/html")); // HTML 类型
        _mime_type.insert(std::make_pair(".jpg", "image/jpeg")); // JPEG 图片
        _mime_type.insert(std::make_pair(".png", "image/png"));  // PNG 图片
        _mime_type.insert(std::make_pair(".default", "text/html")); // 默认文本类型

        _status_code_desc.insert(std::make_pair(100, "Continue"));
        _status_code_desc.insert(std::make_pair(200, "OK"));
        _status_code_desc.insert(std::make_pair(201, "Created"));
        _status_code_desc.insert(std::make_pair(404, "Not Found"));
    }
private:
    std::unordered_map<int, std::string> _status_code_desc;
};

HttpResponse 中的 AddCode 方法,我们之前默认是不论什么都是 OK,现在我们对其进行专属化处理

// 添加 状态码 和 状态码描述
void AddCode(int code, std::string desc) {
    _status_code = code;
    _desc = desc;
}

2.2 自动跳转404

我们在 HttpHandlerHandleRequest 方法中,当判定访问的路径内容为空时,就将这个路径改成 404.html

std::string HandleRequest(std::string req) {
    std::cout << "------------------------------------" << std::endl;
    std::cout << req;

    HttpRequest req_obj;
    req_obj.Descrialize(req);

    std::string content = GetFileContent(req_obj.Path());
    HttpResponse rsp;

    if (content.empty()) {
        content = GetFileContent("wwwroot/404.html");
        rsp.AddCode(404, _status_code_desc[404]);
        rsp.AddHeader("Content-Length", std::to_string(content.size()));
        rsp.AddHeader("Content-type", _mime_type[".default"]);
        rsp.AddBodyText(content);
    } else {
        rsp.AddCode(200, _status_code_desc[200]);
        rsp.AddHeader("Content-Length", std::to_string(content.size()));
        rsp.AddHeader("Content-type", _mime_type[req_obj.Suffix()]);
        rsp.AddBodyText(content);
    }

    return rsp.Serialize();
}

🏳️‍🌈三、重定向状态码

在这里插入图片描述

HTTP 状态码 301(永久重定向)和 302(临时重定向)都依赖 Location 选项。

无论是 HTTP 301 还是 HTTP 302 重定向,都需要依赖 Location 选项来指定资源的新位置。这个 Location 选项是一个标准的 HTTP 响应头部,用于告诉浏览器应该将请求重定向到哪个新的 URL 地址。

我们现在 default.html 中添加 测试重定向 的选项

在这里插入图片描述

我们测试永久重定向,将当前的网址重定向到 qq.com

std::string HandleRequest(std::string req) {
    std::cout << "------------------------------------" << std::endl;
    std::cout << req;

    HttpRequest req_obj;
    req_obj.Descrialize(req);
    HttpResponse rsp;
    if (req_obj.Url() == "/redirect") {
        // 重定向处理
        std::string redirect_path = "https://www.qq.com";
        rsp.AddCode(302, _status_code_desc[302]);
        rsp.AddHeader("Location", redirect_path);
        rsp.AddHeader("Content-Type", "text/plain"); // 添加 Content-Type
        rsp.AddHeader("Content-Length", "0");        // 显式设置内容长度为 0
        rsp.AddBodyText("");                         // 确保响应体为空
    } else {
        std::string content = GetFileContent(req_obj.Path());

        if (content.empty()) {
            content = GetFileContent("wwwroot/404.html");
            rsp.AddCode(404, _status_code_desc[404]);
            rsp.AddHeader("Content-Length", std::to_string(content.size()));
            rsp.AddHeader("Content-type", _mime_type[".default"]);
            rsp.AddBodyText(content);
        } else {
            rsp.AddCode(200, _status_code_desc[200]);
            rsp.AddHeader("Content-Length", std::to_string(content.size()));
            rsp.AddHeader("Content-type", _mime_type[req_obj.Suffix()]);
            rsp.AddBodyText(content);
        }
    }
    return rsp.Serialize();
}

在这里插入图片描述

🏳️‍🌈四、注册等多功能服务

因为存在两种主要的提交方式,分别是 POSTGET,因此参数的位置会不一样,

GET 下,位于 网址 的 后面

在这里插入图片描述

POST 下,位于请求正文中

所以我们要根据不同的情况,分出响应的 参数,以及路径

4.1 HttpRequest 类

需要改的主要有三部分

  1. 增加新的成员变量_args 用来记录参数,_isexcute 用来记录是否有参数
  2. 构造函数中给 _isexcute 默认为false
  3. 更改 Descrialize 细节,使其区分 GETPOST,并且能够准确提取出 _args_path
class HttpRequest{
        private:
        public:
            HttpRequest() : _blank_line(_base_sep), _path(_prefix_path), _isexcute(false) {}

            void Descrialize(std::string& reqstr){
                // 基本的反序列化
                _req_line = GetLine(reqstr);    // 读取第一行请求行
                // 请求报头
                std::string header;
                do{
                    header = GetLine(reqstr);
                    // 如果既不是空,也不是空行,就是请求报头,加入到请求报头列表中
                    if(header.empty()) break;
                    else if(header == _base_sep) break;
                    _req_headers.push_back(header);
                }while(true);

                // 正文
                if(!reqstr.empty())
                    _req_body = reqstr;

                // 进一步反序列化请求行
                PraseReqLine();
                // 分割请求报头,获取键值对
                PraseHeader(); 

                // 判断是否需要动态执行
                if(_method == "POST"){
                    _isexcute = true;
                    _args = _req_body;
                    LOG(LogLevel::INFO) << "POST _path : " << _path;
                    LOG(LogLevel::INFO) << "POST _args : " << _args;
                } else if(_method == "GET"){
                    auto pos = _path.find("?");
                    if(pos != std::string::npos){
                        _isexcute = true;
                        _args = _path.substr(pos + 1);
                        _path = _path.substr(0, pos);
                        LOG(LogLevel::INFO) << "GET _path : " << _path;
                        LOG(LogLevel::INFO) << "GET _args : " << _args;
                    }
                }
            }
            std::string Args(){
                LOG(LogLevel::INFO) << "client want _args : " << _args;  
                return _args;
            }
            bool Isexecute(){
                LOG(LogLevel::INFO) << "client want _isexcute : " << _isexcute;  
                return _isexcute;
            }
        private:
            bool _isexcute;                              // 是否需要动态执行
            std::string _args;                           // 动态执行的参数
    };

4.2 HttpHandler 类

增加功能路由表也就是映射目标路径和方法unoredered_map。同时也要构建能够处理相应操作的回调函数类型

using http_handler_t = std::function<HttpResponse(HttpRequest&)>;
std::unordered_map<std::string, http_handler_t> _route; // 功能路由表

增加注册服务的相关功能

	// 注册服务功能
    void RegisterHandler(std::string funcname, http_handler_t service) {
        std::string name = _prefix_path + funcname;
        _route.insert(std::make_pair(name, service));
    }

    // 判断是否存在该功能
    bool HasHandler(std::string funcname) {
        auto iter = _route.find(funcname);
        if (iter == _route.end())
            return false;
        else
            return true;
    }

将http请求处理主要分为三块

  1. 重定向处理
  2. 动态操作处理
  3. 静态页面变化处理
std::string HandleRequest(std::string req) {
    std::cout << "------------------------------------" << std::endl;
    std::cout << req;

    HttpRequest req_obj;
    req_obj.Descrialize(req);
    HttpResponse rsp;
    if (req_obj.Url() == "/redirect") {
        LOG(LogLevel::DEBUG) << "重定向服务";
        // 重定向处理
        std::string redirect_path = "https://www.qq.com";
        rsp.AddCode(302, _status_code_desc[302]);
        rsp.AddHeader("Location", redirect_path);
        rsp.AddHeader("Content-Type", "text/plain"); // 添加 Content-Type
        rsp.AddHeader("Content-Length", "0");        // 显式设置内容长度为 0
        rsp.AddBodyText("");                         // 确保响应体为空
    } else if (req_obj.Isexecute()) {
        LOG(LogLevel::DEBUG) << "注册服务";
        if (HasHandler(req_obj.Path())) {
            LOG(LogLevel::DEBUG) << "找到注册服务";
            rsp = _route[req_obj.Path()](req_obj);
        } else {
            LOG(LogLevel::DEBUG) << "没有找到注册服务";
            std::string content = GetFileContent("wwwroot/404.html");
            rsp.AddCode(404, _status_code_desc[404]);
            rsp.AddHeader("Content-Length", std::to_string(content.size()));
            rsp.AddHeader("Content-type", _mime_type[".default"]);
            rsp.AddBodyText(content);
        }
    } else {
        LOG(LogLevel::DEBUG) << "静态页面服务";
        std::string content = GetFileContent(req_obj.Path());
        if (content.empty()) {
            content = GetFileContent("wwwroot/404.html");
            rsp.AddCode(404, _status_code_desc[404]);
            rsp.AddHeader("Content-Length", std::to_string(content.size()));
            rsp.AddHeader("Content-type", _mime_type[".default"]);
            rsp.AddBodyText(content);
        } else {
            rsp.AddCode(200, _status_code_desc[200]);
            rsp.AddHeader("Content-Length", std::to_string(content.size()));
            rsp.AddHeader("Content-type", _mime_type[req_obj.Suffix()]);
            rsp.AddBodyText(content);
        }
    }
    return rsp.Serialize();
}

4.3 TcpServer.cpp

这里我们需要将注册服务具体化,并添加到 功能路由表

现实生活中,我们还需要对这个传进来的参数(用户名、密码等)进行序列化,到数据库中查找等操作,这里就不拓展了,简简单单地使用 success.html 界面表示我们登录成功就行了

HttpResponse Login(HttpRequest& req){
    HttpResponse rsp;
    LOG(LogLevel::INFO) << "进入登录模块" << req.Path() << ", " << req.Args();
    std::string req_args = req.Args();

    // 1. 解析参数格式,得到要的参数
    // 2. 访问数据库,验证对应的用户是否是合法用户,以及...

    // 3. 登录成功
    HttpHandler httphandler;
    std::string content = httphandler.GetFileContent("wwwroot/success.html");
    rsp.AddCode(200, "OK");
    rsp.AddHeader("Content-Length", std::to_string(content.size()));
    rsp.AddHeader("Content-Type", "text/html");
    rsp.AddHeader("Set-Cokkie", "req_args");
    rsp.AddBodyText(content);

    return rsp;
}

4.4 测试

当 ·login 方法是 GET
在这里插入图片描述

在这里插入图片描述

当是 POST
在这里插入图片描述

在这里插入图片描述


👥总结

本篇博文对 【Linux网络】Http服务优化 - 增加请求后缀、状态码描述、重定向、自动跳转及注册多功能服务 做了一个较为详细的介绍,不知道对你有没有帮助呢

觉得博主写得还不错的三连支持下吧!会继续努力的~

评论 37
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值