Cookie

Cookie是用来标识http请求对应的用户的

HTTP Cookie(也称为 Web Cookie、浏览器 Cookie 或简称 Cookie)是服务器发送到

浏览器并保存在浏览器上的一小块数据,它会在浏览器之后向同一服务器再次发

起请求时被携带并发送到服务器上。通常,它用于告知服务器HTTP请求来自哪个用户,具体作用有,保持用户的登录状态、记录用户的偏好习惯

工作原理

1.当用户第一次访问服务器时,服务器会在HTTP响应的响应头中设置 Set-Cookie 选项,用于设置 Cookie 在浏览器上,cookie的本质就是数据,其保存方式有两种,一种是保存在内存中,那这就是会话cookie,浏览器进程退出cookie就失效了,注意不是页面进程退出。还有一种是保存在文件中,那就是持久cookie,浏览器进程退出,该cookie也不会消失,持久cookie不是永久的,而是到了期限时间就失效。

2.在浏览器被设置cookie之后,浏览器发送的http请求中会携带 Cookie 选项,将之前保存的用户标识信息一起发送给服务器。

cookie分类

1.会话 Cookie(Session Cookie):保存在内存中,浏览器进程关闭时失效,注意是浏览器而不是页面。

2.持久 Cookie(Persistent Cookie):带有明确的过期日期或持续时间,保存在文件里, 浏览器退出了也不会失效。

cookie的设置

服务器在 HTTP 响应报文的响应头中添加 Set-Cookie选项,浏览器获取后设置Cookie,具体是保存在内存里还是文件里,看 Set-Cookie 中有没有 expires 字段,有那就是持久cookie,保存在文件中,没有就是会话cookie,保存在内存中

cookie实例:

1、会话级cookie的设置

#include <string>
#include <sys/socket.h>
#include <netinet/in.h>
#include <cstring>
#include <arpa/inet.h>
#include <iostream>
#include <unistd.h>
#include <sys/wait.h>
#include <pthread.h>
#include <vector>
using namespace std;

char timebuffer[1024];

std::string GetMonthName(int month)
{
    std::vector<std::string> months = {"Jan", "Feb", "Mar",
                                       "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
    return months[month];
}
std::string GetWeekDayName(int day)
{
    std::vector<std::string> weekdays = {"Sun", "Mon", "Tue",
                                         "Wed", "Thu", "Fri", "Sat"};
    return weekdays[day];
}
std::string ExpireTimeUseRfc1123(int t) // 秒级别的未来 UTC 时间
{
    time_t timeout = time(nullptr) + t;
    // 这个地方有坑哦
    struct tm *tm = gmtime(&timeout); // 这里不能用 localtime,
    // 因为 localtime 是默认带了时区的.gmtime 获取的就是 UTC 统一时间 char timebuffer[1024];
    //  时间格式如: expires=Thu, 18 Dec 2024 12:00:00 UTC
    snprintf(timebuffer, sizeof(timebuffer),
             "%s, %02d %s %d %02d:%02d:%02d UTC",
             GetWeekDayName(tm->tm_wday).c_str(),
             tm->tm_mday,
             GetMonthName(tm->tm_mon).c_str(),
             tm->tm_year + 1900,
             tm->tm_hour,
             tm->tm_min,
             tm->tm_sec);
    return timebuffer;
}

class TcpServer
{
    static void Service(int fd, struct sockaddr_in &peer)
    {
        cout << "client connected: " << inet_ntoa(peer.sin_addr) << " " << ntohs(peer.sin_port) << endl;
        char buffer[1024];
        while (1)
        {
            int n = read(fd, buffer, sizeof(buffer) - 1);
            if (n > 0)
            {
                buffer[n] = 0;
                cout << buffer;

                const char *hello = "<h1>hello world</h1>";

                string t= ExpireTimeUseRfc1123(60);
                sprintf(buffer, "HTTP/1.0 200 OK\r\nContent-Length:%lu\r\nSet-Cookie: username=zhangsan; path=/a/b\r\n\r\n%s", strlen(hello), hello);
                write(fd, buffer, strlen(buffer));
            }
            else if (n == 0)
            {
                cout << "连接断开,客户端为:" << inet_ntoa(peer.sin_addr) << " " << ntohs(peer.sin_port) << endl;
                break;
            }
        }
    }

    struct ThreadData
    {
        int _fd;
        struct sockaddr_in _peer;

        ThreadData(int fd, struct sockaddr_in peer)
            : _fd(fd), _peer(peer)
        {
        }
    };

    static void *ExecuteThread(void *arg)
    {
        pthread_detach(pthread_self()); // 分离线程,到时候线程函数执行完直接释放tcb
        ThreadData *p = (ThreadData *)arg;
        Service(p->_fd, p->_peer);
        close(p->_fd);
        delete p;
        return nullptr; // BUG----如果线程启动函数执行完毕却没有返回值,那就会出现非法指令的情况
    }

    void ConnectionHandle(int fd, struct sockaddr_in &peer)
    {
        pthread_t t;
        ThreadData *data = new ThreadData(fd, peer);
        pthread_create(&t, nullptr, ExecuteThread, data);
    }

public:
    TcpServer(int port)
        : _port(port)
    {
    }

    void Init()
    {
        _sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_addr.s_addr = htonl(INADDR_ANY);
        local.sin_port = htons(_port);

        int opt = 1;
        setsockopt(_sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
        bind(_sockfd, (struct sockaddr *)&local, sizeof(local));

        listen(_sockfd, 8);
    }

    void Start()
    {
        _isrunning = true;

        while (_isrunning)
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            int fd = accept(_sockfd, (struct sockaddr *)&peer, &len);

            ConnectionHandle(fd, peer);
        }
    }

private:
    int _port;
    int _sockfd;
    bool _isrunning;
};

int main()
{
    TcpServer server(8888);
    server.Init();
    server.Start();

    return 0;
}

使用相同的url访问两次服务器,第一次访问时,http请求中没有Cookie,然后浏览器收到http响应之后在内存中保存了Cookie信息,也就是会话级Cookie,第二次访问时,url请求的路径是/a/b/c,所以http请求中会携带Cookie,信息是username=张三,这样服务器就能识别http请求对应的用户

2、持久级cookie的设置

#include <string>
#include <sys/socket.h>
#include <netinet/in.h>
#include <cstring>
#include <arpa/inet.h>
#include <iostream>
#include <unistd.h>
#include <sys/wait.h>
#include <pthread.h>
#include <vector>
using namespace std;

char timebuffer[1024];

std::string GetMonthName(int month)
{
    std::vector<std::string> months = {"Jan", "Feb", "Mar",
                                       "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
    return months[month];
}
std::string GetWeekDayName(int day)
{
    std::vector<std::string> weekdays = {"Sun", "Mon", "Tue",
                                         "Wed", "Thu", "Fri", "Sat"};
    return weekdays[day];
}
std::string ExpireTimeUseRfc1123(int t) // 秒级别的未来 UTC 时间
{
    time_t timeout = time(nullptr) + t;
    // 这个地方有坑哦
    struct tm *tm = gmtime(&timeout); // 这里不能用 localtime,
    // 因为 localtime 是默认带了时区的.gmtime 获取的就是 UTC 统一时间 char timebuffer[1024];
    //  时间格式如: expires=Thu, 18 Dec 2024 12:00:00 UTC
    snprintf(timebuffer, sizeof(timebuffer),
             "%s, %02d %s %d %02d:%02d:%02d UTC",
             GetWeekDayName(tm->tm_wday).c_str(),
             tm->tm_mday,
             GetMonthName(tm->tm_mon).c_str(),
             tm->tm_year + 1900,
             tm->tm_hour,
             tm->tm_min,
             tm->tm_sec);
    return timebuffer;
}

class TcpServer
{
    static void Service(int fd, struct sockaddr_in &peer)
    {
        cout << "client connected: " << inet_ntoa(peer.sin_addr) << " " << ntohs(peer.sin_port) << endl;
        char buffer[1024];
        while (1)
        {
            int n = read(fd, buffer, sizeof(buffer) - 1);
            if (n > 0)
            {
                buffer[n] = 0;
                cout << buffer;

                const char *hello = "<h1>hello world</h1>";

                string t= ExpireTimeUseRfc1123(60);
                sprintf(buffer, "HTTP/1.0 200 OK\r\nContent-Length:%lu\r\nSet-Cookie: username=zhangsan; path=/a/b; expires=%s\r\n\r\n%s", strlen(hello), timebuffer, hello);
                write(fd, buffer, strlen(buffer));
            }
            else if (n == 0)
            {
                cout << "连接断开,客户端为:" << inet_ntoa(peer.sin_addr) << " " << ntohs(peer.sin_port) << endl;
                break;
            }
        }
    }

    struct ThreadData
    {
        int _fd;
        struct sockaddr_in _peer;

        ThreadData(int fd, struct sockaddr_in peer)
            : _fd(fd), _peer(peer)
        {
        }
    };

    static void *ExecuteThread(void *arg)
    {
        pthread_detach(pthread_self()); // 分离线程,到时候线程函数执行完直接释放tcb
        ThreadData *p = (ThreadData *)arg;
        Service(p->_fd, p->_peer);
        close(p->_fd);
        delete p;
        return nullptr; // BUG----如果线程启动函数执行完毕却没有返回值,那就会出现非法指令的情况
    }

    void ConnectionHandle(int fd, struct sockaddr_in &peer)
    {
        pthread_t t;
        ThreadData *data = new ThreadData(fd, peer);
        pthread_create(&t, nullptr, ExecuteThread, data);
    }

public:
    TcpServer(int port)
        : _port(port)
    {
    }

    void Init()
    {
        _sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_addr.s_addr = htonl(INADDR_ANY);
        local.sin_port = htons(_port);

        int opt = 1;
        setsockopt(_sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
        bind(_sockfd, (struct sockaddr *)&local, sizeof(local));

        listen(_sockfd, 8);
    }

    void Start()
    {
        _isrunning = true;

        while (_isrunning)
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            int fd = accept(_sockfd, (struct sockaddr *)&peer, &len);

            ConnectionHandle(fd, peer);
        }
    }

private:
    int _port;
    int _sockfd;
    bool _isrunning;
};

int main()
{
    TcpServer server(8888);
    server.Init();
    server.Start();

    return 0;
}

使用相同的url访问两次服务器,第一次访问时,http请求中没有Cookie,然后浏览器收到http响应之后在文件中保存了Cookie信息,也就是持久级Cookie,第二次访问时,url请求的路径是/a/b/c,所以http请求中会携带Cookie,信息是username=张三,这样服务器就能识别http请求对应的用户

Set-Cookie 选项和Cookie选项

Set-Cookie: username=peter; expires=Thu, 18 Dec 2024 12:00:00 UTC; path=/; domain=.example.com; secure; HttpOnly

Cookie: username=zhangsan

RFC 1123 互联网时间格式标准

示例:

Tue, 16 Jul 2024 08:30:45 GMT

格式详解

  1. ​星期几​​:3字母英文缩写(Mon, Tue, Wed等)

  2. ​日期​​:2位数字(前导零可选)

  3. ​月份​​:3字母英文缩写(Jan, Feb, Mar等)

  4. ​年份​​:4位数字

  5. ​时间​​:24小时制,HH:MM:SS

  6. ​时间标准​​:GMT (格林尼治标准时间)或UTC (协调世界时)

set-cookie可选属性介绍

1.username指明用户,expires是该持久cookie的失效时间,path和domain作为限制,只有url请求的是该路径的文件或子目录、发送的是特定的域名,那浏览器进程才会在http报头的选项中加上cookie。如果设置了secure,那么只有https请求,才会加上cookie,http就不行了

2.Cookie中每个属性都以分号(;)和空格( )分隔, 属性的名称和值之间使用等号(=)分隔。

注意:

1、http请求中的cookie选项不会把set-cookie中的所有东西都带上,只会带上username=xxxx

2、cookie看似选项一大堆,实则核心就一个username,而http请求带上它,服务器就能识别出HTTP请求对应的用户

3、一次Set-Cookie只能设置一个Cookie的有效字段,如果要设置多个字段,就要多次收到Set-Cookie才可以

  sprintf(buffer, "HTTP/1.0 200 OK\r\nContent-Length:%lu\r\nSet-Cookie: paswwd=63541; username=aqc; path=/a/b;\r\n\r\n%s", strlen(hello), hello);

Set-Cookie中有paswwd和username两个标识,但最后只会保存一个,

 sprintf(buffer, "HTTP/1.0 200 OK\r\nContent-Length:%lu\r\nSet-Cookie: username=aqc; path=/a/b;\r\n\r\n%s", strlen(hello), hello);

不退出浏览器进程,将paswwd的cookie保留下来,然后修改代码,使username成为唯一的标识,然后重新访问

这就是我想表达的了

Session

Session是用来解决Cookie的使用中,用username来标识用户的不足的。

Cookie存储数据在客户端,如果存储的是username这样的重要数据,第一点是很容易被猜到和试出,第二点是服务器即使察觉到username被盗取,因为username一般可能被服务器很多地方使用到,牵扯太大,不好修改,第三点username的Cookie设置既可以是会话级别的,也可以是持久级别的,如果持久级别的话安全问题风险会大大增加。鉴于Cookie直接保存username来标识用户有这三个很大的缺点,我们选择使用sessionid来代替username保存在Cookie中

作原理

1、当用户打开浏览器首次访问服务器时,服务器会创建一个随机的 Session ID,并通过 Set-Cookie 将sessionid发送到客户端,注意这个Cookie设置是会话级的,当浏览器退出时sessionid就会失效,重新打开浏览器访问服务器时会得到一个和上次不同的seesionid,这两个sessionid都对应着同一份用户管理信息

2、和username相比,第一点sessionid对用户没有标识度,第二点sessionid和用户管理数据没什么大联系,可以轻易被失效或修改,第三点服务器对于sessionid只设置会话级别的Cookie,当浏览器退出时sessionid就会失效,鉴于这三点sessionid就解决了Cookie使用username的不足

安全性

由于 Session ID 是在cookie中的,因此session id也存在被窃取的风险, 但由于sessionid是会话级别cookie设置,所以即使被盗取了,如果之前的进程退出,该sessionid就会失效,盗取者也就无法使用。而且sessionid和username相比,被暴力猜出几乎不可能。最重要的是,如果真被盗取了而且前一个进程也没有退出,sessionid依然有效,服务器也可以通过检测登录ip发生变化来主动使sessionid失效,因为sessionid和用户管理数据关系不大,可以轻易被失效

session实例:

#include <string>
#include <sys/socket.h>
#include <netinet/in.h>
#include <cstring>
#include <arpa/inet.h>
#include <iostream>
#include <unistd.h>
#include <sys/wait.h>
#include <pthread.h>
#include <vector>
using namespace std;

char timebuffer[1024];

std::string GetMonthName(int month)
{
    std::vector<std::string> months = {"Jan", "Feb", "Mar",
                                       "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
    return months[month];
}
std::string GetWeekDayName(int day)
{
    std::vector<std::string> weekdays = {"Sun", "Mon", "Tue",
                                         "Wed", "Thu", "Fri", "Sat"};
    return weekdays[day];
}
std::string ExpireTimeUseRfc1123(int t) // 秒级别的未来 UTC 时间
{
    time_t timeout = time(nullptr) + t;
    // 这个地方有坑哦
    struct tm *tm = gmtime(&timeout); // 这里不能用 localtime,
    // 因为 localtime 是默认带了时区的.gmtime 获取的就是 UTC 统一时间 char timebuffer[1024];
    //  时间格式如: expires=Thu, 18 Dec 2024 12:00:00 UTC
    snprintf(timebuffer, sizeof(timebuffer),
             "%s, %02d %s %d %02d:%02d:%02d UTC",
             GetWeekDayName(tm->tm_wday).c_str(),
             tm->tm_mday,
             GetMonthName(tm->tm_mon).c_str(),
             tm->tm_year + 1900,
             tm->tm_hour,
             tm->tm_min,
             tm->tm_sec);
    return timebuffer;
}

class TcpServer
{
    static void Service(int fd, struct sockaddr_in &peer)
    {
        cout << "client connected: " << inet_ntoa(peer.sin_addr) << " " << ntohs(peer.sin_port) << endl;
        char buffer[1024];
        while (1)
        {
            int n = read(fd, buffer, sizeof(buffer) - 1);
            if (n > 0)
            {
                buffer[n] = 0;
                cout << buffer;

                const char *hello = "<h1>hello world</h1>";

                string t= ExpireTimeUseRfc1123(60);
                sprintf(buffer, "HTTP/1.0 200 OK\r\nContent-Length:%lu\r\nSet-Cookie: sessionid=63541; path=/a/b;\r\n\r\n%s", strlen(hello), hello);
                write(fd, buffer, strlen(buffer));
            }
            else if (n == 0)
            {
                cout << "连接断开,客户端为:" << inet_ntoa(peer.sin_addr) << " " << ntohs(peer.sin_port) << endl;
                break;
            }
        }
    }

    struct ThreadData
    {
        int _fd;
        struct sockaddr_in _peer;

        ThreadData(int fd, struct sockaddr_in peer)
            : _fd(fd), _peer(peer)
        {
        }
    };

    static void *ExecuteThread(void *arg)
    {
        pthread_detach(pthread_self()); // 分离线程,到时候线程函数执行完直接释放tcb
        ThreadData *p = (ThreadData *)arg;
        Service(p->_fd, p->_peer);
        close(p->_fd);
        delete p;
        return nullptr; // BUG----如果线程启动函数执行完毕却没有返回值,那就会出现非法指令的情况
    }

    void ConnectionHandle(int fd, struct sockaddr_in &peer)
    {
        pthread_t t;
        ThreadData *data = new ThreadData(fd, peer);
        pthread_create(&t, nullptr, ExecuteThread, data);
    }

public:
    TcpServer(int port)
        : _port(port)
    {
    }

    void Init()
    {
        _sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_addr.s_addr = htonl(INADDR_ANY);
        local.sin_port = htons(_port);

        int opt = 1;
        setsockopt(_sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
        bind(_sockfd, (struct sockaddr *)&local, sizeof(local));

        listen(_sockfd, 8);
    }

    void Start()
    {
        _isrunning = true;

        while (_isrunning)
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            int fd = accept(_sockfd, (struct sockaddr *)&peer, &len);

            ConnectionHandle(fd, peer);
        }
    }

private:
    int _port;
    int _sockfd;
    bool _isrunning;
};

int main()
{
    TcpServer server(8888);
    server.Init();
    server.Start();

    return 0;
}

所谓session,不就是在cookie中保存一个随机字符串,在服务器端保存着对应的用户数据,会话级别的cookie设置嘛

favicon.ico

favicon.ico 这一路上真是见了太多太多了

1.favicon.ico其实是表示一个网站图标文件,通常用来显示在浏览器的标签页上、地址栏旁边和收藏夹中。这个文件名 favicon 是 "favorite icon" 的缩写,而 .ico 是图标文件的后缀

2.浏览器会为了获取图标文件而专门构建对应的tcp连接和http的GET请求,路径就是/favicon.ico

在浏览器中输入url

在浏览器中输入url之后,浏览器进程会利用url中的分隔符将url进行分割得到url的各部分的内容,然后对内容中需要编码的字符进行编码,接着将域名发送给本地DNS服务器,DNS服务器依次对根服务器、顶级域服务器、二级域服务器发送查询报文并接收对应响应,最终得到目标域名的ip地址,然后DNS服务器将IP地址发送给浏览器进程,浏览器收到域名对应ip后开始组装http请求报文,路径和查询字符串放在请求行中,ip和端口放在请求头的Host选项中。接着浏览器进程创建一个TCP通信套接字,和服务器进行连接,然后发送url对应的http请求,收到对应的http响应后,接着浏览器创建另一个套接字发送/favicon.ico路径的GET请求报文

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐