【Linux网络编程】第十三弹---构建HTTP响应与请求处理系统:从HttpResponse到HttpServer的实战

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

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

目录

1、HttpResponse类

1.1、基本结构

1.2、构造析构函数

1.3、添加属性成员函数

1.4、序列化函数

2、HandlerHttpRequest()

2.1、GetFileContent()

3、index.html

3.1、版本一

3.2、版本二

3.3、版本三

4、Content-Type

4.1、HttpRequest类

4.1.1、基本结构

4.1.2、ParseReqLine()

4.1.3、Suffix()

4.2、HttpServer类

4.2.1、基本结构

4.2.2、构造函数

4.2.3、HandlerHttpRequest()


1、HttpResponse类

1.1、基本结构

HttpResponse类成员变量包括基本格式状态行,应答报头,空行和响应正文;还包括基本属性版本,状态码,状态码描述,以及报头的KV结构成员函数包括增加状态码,报头和正文

class HttpResponse
{
public:
    HttpResponse();
    // 添加状态码
    void AddCode(int code);
    void AddHeader(const std::string &k, const std::string &v);
    void AddBodyText(const std::string &body_text);
    std::string Serialize();
    ~HttpResponse();

private:
    // httpresponse base 属性
    std::string _version; // 版本
    int _status_code;     // 状态码
    std::string _desc;    // 状态码描述
    std::unordered_map<std::string, std::string> _headers_kv;

    // 基本的httpresponse的格式
    std::string _status_line;               // 状态行
    std::vector<std::string> _resp_headers; // 应答报头
    std::string _blank_line;                // 空行
    std::string _resp_body_text;            // 响应正文
};

1.2、构造析构函数

构造函数初始化版本和空行,默认可以是固定的!

const static std::string httpversion = "HTTP/1.0";
const static std::string spacesep = " ";

HttpResponse() : _version(httpversion), _blank_line(base_sep)
{}

~HttpResponse()
{}

1.3、添加属性成员函数

添加状态码默认初始化状态码并将状态码描述设置为OK,添加报头将传入的KV数据插入到报头的KV结构中,添加正文初始化正文内容即可!

// 添加状态码
void AddCode(int code)
{
    _status_code = code;
    _desc = "OK";
}
void AddHeader(const std::string &k, const std::string &v)
{
    _headers_kv[k] = v;
}
void AddBodyText(const std::string &body_text)
{
    _resp_body_text = body_text;
}

1.4、序列化函数

序列化即将结构化数据转化为字符串数据,主要有下面四个步骤:

1、构建状态行

2、构建报头

3、空行和正文(无需处理,空行已初始化,正文内容在KV结构中)

4、正式序列化

std::string Serialize() 
{
    // 1.构建状态行
    _status_line = _version + spacesep + std::to_string(_status_code) + spacesep + _desc + base_sep;
    // 2.构建报头
    for (auto &header : _headers_kv)
    {
        std::string header_line = header.first + line_sep + header.second + base_sep;
        _resp_headers.push_back(header_line);
    }
    // 3.空行和正文

    // 4.正式序列化
    std::string responsestr = _status_line;
    for (auto &line : _resp_headers)
    {
        responsestr += line;
    }
    responsestr += _blank_line;
    responsestr += _resp_body_text;

    return responsestr;
}

2、HandlerHttpRequest()

HandlerHttpRequest() 函数构建请求对象并反序列化,然后获取请求路径下的内容,再创建应答类,添加状态码,报头和正文,最后将序列化的消息传给客户端

html 复制代码
std::string HandlerHttpRequest(std::string &reqstr)
{
    HttpRequest req;         // 构建请求对象
    req.Deserialize(reqstr); // 反序列化字符串

    // 最基础的上层处理
    std::string content = GetFileContent(req.Path());
    if (content.empty())
        return std::string(); // TODO

    HttpResponse resp;
    resp.AddCode(200);
    resp.AddHeader("Content-Length", std::to_string(content.size()));
    resp.AddBodyText(content);

    return resp.Serialize();
}

2.1、GetFileContent()

根据传入的文件名,读取文件内容,并返回!

// 必须以二进制读取文件
std::string GetFileContent(const std::string path)
{
    std::ifstream in(path, std::ios::binary); // 以二进制方式打开文件
    if (!in.is_open())
        return std::string();
    in.seekg(0, in.end);       // 将文件读取指针(也称为"get"指针)移动到文件的末尾
    int filesize = in.tellg(); // 获取当前文件读取指针的位置,即文件的总大小,单位字节
    in.seekg(0, in.beg);       // 将文件读取指针重新定位到文件的开始位置

    std::string content;
    content.resize(filesize);                   // 调整 content 的大小为filesize
    in.read((char *)content.c_str(), filesize); // 读取filesize字节的文件内容到 content 中
    in.close();

    return content;
}

3、index.html

浏览器直接使用IP + 端口访问时默认访问的文件,该文件随意添加一些内容,能到服务器发送给客户端的效果即可!

注意:该文件内容是前端语言,可以在下面的链接查看教程:

3.1、版本一

版本一只有标题和主体内容!

w3cschool

html 复制代码
<!DOCTYPE html>
<html>
<head>
    <title>Linux</title>
    <meta charset="UTF-8">
</head>
<body>
    <div id="container" style="width:500px">
    <div id="header" style="background-color:#FFA500;">
    <h1 style="margin-bottom:0;">我的网站</h1></div>
    <div id="menu" style="background-color:#FFD700;height:200px;width:100px;float:left;">
    <b>菜单</b><br>
    HTML<br>
    CSS<br>
    JavaScript</div>
    <div id="content" style="background-color:#EEEEEE;height:200px;width:400px;float:left;">
    内容就在这里</div>
    <div id="footer" style="background-color:#FFA500;clear:both;text-align:center;">
    Copyright © W3Cschools.com</div>
    </div>
</body>
</html>

运行结果

telnet 命令

运行结果

3.2、版本二

版本二我们可以增加图像!

html 复制代码
<!DOCTYPE html>
<html>
<head>
    <title>Linux</title>
    <meta charset="UTF-8">
</head>
<body>
    <div id="container" style="width:500px">
    <div id="header" style="background-color:#FFA500;">
    <h1 style="margin-bottom:0;">我的网站</h1></div>
    <div id="menu" style="background-color:#FFD700;height:200px;width:100px;float:left;">
    <b>菜单</b><br>
    HTML<br>
    CSS<br>
    JavaScript</div>
    <div id="content" style="background-color:#EEEEEE;height:200px;width:400px;float:left;">
    内容就在这里</div>
    <div id="footer" style="background-color:#FFA500;clear:both;text-align:center;">
    Copyright © W3Cschools.com</div>
    </div>

    <div>
        <img src="/image/1.png" alt="一张图片"> 
    </div>
</body>
</html>

获得一个完整的网页,浏览器会先得到html,根据html的标签,检测出我们还要获取其他资源,浏览器会继续发起http请求!

运行结果

3.3、版本三

版本三我们可以增加链接,进行跳转页面,默认页面可以跳转到登录页面,登录页面可以跳转到内容页面,内容页面可以跳转到注册页面,注册页面跳转回默认页面!

index.html

html 复制代码
<!DOCTYPE html>
<html>
<head>
    <title>Linux</title>
    <meta charset="UTF-8">
</head>
<body>
    <div id="container" style="width:500px">
    <div id="header" style="background-color:#FFA500;">
    <h1 style="margin-bottom:0;">我的网站</h1></div>
    <div id="menu" style="background-color:#FFD700;height:200px;width:100px;float:left;">
    <b>菜单</b><br>
    HTML<br>
    CSS<br>
    JavaScript</div>
    <div id="content" style="background-color:#EEEEEE;height:200px;width:400px;float:left;">
    内容就在这里</div>
    <div id="footer" style="background-color:#FFA500;clear:both;text-align:center;">
    Copyright © W3Cschools.com</div>
    </div>
        <a href="/login.html">点击测试: 登录页面</a>
    <div>
        <img src="/image/1.png" alt="一张图片"> 
        <!-- <img src="/image/2.jpg" alt="第二张图片">   -->
    </div>
</body>
</html>

login.html

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>
</body>
</html>

content.html

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="/register.html">进入注册页面</a>
</body>
</html>

register.html

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="/">回到首页</a>
</body>
</html>

运行结果

4、Content-Type

报头内部还有Content-Type,内容的类型 ,可以将类型也打印出来,使用哈希表存储

4.1、HttpRequest类

HttpRequest类需要增加一个文件后缀成员变量,并在解析行函数处理后缀内容

4.1.1、基本结构

HttpRequest类基本结构增加一个文件后缀成员变量!

html 复制代码
class HttpRequest
{
private:
    // 基本的httprequest的格式
    std::string _req_line;                 // 请求行
    std::vector<std::string> _req_headers; // 请求报头
    std::string _blank_line;               // 空行
    std::string _body_text;                // 正文

    // 更具体的属性字段,需要进一步反序列化
    std::string _method;                                      // 请求方法
    std::string _url;                                         // 统一资源定位符
    std::string _path;                                        // 资源路径
    std::string _suffix;                                      // 资源后缀
    std::string _version;                                     // 版本
    std::unordered_map<std::string, std::string> _headers_kv; // 存储每行报文的哈希表
};

4.1.2、ParseReqLine()

ParseReqLine() 增加处理后缀操作,后缀以 . 结尾,从尾部开始查找 . ,找到则截取内容,没找到将后缀设置为 .default!

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

// 解析请求行
void ParseReqLine()
{
    std::stringstream ss(_req_line);   // 以空格为分隔符 cin >>
    ss >> _method >> _url >> _version; // 以空格为分隔符依次将请求行的内容赋值给成员变量

    _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";
    }
}

4.1.3、Suffix()

html 复制代码
std::string Suffix()
{
    return _suffix;
}

4.2、HttpServer类

HttpServer类 需要增加存储类型对应的哈希表 ,并在构造函数插入对应的类型

4.2.1、基本结构

HttpServer类基本结构增加存储类型对应的哈希表!

html 复制代码
class HttpServer
{
private:
    std::unordered_map<std::string, std::string> _mine_type; // 类型对应表
};

4.2.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"));
}

4.2.3、HandlerHttpRequest()

HandlerHttpRequest() 增加添加类型的报头函数!

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())
        return std::string(); // TODO
    resp.AddCode(200);
    resp.AddHeader("Content-Length", std::to_string(content.size()));
    resp.AddHeader("Content-Type", _mine_type[req.Suffix()]);
    resp.AddBodyText(content);

    return resp.Serialize();
}

运行结果

相关推荐
冷眼看人间恩怨7 分钟前
【Qt笔记】QComboBox控件详解
c++·笔记·qt
Hacker_LaoYi15 分钟前
网络协议栈学习(一)socket通信实例
网络·网络协议·学习
山兔115 分钟前
16.1、网络安全风险评估过程
网络·安全·web安全
奇偶变不变24 分钟前
RTOS之事件集
java·linux·jvm·单片机·算法
Rossy Yan27 分钟前
【C++面向对象——类的多态性与虚函数】计算图像面积(头歌实践教学平台习题)【合集】
开发语言·c++·多态·面向对象·虚函数·头歌实践教学平台
Sunsets_Red28 分钟前
CF1548A Web of Lies 题解
c++·学习·算法·信息与通信
过过过呀Glik44 分钟前
在 Ubuntu 上安装与配置 Docker 的完整指南
linux·ubuntu·docker
谁在夜里看海.1 小时前
【Linux】深入理解进程信号机制:信号的产生、捕获与阻塞
linux·运维·服务器
G_whang1 小时前
centos7 下使用 Docker Compose
运维·docker·容器
人才程序员1 小时前
【无标题】
c语言·前端·c++·qt·软件工程·qml·界面