【网络】5.HTTP 协议详解与实现

目录

[一、HTTP 协议基础](#一、HTTP 协议基础)

[1. URL 构成](#1. URL 构成)

[2. 路径](#2. 路径)

[3. 字符处理](#3. 字符处理)

[4. 请求构成](#4. 请求构成)

[5. 应答构成](#5. 应答构成)

[6. 状态码分类](#6. 状态码分类)

[7. 常见报头属性](#7. 常见报头属性)

[8. 重定向](#8. 重定向)

[二、HTTP 通信代码实现](#二、HTTP 通信代码实现)

[1. 主逻辑](#1. 主逻辑)

[2. 请求反序列化](#2. 请求反序列化)

[3. 获取文件后缀与 Content-Type](#3. 获取文件后缀与 Content-Type)

[4. 获取响应内容](#4. 获取响应内容)

[5. 响应序列化](#5. 响应序列化)

[三、HTTP 动态交互](#三、HTTP 动态交互)

[1. 浏览器标准](#1. 浏览器标准)

[2. 两种提交方式](#2. 两种提交方式)

[3. GET 参数处理代码](#3. GET 参数处理代码)

[4. GET 与 POST 的优缺点](#4. GET 与 POST 的优缺点)

[5. 其他 HTTP 方法(基本不用)](#5. 其他 HTTP 方法(基本不用))

四、长连接与状态管理

[1. 短连接 vs 长连接](#1. 短连接 vs 长连接)

[2. 连接与状态](#2. 连接与状态)

[3. Cookie](#3. Cookie)

[4. Session](#4. Session)


一、HTTP 协议基础

1. URL 构成

URL 格式域名 + 路径

  • 域名:通过 DNS(域名服务器)找到对应的 IP 地址

  • 端口:HTTP 协议端口号固定

    • HTTP:80

    • HTTPS:443

  • 服务器定位:IP + 端口就可以找到对应的服务器

URL 通信的本质就是 HTTP 通信。

2. 路径

上网的本质:向远端发送数据,数据传到远端服务器。

路径对应 Linux 服务器上的文件路径。当没有人获取文件时,资源就在服务器内部。

通过 IP + 端口 + 路径 就可以获取全网唯一的文件。

3. 字符处理

在浏览器搜索框中输入内容时,特殊字符(如 /:? 等)会被自动编码。

原因/: 等是 HTTP 协议的特殊字符,如果直接放在 URL 中会导致解析错误。

  • 客户端(浏览器):自动进行 URL 编码

  • 服务器端:需要自动进行 URL 解码

浏览器和服务器通信的模式称为 B/S 模式(Browser/Server)。

4. 请求构成

HTTP 请求由以下部分组成:

  1. 请求行:方法 + URI + HTTP 版本

  2. 请求报头 :一系列的 Key: Value 键值对,描述 HTTP 请求的属性

  3. 空行:分隔报头和正文

  4. 正文:请求携带的数据(可选)

5. 应答构成

HTTP 应答由以下部分组成:

  1. 状态栏:HTTP 版本 + 状态码 + 状态码描述

  2. 响应报头 :一系列的 Key: Value 键值对

  3. 空行:分隔报头和正文

  4. 正文:响应的数据

URI 说明

  • URI 默认为 /,表示请求首页

  • 服务器可能会在路径前加上根目录(如 ./wwwroot),找到对应的文件

  • 如果请求的是目录,服务器会自动拼接首页文件名(如 index.html

请求方法

  • GET:从远端获取资源

  • POST:向远端推送数据

版本号:服务器需要服务不同版本的客户端,因此需要版本号来兼容。

6. 状态码分类

状态码范围 类别 说明
1xx 信息性状态码 接受的请求正在处理
2xx 成功状态码 请求处理完毕
3xx 重定向状态码 需要跳转到其他页面
4xx 客户端错误状态码 客户端请求错误(如 404 不存在)
5xx 服务器错误状态码 服务器内部错误

7. 常见报头属性

  • Content-Type :数据报类型(如 text/htmlimage/jpeg

  • User-Agent:用户信息(浏览器类型、操作系统等),可用于推荐合适的软件版本

  • Accept-Encoding:客户端支持的压缩算法

  • Referer:之前访问的页面(来源页)

  • wget:命令行工具,用于获取网页

8. 重定向

临时重定向(302)

  • 不会更改客户端书签

  • 仅仅做页面跳转

  • 客户端可能会多次访问原地址(如商店临时换位置)

永久重定向(301)

  • 客户端应该更新书签

  • 之后不再访问原地址

  • 对搜索引擎的作用:收到 301 状态码后,会在内部更新索引地址

二、HTTP 通信代码实现

1. 主逻辑

cpp 复制代码
void handelquest(std::shared_ptr<mysocket>& sock, addr& ad) {
    std::string queststr;
    int n = sock->receive(&queststr);
    
    if (n > 0) {
        httprequest req;
        req.deserialize(queststr);           // 反序列化请求
        
        httpresponse resp;
        resp._version = "HTTP/1.1";
        resp._code = 200;
        resp._desc = "OK";
        
        std::string uri = req.uri();
        resp.settar(uri);                     // 设置目标文件
        resp.questresponse();                  // 获取响应内容
        
        std::string retresponse = resp.serialize(); // 序列化响应
        LOG(LogLevel::DEBUG) << retresponse;
        
        sock->sendmsg(retresponse);            // 发送响应
    }
}

2. 请求反序列化

按行读取

cpp 复制代码
static bool readoneline(std::string& bigstring, std::string* out, std::string sep = "\r\n") {
    size_t pos = bigstring.find(sep);
    LOG(LogLevel::DEBUG) << "分隔第一行结果:" << pos;
    
    if (pos == std::string::npos) return false;
    
    *out = bigstring.substr(0, pos);
    bigstring.erase(0, pos + sep.size());
    return true;
}

解析请求行

cpp

复制代码
void praseline(std::string& line) {
    std::stringstream ss(line);
    ss >> _methord >> _uri >> _version;
}

完整反序列化

cpp 复制代码
bool deserialize(std::string& line) {
    std::string queline;
    util::readoneline(line, &queline);
    LOG(LogLevel::INFO) << "分开第一行:" << queline;
    
    praseline(queline);
    LOG(LogLevel::DEBUG) << "_methord: " << _methord;
    LOG(LogLevel::DEBUG) << "_uri: " << _uri;
    LOG(LogLevel::DEBUG) << "_version: " << _version;
    
    // 处理根路径
    if (_uri == "/") {
        _uri = rootpath + _uri + pagehome;
        LOG(LogLevel::DEBUG) << "_uri: " << _uri;
    } else {
        _uri = rootpath + _uri;
    }
    
    return true;
}

3. 获取文件后缀与 Content-Type

cpp 复制代码
std::string getsuffix(const std::string& file) {
    size_t pos = file.rfind(".");
    if (pos == std::string::npos) {
        LOG(LogLevel::WARNING) << "没找到后缀";
        return "";
    }
    
    std::string suffix = file.substr(pos);
    LOG(LogLevel::DEBUG) << "找到后缀:" << suffix;
    
    // 建立后缀到 Content-Type 的映射
    if (suffix == ".html" || suffix == ".htm")
        return "text/html";
    else if (suffix == ".jpg")
        return "image/jpeg";
    else if (suffix == ".png")
        return "image/png";
    else
        return "";
}

4. 获取响应内容

cpp 复制代码
bool questresponse() {
    bool ret = util::get_content(_tarfile, &_text);
    
    if (ret) {
        // 找到文件
        setcode(200);
        int filesize = util::filesize(_tarfile);
        setheader("Content-Length", std::to_string(filesize));
        setheader("Content-Type", getsuffix(_tarfile));
        LOG(LogLevel::INFO) << "文件大小" << filesize;
        return true;
    } else {
        // 文件不存在,返回 404 页面
        setcode(404);
        _tarfile = rootpath + page404;
        util::get_content(_tarfile, &_text);
        int filesize = util::filesize(_tarfile);
        setheader("Content-Length", std::to_string(filesize));
        setheader("Content-Type", getsuffix(_tarfile));
        LOG(LogLevel::INFO) << "404路径" << _tarfile;
        LOG(LogLevel::WARNING) << "404";
        LOG(LogLevel::INFO) << "404大小" << filesize;
        return false;
    }
}

5. 响应序列化

cpp 复制代码
std::string serialize() {
    std::ostringstream oss;
    
    // 状态行
    oss << _version << " " << _code << " " << _desc << "\r\n";
    LOG(LogLevel::DEBUG) << _version << " " << _code << " " << _desc;
    
    // 响应报头
    for (const auto& header : _headers) {
        oss << header.first << ": " << header.second << "\r\n";
    }
    
    // 空行
    if (!_blankline.empty()) {
        oss << _blankline;
    } else {
        oss << "\r\n";
    }
    
    // 正文
    oss << _text;
    
    return oss.str();
}

三、HTTP 动态交互

1. 浏览器标准

浏览器是流量的入口,各家浏览器有自己的技术特点,因此需要统一的标准。但标准的执行情况并不理想。

2. 两种提交方式

GET 方式

  • 在 URL 中提交参数

  • 格式:/路径?参数1=值1&参数2=值2

  • 示例:/register?reg_user=111&reg_email=111%401.com&reg_pw=111

POST 方式

  • 在请求正文中提交参数

  • 不回显,相对更私密

3. GET 参数处理代码

路由注册

cpp 复制代码
using http_func_t = std::function<void(httprequest& quest, httpresponse& response)>;
std::unordered_map<std::string, http_func_t> _route;

void enquefunc(std::string rout, http_func_t func) {
    _route[rout] = func;
}

解析请求行中的参数

cpp 复制代码
bool deserialize(std::string& line) {
    std::string queline;
    util::readoneline(line, &queline);
    LOG(LogLevel::INFO) << "分开第一行:" << queline;
    
    praseline(queline);
    
    // 查找问号,判断是否有参数
    size_t p2 = queline.find('?');
    if (p2 == std::string::npos) {
        LOG(LogLevel::DEBUG) << "没找到问号";
        // 处理普通文件请求
        if (_uri == "/") {
            _uri = rootpath + _uri + pagehome;
        } else {
            _uri = rootpath + _uri;
        }
        return true;
    } else {
        LOG(LogLevel::DEBUG) << "找到问号";
        _istxec = 1;  // 标记为需要执行
        _args = queline.substr(p2 + 1);  // 提取参数字符串
        
        size_t p1 = queline.find('/');
        _uri = queline.substr(p1, p2 - p1);  // 提取路径部分
        return true;
    }
}

4. GET 与 POST 的优缺点

GET

  • 优点:方便、直观

  • 缺点:有长度限制(URL 长度限制),参数在 URL 中暴露

POST

  • 优点:可传输长数据,参数不回显,相对更私密

  • 缺点:实现稍复杂

安全性:两种方式传输的数据都不加密。现在的 HTTPS 先加密数据再传输,解决安全问题。

5. 其他 HTTP 方法(基本不用)

方法 作用
PUT 传输文件,将指定文件放到 URL 指定位置
HEAD 仅返回报头,用于测试服务器是否可用
DELETE 删除文件
OPTIONS 查询服务器支持的方法

四、长连接与状态管理

1. 短连接 vs 长连接

一个网页包含 3 张图片时,上面的代码会发起 4 次请求:

  • 1 次请求 HTML 页面

  • 3 次请求图片资源

每次请求都要创建进程、拷贝内容、发送,开销很大。这是短连接,HTTP/1.0 使用较多。

HTTP/1.1 长连接

  • 可以一次连接发送多个请求和响应

  • 提高效率

实现

  • 客户端在请求报头中加入:Connection: keep-alive

  • 服务器在响应报头中也返回:Connection: keep-alive

  • 之后就可以用同一个 TCP 连接通信

由于 HTTP 协议固定有空行分隔,服务器可以拆分出多个报头,连续读到多个报文。

2. 连接与状态

HTTP 协议的特点

  • 无连接:每次请求处理完就断开连接(逻辑上)

  • 无状态:服务器不记忆任何客户端信息

注意:长短连接不是 HTTP 协议做的,而是由服务器和底层的 TCP 协议决定。

  • 长连接时,服务器决定积累多个响应后再通过 HTTP 发送

  • 这不是 HTTP 协议自己决定的

HTTP 是无状态的,但网站只需要登录一次就能持续使用,这靠 Cookie 实现。

服务端设置 Cookie

cpp

复制代码
setheader("Set-Cookie", "username=john; Path=/");

添加这行代码后,浏览器会存储对应的 Cookie。

后续请求中,浏览器会自动带上 Cookie:

Cookie 的属性:

  • Path:指定 Cookie 生效的路径

  • Expires:过期时间(不设置则默认为会话 Cookie,浏览器关闭即失效)

4. Session

Cookie 存储在本地,可能因木马、病毒等泄露。

Session 机制

  • 将用户信息存储在服务器端的 Session 节点中

  • 本地只保存一个 Session ID

  • 用户使用时携带 Session ID,服务器通过 ID 查找用户信息

安全性增强

  • 服务器可以用 IP 溯源

  • 地址变更、异常登录检测等方式守护信息安全

  • 即使 Cookie 被窃取,没有对应的服务器 Session 数据也无法登录

相关推荐
mjhcsp1 小时前
C++剪枝解析
c++·剪枝
Ralph_Y1 小时前
正则表达式
开发语言·c++·正则表达式
钓鱼的肝1 小时前
[GESP-4.2503.T2]二阶矩阵
c++·算法·矩阵
小小unicorn2 小时前
[微服务即时通讯系统]文件存储子服务的实现与测试
c++·redis·微服务·云原生·架构
草莓熊Lotso2 小时前
MySQL 数据库基础入门:从概念到实战
linux·运维·服务器·数据库·c++·人工智能·mysql
HalvmånEver2 小时前
6.高并发内存池的内存释放全流程
开发语言·c++·项目学习··高并发内存池
OxyTheCrack2 小时前
【C++】简述Observer观察者设计模式附样例(C++实现)
开发语言·c++·笔记·设计模式
小小unicorn2 小时前
[微服务即时通讯系统]3.服务端-环境搭建
数据库·c++·redis·微服务·云原生·架构
格林威2 小时前
工业相机图像高速存储(C++版):先存内存,后批量转存方法,附堡盟相机实战代码!
开发语言·c++·人工智能·数码相机·计算机视觉·视觉检测·堡盟相机