应用层协议——HTTP

目录

HTTP协议

认识URL

urlencode和urldecode

HTTP协议请求与响应格式

HTTP请求

HTTP响应

服务器代码

index.html

代码效果

HTTP的方法

HTTP的状态码

HTTP常见的Header


HTTP协议

虽然我们说,应用层协议是我们程序猿自己定的.但实际上,已经有大佬们定义了一些现成的,又非常好用的应用层协议,供我们直接参考使用.HTTP(超文本传输协议)就是其 中之一。

在互联网世界中,HTTP(Hyper Text Transfer Protocol,超文本传输协议)是一个至关重要的协议。它定义了客户端(如浏览器)与服务器之间如何通信,以交换或传输超文本(如HTML文档)。 HTTP协议是客户端与服务器之间通信的基础。客户端通过HTTP协议向服务器发送请求,服务器收到请求后处理并返回响应。HTTP协议是一个无连接、无状态的协 议,即每次请求都需要建立新的连接,且服务器不会保存客户端的状态信息。

认识URL

平时我们俗称的"网址"其实就是说的URL

urlencode和urldecode

像/?:等这样的字符,已经被url当做特殊意义理解了.因此这些字符不能随意出现.

比如,某个参数中需要带有这些特殊字符,就必须先对特殊字符进行转义. 转义的规则如下:

将需要转码的字符转为16进制,然后从右到左,取4位(不足4位直接处理),每2位 做一位,前面加上%,编码成%XY格式。

如下图搜索C++中"+" 被转义成了"%2B"

urldecode 就是urlencode的逆过程;

在线编码工具:http://tool.chinaz.com/tools/urlencode.aspx

选中其中的URL编码/解码模式,在输入C++后点击编码就能得到编码后的结果。

同理C%2B%2B点击解码就会得到C++

HTTP协议请求与响应格式

HTTP请求

HTTP请求协议格式如下:

HTTP请求由以下四部分组成:

  • 请求行:[请求方法]+[url]+[http版本]
  • 请求报头:请求的属性,这些属性都是以key: value的形式按行陈列的。
  • 空行:遇到空行表示请求报头结束。
  • 请求正文:请求正文允许为空字符串,如果请求正文存在,则在请求报头中会有一个Content-Length属性来标识请求正文的长度。

其中,前面三部分是一般是HTTP协议自带的,是由HTTP协议自行设置的,而请求正文一般是用户的相关信息或数据,如果用户在请求时没有信息要上传给服务器,此时请求正文就为空字符串。

代码如下

class HttpRequest
{
public:
    HttpRequest()
    {
    }
    std::string Serialize()
    {
        return std::string();
    }
    void ParseReqLine(std::string &reqline)
    {
        // GET / HTTP/1.1
        std::stringstream ss(reqline);
        ss >> _method >> _uri >> _version;
    }
    bool Deserialize(std::string &reqstr)
    {
        // 1. 提取请求行
        std::string reqline;
        bool res = Util::ReadOneLine(reqstr, &reqline, glinespace);
        LOG(LogLevel::DEBUG) << reqline;

        // 2. 对请求行进行反序列化
        ParseReqLine(reqline);
        if (_uri == "/")
            _uri = webroot + _uri + homepage; // ./wwwroot/index.html
        else
            _uri = webroot + _uri; // ./wwwroot/a/b/c.html

        LOG(LogLevel::DEBUG) << "_method: " << _method;
        LOG(LogLevel::DEBUG) << "_uri: " << _uri;
        LOG(LogLevel::DEBUG) << "_version: " << _version;

        // ./wwwroot/XXX.YYY
        return true;
    }
    std::string Uri() { return _uri; }
    ~HttpRequest()
    {
    }

private:
    std::string _method;  // 请求方法
    std::string _uri;     // URI
    std::string _version; // HTTP版本

    std::unordered_map<std::string, std::string> _headers; // 请求报头
    std::string _blankline;                                // 空行
    std::string _text;                                     // 请求正文
};

HTTP响应

HTTP响应由以下四部分组成:

  • 状态行:[http版本]+[状态码]+[状态码描述]
  • 响应报头:响应的属性,这些属性都是以key: value的形式按行陈列的。
  • 空行:遇到空行表示响应报头结束。
  • 响应正文:响应正文允许为空字符串,如果响应正文存在,则响应报头中会有一个Content-Length属性来标识响应正文的长度。比如服务器返回了一个html页面,那么这个html页面的内容就是在响应正文当中的。
     

代码如下:

class HttpResponse
{
public:
    HttpResponse() : _blankline(glinespace), _version("HTTP/1.0")
    {
    }
    // 实现: 成熟的http,应答做序列化,不要依赖任何第三方库!
    std::string Serialize()
    {
        std::string status_line = _version + gspace + std::to_string(_code) + gspace + _desc + glinespace;
        std::string resp_header;
        for (auto &header : _headers)
        {
            std::string line = header.first + glinesep + header.second + glinespace;
            resp_header += line;
        }

        return status_line + resp_header + _blankline + _text;
    }

    void SetTargetFile(const std::string &target)
    {
        _targetfile = target;
    }

    void SetCode(int code)
    {
        _code = code;
        switch (_code)
        {
        case 200:
            _desc = "OK";
            break;
        case 404:
            _desc = "Not Found";
            break;
        default:
            break;
        }
    }

    bool MakeResponse()
    {
        bool res = Util::ReadFileContent(_targetfile, &_text); // 浏览器请求的资源,一定会存在吗?出错呢?
        if (!res)
        {
            SetCode(404);
            _targetfile=webroot+page_404;
            Util::ReadFileContent(_targetfile, &_text);
        }
        SetCode(200);
        return true;
    }
    bool Deserialize(std::string &reqstr)
    {
        return true;
    }
    ~HttpResponse() {}

public:
    std::string _version;
    int _code;         // 404
    std::string _desc; // "Not Found"

    std::unordered_map<std::string, std::string> _headers;
    std::string _blankline;
    std::string _text;

    // 其他属性
    std::string _targetfile;
};

Http类

class Http
{
public:
    Http(uint16_t port) : tsvrp(std::make_unique<TcpServer>(port))
    {
    }
    void HandlerHttpRquest(std::shared_ptr<Socket> &sock, InetAddr &client) {
    // 1. 接收HTTP请求数据
    std::string httpreqstr;
    int n = sock->Recv(&httpreqstr); // 读取请求数据

    if (n > 0) {
        // 2. 解析HTTP请求
        HttpRequest req;
        req.Deserialize(httpreqstr);

        // 3. 生成HTTP响应
        HttpResponse resp;
        resp.SetTargetFile(req.Uri()); // 设置目标文件路径
        resp.MakeResponse();           // 读取文件内容或返回404

        // 4. 发送响应
        std::string response_str = resp.Serialize();
        sock->Send(response_str);
    }
}
    void Start()
    {
        tsvrp->Start([this](std::shared_ptr<Socket> &sock, InetAddr &client)
                     { this->HandlerHttpRquest(sock, client); });
    }
    ~Http()
    {
    }

private:
    std::unique_ptr<TcpServer> tsvrp;
};

服务器代码

#include "Http.hpp"

//http port
int main(int argc, char *argv[])
{
    if(argc != 2)
    {
        std::cout << "Usage: " << argv[0] << " port" << std::endl;
        exit(USAGE_ERR);
    }
    uint16_t port = std::stoi(argv[1]);

    std::unique_ptr<Http> httpsvr = std::make_unique<Http>(port);
    httpsvr->Start();
    return 0;
}

http协议底层代码还是TCP socket,我们在服务器创建Http类的智能指针,并启动服务器

在Start()函数中使用lambda表达式捕获类中的HandlerHttpRquest()

在HandlerHttpRquest()中

1. 接收HTTP请求数据

当你在浏览器中输入网址(如 http://106.54.48.112:8080/index.html)并访问时,浏览器会通过 TCP 连接向你的服务器发送 HTTP 请求数据。你的服务器通过 sock->Recv()读取的正是浏览器发送的原始 HTTP 请求数据。

2. 解析HTTP请求

在HTTP请求类中实现的反序列化读取_method , _uri , _version,主要是获得uri这样就可以访问服务器上wwwroot目录下的资源。

3. 生成HTTP响应

在获得HTTP请求后,要告诉浏览器资源的路径即设置路径,然后完成响应。

在 MakeResponse方法中调用Util::ReadFileContent(_targetfile, &_text)的目的是读取客户端请求的目标文件内容(106.54.48.112:8080/index.html),并将其存储到 HTTP 响应的 _text 成员变量中,以便后续通过 Serialize()方法将文件内容作为 HTTP 响应正文返回给浏览器

4. 发送响应

即把序列化后的字符串发给浏览器

注:在请求与响应中要严格按照图片中的结构完成序列化,否则浏览器与服务器无法建立连接

// 工具类

class Util
{
public:
    static bool ReadFileContent(const std::string &filename, std::string *out)
    {
        // version1
        std::ifstream in(filename);
        if (!in.is_open())
        {
            return false;
        }
        std::string line;
        while (std::getline(in, line))
        {
            *out += line;
        }
        in.close();
        return true;
    }
    static bool ReadOneLine(std::string &bigstr, std::string *out, const std::string &sep /*\r\n*/)
    {
        auto pos = bigstr.find(sep);
        if (pos == std::string::npos)
            return false;

        *out = bigstr.substr(0, pos);
        bigstr.erase(0, pos + sep.size());
        return true;
    }
    static int FileSize(const std::string &filename)
    {
        std::ifstream in(filename, std::ios::binary);
        if (!in.is_open())
            return -1;
        in.seekg(0, in.end);
        int filesize = in.tellg();
        in.seekg(0, in.beg);
        in.close();
        return filesize;
    }
};

index.html

以ai帮我们完成网址首页为例

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Default Home Page</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            margin: 0;
            padding: 0;
            background-color: #f4f4f4;
            color: #333;
        }
        header {
            background-color: #007bff;
            color: #fff;
            padding: 10px 20px;
            text-align: center;
        }
        nav {
            background-color: #343a40;
            padding: 10px 0;
        }
        nav a {
            color: #fff;
            text-decoration: none;
            padding: 10px 20px;
            display: inline-block;
        }
        nav a:hover {
            background-color: #5a6268;
        }
        .container {
            padding: 20px;
        }
        .welcome {
            text-align: center;
            margin-bottom: 20px;
        }
        .welcome h1 {
            margin: 0;
        }
        .content {
            background-color: #fff;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
        }
        footer {
            background-color: #343a40;
            color: #fff;
            text-align: center;
            padding: 10px 0;
            position: fixed;
            width: 100%;
            bottom: 0;
        }
    </style>
</head>
<body>
    <header>
        <h1>Welcome to Our Website</h1>
    </header>
    <nav>
        <a href="#">Home</a>
        <a href="Login.html">Login</a> <!-- 跳转到登录页面 -->
        <a href="Register.html">Register</a> <!-- 跳转到注册页面 -->
        <a href="#">About</a>
        <a href="#">Contact</a>
    </nav>
    <div class="container">
        <div class="welcome">
            <h1>Welcome to Our Default Home Page</h1>
            <p>This is a simple default home page template.</p>
        </div>
        <div class="content">
            <h2>Introduction</h2>
            <p>This is a basic HTML template for a default home page. It includes a header, navigation bar, a welcome section, and a content area. You can customize this template to suit your needs.</p>
        </div>
    </div>
    <footer>
        <p>&copy; 2025 Your Company Name. All rights reserved.</p>
    </footer>
</body>
</html>

代码效果

服务器日志的消息

浏览器输入服务器ip+端口号

网址默认首页

HTTP的方法

HTTP常见的方法如下:

方法

说明

支持的HTTP协议版本

GET

获取资源

1.0、1.1

POST

传输实体主体

1.0、1.1

PUT

传输文件

1.0、1.1

HEAD

获得报文首部

1.0、1.1

DELETE

删除文件

1.0、1.1

OPTIONS

询问支持的方法

1.1

TRACE

追踪路径

1.1

CONNECT

要求用隧道协议连接代理

1.1

LINK

建立和资源之间的联系

1.0

UNLINE

断开连接关系

1.0

其中最常用的就是GET方法和POST方法。

GET方法和POST方法

GET方法一般用于获取某种资源信息,而POST方法一般用于将数据上传给服务器。但实际我们上传数据时也有可能使用GET方法,比如百度提交数据时实际使用的就是GET方法。

GET方法和POST方法都可以带参:

  • GET方法是通过url传参的。
  • POST方法是通过正文传参的。

从GET方法和POST方法的传参形式可以看出,POST方法能传递更多的参数,因为url的长度是有限制的,POST方法通过正文传参就可以携带更多的数据。

此外,使用POST方法传参更加私密,因为POST方法不会将你的参数回显到url当中,此时也就不会被别人轻易看到。不能说POST方法比GET方法更安全,因为POST方法和GET方法实际都不安全,要做到安全只能通过加密来完成

HTTP的状态码

HTTP的状态码如下:

类别分类原因短语
1XXInformational(信息性状态码)接收的请求正在处理
2XXSuccess(成功状态码)请求正常处理完毕
3XXRedirection(重定向状态码)需要进行附加操作以完成请求
4XXClient Error(客户端错误状态码)服务器无法处理请求
5XXServer Error(服务器错误状态码)服务器处理请求出错

最常见的状态码,比如200(OK),404(Not Found),403(Forbidden请求权限不够),302(Redirect),504(Bad Gateway)。

Redirection(重定向状态码)

重定向就是通过各种方法将各种网络请求重新定个方向转到其它位置,此时这个服务器相当于提供了一个引路的服务。

重定向又可分为临时重定向和永久重定向,其中状态码301表示的就是永久重定向,而状态码302和307表示的是临时重定向。

临时重定向和永久重定向本质是影响客户端的标签,决定客户端是否需要更新目标地址。如果某个网站是永久重定向,那么第一次访问该网站时由浏览器帮你进行重定向,但后续再访问该网站时就不需要浏览器再进行重定向了,此时你访问的直接就是重定向后的网站。而如果某个网站是临时重定向,那么每次访问该网站时如果需要进行重定向,都需要浏览器来帮我们完成重定向跳转到目标网站。

临时重定向演示

进行临时重定向时需要用到Location字段,Location字段是HTTP报头当中的一个属性信息,该字段表明了你所要重定向到的目标网站。

我们这里要演示临时重定向,可以将HTTP响应当中的状态码改为302,然后跟上对应的状态码描述,此外,还需要在HTTP响应报头当中添加Location字段,这个Location后面跟的就是你需要重定向到的网页,比如我们这里将其设置为qq的首页。

代码如下

class HttpResponse
{
public:
    HttpResponse() : _blankline(glinespace), _version("HTTP/1.0")
    {
    }
    
    void SetCode(int code)
    {
        _code = code;
        switch (_code)
        {
        case 200:
            _desc = "OK";
            break;
        case 404:
            _desc = "Not Found";
            break;
        case 302:
            _desc = "See Other";
            break;
        default:
            break;
        }
    }
    void SetHeader(const std::string &key, const std::string &value)
    {
        auto iter = _headers.find(key);
        if (iter != _headers.end())
            return;
        _headers.insert(std::make_pair(key, value));
    }

    bool MakeResponse()
    {
        if (_targetfile == "./wwwroot/redir_test")
        {
            SetCode(302);
            SetHeader("Location", "https://www.qq.com/");
            return true;
        }
        bool res = Util::ReadFileContent(_targetfile, &_text); // 浏览器请求的资源,一定会存在吗?出错呢?
        if (!res)
        {
            SetCode(404);
            _targetfile = webroot + page_404;
            Util::ReadFileContent(_targetfile, &_text);
        }

        SetCode(200);
        return true;
    }
    ~HttpResponse() {}

public:
    std::string _version;
    int _code;         // 404
    std::string _desc; // "Not Found"

    std::unordered_map<std::string, std::string> _headers;
    std::string _blankline;
    std::string _text;

    // 其他属性
    std::string _targetfile;
};

在浏览器输入106.54.48.112:8080/redir_test就会自动跳转到qq的首页

上述操作中是浏览器的底层根据Location自己定位网址的。

HTTP常见的Header

HTTP常见的Header如下:

  • Content-Type:数据类型(text/html等)。
  • Content-Length:正文的长度。
  • Host:客户端告知服务器,所请求的资源是在哪个主机的哪个端口上。
  • User-Agent:声明用户的操作系统和浏览器的版本信息。
  • Referer:当前页面是哪个页面跳转过来的。
  • Location:搭配3XX状态码使用,告诉客户端接下来要去哪里访问。
  • Cookie:用于在客户端存储少量信息,通常用于实现会话(session)的功能。
     
评论 31
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值