【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;
}
相关推荐
mozun20202 小时前
VS BUG(6) LINK : fatal error LNK1158: 无法运行“rc.exe”
c++·bug·vs·链接器·资源文件
夜夜敲码2 小时前
C语言教程(十八):C 语言共用体详解
c语言·开发语言
一只很酸de橘子2 小时前
关于https请求丢字符串导致收到报文解密失败问题
网络协议·http·https
潘yi.3 小时前
web技术与nginx网站环境部署
服务器·网络·nginx
安顾里3 小时前
Linux命令-iostat
linux·运维·服务器
whoarethenext3 小时前
初始https附带c/c++源码使用curl库调用
服务器·c++·qt·https·curl
100编程朱老师4 小时前
面试:什么叫Linux多路复用 ?
linux·运维·服务器
群联云防护小杜4 小时前
云服务器主动防御策略与自动化防护(下)
运维·服务器·分布式·安全·自动化·音视频
PPIO派欧云4 小时前
PPIO X OWL:一键开启任务自动化的高效革命
运维·人工智能·自动化·github·api·教程·ppio派欧云
Jtti4 小时前
Jtti:nginx服务器如何限制访问频率
服务器·网络·nginx