一、HTTP协议

(1)虽然我们说,应用层协议是我们程序员自己定的。但实际上,已经有大佬们定义了一些现成的,又非常好用的应用层协议,供我们直接参考使用,HTTP(超文本传输协议)就是其中之一。
(2)在互联网世界中,HTTP(HyperText Transfer Protocol,超文本传输协议)是一个至关重要的议。
它定义了客户端(如浏览器)与服务器之间如何通信,以交换或传输超文本(如HTML文档)。
(3)HTTP协议是客户端与服务器之间通信的基础。客户端通过HTTP协议向服务器发送请求,服务器收到请求后处理并返回响应。HTTP协议是一个无连接、无状态的协议,即每次请求都需要建立新的连接,且服务器不会保存客户端的状态信息。

二、认识URL

URL(UniformResourceLocator,统一资源定位符),俗称“网址”,是互联网上用来唯一定位资源
(网页、图片、视频、接口等)的标准化字符串。

三、urlencode和urldecode

像口/?:口等这样的字符,已经被url当做特殊意义理解了,因此这些字符不能随意出现,比如,某
个参数中需要带有这些特殊字符,就必须先对特殊字符进行转义。

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

注意:这个编码过程是网页自己做的,服务器拿到的是已经转换过的

四、HTTP协议请求与响应格式

4.1HTTP请求格式

(1)首行:[方法]+[url]+[版本]。
(2)Header:请求的属性,冒号分割的键值对;每组属性之间使用Vr\n分隔;遇到空行表示Header部分结束。
(3)Body:空行后面的内容都是Body。Body允许为空字符串。如果Body存在,则在Header中会有Content-Length属性来标识Body的长度。

4.2HTTP响应格式

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

4.3使用fiddler进行抓包

五、HTTP服务器的实现(随请求响应版本)

5.1代码及整体文件结构

对应的目录结构如下:

封装套接字(Sock.hpp文件):

#pragma once
#include <iostream>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <cstring>
#include "Log.hpp"

const int backlog = 10;
extern log lg;

enum
{
    SocketError = 2,
    BindError,
    ListenError
};

class Sock
{
public:
    Sock()
    : _sockfd(-1)
    {
    }

    ~Sock()
    {
        if (_sockfd >= 0)
        {
            close(_sockfd);
        }
    }

    // 创建套接字
    void Socket()
    {
        _sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (_sockfd < 0)
        {
            lg.logmessage(Fatal, "socket fail, %s, %d", errno, strerror(errno));
            exit(SocketError);
        }
        int opt = 1;
        setsockopt(_sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
    }

    // 创建一个绑定接口
    void Bind(const uint16_t port)
    {
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(port);
        local.sin_addr.s_addr = INADDR_ANY;

        if (bind(_sockfd, (struct sockaddr *)&local, sizeof(local)) < 0)
        {
            lg.logmessage(Fatal, "bind fail, %s, %d", errno, strerror(errno));
            exit(BindError);
        }
    }

    void Listen()
    {
        if (listen(_sockfd, backlog) < 0)
        {
            lg.logmessage(Fatal, "listen fail, %s, %d", errno, strerror(errno));
            exit(ListenError);
        }
    }

    int Accept(std::string *clientip, uint16_t *clientport)
    {
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);
        int newfd = accept(_sockfd, (struct sockaddr *)&peer, &len);
        if (newfd < 0)
        {
            lg.logmessage(Warning, "accept fail, %s, %d", errno, strerror(errno));
            return -1;
        }

        char ipstr[64];
        inet_ntop(AF_INET, &peer.sin_addr, ipstr, sizeof(ipstr));
        // 获取远端主机的信息
        *clientport = ntohs(peer.sin_port);
        *clientip = ipstr;

        return newfd;
    }

    bool Connect(const std::string &ip, const uint16_t &port)
    {
        struct sockaddr_in peer;
        memset(&peer, 0, sizeof(peer));
        peer.sin_family = AF_INET;
        peer.sin_port = htons(port);
        inet_pton(AF_INET, ip.c_str(), &peer.sin_addr);

        int n = connect(_sockfd, (struct sockaddr *)&peer, sizeof(peer));
        if (n == -1)
        {
            std::cerr << "connect to" << ip << ":" << port << std::endl;
            return false;
        }
        return true;
    }

    void Close()
    {
        close(_sockfd);
    }

    int Fd()
    {
        return _sockfd;
    }

private:
    int _sockfd;
};

日志文件(Log.hpp文件):

#pragma once

#include <iostream>
#include <stdarg.h>
#include <time.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
#include <fcntl.h>
#include<string.h>

#define SIZE 1024

// 设置日志等级
#define Info 0
#define Debug 1
#define Warning 2
#define Error 3
#define Fatal 4

#define Screen 1
#define Onefile 2
#define Classfile 3

#define logFile "log.txt"

class log
{
public:
    log()
    {
        PrintMethod = Screen;
        path = "./log/";
    }
    void Enable(int method)
    {
        PrintMethod = method;
    }

    std::string levelToString(int level)
    {
        switch (level)
        {
        case Info:
            return "Info";
        case Debug:
            return "Debug";
        case Warning:
            return "Warning";
        case Error:
            return "Error";
        default:
            return "None";
        }
    }

    void logmessage(int level, const char *format, ...) // 后面的省略号表示可变参数
    {

        char leftbuffer[SIZE];

        time_t t = time(nullptr);
        struct tm *ctime = localtime(&t);

        snprintf(leftbuffer, sizeof(leftbuffer), "[%s],[%d-%d-%d %d:%d:%d]", levelToString(level).c_str(),
                 ctime->tm_year + 1900, ctime->tm_mon, ctime->tm_mday,
                 ctime->tm_hour, ctime->tm_min, ctime->tm_sec);

        char rightbuffer[SIZE];

        va_list s;
        va_start(s, format);
        vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);
        va_end(s);

        char logtxt[SIZE * 3];
        snprintf(logtxt, sizeof(logtxt), "%s %s\n", leftbuffer, rightbuffer);

        // printf("%d-%d-%d %d:%d:%d\n",ctime->tm_year + 1900, ctime->tm_mon, ctime->tm_mday, ctime->tm_hour,ctime->tm_min,ctime->tm_sec);

        //printf("%s", logtxt);
        PrintLog(level,logtxt);
        // 格式:默认部分+自定义部分(可变参数部分)
    }

    void PrintLog(int level, const std::string& logtxt)
    {
        switch(PrintMethod)
        {
            case Screen:
                std::cout << logtxt << std::endl;
            break;
            case Onefile:
                printOneFile(logFile, logtxt);
            break;
            case Classfile:
                printClassFile(level, logtxt);
            break;
            default:
            break;
        }
    }
    void printOneFile(const std::string &logname, const std::string &logtxt)
    { 
        std::string _logname = path + logname;
        int fd = open(_logname.c_str(),O_WRONLY|O_CREAT|O_APPEND,0666);
        if(fd < 0)
        {
            return  ;
        }
        write(fd,logtxt.c_str(),logtxt.size());
        close(fd);

    }
    void printClassFile(int level, const std::string &logtxt)
    {
        std::string filename = logFile;
        filename += ".";
        filename += levelToString(level);
        printOneFile(filename, logtxt);
    }
    ~log()
    {
    }
private:
    int PrintMethod;
    std::string path;
};


log lg;

HTTP头文件(HTTPServer.hpp文件):

#pragma once
#include <iostream>
#include <string>
#include <sstream>
#include <pthread.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <fstream>
#include <unordered_map>
#include <vector>
#include "Socket.hpp"
#include "Log.hpp"

static const int defaultport = 8080;
class HttpServer;
const std::string wwwroot = "./wwwroot"; // web根目录
const std::string sep = "\r\n";
const std::string homepage = "idex.html";

struct ThreadData
{
public:
    ThreadData(int fd, HttpServer *ts)
        : sockfd(fd), httpsvr(ts)
    {
    }

public:
    int sockfd;
    HttpServer *httpsvr;
};

class HttpRequest
{
public:
    void Deserialize(std::string req)
    {
        // 反序列化
        while (true)
        {
            // 处理第一行(请求行)
            std::size_t pos = req.find(sep);
            if (pos == std::string::npos)
            {
                break;
            }
            std::string temp = req.substr(0, pos);
            if (temp.empty())
            {
                break;
            }
            _req_header.push_back(temp);
            req.erase(0, pos + sep.size());
            // 继续对报头处理,获取正文长度,从而截取正文
            // 。。。。
        }
        _text = req;
    }

    // 对序列化后的结果进行解析
    void Parse()
    {
        // 以空格作为分隔符进行处理
        std::stringstream ss(_req_header[0]);
        ss >> method >> url >> http_version;
        file_path = wwwroot;
        if (url == "/" || url == "/idex.html")
        {
            file_path += "/";
            file_path += homepage;
        }
        else
        {
            file_path += url;
        }

        auto pos = file_path.rfind(".");
        if (pos == std::string::npos)
        {
            suffix = ".html";
        }
        else
        {
            suffix = file_path.substr(pos);
        }
    }

    void DebugPrintf()
    {
        // 把报头打印出来
        for (auto &line : _req_header)
        {
            std::cout << "-----------------------------------------------------------------" << std::endl;
            std::cout << line << 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;

    std::string suffix;
};

class HttpServer
{
public:
    HttpServer(uint16_t port = defaultport)
        : _port(port)
    {
        content_type.insert({".html", "text/html"});
        content_type.insert({".png", "image/png"});
    }

    static std::string ReadHtmlContent(const std::string &htmlpath)
    {
        // 读文件操作
        std::ifstream in(htmlpath, std::ios::binary);
        if (!in.is_open())
        {
            return "";
        }

        //二进制读取
        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());

        //字符串读取
        //std::string content;
        //std::string line;
        //while (std::getline(in, line))
        //{
        //    content += line;
        //}

        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 HandleHTTP(int sockfd)
    {
        char buffer[4096];
        ssize_t n = recv(sockfd, buffer, sizeof(buffer) - 1, 0);
        if (n > 0)
        {
            buffer[n] = 0;
            std::cout << buffer;

            // 对读到的请求进行处理
            HttpRequest req;
            req.Deserialize(buffer);
            req.Parse();

            req.DebugPrintf();

            // 返回响应
            bool ok = true;
            std::string test = ReadHtmlContent(req.file_path);
            if (test.empty())
            {
                ok = false;
                std::string err_html = wwwroot;
                err_html += "/";
                err_html += "err.html";
                test = ReadHtmlContent(err_html);
            }
            std::string response_line;
            if (ok == true)
            {
                response_line = "HTTP/1.1 200 OK\r\n";
            }
            else
            {
                response_line = "HTTP/1.1 404 Not Found\r\n";
            }
            std::string response_handler = "content_length:";
            response_handler += std::to_string(test.size());
            response_handler += "\r\n";

            // 请求报头(添加数据类型)
            response_handler += "Content_Type: ";
            response_handler += SuffixToDesc(req.suffix);
            response_handler += "\r\n";
  
            response_handler += "Set-Cookie: name=tjp";
            response_handler += "\r\n";
            response_handler += "Set-Cookie: password=123456";
            response_handler += "\r\n";

            // 还要带一个空行
            std::string block_line = "\r\n";

            std::string response = response_line;
            response += response_handler;
            response += block_line;
            response += test;

            send(sockfd, response.c_str(), response.size(), 0);
        }
        close(sockfd);
    }

    static void *ThreadRun(void *args)
    {
        ThreadData *td = static_cast<ThreadData *>(args);
        pthread_detach(pthread_self());

        td->httpsvr->HandleHTTP(td->sockfd);

        delete td;
        return nullptr;
    }

    bool Start()
    {
        _listen_sock.Socket();
        _listen_sock.Bind(_port);
        _listen_sock.Listen();
        for (;;)
        {
            std::string clientip;
            uint16_t clientport;
            int sockfd = _listen_sock.Accept(&clientip, &clientport);
            if (sockfd < 0)
            {
                continue; // 忽略失败的连接请求
            }
            pthread_t tid;
            ThreadData *td = new ThreadData(sockfd, this);
            pthread_create(&tid, nullptr, ThreadRun, td);
        }
    }

    ~HttpServer()
    {
    }

private:
    Sock _listen_sock;
    uint16_t _port;
    std::unordered_map<std::string, std::string> content_type;
};

HTTP服务器运行文件(HTTPServer.cc文件):

#include "HttpServer.hpp"
#include<iostream>
#include<memory>


int main(  )
{
    std::unique_ptr<HttpServer> svr(new HttpServer());
    svr->Start();
    
    
    return 0;
}

网页代码(idex.html文件):

<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>爱心表白</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            min-height: 100vh;
            display: flex;
            justify-content: center;
            align-items: center;
            background-color: #000;
            overflow: hidden;
        }

        /* 爱心容器 */
        .heart {
            width: 150px;
            height: 150px;
            position: relative;
            animation: beat 1s infinite alternate;
        }

        /* 爱心形状 */
        .heart::before,
        .heart::after {
            content: '';
            width: 75px;
            height: 120px;
            background-color: #ff4d6d;
            border-radius: 50px 50px 0 0;
            position: absolute;
            top: 0;
        }

        .heart::before {
            left: 75px;
            transform: rotate(-45deg);
            transform-origin: 0 100%;
        }

        .heart::after {
            left: 0;
            transform: rotate(45deg);
            transform-origin: 100% 100%;
        }

        /* 跳动动画 */
        @keyframes beat {
            0% {
                transform: scale(1);
            }

            100% {
                transform: scale(1.15);
            }
        }

        /* 表白文字 */
        .text {
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            color: #fff;
            font-size: 24px;
            font-weight: bold;
            z-index: 10;
            text-shadow: 0 0 10px #ff4d6d, 0 0 20px #ff4d6d;
            white-space: nowrap;
        }
    </style>
</head>

<body>
    <div class="heart"></div>
    <div class="text">hello world</div>

    <a href="http://182.254.175.111:8080/love.html">点击跳转</a>
    <a href="http://www.baidu.com">点击跳转</a>
</body>

</html>

err.html文件:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>404 - 页面未找到</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: "Microsoft YaHei", sans-serif;
        }

        body {
            background-color: #f8f9fa;
            display: flex;
            justify-content: center;
            align-items: center;
            min-height: 100vh;
            text-align: center;
            padding: 20px;
        }

        .container {
            max-width: 600px;
            width: 100%;
        }

        .error-code {
            font-size: 10rem;
            font-weight: bold;
            color: #dc3545;
            margin-bottom: 20px;
        }

        .title {
            font-size: 2rem;
            color: #333;
            margin-bottom: 15px;
        }

        .desc {
            font-size: 1.1rem;
            color: #666;
            margin-bottom: 30px;
            line-height: 1.6;
        }

        .back-btn {
            display: inline-block;
            padding: 12px 30px;
            background-color: #007bff;
            color: white;
            text-decoration: none;
            border-radius: 6px;
            font-size: 1rem;
            transition: background 0.3s;
        }

        .back-btn:hover {
            background-color: #0056b3;
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="error-code">404</div>
        <h1 class="title">页面未找到</h1>
        <p class="desc">抱歉,你访问的页面不存在或已被移动。<br>请检查网址是否正确,或返回首页继续浏览。</p>
        <a href="/" class="back-btn">返回首页</a>
    </div>
</body>
</html>

love.html文件:

<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>爱心表白</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            min-height: 100vh;
            display: flex;
            justify-content: center;
            align-items: center;
            background-color: #000;
            overflow: hidden;
        }

        /* 爱心容器 */
        .heart {
            width: 150px;
            height: 150px;
            position: relative;
            animation: beat 1s infinite alternate;
        }

        /* 爱心形状 */
        .heart::before,
        .heart::after {
            content: '';
            width: 75px;
            height: 120px;
            background-color: #ff4d6d;
            border-radius: 50px 50px 0 0;
            position: absolute;
            top: 0;
        }

        .heart::before {
            left: 75px;
            transform: rotate(-45deg);
            transform-origin: 0 100%;
        }

        .heart::after {
            left: 0;
            transform: rotate(45deg);
            transform-origin: 100% 100%;
        }

        /* 跳动动画 */
        @keyframes beat {
            0% {
                transform: scale(1);
            }

            100% {
                transform: scale(1.15);
            }
        }

        /* 表白文字 */
        .text {
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            color: #fff;
            font-size: 24px;
            font-weight: bold;
            z-index: 10;
            text-shadow: 0 0 10px #ff4d6d, 0 0 20px #ff4d6d;
            white-space: nowrap;
        }
    </style>
</head>

<body>
    <div class="heart"></div>
    <div class="text">我爱你</div>
</body>

</html>

kiss.html文件:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>浪漫雪天 | 情侣接吻</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        /* 页面整体样式 */
        body {
            min-height: 100vh;
            background: linear-gradient(to bottom, #1a1a2e, #16213e); /* 深夜渐变背景 */
            display: flex;
            justify-content: center;
            align-items: center;
            overflow: hidden;
            position: relative;
        }

        /* 雪花动画样式 */
        .snow {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            pointer-events: none;
            background-image: 
                radial-gradient(3px 3px, white, transparent),
                radial-gradient(4px 4px, white, transparent);
            background-size: 100px 100px, 80px 80px;
            animation: snowFall 10s linear infinite;
        }

        /* 雪花下落动画 */
        @keyframes snowFall {
            0% { background-position: 0 0, 0 0; }
            100% { background-position: 100px 1000px, 80px 800px; }
        }

        /* 动图容器 */
        .gif-container {
            z-index: 10;
            border-radius: 20px;
            box-shadow: 0 0 50px rgba(255, 255, 255, 0.3);
            overflow: hidden;
        }

        /* 动图样式 */
        .couple-gif {
            width: 500px;
            max-width: 90vw;
            height: auto;
            display: block;
        }
    </style>
</head>
<body>
    <!-- 雪花背景 -->
    <div class="snow"></div>

    <!-- 情侣动图 -->
    <div class="gif-container">
        <img 
            class="couple-gif" 
            src="https://pic.ntimg.cn/file/20220112/10022400_171104684000_2.gif" 
            alt="雪天情侣接吻动图"
        >
    </div>
</body>
</html>

5.2运行结果

六、HTTP的方法

七、HTTP常见方法

7.1GET方法(重点)

用途:用于请求URL指定的资源
示例:GET /index.html HTTP/1.1
特性:指定资源经服务器端解析后返回响应内容。
注意:我们使用GET方法提交参数给服务器的时候,我们提交的参数是通过urI提交给服务器的

7.2POST方法

用途:用于传输实体的主体,通常用于提交表单数据。
示例:POST /submit.cgi HTTP/1.1
特性:可以发送大量的数据给服务器,并且数据包含在请求体中。
注意:POST方法提交参数给服务器的时候,才通的是请求正文提交数据给服务器。

注意:POET方法相对GET方法更私密,但并不是更安全,GET方法提交参数会导致参数数量受限,而POST不会

八、HTTP状态码

最常见的状态码,比如200(OK)、404(Not Found)、403(Forbidden)、302(Redirect,重定向)、504(Bad Gateway)

九、重定向

重定向就是让服务器指导浏览器,让浏览器访问新的地址。

9.1HTTP状态码301(永久重定向)

(1)当服务器返回HTTP301状态码时,标识请求的资源已经被永久移动到新的位置。
(2)在这种情况下,服务器会在响应中添加一个Location头部,用于指定资源的新位置。这个Location头部包含了新的URL地址,浏览器会自动重定向到该地址。
例如,在HTTP响应中,可能会看到类似于以下的头部信息:

9.2HTTP状态码302(临时重定向)

(1)当服务器返回HTTP302状态码时,表示请求的资源临时被移动到新的位置。
(2)同样地,服务器也会在响应中添加一个Location头部来指定资源的新位置。浏览器会暂时使用新的URL进行后续的请求,但不会缓存这个重定向。
例如,在HTTP响应中,可能会看到类似于以下的头部信息:

十、HTTP常见Header

10.1常见的报头

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

10.2connection报头

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

10.2.1核心作用

(1)管理持久连接:Connection字段还用于管理持久连接(也称为长连接)。持久连接允许客户端和服务器。
在请求/响应完成后不立即关闭TCP连接,以便在同一个连接上发送多个请求和接收多个响应。
(2)持久连接(长连接)
HTTP/1.1:在HTTP/1.1协议中,默认使用持久连接。当客户端和服务器都不明确指定关闭连接时,连接将保持打开状态,以便后续的请求和响应可以复用同一个连接。

HTTP/1.0:在HTTP/1.0协议中,默认连接是非持久的。如果希望在HTTP/1.0上实现持久连接,需要在请求头中显式设置Connection: keep-alive。

10.2.2语法格式

Connection:keep-alive:表示希望保持连接以复用TCP连接
Connection:close:表示请求/响应完成后,应该关闭TCP连接。

十一、HTTP cookie

HTTP协议本身是无状态的,也就是HTTP不记录你上次是否访问过了。HTTP对登录用户的会话保持功能(HTTPCookie)HTTPCookie是服务器通过Set-Cookie响应头下发、浏览器本地存储并在后续请求自动带回的键值对小文本,用来让无状态的HTTP协议具备状态记忆能力。

11.1核心用途

会话管理:登录态、购物车、会话ID。
个性化:主题、语言、字体大小等偏好。
用户跟踪:行为分析、广告归因、跨站统计。

11.2工作流程

1、首次请求:浏览器发请求,无Cookie。
2、服务器下发:响应头带Set-Cookie,浏览器保存(按域名+路径隔离)。
3、后续请求:浏览器自动匹配域名/路径,在请求头Cookie中带回。
4、服务器识别:读取Cookie识别用户/会话,返回对应资源

11.3分类

(1)会话Cookie(Session Cookie):在浏览器关闭时失效
(2)持久Cookie(PersistentCookie):带有明确的过期日期或持续时间,可以跨多个浏览器会话存在
(3)如果cookie是一个持久性的cookie,那么它其实就是浏览器相关的,特定目录下的一个文件。但直接查看这些文件可能会看到乱码或无法读取的内容,因为cookie文件通常以二进制或sqlite格式存储。一般我们查看,直接在浏览器对应的选项中直接查看即可。
在浏览器中查看cookie:

11.4认识 cookie

(1)HTTP存在一个报头选项:Set-Cookie,可以用来进行给浏览器设置Cookie值。
(2)在HTTP响应头中添加,客户端(如浏览器)获取并自行设置并保存Cookie。

11.5基本格式

十二、HTTP Session

12.1定义

HTTPSession是服务器用来跟踪用户与服务器交互期间用户状态的机制。由于HTTP协议是无状态的(每个请求都是独立的),因此服务器需要通过Session来记住用户的信息。

12.2工作原理

当用户首次访问网站时,服务器会为用户创建一个唯一的SessionID,并通过Cookie将其发送到客户端。客户端在之后的请求中会携带这个SessionID,服务器通过SessionID来识别客户,从而获取用户的会话信息。服务器通常会将Session信息存储在内存、数据库或缓存中。

12.3安全性:

与Cookie相似,由于Session ID是在客戸端和服务器之间传递的,因此也存在被窃取的风险。但是一般虽然Cookie被盗取了,但是用户只泄漏了一个SessionID,私密信息暂时没有被泄露的风险SessionID便于服务端进行客户端有效性的管理,比如异地登录。可以通过HTTPS和设置合适的Cookie属性(如 HttpOnly和Secure)来增强安全性。

12.4超时和失效:

Session可以设置超时时间,当超过这个时间后,Session会自动失效。服务器也可以主动使Session失效,例如当用户登出时。
注意:SessionID是服务器统一分配管理的

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐