第11讲:应用层协议 HTTP

目录

  • [HTTP 协议](#HTTP 协议)
  • [认识 URL](#认识 URL)
  • [HTTP 协议请求与响应格式](#HTTP 协议请求与响应格式)
  • [HTTP 的方法](#HTTP 的方法)
  • [HTTP 的状态码](#HTTP 的状态码)
  • [HTTP 常见 Header](#HTTP 常见 Header)
  • [最简单的 HTTP 服务器](#最简单的 HTTP 服务器)
  • 实验
  • [附录:HTTP 历史及版本核心技术与时代背景](#附录:HTTP 历史及版本核心技术与时代背景)

HTTP 协议

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

在互联网世界中,HTTP(HyperText Transfer Protocol,超文本传输协议)是一个至关重要的协议。它定义了客户端(如浏览器)与服务器之间如何通信,以交换或传输超文本(如 HTML 文档)。

HTTP 协议是客户端与服务器之间通信的基础。客户端通过 HTTP 协议向服务器发送请求,服务器收到请求后处理并返回响应。HTTP 是一种无状态协议 ,服务器不会仅凭协议本身保存客户端的状态信息;底层 TCP 连接是否复用,则取决于 HTTP 版本和 Connection 等约定,HTTP/1.1 默认使用持久连接。

认识 URL

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

urlencode 和 urldecode(了解)

像 /、?、: 等字符,已经被 URL 当作具有特殊含义的字符,因此不能随意出现。比如,某个参数中需要带有这些特殊字符,就必须先对特殊字符进行转义。

转义规则如下:先将待编码内容转换为 UTF-8 字节序列,再把需要转义的每个字节写成两位十六进制数,并在前面加上 %,形成 %HH 格式。

例如:

+ 被转义成了 %2B。

urldecode 就是 urlencode 的逆过程。

UrlEncode编码/UrlDecode解码:https://tool.chinaz.com/Tools/urlencode.aspx

HTTP 协议请求与响应格式

HTTP 请求

  • 首行: [方法] + [URL] + [版本]。
  • **Header:**请求的属性,是用冒号分隔的键值对;每组属性之间使用 \r\n 分隔;遇到空行表示 Header 部分结束。
  • **Body:**空行后面的内容都是 Body。Body 允许为空字符串;如果 Body 存在,则 Header 中会有一个 Content-Length 属性来标识 Body 的长度。
编写 HTTP 请求的代码:验证 HTTP 请求
  1. 需要基于历史代码,先架构出一个基本的 HTTP 服务器,然后用浏览器进行验证。
  2. 需要验证前端内容,直接使用 AI 即可。

HTTP 响应

  • 首行: [版本号] + [状态码] + [状态码解释]。
  • **Header:**响应的属性,是用冒号分隔的键值对;每组属性之间使用 \r\n 分隔;遇到空行表示 Header 部分结束。
  • **Body:**空行后面的内容都是 Body。Body 允许为空字符串;如果 Body 存在,则 Header 中会有一个 Content-Length 属性来标识 Body 的长度。如果服务器返回了一个 HTML 页面,那么 HTML 页面内容就在 Body 中。

基本的应答格式:

HTTP 的方法

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

HTTP 常见方法

1. GET 方法(重点)
  • **用途:**用于请求 URL 指定的资源。
  • 示例: GET /index.html HTTP/1.1
  • **特性:**指定资源经服务器端解析后返回响应内容。

要通过历史写的 HTTP 服务器验证 GET 方法,这里需要了解一下 FORM 表单的问题,也要引入 Web 根目录和文件读取的基本操作。

cpp 复制代码
std::string GetFileContentHelper(const std::string &path)
{
    // 一份简单的读取二进制文件的代码
    std::ifstream in(path, std::ios::binary);
    if (!in.is_open())
        return "";

    in.seekg(0, in.end);
    int filesize = in.tellg();
    in.seekg(0, in.beg);

    std::string content;
    content.resize(filesize);
    in.read(content.data(), filesize);

    // std::vector<char> content(filesize);
    // in.read(content.data(), filesize);

    in.close();
    return content;
}
2. POST 方法(重点)
  • **用途:**用于传输实体的主体,通常用于提交表单数据。
  • 示例: POST /submit.cgi HTTP/1.1
  • **特性:**可以发送大量的数据给服务器,并且数据包含在请求体中。

要通过历史写的 HTTP 服务器验证 POST 方法,这里需要了解一下 FORM 表单的问题。

3. PUT 方法(不常用)
  • **用途:**用于传输文件,将请求报文主体中的文件保存到请求 URL 指定的位置。
  • 示例: PUT /example.html HTTP/1.1
  • **特性:**不太常用,但在某些情况下(如 RESTful API 中)用于更新资源。
4. HEAD 方法
  • **用途:**与 GET 方法类似,但不返回报文主体部分,仅返回响应头。
  • 示例: HEAD /index.html HTTP/1.1
  • **特性:**用于确认 URL 的有效性及资源更新的日期时间等。
bash 复制代码
# curl -i 显示响应头与响应体
$ curl -i www.baidu.com
HTTP/1.1 200 OK
Accept-Ranges: bytes
Cache-Control: private, no-cache, no-store, proxy-revalidate, no-transform
Connection: keep-alive
Content-Length: 2381
Content-Type: text/html
Date: Sun, 16 Jun 2024 08:38:04 GMT
Etag: "588604dc-94d"
Last-Modified: Mon, 23 Jan 2017 13:27:56 GMT
Pragma: no-cache
Server: bfe/1.0.8.18
Set-Cookie: BDORZ=27315; max-age=86400; domain=.baidu.com; path=/

<!DOCTYPE html>
...

# 使用 HEAD 方法,只会返回响应头
$ curl --head www.baidu.com
HTTP/1.1 200 OK
Accept-Ranges: bytes
Cache-Control: private, no-cache, no-store, proxy-revalidate, no-transform
Connection: keep-alive
Content-Length: 277
Content-Type: text/html
Date: Sun, 16 Jun 2024 08:43:38 GMT
Etag: "575e1f71-115"
Last-Modified: Mon, 13 Jun 2016 02:50:25 GMT
Pragma: no-cache
Server: bfe/1.0.8.18
5. DELETE 方法(不常用)
  • **用途:**用于删除文件,是 PUT 的相反方法。
  • 示例: DELETE /example.html HTTP/1.1
  • **特性:**按请求 URL 删除指定的资源。
6. OPTIONS 方法
  • **用途:**用于查询针对请求 URL 指定的资源所支持的方法。
  • 示例: OPTIONS * HTTP/1.1
  • **特性:**返回允许的方法,如 GET、POST 等。

不支持的效果:

bash 复制代码
# 搭建一个 nginx 用来测试
# sudo apt install nginx
# sudo nginx          # 开启
# ps ajx | grep nginx # 查看
# sudo nginx -s stop  # 停止服务

$ sudo nginx -s stop
$ ps ajx | grep nginx
2944845 2945390 2945389 2944845 pts/1 2945389 S+ 1002 0:00 grep --color=auto nginx

$ sudo nginx
$ ps axj | grep nginx
      1 2945393 2945393 2945393 ?      -1 Ss    0 0:00 nginx: master process nginx
2945393 2945394 2945393 2945393 ?      -1 S    33 0:00 nginx: worker process
2945393 2945395 2945393 2945393 ?      -1 S    33 0:00 nginx: worker process
2944845 2945397 2945396 2944845 pts/1 2945396 S+ 1002 0:00 grep --color=auto nginx

# -X(大写 X)指明方法
$ curl -X OPTIONS -i 127.0.0.1/
HTTP/1.1 405 Not Allowed
Server: nginx/1.18.0 (Ubuntu)
Date: Sun, 16 Jun 2024 08:48:22 GMT
Content-Type: text/html
Content-Length: 166
Connection: keep-alive

<html>
<head><title>405 Not Allowed</title></head>
<body>
<center><h1>405 Not Allowed</h1></center>
<hr><center>nginx/1.18.0 (Ubuntu)</center>
</body>
</html>

支持的效果:

text 复制代码
HTTP/1.1 200 OK
Allow: GET, HEAD, POST, OPTIONS
Content-Type: text/plain
Content-Length: 0
Server: nginx/1.18.0 (Ubuntu)
Date: Sun, 16 Jun 2024 09:04:44 GMT
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization

// 注意:这里没有响应体,因为 Content-Length 为 0

HTTP 的状态码

最常见的状态码包括 200 OK、404 Not Found、403 Forbidden、302 Found(重定向)和 504 Gateway Timeout。

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

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

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

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

HTTP 状态码 301(永久重定向)和 302(临时重定向)都依赖 Location 选项。

HTTP 状态码 301(永久重定向)
  • 当服务器返回 HTTP 301 状态码时,表示请求的资源已经被永久移动到新的位置。
  • 在这种情况下,服务器会在响应中添加一个 Location 头部,用于指定资源的新位置。这个 Location 头部包含新的 URL 地址,浏览器会自动重定向到该地址。
  • 例如,在 HTTP 响应中,可能会看到类似以下的头部信息:
text 复制代码
HTTP/1.1 301 Moved Permanently\r\n
Location: /new-location\r\n
HTTP 状态码 302(临时重定向)
  • 当服务器返回 HTTP 302 状态码时,表示请求的资源临时被移动到新的位置。
  • 同样地,服务器也会在响应中添加一个 Location 头部来指定资源的新位置。浏览器会暂时使用新的 URL 进行后续请求,但不会缓存这个重定向。
  • 例如,在 HTTP 响应中,可能会看到类似以下的头部信息:
text 复制代码
HTTP/1.1 302 Found\r\n
Location: /new-location\r\n

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

HTTP 常见 Header

  • Content-Type:数据类型(如 text/html)。
  • Content-Length:Body 的长度。
  • Host:客户端告知服务器,所请求的资源位于哪个主机的哪个端口上。
  • User-Agent:声明用户的操作系统和浏览器版本信息。
  • Referer:当前页面是从哪个页面跳转过来的。
  • Location:搭配 3xx 状态码使用,告诉客户端接下来要去哪里访问。
  • Cookie:用于在客户端存储少量信息,通常用于实现会话(session)功能。

关于 Connection 报头

HTTP 中的 Connection 字段是 HTTP 报文头的一部分,主要用于控制和管理客户端与服务器之间的连接状态。

核心作用
  • 管理持久连接: Connection 字段可用于管理持久连接(也称为长连接)。持久连接允许客户端和服务器在请求/响应完成后不立即关闭 TCP 连接,以便在同一个连接上发送多个请求和接收多个响应。
持久连接(长连接)
  • **HTTP/1.1:**默认使用持久连接。当客户端和服务器都不明确指定关闭连接时,连接将保持打开状态,以便后续的请求和响应可以复用同一个连接。
  • **HTTP/1.0:**默认连接是非持久的。如果希望在 HTTP/1.0 上实现持久连接,需要在请求头中显式设置 Connection: keep-alive。
语法格式
  • Connection: keep-alive:表示希望保持连接,以复用 TCP 连接。
  • Connection: close:表示请求/响应完成后应该关闭 TCP 连接。

补充阅读: User-Agent 里的历史故事。

常见 Header 速查表

下面附上一张关于 HTTP 常见 Header 的表格:

字段名 含义 样例
Accept 客户端可接受的响应内容类型 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding 客户端支持的数据压缩格式 Accept-Encoding: gzip, deflate, br
Accept-Language 客户端可接受的语言类型 Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Host 请求的主机名和端口号 Host: www.example.com:8080
User-Agent 客户端的软件环境信息 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36
Cookie 客户端发送给服务器的 HTTP Cookie 信息 Cookie: session_id=abcdefg12345; user_id=123
Referer 请求的来源 URL Referer: /previous_page.html
Content-Type 实体主体的媒体类型 Content-Type: application/x-www-form-urlencoded(表单提交)或 Content-Type: application/json(JSON 数据)
Content-Length 实体主体的字节大小 Content-Length: 150
Authorization 认证信息,如用户名和密码 Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==(Base64 编码后的用户名和密码)
Cache-Control 缓存控制指令 请求时:Cache-Control: no-cache 或 Cache-Control: max-age=3600;响应时:Cache-Control: public, max-age=3600
Connection 请求完成后关闭还是保持连接 Connection: keep-alive 或 Connection: close
Date 请求或响应的日期和时间 Date: Wed, 21 Oct 2023 07:28:00 GMT
Location 重定向的目标 URL(与 3xx 状态码配合使用) Location: /new_location.html(与 302 状态码配合使用)
Server 服务器类型 Server: Apache/2.4.41 (Unix)
Last-Modified 资源的最后修改时间 Last-Modified: Wed, 21 Oct 2023 07:20:00 GMT
ETag 资源的唯一标识符,用于缓存 ETag: "3f80f-1b6-5f4e2512a4100"
Expires 响应过期的日期和时间 Expires: Wed, 21 Oct 2023 08:28:00 GMT

最简单的 HTTP 服务器

实现一个最简单的 HTTP 服务器,只在网页上输出 hello world。只要按照 HTTP 协议的要求构造数据,就很容易做到。

c 复制代码
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

void Usage()
{
    printf("usage: ./server [ip] [port]\n");
}

int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage();
        return 1;
    }

    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd < 0)
    {
        perror("socket");
        return 1;
    }

    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr(argv[1]);
    addr.sin_port = htons(atoi(argv[2]));

    int ret = bind(fd, (struct sockaddr *)&addr, sizeof(addr));
    if (ret < 0)
    {
        perror("bind");
        return 1;
    }

    ret = listen(fd, 10);
    if (ret < 0)
    {
        perror("listen");
        return 1;
    }

    for (;;)
    {
        struct sockaddr_in client_addr;
        socklen_t len = sizeof(client_addr);
        int client_fd = accept(fd, (struct sockaddr *)&client_addr, &len);
        if (client_fd < 0)
        {
            perror("accept");
            continue;
        }

        // 用一个足够大的缓冲区直接把数据读完
        char input_buf[1024 * 10] = {0};
        ssize_t read_size = read(client_fd, input_buf, sizeof(input_buf) - 1);
        if (read_size < 0)
        {
            return 1;
        }

        printf("[Request] %s", input_buf);

        char buf[1024] = {0};
        const char *hello = "<h1>hello world</h1>";
        sprintf(buf,
                "HTTP/1.0 200 OK\r\nContent-Length: %zu\r\n\r\n%s",
                strlen(hello), hello);
        write(client_fd, buf, strlen(buf));
        close(client_fd);
    }

    return 0;
}

编译并启动服务。在浏览器中输入 [ip]:[port] 对应的地址,就能看到显示结果 Hello World。

备注

  • 此处使用 9090 端口号启动了 HTTP 服务器。虽然 HTTP 服务器一般使用 80 端口,但这只是一个通用习惯,并不是说 HTTP 服务器不能使用其他端口号。
  • 使用 Chrome 测试服务器时,可以看到服务器打印出的请求中还有一个 GET /favicon.ico HTTP/1.1 请求。
  • 大家可自行查找资料,理解 favicon.ico 的作用。

实验

把返回的状态码改成 404、403、504 等,观察浏览器上分别会出现什么样的效果。

附录:HTTP 历史及版本核心技术与时代背景

HTTP(Hypertext Transfer Protocol,超文本传输协议)作为互联网中浏览器和服务器间通信的基石,经历了从简单到复杂、从单一到多样的发展过程。以下按照时间顺序介绍 HTTP 的主要版本、核心技术及其对应的时代背景。

HTTP/0.9

核心技术:

  • 仅支持 GET 请求方法。
  • 仅支持纯文本传输,主要是 HTML 格式。
  • 无请求和响应头信息。

时代背景:

  • 1991 年,HTTP/0.9 作为 HTTP 协议的最初版本,用于传输基本的超文本 HTML 内容。
  • 当时的互联网还处于起步阶段,网页内容相对简单,主要以文本为主。

HTTP/1.0

核心技术:

  • 引入 POST 和 HEAD 请求方法。
  • 引入请求和响应头信息,支持多种数据格式(MIME)。
  • 支持缓存(cache)。
  • 支持状态码(status code)、多字符集等。

时代背景:

  • 1996 年,随着互联网的快速发展,网页内容逐渐丰富,HTTP/1.0 版本应运而生。
  • 为了满足日益增长的网络应用需求,HTTP/1.0 增加了更多功能和灵活性。
  • 然而,HTTP/1.0 的默认工作方式是每次 TCP 连接只能发送一个请求,性能上存在一定局限。

HTTP/1.1

核心技术:

  • 引入持久连接(persistent connection),支持管道化(pipelining)。
  • 允许在单个 TCP 连接上进行多个请求和响应,提高了性能。
  • 引入分块传输编码(chunked transfer encoding)。
  • 支持 Host 头,允许在一个 IP 地址上部署多个 Web 站点。

时代背景:

  • 1999 年,随着网页加载的外部资源越来越多,HTTP/1.0 的性能问题愈发突出。
  • HTTP/1.1 通过引入持久连接和管道化等技术,有效提高了数据传输效率。
  • 同时,互联网应用开始呈现多元化、复杂化趋势,HTTP/1.1 的出现满足了这些需求。

HTTP/2.0

核心技术:

  • 多路复用(multiplexing),一个 TCP 连接可以承载多个 HTTP 请求流。
  • 二进制帧格式(binary framing),优化数据传输。
  • 头部压缩(header compression),减少传输开销。
  • 服务器推送(server push),提前向客户端发送资源。

时代背景:

  • 2015 年,随着移动互联网的兴起和云计算技术的发展,网络应用对性能的要求越来越高。
  • HTTP/2.0 通过多路复用、二进制帧格式等技术,显著提高了数据传输效率和网络性能。
  • HTTP/2.0 在实际部署中通常配合 HTTPS 使用,以提高数据传输的安全性。

HTTP/3.0

核心技术:

  • 使用 QUIC 协议替代 TCP,形成基于 UDP 构建的多路复用传输协议。
  • 减少传统 TCP 与 TLS 分步建连带来的等待时间,提高连接建立速度。
  • 缓解 TCP 层面的队头阻塞问题,提高数据传输效率。

时代背景:

  • 2022 年,随着 5G、物联网等技术的快速发展,网络应用对实时性、可靠性的要求越来越高。
  • HTTP/3.0 通过使用 QUIC 协议,提高了连接建立速度和数据传输效率,满足了这些需求。
  • HTTP/3.0 将加密能力集成到 QUIC 中,保证数据传输的安全性。
相关推荐
爱和冰阔落9 分钟前
【Linux网络】JsonCpp 怎么实现序列化?从 Request/Response 到网络计算器消息设计
linux·运维·网络·php
我是koten6 小时前
【运维杂谈】K8s遇到Terminating的名称空间删除不了怎么办?(解决方案亲测有效)
linux·运维·服务器·云原生·kubernetes·k8s·名称空间
2601_9679873714 小时前
安装 Redis 或 Redis Stack
linux·windows·redis·macos·安装
feng_you_ying_li14 小时前
linux之数据链路层
linux·网络·智能路由器
GeW15 小时前
高端化走到深处,是数据库和Linux的事
linux
程序员初一16 小时前
Linux 远程桌面有四种做法
linux·ssh
程序开发阿元17 小时前
1C1G 能跑什么:把留常驻服务和不留摆进一张内存表
linux·ssh
xbzb19 小时前
自定义 yum 软件源:Linux 上 createrepo,Windows 上纯 Python 也能造
linux·运维·windows·python·yum·createrepo
Android系统攻城狮19 小时前
Linux FFmpeg深度解析之avio_open调用流程与实战(三十八)
linux·运维·服务器·ffmpeg·音视频进阶·ffmpeg音视频