【Linux网络编程】第十四弹---构建功能丰富的HTTP服务器:从状态码处理到服务函数扩展

✨个人主页:熬夜学编程的小林

💗系列专栏: 【C语言详解】 【数据结构详解】【C++详解】【Linux系统编程】【Linux网络编程】

目录

1、状态码

[1.1、 HttpServer类(404)](#1.1、 HttpServer类(404))

1.1.1、基本结构

1.1.2、构造函数

1.1.3、HandlerHttpRequest()

1.1.4、login.html

1.1.5、404.html

1.2、重定向状态码

1.2.1、测试重定向

2、查看HTTP方法

3、增加表单

3.1、POST方法

3.2、GET方法

[3.3、GET vs POST](#3.3、GET vs POST)

4、增加服务函数

4.1、HttpServer类

4.2、InsertService()

4.3、IsServiceExists()

4.4、HandlerHttpRequest()

4.4、ServerMain.cc


1、状态码

最常见的状态码, 比如 200(OK), 404(Not Found), 403(Forbidden), 302(Redirect, 重定向), 504(Bad Gateway)

状态码 含义 应用样例
100 Continue 上传大文件时, 服务器告诉客户端可以继续上传
200 OK 访问网站首页, 服务器返回网页内容
201 Created 发布新文章, 服务器返回文章创建成功的信息
204 No Content 删除文章后, 服务器返回"无内容"表示操作成功
301 Moved Permanently 网站换域名后, 自动跳转到新域名; 搜索引擎更新网站链接时使用
302 Found 或 See Other 用户登录成功后, 重定向到用户首页
304 Not Modified 浏览器缓存机制, 对未修改的资源返回304 状态码
400 Bad Request 填写表单时, 格式不正确导致提交失败
401 Unauthorized 访问需要登录的页面时, 未登录或认证失败
403 Forbidden 尝试访问你没有权限查看的页面
404 Not Found 访问不存在的网页链接
500 Internal Server Error 服务器崩溃或数据库错误导致页面无法加载
502 Bad Gateway 使用代理服务器时, 代理服务器无法从上游服务器获取有效响应
503 Service Unavailable 服务器维护或过载, 暂时无法处理请求

HTTP 的状态码还有对应的描述信息,我们此处也可以使用哈希表存储状态码与描述信息映射关系!

1.1、 HttpServer类(404)

HttpServer类 增加一个状态码转描述信息的哈希表,并在构造函数插入对象的映射关系内容!

1.1.1、基本结构

HttpServer类 增加一个状态码转描述信息的哈希表成员变量!

html 复制代码
class HttpServer
{
private:
    std::unordered_map<std::string, std::string> _mine_type; // 类型对应表
    std::unordered_map<int, std::string> _code_to_desc; // 状态转描述表
};

1.1.2、构造函数

构造函数插入对象的映射关系内容!

html 复制代码
HttpServer()
{
    _mine_type.insert(std::make_pair(".html", "text/html"));
    _mine_type.insert(std::make_pair(".jpg", "image/jpg"));
    _mine_type.insert(std::make_pair(".png", "image/png"));
    _mine_type.insert(std::make_pair(".default", "text/html"));

    _code_to_desc.insert(std::make_pair(100,"Continue"));
    _code_to_desc.insert(std::make_pair(200,"OK"));
    _code_to_desc.insert(std::make_pair(201,"Created"));
    _code_to_desc.insert(std::make_pair(404,"Not Found"));
}

1.1.3、HandlerHttpRequest()

html 复制代码
const static std::string html_404 = "404.html";

std::string HandlerHttpRequest(std::string &reqstr)
{
    std::cout << "---------------------------------------------" << std::endl;
    std::cout << reqstr;
    std::cout << "---------------------------------------------" << std::endl;

    HttpRequest req;         // 构建请求对象
    req.Deserialize(reqstr); // 反序列化字符串

    // 最基础的上层处理
    HttpResponse resp;
    std::string content = GetFileContent(req.Path());

    if (content.empty())
    {
        content = GetFileContent("wwwroot/404.html"); // 在默认错误文件获取内容
        // resp.AddCode(404, "Not Found");
        resp.AddCode(404, _code_to_desc[404]); // 内容为空添加404状态码
        resp.AddHeader("Content-Length", std::to_string(content.size()));
        resp.AddHeader("Content-Type", _mine_type["./html"]); // 类型默认为./html
        resp.AddBodyText(content);
    }
    else
    {
        // resp.AddCode(200, "OK");
        resp.AddCode(200, _code_to_desc[200]);
        resp.AddHeader("Content-Length", std::to_string(content.size()));
        resp.AddHeader("Content-Type", _mine_type[req.Suffix()]);
        resp.AddBodyText(content);
    }

    return resp.Serialize();
}

AddCode()

html 复制代码
void AddCode(int code, const std::string &desc)
{
    _status_code = code;
    _desc = desc;
}

1.1.4、login.html

login.html 文件增加一个测试404页面的链接!

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title>登录页面</title>
</head>
<body>
    <h1>登录页面</h1>
    <a href="/content.html">进入内容页面</a><br>
    <a href="/a/b/c.html">测试404</a>
</body>
</html>

1.1.5、404.html

404.html 为404页面内容!

html 复制代码
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>(404) The page you were looking for doesn't exist.</title>
  <link rel="stylesheet" type="text/css" href="//cloud.typography.com/746852/739588/css/fonts.css" />
  <style type="text/css">
    html,
    body {
      margin: 0;
      padding: 0;
      height: 100%;
    }

    body {
      font-family: "Whitney SSm A", "Whitney SSm B", "Helvetica Neue", Helvetica, Arial, Sans-Serif;
      background-color: #2D72D9;
      color: #fff;
      -moz-font-smoothing: antialiased;
      -webkit-font-smoothing: antialiased;
    }

    .error-container {
      text-align: center;
      height: 100%;
    }

    @media (max-width: 480px) {
      .error-container {
        position: relative;
        top: 50%;
        height: initial;
        -webkit-transform: translateY(-50%);
        -ms-transform: translateY(-50%);
        transform: translateY(-50%);
      }
    }

    .error-container h1 {
      margin: 0;
      font-size: 130px;
      font-weight: 300;
    }

    @media (min-width: 480px) {
      .error-container h1 {
        position: relative;
        top: 50%;
        -webkit-transform: translateY(-50%);
        -ms-transform: translateY(-50%);
        transform: translateY(-50%);
      }
    }

    @media (min-width: 768px) {
      .error-container h1 {
        font-size: 220px;
      }
    }

    .return {
      color: rgba(255, 255, 255, 0.6);
      font-weight: 400;
      letter-spacing: -0.04em;
      margin: 0;
    }

    @media (min-width: 480px) {
      .return {
        position: absolute;
        width: 100%;
        bottom: 30px;
      }
    }

    .return a {
      padding-bottom: 1px;
      color: #fff;
      text-decoration: none;
      border-bottom: 1px solid rgba(255, 255, 255, 0.6);
      -webkit-transition: border-color 0.1s ease-in;
      transition: border-color 0.1s ease-in;
    }

    .return a:hover {
      border-bottom-color: #fff;
    }
  </style>
</head>

<body>

<div class="error-container">
  <h1>404</h1>
  <p class="return">Take me back to <a href="/">designernews.co</a></p>
</div>

</body>
</html>

1.2、重定向状态码

以下是仅包含重定向相关状态码的表格:

状态码 含义 是否为临时重定向 应用样例
301 Moved Permanently 否(永久重定向) 网站换域名后, 自动跳转到新域名;搜索引擎更新网站 链接时使用
302 Found 或 See Other 是(临时重定向) 用户登录成功后,重定向到用户首页
307 Temporary Redirect 是(临时重定向) 临时重定向资源到新的位置(较少使用)
308 Permanent Redirect 否(永久重定向) 永久重定向资源到新的位置(较少使用)

关于重定向的验证,以 301 为代表

HTTP 状态码301(永久重定向)和 302(临时重定向) 都依赖 Location 选项。以下是关于两者依赖 Location 选项的详细说明:
HTTP 状态码 301(永久重定向):

  • 当服务器返回 HTTP 301 状态码 时,表示请求的资源已经被永久移动到新的位置

  • 在这种情况下,服务器会在响应中添加一个 Location 头部,用于指定资源的新位置。这个 Location 头部包含了新的 URL 地址,浏览器会自动重定向到该地址。

  • 例如,在 HTTP 响应中,可能会看到类似于以下的头部信息:

    HTTP/1.1 301 Moved Permanently\r\n
    Location: https://www.new-url.com\r\n

HTTP 状态码 302(临时重定向):

  • 当服务器返回 HTTP 302 状态码时,表示请求的资源临时被移动到新的位置

  • 同样地,服务器也会在响应中添加一个 Location 头部来指定资源的新位置。浏览器会暂时使用新的 URL 进行后续的请求,但不会缓存这个重定向。

  • 例如,在 HTTP 响应中,可能会看到类似于以下的头部信息:

    HTTP/1.1 302 Found\r\n
    Location: https://www.new-url.com\r\n

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

1.2.1、测试重定向

在登录页面增加测试重定向的代码!

login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title>登录页面</title>
</head>
<body>
    <h1>登录页面</h1>
    <a href="/content.html">进入内容页面</a><br>
    <a href="/a/b/c.html">测试404</a><br>
    <a href="/redir">测试重定向</a><br>

</body>
</html>

优化:当我们点击测试重定向的时候,直接进入https://www.qq.com

构造函数需要插入状态码与状态描述

HttpServer()
{
    _mine_type.insert(std::make_pair(".html", "text/html"));
    _mine_type.insert(std::make_pair(".jpg", "image/jpg"));
    _mine_type.insert(std::make_pair(".png", "image/png"));
    _mine_type.insert(std::make_pair(".default", "text/html"));

    _code_to_desc.insert(std::make_pair(100, "Continue"));
    _code_to_desc.insert(std::make_pair(200, "OK"));
    _code_to_desc.insert(std::make_pair(201, "Created"));
    _code_to_desc.insert(std::make_pair(301, "Moved Permanently"));
    _code_to_desc.insert(std::make_pair(302, "Found"));
    _code_to_desc.insert(std::make_pair(404, "Not Found"));
}

HandlerHttpRequest()

std::string HandlerHttpRequest(std::string &reqstr)
{
    HttpRequest req; // 构建请求对象
    HttpResponse resp;
    req.Deserialize(reqstr); // 反序列化字符串
    if (req.Path() == "wwwroot/redir")
    {
        // 重定向处理
        std::string redir_path = "https://www.qq.com"; // 以什么协议去访问
        resp.AddCode(302, _code_to_desc[302]);
        resp.AddHeader("Location", redir_path);
    }
    else
    {
        // 处理静态资源
        std::string content = GetFileContent(req.Path());
        if (content.empty())
        {
            content = GetFileContent("wwwroot/404.html"); // 在默认错误文件获取内容
            resp.AddCode(404, _code_to_desc[404]); // 内容为空添加404状态码
            resp.AddHeader("Content-Length", std::to_string(content.size()));
            resp.AddHeader("Content-Type", _mine_type["./html"]); // 类型默认为./html
            resp.AddBodyText(content);
        }
        else
        {
            // resp.AddCode(200, "OK");
            resp.AddCode(200, _code_to_desc[200]);
            resp.AddHeader("Content-Length", std::to_string(content.size()));
            resp.AddHeader("Content-Type", _mine_type[req.Suffix()]);
            resp.AddBodyText(content);
        }
    }
    return resp.Serialize();
}

2、查看HTTP方法

在Request类增加获取方法的成员函数即可!

std::string Method()
{
    LOG(DEBUG, "Client request method is %s\n", _method.c_str());
    return _method;
}

调用方法:

req.Method();

注意:此处可以使用Postman软件!

3、增加表单

在登录页面我们可以增加表单

3.1、POST方法

POST方法增加表单,参数内容是存放在正文(参数以问号分割),因此我们需要继续解析请求行!

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title>登录页面</title>
</head>

<body>
    <h1>登录页面</h1>
    <a href="/content.html">进入内容页面</a><br>
    <a href="/a/b/c.html">测试404</a><br>
    <a href="/redir">测试重定向</a><br>

    <div>
        <form action="/login" method="post">
            用户名: <input type="text" name="username" value="."><br>
            密码: <input type="password" name="userpasswd" value=""><br>
            <input type="submit" value="提交">
        </form>
    </div>

</body>

</html>

解析请求行

const static std::string arg_sep = "?";

// 解析请求行
void ParseReqLine()
{
    std::stringstream ss(_req_line); // 以空格为分隔符 cin >>
    // /a/b/c.html or /login?user=xxx&passwd=1234 /register
    ss >> _method >> _url >> _version; // 以空格为分隔符依次将请求行的内容赋值给成员变量
    
    // 以GET方式请求资源,则将参数内容存放到正文
    if (strcasecmp(_method.c_str(), "GET") == 0)
    {
        auto pos = _url.find(arg_sep);
        if (pos != std::string::npos)
        {
            _body_text = _url.substr(pos + arg_sep.size());
            _url.resize(pos); // url 只取参数前的内容
        }
    }


    _path += _url;
    // 只有web根目录返回index.html
    if (_path[_path.size() - 1] == '/')
    {
        _path += homepage;
    }

    // wwwroot/index.html
    // wwwroot/image/1.png
    auto pos = _path.rfind(suffixsep);
    if (pos != std::string::npos)
    {
        _suffix = _path.substr(pos);
    }
    else
    {
        _suffix = ".default";
    }
}

3.2、GET方法

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title>登录页面</title>
</head>

<body>
    <h1>登录页面</h1>
    <a href="/content.html">进入内容页面</a><br>
    <a href="/a/b/c.html">测试404</a><br>
    <a href="/redir">测试重定向</a><br>

    <div>
        <form action="/login" method="get">
            用户名: <input type="text" name="username" value="."><br>
            密码: <input type="password" name="userpasswd" value=""><br>
            <input type="submit" value="提交">
        </form>
    </div>

</body>

</html>

3.3、GET vs POST

1、GET一般用来获取静态资源,也可以通过url向服务器传递参数

2、POST 可以通过http request的正文来进行参数传递

3、url 传递参数,参数的体量一定不大,正文可以很大

4、POST方法比GET方法传参更加私密,但是都不安全!对http的参数进行加密 -> https协议!

4、增加服务函数

前面获取的都是静态资源,我们也可以通过函数获取其他资源,此处就用到回调函数!

4.1、HttpServer类

要执行回调函数,需要在HttpServer类增加一个服务列表 ,用于存储需要执行的服务,在HandlerHttpRequest()函数中处理服务,此处只测试一个服务!

// 回调函数声明
using func_t = std::function<HttpResponse(HttpRequest&)>;

class HttpServer
{
private:
    std::unordered_map<std::string, std::string> _mine_type; // 类型对应表
    std::unordered_map<int, std::string> _code_to_desc;      // 状态转描述表
    std::unordered_map<std::string, func_t> _service_list;   // 服务列表
};

4.2、InsertService()

InsertService() 函数插入需要执行的方法!

void InsertService(const std::string &servicename, func_t f)
{
    std::string s = prefixpath + servicename;
    _service_list[s] = f;
}

4.3、IsServiceExists()

IsServiceExists() 判断服务列表中是否存在服务,存在才需要处理!

bool IsServiceExists(const std::string servicename)
{
    auto iter = _service_list.find(servicename);
    if(iter == _service_list.end()) return false;
    else return true;
}

4.4、HandlerHttpRequest()

HandlerHttpRequest()函数处理请求,讨论三种情况,重定向情况,调用服务情况以及处理静态资源情况

std::string HandlerHttpRequest(std::string &reqstr)
{
    std::cout << "---------------------------------------------" << std::endl;
    std::cout << reqstr;
    std::cout << "---------------------------------------------" << std::endl;

    HttpRequest req; // 构建请求对象
    HttpResponse resp;
    req.Deserialize(reqstr); // 反序列化字符串

    if (req.Path() == "wwwroot/redir")
    {
        // 重定向处理
        std::string redir_path = "https://www.qq.com"; // 以什么协议去访问
        resp.AddCode(302, _code_to_desc[302]);
        resp.AddHeader("Location", redir_path);
    }
    // 最后服务新增,有参数需要请求服务
    else if(!req.GetRequestBody().empty())
    {
        if(IsServiceExists(req.Path()))
        {
            resp = _service_list[req.Path()](req);
        }
    }
    else
    {
        // 处理静态资源
        std::string content = GetFileContent(req.Path());
        if (content.empty())
        {
            content = GetFileContent("wwwroot/404.html"); // 在默认错误文件获取内容
            // resp.AddCode(404, "Not Found");
            resp.AddCode(404, _code_to_desc[404]); // 内容为空添加404状态码
            resp.AddHeader("Content-Length", std::to_string(content.size()));
            resp.AddHeader("Content-Type", _mine_type["./html"]); // 类型默认为./html
            resp.AddBodyText(content);
        }
        else
        {
            // resp.AddCode(200, "OK");
            resp.AddCode(200, _code_to_desc[200]);
            resp.AddHeader("Content-Length", std::to_string(content.size()));
            resp.AddHeader("Content-Type", _mine_type[req.Suffix()]);
            resp.AddBodyText(content);
        }
    }
    return resp.Serialize();
}

4.4、ServerMain.cc

ServerMain.cc文件实现服务端主函数!

#include "TcpServer.hpp" // 会话层
#include "Http.hpp"

HttpResponse Login(HttpRequest &req)
{
    HttpResponse resp;
    std::cout << "外部已经拿到了参数了: " << std::endl;
    req.GetRequestBody();
    std::cout << "####################" << std::endl;
    resp.AddCode(200,"OK");
    resp.AddBodyText("<html><h1>result done</h1></html>");

    // username=hahahahah&userpasswd=123456

    // 1.pipe
    // 2.dup2
    // 3.fork()
    // 4.exec* -> Python,PHP,Java!业务处理

    return resp;
}
// ./httpserver 8888
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        std::cerr << "Usage: " << argv[0] << "local-port" << std::endl;
        exit(0);
    }

    uint16_t port = std::stoi(argv[1]);

    HttpServer hserver;
    hserver.InsertService("/login",Login);
    // hserver.InsertService("/register",Register);
    // hserver.InsertService("/Search",Search);


    std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(
        std::bind(&HttpServer::HandlerHttpRequest, &hserver, std::placeholders::_1),
        port);
    tsvr->Loop();

    return 0;
}
相关推荐
PyAIGCMaster7 分钟前
ubuntu装P104驱动
linux·运维·ubuntu
奈何不吃鱼8 分钟前
【Linux】ubuntu依赖安装的各种问题汇总
linux·运维·服务器
icy、泡芙10 分钟前
T527-----音频调试
linux·驱动开发·音视频
aherhuo13 分钟前
kubevirt网络
linux·云原生·容器·kubernetes
爱码小白13 分钟前
网络编程(王铭东老师)笔记
服务器·网络·笔记
CYBEREXP200816 分钟前
MacOS M3源代码编译Qt6.8.1
c++·qt·macos
zzzhpzhpzzz22 分钟前
Ubuntu如何查看硬件型号
linux·运维·ubuntu
蜜獾云24 分钟前
linux firewalld 命令详解
linux·运维·服务器·网络·windows·网络安全·firewalld
陌北v126 分钟前
Docker Compose 配置指南
运维·docker·容器·docker-compose
柒烨带你飞35 分钟前
路由器转发数据报的封装过程
网络·智能路由器