当前位置: 首页 > news >正文

【Linux网络篇】:HTTP协议深度解析---基础概念与简单的HTTP服务器实现

✨感谢您阅读本篇文章,文章内容是个人学习笔记的整理,如果哪里有误的话还请您指正噢✨
✨ 个人主页:余辉zmh–CSDN博客
✨ 文章所属专栏:Linux篇–CSDN博客

在这里插入图片描述

文章目录

  • 一.三个预备知识
    • 认识域名
    • 认识URL
    • 认识URL编码和解码
  • 二.http请求和响应的格式
  • 三.实现一个简单的HTTP服务器
    • HTTP服务器实现-version 1
    • HTTP服务器实现-version 2
  • 四.HTTP的细节字段
    • HTTP的请求方法
    • HTTP的状态码
    • HTTP的常见Header

一.三个预备知识

认识域名

1.定义

  • 域名是互联网上用于标识和定位网站的一串字符串,比如baidu.com。他相当于网站的“门牌号”,方便用于记忆和访问。

2.结构

域名通常由多个部分组成,从右到左依次是:

  • 顶级域名:比如.comorg.net.cn等。
  • 二级域名:比如baidubaidu.com中。
  • 三级域名:比如wwwwww.baidu.com中。

3.作用

  • 域名将用户可读的地址转换为机器可以识别的IP地址(比如1.111.11.1:8888)。
  • 通过DNS(域名系统)实现域名到IP地址的映射。

我们平常访问各种网页使用的域名,首先会被解析为IP地址,在网络通信时,正真用到的其实是IP地址

使用一个IP地址在浏览器访问时,默认采用的协议就是http或者https,这种知名的协议绑定的端口号一般都是固定的。

http协议绑定的端口号是80https协议绑定的端口号是443

认识URL

1.定义

  • URL是用于标识和定位互联网上资源的完整地址,比如http://www.example.com/page?param=value

平常我们访问的网址其实就是URL——统一资源定位符

所有网络上的资源(图片,视频,文章等),都可以用唯一的一个”字符串“标识,并且可以获取到。

2.结构

URL通常由以下部分组成:

  • 协议:比如http://或者https://,表示访问资源的方式。
  • 域名:比如www.example.com,表示资源所在的服务器(IP地址标识)。
  • 端口:比如80443,表示服务器上的服务端口(默认可以省略)。
  • 路径:比如/path,表示资源所在服务器上的具体位置。
  • 查询参数:比如?pararm=value,表示传递给服务器的额外信息。
  • 锚点:比如#section,表示页面内的特定位置。

认识URL编码和解码

/ ? : #等这样的字符,已经被URL当作特殊意义理解了,因此这些字符不能随意出现。

少量的情况下,提交或者获取的资源地址本身可能包含和URL中特殊字符冲突的字符,所以BS双方(浏览器和服务器)要进行编码(urlencode)和解码(urldecode)的工作。

URL编码(urlencode)

  • 定义

    URL编码是将URL中的特殊字符(比如/ ? : #等)转换为%后跟十六位进制ASCLL码的形式,以便在互联网上安全传输。

  • 常见编码规则

    字母,数字,- _ . ~这些字符不编码;其他字符编码为%xx,其中XX是字符的十六进制ASCLL码。

URL解码(urldecode)

  • 定义

    URL解码是将URL编码后的字符串转换为原始字符。

实际应用

  • 浏览器自动编码:

    用户在浏览器输入URL时,浏览器会自动对特殊字符进行URL编码;比如,输入https://example.com?page?name=张三,浏览器会自动编码为https://example.com/page?name=%E5%BC%A0%E4%b8%89

  • 服务器解码:

    服务器收到请求后,会先解码URL,再解析参数;比如服务器收到name=%E5%BC%A0%E4%b8%89,解码后得到name=张三

最后总结上面三点

域名:是网站的“门牌号”,方便用户记忆,通过DNS转换为IP地址。

URL:是完整的资源地址,包含协议,域名,路径,参数等信息,用于定位和访问互联网上的资源。

URL编码:将特殊字符转换为%xx格式,确保URL的合法性和安全性。

URL解码:将%xx格式转换为原始字符,便于服务器解析。

二.http请求和响应的格式

在这里插入图片描述

先大概了解格式是什么样子,后面会讲解每个细节字段。

三.实现一个简单的HTTP服务器

主程序

HttpServer.cc:用于启动服务器

#include <iostream>
#include "HttpServer.hpp"static void Usage(const std::string &proc){std::cout << "/r/nUsage: " << proc << "serverport/r/n";
}int main(int argc, char *argv[]){if (argc != 2){Usage(argv[0]);exit(0);}uint16_t port = std::stoi(argv[1]);HttpServer *httpsvr = new HttpServer(port);httpsvr->InitServer();httpsvr->StartServer();return 0;
}

HTTP服务器实现-version 1

HttpServer.hpp

#pragma once#include <iostream>
#include <string>
#include <pthread.h>
#include <vector>
#include <sstream>
#include <fstream>
#include "Socket.hpp"
#include "Log.hpp"// 注意:我这里使用的Sokcet.hpp文件是我自己封装的Socket套接字类,具体实现可以看我上一篇文章extern Log log;class HttpServer;class ThreadData{
public:ThreadData(const int &sockfd, HttpServer *svr): _sockfd(sockfd), httpsvr(svr){}public:int _sockfd;HttpServer *httpsvr;
};class HttpServer{
public:HttpServer(const uint16_t port):_port(port){}// 初始化服务器void InitServer(){_listensock.Socket();_listensock.Bind(_port);_listensock.Listen();log(INFO, "HttpServer Init ... Done");}// 启动服务器void StartServer(){while(true){std::string clientip;uint16_t clientport;int sockfd = _listensock.Accept(&clientip, &clientport);if (sockfd < 0){continue;}log(INFO, "httpserver get a connect, sockfd: %d", sockfd);ThreadData *td = new ThreadData(sockfd, this);pthread_t tid;pthread_create(&tid, nullptr, ThreadRun, td);}}private:void HttpHandler(const int &sockfd){char buffer[1024];ssize_t n = recv(sockfd, buffer, sizeof(buffer), 0);if(n > 0){buffer[n] = 0;std::cout << buffer << std::endl;           // 假设读取到的是一个完整的,独立的http请求std::string text ="hello world!";                       // 响应正文部分std::string response_line = "HTTP/1.0 200 OK\r\n";      // 状态行std::string response_header = "Content-Length: ";response_header += std::to_string(text.size());response_header += "\r\n";                              // 响应报头std::string blank_line = "\r\n";                        // 空行分隔符// 构建响应字符串std::string response = response_line;response += response_header;response += blank_line;response += text;// 发送响应ssize_t k = send(sockfd, response.c_str(), response.size(), 0);}close(sockfd);}static void *ThreadRun(void *args){pthread_detach(pthread_self());    // 线程分离ThreadData *td = static_cast<ThreadData *>(args);td->httpsvr->HttpHandler(td->_sockfd);delete td;                         // 释放线程信息对象return nullptr;}~HttpServer(){}
private:uint16_t _port;MySocket _listensock;
};

测试结果

在这里插入图片描述

服务器收到的请求:

在这里插入图片描述

HTTP服务器实现-version 2

在实现version2版本的服务器之前,先来理解两个点:

1.HTTP请求行中的URL作用

  • 定义

    HTTP请求行中的URL是客户端请求的资源路径,比如GET /index.html HTTP/1.1;这里的/index.html就是URL。

  • 作用

    • 定位资源:URL告诉服务器客户端向要访问的具体资源(比如网页,图片,文件等)。
    • 传递参数:URL可以包含查询参数(比如?name=张三),用于向服务器传递额外的信息。
    • 区分请求:不同的URL对应不同的资源,服务器根据URL返回不同的内容。

2.web根目录/

  • 定义

    web根目录是服务器上存放网站文件的根文件,所有HTTP请求的URL都是相对于这个目录的。

  • 作用

    • 文件组织:web根目录下通常包含网站的所有文件,比如HTML,CSS,JavaScript,图片等。
    • 安全隔离:服务器只允许访问web根目录下的文件,防止用户访问服务器上的其他敏感文件。
    • URL映射:URL路径直接映射到web根目录下的文件路径。
  • 示例

    假设web根目录是/wwwroot

    • 请求GET /index.html:服务器返回/wwwroot/index.html文件。
    • 请求GET /a/b/hello.html:服务器返回/wwwroot/a/b/hello.html文件。

明白了上面两点后,再来修改自己写的服务器,也实现通过不同的URL地址访问不同的网页资源。

#pragma once#include <iostream>
#include <string>
#include <pthread.h>
#include <vector>
#include <sstream>
#include <fstream>
#include "Socket.hpp"
#include "Log.hpp"extern Log log;const std::string wwwroot = "./wwwroot";      // web根目录
const std::string seq = "\r\n";               // 分隔符
const std::string homepage = "index.html";    // 首页class HttpServer;class ThreadData{
public:ThreadData(const int &sockfd, HttpServer *svr): _sockfd(sockfd), httpsvr(svr){}public:int _sockfd;HttpServer *httpsvr;
};class HttpRequest{
public:// 反序列化void Deserialize(std::string req){while(true){std::size_t pos = req.find(seq);if (pos == std::string::npos){break;}std::string cur = req.substr(0, pos);if (cur.empty()){break;}req_header.push_back(cur);req.erase(0, pos + seq.size());}text = req;}void Parse(){std::stringstream ss(req_header[0]);ss >> method >> url >> http_version;file_path = wwwroot;                         // ./wwwrootif (url == "/" || url == "/index.html"){file_path += "/";file_path += homepage;                   // ./wwwroot/index.html}else{file_path += url;                        // ./wwwroot/...}}void DebugPrint(){for(auto &line : req_header){std::cout << "-----------------------" << std::endl;std::cout << line << std ::endl<< std::endl;}std::cout << "method: " << method << std::endl;std::cout << "url: " << url << std::endl;std::cout << "http_version: " << http_version << std::endl;std::cout << "file_path: " << file_path << std::endl;std::cout << text << std::endl;}public:std::vector<std::string> req_header;     // 请求报头的每一行存放到数组中std::string text;                        // 请求正文// 请求行解析之后的结果std::string method;std::string url;std::string http_version;std::string file_path;
};class HttpServer{
public:HttpServer(const uint16_t port):_port(port){}// 初始化服务器void InitServer(){_listensock.Socket();_listensock.Bind(_port);_listensock.Listen();log(INFO, "HttpServer Init ... Done");}// 启动服务器void StartServer(){while(true){std::string clientip;uint16_t clientport;int sockfd = _listensock.Accept(&clientip, &clientport);if (sockfd < 0){continue;}log(INFO, "httpserver get a connect, sockfd: %d", sockfd);ThreadData *td = new ThreadData(sockfd, this);pthread_t tid;pthread_create(&tid, nullptr, ThreadRun, td);}}private:// 根据路径路径打开对应的的网页文件 获取响应正文部分static std::string ReadHtmlContent(const std::string &htmlpath){std::ifstream in(htmlpath);if (!in.is_open()){return "404";}std::string line;std::string content;while (std::getline(in, line)){content += line;}in.close();return content;}void HttpHandler(const int &sockfd){char buffer[1024];ssize_t n = recv(sockfd, buffer, sizeof(buffer), 0);if(n > 0){buffer[n] = 0;std::cout << buffer << std::endl;           // 假设读取到的是一个完整的,独立的http请求HttpRequest req;req.Deserialize(buffer);                    // 先反序列化req.Parse();                                // 解析//req.DebugPrint();                         // 测试打印std::string text;                                       // 响应正文部分text = ReadHtmlContent(req.file_path);std::string response_line = "HTTP/1.0 200 OK\r\n";      // 状态行std::string response_header = "Content-Length: ";response_header += std::to_string(text.size());response_header += "\r\n";                              // 响应报头std::string blank_line = "\r\n";                        // 空行分隔符// 构建响应字符串std::string response = response_line;response += response_header;response += blank_line;response += text;// 发送响应ssize_t k = send(sockfd, response.c_str(), response.size(), 0);}close(sockfd);}static void *ThreadRun(void *args){pthread_detach(pthread_self());    // 线程分离ThreadData *td = static_cast<ThreadData *>(args);td->httpsvr->HttpHandler(td->_sockfd);delete td;                         // 释放线程信息对象return nullptr;}~HttpServer(){}
private:uint16_t _port;MySocket _listensock;
};

网页一

index.html

<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>hello world</title>
</head>
<body><h1>Hello world!</h1><h1>第一张网页</h1><h1>第一张网页</h1><h1>第一张网页</h1><h1>第一张网页</h1><h1>第一张网页</h1><h1>第一张网页</h1><a href="http://1.117.74.41:28080/a/b/hello.html">到第二张网页</a><a href="http://1.117.74.41:28080/x/world.html">到第三张网页</a>
</body>
</html>

网页二

hello.html

<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>hello world</title>
</head>
<body><h1>第二张网页</h1><h1>第二张网页</h1><h1>第二张网页</h1><h1>第二张网页</h1><h1>第二张网页</h1><h1>第二张网页</h1><h1>第二张网页</h1><a href="http://1.117.74.41:28080">回到首页</a><a href="http://1.117.74.41:28080/x/world.html">到第三张网页</a>
</body>
</html>

网页三

world.html

<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>hello world</title>
</head>
<body><h1>第三张网页</h1><h1>第三张网页</h1><h1>第三张网页</h1><h1>第三张网页</h1><h1>第三张网页</h1><h1>第三张网页</h1><h1>第三张网页</h1><a href="http://1.117.74.41:28080">回到首页</a><a href="http://1.117.74.41:28080/a/b/hello.html">到第二张网页</a>
</body>
</html>

网页资源目录结构:

在这里插入图片描述

测试结果

访问第一张网页资源:

在这里插入图片描述

服务器收到的请求:

在这里插入图片描述

访问第二张网页资源:

在这里插入图片描述

服务器收到的请求:

在这里插入图片描述

访问第三张网页资源:

在这里插入图片描述

服务器收到的请求:

在这里插入图片描述

四.HTTP的细节字段

HTTP的请求方法

我们在日常使用网站时,比如登录时需要提交我们的用户名和密码等数据,而数据提交给服务器最常见的方式就是表单(属于web前端方面的知识,了解即可)。

当用户填写表单后,输入用户名和密码后,点击提交按钮,就会触发表单的submit事件,然后由浏览器发送请求,我们输入的数据就会作为参数提交,具体如何提交参数,不同的请求方法有不同的方式,最常用的就是GETPOST方法。

GET方法和POST方法都支持参数的提交

  • 如果使用GET方法时,提交的参数是通过URL提交的,以?为起始符号,多个参数之间使用&符号分割;参数数量有效,并且不私秘

修改第一张网页为登陆界面进行测试:

<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head>
<body><form action="/a/b/hello.html" method="get">name: <input type="text" name="name"><br>password: <input type="password" name="passwd"><br><input type="submit" value="提交"></form>
</body>
</html>

测试结果:

输入的参数name=zmh以及passwd=123456通过URL传参

在这里插入图片描述

服务器收到的请求:

在这里插入图片描述

  • 如果使用post方法时,提交的参数是通过请求正文提交的
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head>
<body><form action="/a/b/hello.html" method="post">name: <input type="text" name="name"><br>password: <input type="password" name="passwd"><br><input type="submit" value="提交"></form>
</body>
</html>

测试结果:

在这里插入图片描述

服务器收到的请求:

输入的参数通过请求正文提交

在这里插入图片描述

HTTP的状态码

1. 2xx(成功)

  • 200 OK

    请求成功,服务器已返回请求的数据。这是最常见的状态码,表示一切正常。

  • 201 Created

    请求成功,并且服务器创建了新的资源(比如 POST 请求创建了一个新用户)。

  • 204 No Content

    请求成功,但服务器没有返回任何内容(比如 DELETE 请求后,资源已删除)。

2. 3xx(重定向)

  • 301 Moved Permanently(永久重定向)

    请求的资源已永久移动到新的 URL,浏览器会自动跳转到新地址。

  • 302 Found(临时重定向)

    请求的资源临时移动到新的 URL,浏览器会跳转到新地址,但下次可能还会请求原地址。

3. 4xx(客户端错误)

  • 400 Bad Request

    请求语法错误,服务器无法理解(比如参数格式不对)。

  • 401 Unauthorized

    请求需要身份验证,客户端未提供有效的认证信息。

  • 403 Forbidden

    服务器理解请求,但拒绝执行(比如权限不足)。

  • 404 Not Found

    请求的资源不存在,服务器找不到对应的文件或页面。

  • 405 Method Not Allowed

    请求的 HTTP 方法(如 GET、POST)不被服务器支持。

4. 5xx(服务器错误)

  • 500 Internal Server Error

    服务器内部错误,无法完成请求(比如代码异常)。

  • 502 Bad Gateway

    服务器作为网关或代理,从上游服务器收到无效响应。

  • 503 Service Unavailable

    服务器暂时不可用(比如过载或维护)。

  • 504 Gateway Timeout

    服务器作为网关或代理,等待上游服务器响应超时。

补充内容

1.实现一个自己的404错误界面

err.html:错误文件

<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>hello world</title>
</head>
<body><h1>你要访问的文件是: 404</h1>
</body>
</html>

修改一下源代码:

如果访问一个不存在的资源,就访问错误文件err.html,显示错误界面:

在这里插入图片描述

测试结果:

在这里插入图片描述

查看服务器发送的响应:

(这里使用的抓包工具是Fiddler

在这里插入图片描述

2.什么是临时重定向和永久重定向

临时重定向

  • 服务器告诉浏览器:”资源暂时在另一个地方,请去哪里找,但下次可能还会变。“
  • 浏览器会跳转到新地址,但下一次请求时仍会尝试访问源地址。
  • 搜索引擎不会更新索引,仍会保留原地址。

永久重定向

  • 服务器告诉浏览器:”资源已永久移动到新地址,以后请直接访问新地址。“
  • 浏览器会跳转到新地址,并记住新地址,下一次直接访问新地址。
  • 搜索引擎会更新索引,将原地址的权重转移到新地址。

重定向响应报头

  • Location字段
    • 当服务器返回301(永久重定向)或302(临时重定向)时,必须在响应报头中包含Location字段。
    • 这个字段的值是新地址的URL,浏览器会根据这个地址跳转。

示例

修改一下服务器源代码,如果访问一个不存在的资源,不再是访问上面的错误文件err.html,而是临时重定向到新地址http://www.qq.com,由浏览器二次发送请求。

在这里插入图片描述

输入一个不存在的资源路径进行访问,比如:1.117.74.41:28080/q,就会自动跳转到新地址:

在这里插入图片描述

查看服务器发送的响应:

在这里插入图片描述

HTTP的常见Header

1.请求头(Request Header)

  • Host
    • 作用:指定请求的目标服务器的域名和端口号
    • 示例:Host: www.example.com:80
    • 说明:HTTP/1.1版本协议要求必须包含该字段,用于区分同一IP上的多个虚拟机。
  • 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
    • 说明:服务器可以根据该字段返回适配的页面(比如移动端或者PC端)
  • Referer
    • 作用:标识请求来源的URL
    • 示例:Referer: https://www.example.com/page
    • 说明:服务器可以根据该字段判断请求是否来自合法来源,防止CSRF攻击。
  • Cookie
    • 作用:携带客户端存储的Cookie信息。
    • 示例:Cookie: session=abc123456
    • 说明:用于维持用户会话状态,比如登录信息,购物车数据等(这个字段后面会重点讲)。

2. 响应头(Response Headers)

  • Content-Type

    • 作用:指定响应内容的 MIME 类型。
    • 示例:Content-Type: text/html; charset=UTF-8
    • 说明:浏览器根据此字段解析响应内容(如 HTML、JSON、图片等)。
  • Content-Length

    • 作用:指定响应内容的字节数。
    • 示例:Content-Length: 1234
    • 说明:浏览器根据此字段判断响应是否接收完整。
  • Location

    • 作用:指定重定向的目标 URL。
    • 示例:Location: https://www.example.com/new-page
    • 说明:当服务器返回 301(永久重定向)或 302(临时重定向)时,浏览器会根据此字段跳转到新地址(这个字段在前面已经提到过了)。
  • Set-Cookie

    • 作用:服务器要求客户端存储的 Cookie 信息。
    • 示例:Set-Cookie: session=abc123; Path=/; HttpOnly
    • 说明:用于维持用户会话状态,如登录信息、购物车数据等(这个后面重点讲)。

补充内容

1.长连接和短连接

长连接

  • 定义

    长连接是指客户端和服务端建立连接后,保持连续不关闭,后续请求可以复用这个连接。

  • 特点

    • 复用连接:多个HTTP请求可以共用一个TCP连接,避免重复建立连接的开销
    • 减少延迟:避免每次请求建立连接都重新握手和挥手,减少网络延迟
    • 提高性能:适合需要频繁请求的场景
  • 实现方式

    通过响应头Connection:keep-alive字段实现;HTTP/1.1默认支持长连接。

短连接

  • 定义

    短连接是指客户端和服务器建立连接后,发送完请求并收到响应后立即关闭连接。

  • 特点

    • 每次请求都建立新连接:每次HTTP请求都需要重新建立TCP连接,请求完后立即关闭
    • 增加延迟:每次请求建立连接,都需要握手和挥手,增加网络延迟
    • 资源消耗:频繁建立和关闭连接会消耗服务器和客户端的资源(比如CPU,内存,网络带宽)
  • 实现方式

    通过Connection:close字段实现,服务器响应后立即关闭连接;HTTP/1.0默认使用短连接,除非设置长连接响应头字段。

在这里插入图片描述

一个巨大的网页是包含非常多的资源的(比如图片等资源),请求获取网页资源时也要请求获取网页中包含的其他资源

网页加载的流程

  • 初始请求

    当用户访问一个网页时,浏览器会先发送一个HTTP请求,获取该网页的HTML文件。

  • 解析HTML文件

    浏览器解析网页的HTML文件,发现其中包含多个资源(比如图片,CSS,JavaScript等),例如:

    <img src="/images/logo.png" />
    <img src="/images/banner.jpg" />
    
  • 长连接复用

    如果服务器支持长连接,浏览器就会复用同一个TCP连接,依次发送多个HTTP请求获取HTML文件中包含的资源,然后服务器依次处理并返回响应。

    1.请求GET /images/logo.png

    2.请求GET /images/banner.jpg

  • 连接关闭

    当所有资源加载完后,或者连接超时,浏览器就会关闭连接;如果后续需要加载新资源,浏览器就会重新建立连接。

在实际效果中,采用长连接减少了连接建立和关闭的开销,网页加载速度更快,现代网页大多都是采用长连接

修改上面的服务器代码以及在网站首页中添加几张图片,模拟实现网页加载的效果

HttpServer.hpp修改:

#pragma once#include <iostream>
#include <string>
#include <pthread.h>
#include <vector>
#include <sstream>
#include <fstream>
#include <unordered_map>
#include "Socket.hpp"
#include "Log.hpp"extern Log log;const std::string wwwroot = "./wwwroot";      // web根目录
const std::string seq = "\r\n";               // 分隔符
const std::string homepage = "index.html";    // 首页class HttpServer;class ThreadData{
public:ThreadData(const int &sockfd, HttpServer *svr): _sockfd(sockfd), httpsvr(svr){}public:int _sockfd;HttpServer *httpsvr;
};class HttpRequest{
public:// 反序列化void Deserialize(std::string req){while(true){std::size_t pos = req.find(seq);if (pos == std::string::npos){break;}std::string cur = req.substr(0, pos);if (cur.empty()){break;}req_header.push_back(cur);req.erase(0, pos + seq.size());}text = req;}void DebugPrint(){for(auto &line : req_header){ std::cout << "-----------------------" << std::endl;std::cout << line << std ::endl<< std::endl;}std::cout << "method: " << method << std::endl;std::cout << "url: " << url << std::endl;std::cout << "http_version: " << http_version << std::endl;std::cout << "file_path: " << file_path << std::endl;std::cout << text << std::endl;}void Parse(){std::stringstream ss(req_header[0]);ss >> method >> url >> http_version;file_path = wwwroot;                         // ./wwwrootif (url == "/" || url == "/index.html"){file_path += "/";file_path += homepage;                   // ./wwwroot/index.html}else{file_path += url;                        // ./wwwroot/...}// 修改点一:std::size_t pos = file_path.rfind(".");if (pos == std::string::npos){suffix = ".html";}else{suffix = file_path.substr(pos);}}public:std::vector<std::string> req_header;     // 请求报头的每一行存放到数组中std::string text;                        // 请求正文// 请求行解析之后的结果std::string method;std::string url;std::string http_version;std::string file_path;std::string suffix;                      // 后缀字符串
};class HttpServer{
public:HttpServer(const uint16_t port):_port(port){// 修改点二:content_type.insert({".html", "text/html"});content_type.insert({".png", "image/png"});content_type.insert({"jpg", "image/jpg"});}// 初始化服务器void InitServer(){_listensock.Socket();_listensock.Bind(_port);_listensock.Listen();log(INFO, "HttpServer Init ... Done");}// 启动服务器void StartServer(){while(true){std::string clientip;uint16_t clientport;int sockfd = _listensock.Accept(&clientip, &clientport);if (sockfd < 0){continue;}log(INFO, "httpserver get a connect, sockfd: %d", sockfd);ThreadData *td = new ThreadData(sockfd, this);pthread_t tid;pthread_create(&tid, nullptr, ThreadRun, td);}}private:// 根据路径路径打开对应的的网页文件 获取响应正文部分static std::string ReadHtmlContent(const std::string &htmlpath){// 修改点三:std::ifstream in(htmlpath, std::ios::binary);if(!in.is_open()){return "404";}in.seekg(0, std::ios_base::end);auto len = in.tellg();in.seekg(0, std::ios_base::beg);std::string content;content.resize(len);in.read((char *)content.c_str(), content.size());in.close();return content;}std::string SuffixToDesc(const std::string &suffix){auto iter = content_type.find(suffix);if(iter==content_type.end()){return content_type[".html"];}else{return content_type[suffix];}}void HttpHandler(const int &sockfd){char buffer[1024];ssize_t n = recv(sockfd, buffer, sizeof(buffer), 0);if(n > 0){buffer[n] = 0;std::cout << buffer << std::endl;           // 假设读取到的是一个完整的,独立的http请求HttpRequest req;req.Deserialize(buffer);                    // 先反序列化req.Parse();                                // 解析//req.DebugPrint();                         // 测试打印std::string text;                                           // 响应正文部分text = ReadHtmlContent(req.file_path);bool ok = true;if (text == "404"){ok = false;std::string err_file_path = wwwroot;err_file_path += "/err.html";text = ReadHtmlContent(err_file_path);}std::string response_line;                                   // 状态行if(ok){response_line = "HTTP/1.1 200 OK\r\n"; }else{//response_line = "HTTP/1.0 404 Not Found\r\n";   response_line = "HTTP/1.0 302 Found\r\n";                // 临时重定向}std::string response_header = "Content-Length: ";            // 响应报头response_header += std::to_string(text.size());response_header += "\r\n";                                  // if(!ok){//     response_header += "Location: http://www.qq.com\r\n";   // 新地址的URL// }// 修改点四:response_header += "Content-Type: ";response_header += SuffixToDesc(req.suffix);response_header += "\r\n";std::string blank_line = "\r\n";                            // 空行分隔符// 构建响应字符串std::string response = response_line;response += response_header;response += blank_line;response += text;// 发送响应ssize_t k = send(sockfd, response.c_str(), response.size(), 0);}close(sockfd);}static void *ThreadRun(void *args){pthread_detach(pthread_self());    // 线程分离ThreadData *td = static_cast<ThreadData *>(args);td->httpsvr->HttpHandler(td->_sockfd);delete td;                         // 释放线程信息对象return nullptr;}~HttpServer(){}
private:uint16_t _port;MySocket _listensock;std::unordered_map<std::string, std::string> content_type;
};

index.html修改:

<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head>
<body><h1>第一张网页</h1><h1>第一张网页</h1><img src="/images/1.png" /><img src="/images/2.jpg" />
</body>
</html>

网页资源目录结构:

在这里插入图片描述

测试结果:

在这里插入图片描述

服务器收到的请求:先是收到网页资源请求,然后是两张图片资源的请求

在这里插入图片描述

2.HTTP对登陆用户的会话保持功能

我们在日常使用网站时,比如B站,第一次使用时会让我们先进行用户登录,当登录之后关闭网页,再打开该网页通常都是直接显示已登录状态,不用再次登录,这就是登录用户的会话保持功能。

具体实现则是通过CookieSession机制

Cookie机制

  • 定义

    Cookie是服务器存储在客户端(浏览器)中的一小段文本信息,用于标识用户身份或记录用户状态。

  • 工作流程

    1.用户登录:用户输入用户名和密码,提交登录表单。

    2.服务器验证:服务器验证用户名和密码,如果正确,生成一个唯一的会话ID(Session ID)。

    3.设置Cookie:服务器在响应头中设置Set-Cookie字段,将会话ID发送给浏览器。

    4.浏览器存储:浏览器将Cookie存储在本地后续请求时自动携带。

    5.会话保持:用户访问其他页面时,浏览器会自动在请求报头中携带Cookie。

    6.服务器验证:服务器根据Cookie中的会话ID识别用户身份,保持会话状态。

Session机制

  • 定义

    Session是服务器存储用户会话信息的机制,通常与会话ID关联。

  • 工作流程

    1.用户登录:用户输入用户名和密码,提交登录表单。

    2.服务器验证:服务器验证用户名和密码,如果正确,生成一个唯一的会话ID(Session ID)。

    3.存储会话信息:服务器将用户信息(比如用户ID,权限等)存储在会话中,会话ID通过Cookie发送给浏览器。

    4.会话保持:用户访问其他页面时,浏览器会自动在请求报头中携带Cookie。

    6.服务器验证:服务器根据Cookie中的会话ID识别用户身份,保持会话状态。

  • 会话存储方式

    • 内存存储:会话信息存储在服务器内存中,适合单机部署。
    • 数据库存储:会话信息存储在数据库中,适合分布式部署。
    • Redis存储:会话信息存储在Redis中,适合高并发场景。

接下来先在我们自己的服务器发送的响应报头中添加一个Set-Cookie字段,模拟实现Cookie机制看看效果

在这里插入图片描述

修改inde.html首页为登陆界面:

<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head>
<body><form action="/a/b/hello.html" method="post">name: <input type="text" name="name"><br>password: <input type="password" name="passwd"><br><input type="submit" value="提交"></form>
</body>
</html>

测试结果:

从登录界面进入到第二张网页后,点击刷新旁边的三角,找到Cookie点击,就能看到当前存储的Cookie文件

在这里插入图片描述

在这里插入图片描述

服务器收到的请求:

当从登录界面进入第二张网页,再次发送请求时就会将Cookie文件中的session=abc123456发送过去。

在这里插入图片描述

注意事项

1.核心机制

  • Cookie 和 Session 本质上相同:都是通过会话 ID 关联客户端和服务器端的会话数据。

  • 客户端存储会话 ID:会话 ID 存储在客户端(Cookie),客户端每次请求时携带。

  • 服务器存储会话数据:会话数据(如用户信息、权限、购物车等)存储在服务器。

2. 区别

  • Cookie 机制

    • 重点:强调会话 ID 由客户端存储。
    • 适用场景:适合存储少量数据(如会话 ID、用户偏好设置)。
  • Session 机制

    • 重点:强调会话数据由服务器存储。
    • 适用场景:适合存储大量数据(如用户信息、权限、购物车等)。

总结

  • Cookie和Session本质上相同

    都是将会话ID存储在客户端中,会话数据存储在服务器上;客户端只能访问会话ID,无法直接修改会话数据。

  • 区别在于概念和实现:Cookie强调客户端存储会话ID,Session强调服务器存储会话数据。

  • 实际应用中:Session通常依赖Cookie机制实现,但是Session更灵活,安全,适合存储大量数据。

以上就是关于应用层协议HTTP的讲解,如果哪里有错的话,可以在评论区指正,也欢迎大家一起讨论学习,如果对你的学习有帮助的话,点点赞关注支持一下吧!!!

相关文章:

  • JUC并发编程(一)
  • 38、响应处理-【源码分析】-HTTPMessageConverter原理
  • 7.文本内容处理sort,uniq,out,cat,comm,diff
  • n8n部署工作流websecscan-ai-powered-website-security-auditor
  • springboot04
  • 【PhysUnits】15.10 类型级别的乘法运算(mul.rs)
  • ps黑白调整
  • Linux 权限管理入门:从基础到实践
  • 向量空间的练习题目
  • 【深度学习-Day 21】框架入门:神经网络模型构建核心指南 (Keras PyTorch)
  • 告别printf!嵌入式系统高效日志记录方案
  • 第四十天打卡
  • Java 2D 图形类总结与分类
  • 放弃 tsc+nodemon 使用 tsx 构建Node 环境下 TypeScript + ESM 开发环境搭建指南
  • QT入门学习(二)---继承关系、访问控制和变量定义
  • 【环境搭建】Java、Python、Nodejs等开发环境搭建
  • Java垃圾回收机制详解:从原理到实践
  • ubuntu24.04 查看时区并设置Asia/Shanghai时区
  • 【Python序列化】TypeError: Object of type xxx is not JSON serializable问题的解决方案
  • Golang学习之旅
  • 免费申请注册网站/正规seo多少钱
  • 外贸简单网站建设/百度人工客服电话是多少
  • 用 net做网站/网站收录怎么弄
  • 天津武清网站建设/培训课程有哪些
  • 高明网站建设公司/培训机构好还是学校好
  • 深圳宝安区是市中心吗/seo排名工具提升流量