【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.`构造函数 时,初始化映射

  1. 处理请求时,将映射结果添加到响应报头中
复制代码
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服务优化 - 增加请求后缀、状态码描述、重定向、自动跳转及注册多功能服务 做了一个较为详细的介绍,不知道对你有没有帮助呢

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

相关推荐
Johny_Zhao1 天前
OpenClaw安装部署教程
linux·人工智能·ai·云计算·系统运维·openclaw
chlk1233 天前
Linux文件权限完全图解:读懂 ls -l 和 chmod 755 背后的秘密
linux·操作系统
舒一笑3 天前
Ubuntu系统安装CodeX出现问题
linux·后端
改一下配置文件3 天前
Ubuntu24.04安装NVIDIA驱动完整指南(含Secure Boot解决方案)
linux
不可能的是3 天前
前端 SSE 流式请求三种实现方案全解析
前端·http
深紫色的三北六号3 天前
Linux 服务器磁盘扩容与目录迁移:rsync + bind mount 实现服务无感迁移(无需修改配置)
linux·扩容·服务迁移
SudosuBash3 天前
[CS:APP 3e] 关于对 第 12 章 读/写者的一点思考和题解 (作业 12.19,12.20,12.21)
linux·并发·操作系统(os)
哈基咪怎么可能是AI4 天前
为什么我就想要「线性历史 + Signed Commits」GitHub 却把我当猴耍 🤬🎙️
linux·github
十日十行5 天前
Linux和window共享文件夹
linux
木心月转码ing5 天前
WSL+Cpp开发环境配置
linux