HTTP协议中的Cookie和Session


img

HTTP协议中的Cookie和Session

1、HTTP协议中的Cookie

1.1、概念

HTTP Cookie(也称为 Web Cookie、浏览器 Cookie 或简称 Cookie)是服务器发送到用户浏览器并保存在浏览器上的一小块数据,它会在浏览器之后向同一服务器再次发起请求时被携带并发送到服务器上。通常,它用于告知服务端两个请求是否来自同一浏览器,如保持用户的登录状态、记录用户偏好等。


1.2、工作原理

  • 当用户第一次访问网站时,服务器会在 HTTP 响应报头中设置 Set-Cookie字段,用于发送 Cookie 到用户的浏览器。
  • 浏览器在接收到 Cookie 后,会将其保存在本地(通常是按照域名进行存储)。
  • 在之后的请求中,浏览器会自动在 HTTP 请求头中携带 Cookie 字段,将之前保存的 Cookie 信息发送给服务器。


1.3、分类

  • 会话 Cookie(Session Cookie):在浏览器关闭时失效。

  • 持久 Cookie(Persistent Cookie):带有明确的过期日期或持续时间,可以跨多个浏览器会话存在(存在本本地–硬盘中)。

  • 如果 cookie 是一个持久性的 cookie,那么它其实就是浏览器相关的,特定目录下的一个文件。但直接查看这些文件可能会看到乱码或无法读取的内容,因为 cookie 文件通常以二进制或 sqlite 格式存储。一般我们查看,直接在浏览器对应的选项中直接查看即可。

这里打开了bilibili,是已登录状态。


1.4、安全性和用途

由于Cookie是存储在客户端的,因此存在被窃取或被篡改的风险。

用途

  • 用户认证和会话管理(最重要)
  • 跟踪用户行为
  • 缓存用户偏好等

1.5、Cookie基本格式

Set-Cookie: <name>=<value>
其中 <name> 是 Cookie 的名称,<value> 是 Cookie 的值。

完整的Set- Cookie示例:

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

时间格式必须遵守 RFC 1123 标准,具体格式样例:Tue, 01 Jan 2030 12:34:56 GMT 或者 UTC(推荐)

  • Tue: 星期二(星期几的缩写)

  • ,: 逗号分隔符

  • 01: 日期(两位数表示)

  • Jan: 一月(月份的缩写)

  • 2030: 年份(四位数)

  • 12:34:56: 时间(小时、分钟、秒)

  • GMT: 格林威治标准时间(时区缩写)

  • UTC: 协调世界时间(时区缩写)

GMT和UTC区别:

  • 计算方式:GMT 基于地球的自转和公转,而 UTC 基于原子钟。

  • 准确度:由于 UTC 基于原子钟,它比基于地球自转的 GMT 更加精确。

在实际使用中,GMT 和 UTC 之间的差别通常很小,大多数情况下可以互换使用。但在需要高精度时间计量的场合,如科学研究、网络通信等,UTC 是更为准确的选择。

以下是对 Set-Cookie 头部字段的简洁介绍:

属性描述
usernamepeter这是 Cookie 的名称和值,标识用户名为 “peter”。
expiresThu, 18 Dec 2024 12:00:00 UTC指定 Cookie 的过期时间。在这个例子中,Cookie 将在 2024 年 12 月 18 日 12:00:00 UTC 后过期。
path/定义 Cookie 的作用范围。这里设置为根路径 /,意味着 Cookie 对 .example.com 域名下的所有路径都可用。
domain.example.com指定哪些域名可以接收这个 Cookie。点前缀(.)表示包括所有子域名。
secure-指示 Cookie 只能通过 HTTPS 协议发送,不能通过 HTTP 协议发送,增加安全性。
HttpOnly-阻止客户端脚本(如 JavaScript)访问此 Cookie,有助于防止跨站脚本攻击(XSS)。

注意

  • 每个 Cookie 属性都以分号(;)和空格( )分隔

  • 名称和值之间使用等号(=)分隔。

  • 如果 Cookie 的名称或值包含特殊字符(如空格、分号、逗号等),则需要进行 URL 编码。


1.6、代码Cookie测试

引用了之前的http的代码:代码在第7节

这里仅修改了Http.hpp文件的HttpService类:

  • Http.hpp文件:也就是添加了6个函数(GetMonName、GetWeekname、ExpireTimeUseRFC1123、ProveCookieWrite、ProveCookieTimeOut、ProvePath)和一个响应报头(Set-Cookie)。
#pragma once

#include <iostream>
#include <sstream>
#include <fstream>
#include <string>
#include <vector>
#include <functional>
#include <unordered_map>

const std::string SEP = "\r\n";
const std::string REQ_HEADER_SEP = ": ";
const std::string wwwroot = "wwwroot";
const std::string homepage = "index.html";
const std::string httpverison = "HTTP/1.0";
const std::string SPACE = " ";
const std::string CONF_MIME_TYPE_FILE = "conf/Mime_Type.txt";
const std::string CONF_STATUS_CODE_DESC_FILE = "conf/Status_Code_Desc.txt";
const std::string CONF_SEP = ": ";
const std::string REQ_PATH_SUFFIX_SEP = ".";
const std::string errorpage = "404.html";
const std::string URI_SEP = "?";
const std::string PATH_SEP = "/";

class HttpResponse;
class HttpRequest;
using func_t = std::function<std::shared_ptr<HttpResponse>(std::shared_ptr<HttpRequest>)>;

class HttpRequest
{
   // GET / HTTP/1.1
   // Host: 129.204.171.204:8888
   // User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.6.1 Safari/605.1.15
   // Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
   // Accept-Encoding: gzip, deflate
   // Accept-Language: zh-CN,zh-Hans;q=0.9
   // Upgrade-Insecure-Requests: 1
   // 空行
   // 请求正文

private:
   std::string GetOneLine(std::string &reqstr)
   {
       if (reqstr.empty())
           return std::string();
       auto pos = reqstr.find(SEP);
       if (pos == std::string::npos)
           return std::string();
       std::string line = reqstr.substr(0, pos); // 读到空行就是substr(0, 0),就是空
       // 删除该行,准备读取下一行
       reqstr.erase(0, pos + SEP.size());
       // 读取成功
       return line.empty() ? SEP : line;
   }

public:
   HttpRequest() : _blank_line(SEP), _path(wwwroot) {}
   bool DeSerialize(std::string &reqstr)
   {
       // 先读取第一行,是请求行
       _req_line = GetOneLine(reqstr);
       while (true)
       {
           // 这里读取请求报头行
           std::string line = GetOneLine(reqstr);
           if (line.empty())
           {
               break; // 读取不成功
           }
           else if (line == SEP)
           {
               // 读到空行
               _req_text = reqstr; // 剩下的都是正文
               break;
           }
           else
           {
               _req_header.emplace_back(line);
           }
       }
       ParseReqLine();
       ParseReqHeader();
       return true;
   }

   bool ParseReqLine()
   {
       if (_req_line.empty())
           return false;
       std::stringstream ss(_req_line); // 按空格把数据分开
       ss >> _req_method >> _uri >> _version;

       // http://129.204.171.204:8888/index.html?username=xp&password=12354
       if (strcasecmp(_req_method.c_str(), "get") == 0) // 如果请求方法是get,那可能需要分离uri,因为提交参数会放到uri中
       {
           auto pos = _uri.find(URI_SEP);
           if (pos != std::string::npos)
           {
               _args = _uri.substr(pos + 1);
               _uri.resize(pos); // 只要前面那部分
           }
           else
           {
               _args = std::string(); // 没有提交参数
           }
           LOG(INFO, "uri: %s, args : %s", _uri.c_str(), _args.c_str());
       }
       else if (strcasecmp(_req_method.c_str(), "post") == 0)
       {
       }
       _path += _uri;

       if (_path[_path.size() - 1] == '/')
       {
           _path += homepage;
       }

       auto pos = _path.rfind(REQ_PATH_SUFFIX_SEP); // 找后缀
       if (pos == std::string::npos)
       {
           return false;
       }
       _suffix = _path.substr(pos);
       return true;
   }

   std::string Path()
   {
       return _path;
   }

   std::string Suffix()
   {
       return _suffix;
   }

   std::string Method()
   {
       return _req_method;
   }

   std::string Args()
   {
       return _args;
   }

   std::string ReqText()
   {
       return _req_text;
   }

   bool ParseReqHeader()
   {
       for (auto header : _req_header)
       {
           auto pos = header.find(REQ_HEADER_SEP);
           if (pos == std::string::npos)
               return false;
           _headerskv[header.substr(0, pos)] = header.substr(pos + REQ_HEADER_SEP.size());
       }
       return true;
   }

   bool IsExec()
   {
       return !_args.empty() || !_req_text.empty(); // 只要有提交参数或者有正文数据(post会把提交参数放正文)就执行回调函数(所以这个成员函数必须在AddText前执行,防止正文数据混淆)
   }

   void DebugPrint()
   {
       std::cout << "request: " << std::endl;
       std::cout << "$$$ " << _req_line << std::endl;
       for (auto &header : _req_header)
       {
           std::cout << "### " << header << std::endl;
       }
       std::cout << "*** " << _blank_line << std::endl;
       std::cout << "@@@ " << _req_text << std::endl;

       // std::cout << "---------------------" << std::endl;
       // std::cout << "$$$ " << _req_method << " " << _uri << " " << _version << " " << _path << std::endl;
       // for (auto &header : _headerskv)
       // {
       //     std::cout << "### " << header.first << "--" << header.second << std::endl;
       // }
       // std::cout << "*** " << _blank_line << std::endl;
       // std::cout << "@@@ " << _req_text << std::endl;
   }
   ~HttpRequest() {}

private:
   std::string _req_line;                // 请求行
   std::vector<std::string> _req_header; // 请求报头
   std::string _blank_line;              // 空行
   std::string _req_text;                // 请求正文

   // 细分
   std::string _req_method;                                 // 请求行的请求方法
   std::string _uri;                                        // 请求行的uri
   std::string _args;                                       // uri分离出来的提交参数(比如账号密码)
   std::string _path;                                       // 请求路径
   std::string _suffix;                                     // 请求后缀
   std::string _version;                                    // 请求行的版本
   std::unordered_map<std::string, std::string> _headerskv; // 请求报头的kv结构
};

class HttpResponse
{
public:
   HttpResponse() : _version(httpverison), _blank_line(SEP) {}

   void ADDStatusLine(int status_code, std::string desc)
   {
       _status_code = status_code;
       // _desc = "OK";
       _desc = desc;
       _status_line = _version + SPACE + std::to_string(_status_code) + SPACE + _desc + SEP;
   }

   void ADDHeader(const std::string &k, const std::string &v)
   {
       _headerskv[k] = v;
   }
   void ADDText(std::string text)
   {
       _resp_text = text;
   }

   void Serialize(std::string *out)
   {
       std::string status_line = _version + SPACE + std::to_string(_status_code) + SPACE + _desc + SEP;
       for (auto &header : _headerskv)
       {
           _resp_header.emplace_back(header.first + REQ_HEADER_SEP + header.second);
       }

       // 序列化
       std::string resptr;
       resptr = status_line;
       for (auto &header : _resp_header)
       {
           resptr += header + SEP;
       }
       resptr += _blank_line;
       resptr += _resp_text;
       *out = resptr;
   }

   void DebugPrint()
   {
       std::cout << "response: " << std::endl;
       std::cout << "$$$ " << _status_line;
       for (auto &header : _resp_header)
       {
           std::cout << "### " << header << std::endl;
       }
       std::cout << "*** " << _blank_line << std::endl;
       // std::cout << "@@@ " << "_resp_text" << std::endl; // 正文是二进制
   }

   ~HttpResponse() {}

private:
   // 构建应答的必要字段
   int _status_code;     // 状态码
   std::string _desc;    // 状态描述
   std::string _version; // 版本

   std::string _status_line;              // 状态行
   std::vector<std::string> _resp_header; // 响应报头
   std::string _blank_line;               // 空行
   std::string _resp_text;                // 响应正文

   std::unordered_map<std::string, std::string> _headerskv; // 响应报头的kv结构
};

class Factory
{
public:
   // static?
   std::shared_ptr<HttpRequest> BuildRequest()
   {
       return std::make_shared<HttpRequest>();
   }

   std::shared_ptr<HttpResponse> BuildResponse()
   {
       return std::make_shared<HttpResponse>();
   }

private:
};

class HttpService
{
private:
   bool LoadMimeType()
   {
       std::ifstream in(CONF_MIME_TYPE_FILE); // 默认是读
       if (!in.is_open())
       {
           std::cerr << "load mime error" << std::endl;
           return false;
       }
       std::string line;
       while (std::getline(in, line))
       {
           auto pos = line.find(CONF_SEP);
           if (pos == std::string::npos)
           {
               std::cerr << "find CONF_SEP error" << std::endl;
               return false;
           }
           _mime_type[line.substr(0, pos)] = line.substr(pos + CONF_SEP.size());
       }
       return true;
   }

   bool LoadStatusDesc()
   {
       std::ifstream in(CONF_STATUS_CODE_DESC_FILE); // 默认是读
       if (!in.is_open())
       {
           std::cerr << "load status desc error" << std::endl;
           return false;
       }
       std::string line;
       while (std::getline(in, line))
       {
           auto pos = line.find(CONF_SEP);
           if (pos == std::string::npos)
           {
               std::cerr << "find CONF_SEP error" << std::endl;
               return false;
           }
           _status_code_to_desc[std::stoi(line.substr(0, pos))] = line.substr(pos + CONF_SEP.size());
       }
       return true;
   }

public:
   HttpService()
   {
       LoadMimeType();
       // for (auto &e : _mime_type)
       // {
       //     std::cout << e.first << "--" << e.second << std::endl;
       // }
       // std::cout << "-----------------------" << std::endl;

       LoadStatusDesc();
       // for (auto &e : _status_to_desc)
       // {
       //     std::cout << e.first << "--" << e.second << std::endl;
       // }
   }

   std::string ReadFileRequest(const std::string &path, int *size)
   {
       // 要按二进制打开
       std::ifstream in(path, std::ios::binary);
       if (!in.is_open())
       {
           std::cerr << "open file error" << std::endl;
           return std::string();
       }
       in.seekg(0, in.end);
       int filesize = in.tellg();
       in.seekg(0, in.beg);

       std::string content;
       content.resize(filesize);
       in.read((char *)content.c_str(), filesize);
       in.close();
       *size = filesize;
       return content;
   }

   void AddHandler(std::string functionname, func_t f)
   {
       // 添加方法处理 提交参数 的方法
       std::string fn = wwwroot + PATH_SEP + functionname; // wwwroot/方法名
       _funcs[fn] = f;
   }

   std::string GetMonName(int month)
   {
       std::vector<std::string> months = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
       return months[month];
   }

   std::string GetWeekname(int day)
   {
       std::vector<std::string> weekdays = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
       return weekdays[day];
   }

   std::string ExpireTimeUseRFC1123(int t) // t是秒级别
   {
       time_t timeout = time(nullptr) + t; // 当前时间加t
       struct tm *tm = gmtime(&timeout);   // 这里不能用 localtime,因为 localtime 是默认带了时区的. gmtime 获取的就是 UTC 统一时间
       char buff[1024];
       // 时间格式如: expires=Thu, 18 Dec 2024 12:00:00 UTC
       snprintf(buff, sizeof(buff), "%s, %02d %s %d %02d:%02d:%02d UTC",
                GetWeekname(tm->tm_wday).c_str(), // wday -- 一周的第几天 , mday -- 一月的第几天
                tm->tm_mday,
                GetMonName(tm->tm_mon).c_str(),
                tm->tm_year + 1900,
                tm->tm_hour,
                tm->tm_min,
                tm->tm_sec);
       return buff;
   }

   std::string ProveCookieWrite() // 证明 cookie 能被写入浏览器
   {
       return "username=xp;";
   }
   std::string ProveCookieTimeOut()
   {
       return "username=xp; expires=" + ExpireTimeUseRFC1123(2) + ";"; // 让 cookie 2秒 后过期
   }
   std::string ProvePath()
   {
       return "username=xp; path=/a/b;";
   }

   std::string HandlerHttpService(std::string &request)
   {
#ifdef HTTP
       std::cout << "-------------------------" << std::endl;
       std::cout << request;
       std::string response = "HTTP/1.0 200 OK\r\n"; // 404 NOT Found
       response += "\r\n";
       response += "<html><body><h1>hello ynu!</h1></body></html>";
       return response;
#else
       // std::cout << "request : " << request;

       // 对请求进行反序列化
       Factory fac; // 也可以给Build静态
       auto reqptr = fac.BuildRequest();
       reqptr->DeSerialize(request);
       reqptr->DebugPrint();
       // DebugPrint();

       std::string resource = reqptr->Path();
       std::string suffix = reqptr->Suffix();
       auto resptr = fac.BuildResponse();

       // std::cout << "text :" << reqptr->ReqText() << std::endl;
       if (reqptr->IsExec())
       {
           // 提交参数了
           // 处理参数
           resptr = _funcs[resource](reqptr);
       }
       else
       {
           // std::string newurl = "https://github.com/Xpccccc";
           std::string newurl = "http://129.204.171.204:8888/Image/2.png";
           int status_code = 0;

           // 对请求的地址进行判断
           if (resource == "wwwroot/redir")
           {
               cout << "test========================" << endl;
               status_code = 302;
               resptr->ADDStatusLine(status_code, _status_code_to_desc[status_code]);
               resptr->ADDHeader("Content-Type", _mime_type[suffix]);

               resptr->ADDHeader("Location", newurl);
           }
           else if (resource == "wwwroot/404.html")
           {
           }
           else
           {
               status_code = 200;
               int filesize = 0;
               // std::cout << "debug resource : " << resource << std::endl;

               std::string textcontent = ReadFileRequest(resource, &filesize); // 不存在文件会输出open file error
               // std::cout << "debug" << std::endl;
               if (textcontent.empty())
               {
                   // 空内容说明读到不存在的页面
                   status_code = 404;
                   resptr->ADDStatusLine(status_code, _status_code_to_desc[status_code]);
                   std::string text404 = ReadFileRequest(wwwroot + PATH_SEP + errorpage, &filesize);
                   // std::cout << "text404" << text404 << std::endl;
                   resptr->ADDHeader("Content-Length", std::to_string(filesize));
                   resptr->ADDHeader("Content-Type", _mime_type[".html"]);
                   resptr->ADDText(text404);
               }
               else
               {
                   resptr->ADDStatusLine(status_code, _status_code_to_desc[status_code]);
                   resptr->ADDHeader("Content-Length", std::to_string(filesize));
                   resptr->ADDHeader("Content-Type", _mime_type[suffix]);

                   resptr->ADDText(textcontent);
               }
           }

           // std::cout << "suffix : " << suffix << std::endl;

           // resptr->ADDStatusLine(200);
           // resptr->ADDStatusLine(301);
           // resptr->ADDHeader("Content-Length", std::to_string(filesize));
           // resptr->ADDHeader("Content-Type", _mime_type[suffix]);
           // resptr->ADDHeader("Location", "https://github.com/Xpccccc");
           // std::cout << "suffix Content-Type : " << _mime_type[suffix] << std::endl;

           // resptr->ADDText(textcontent);
       }

       // 添加Cookie
       resptr->ADDHeader("Set-Cookie", ProveCookieWrite());
       //resptr->ADDHeader("Set-Cookie", ProveCookieTimeOut());
       // resptr->ADDHeader("Set-Cookie", ProvePath());

       std::string response;

       resptr->Serialize(&response);
       resptr->DebugPrint();

       return response;

#endif
   }

   ~HttpService() {}

private:
   std::unordered_map<std::string, std::string> _mime_type;   // Content-Type
   std::unordered_map<int, std::string> _status_code_to_desc; // 状态码及其描述
   std::unordered_map<std::string, func_t> _funcs;            // 状态码及其描述
};

运行结果:

  • 不添加Set-Cookie报头:可以看到,在Edge浏览器中没有Cookie数据。

  • 添加Set-Cookie报头:可以看到,在Edge浏览器中多了Cookie数据,并且在关闭浏览器后重新打开该网站,还是有Cookie数据(可能有Cookie默认失效时间),说明Cookie数据就是存在客户端(浏览器)的。

  • 测试Cookie过期时间:可以看到,Cookie 2 秒后到期。

  • 测试Cookie提交路径:可以看到,只有在访问/a/b目录才会提交Cookie数据。


2、HTTP协议中的Session

前面我们是单独使用Cookie,存在安全性问题,即Cookie存在客户端会有被窃取的风险。

怎么解决?

这里引入Session来解决这个问题。


2.1、概念

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


2.2、工作原理

当用户首次访问网站时,服务器会为用户创建一个唯一的 Session ID,并通过Cookie 将其发送到客户端。

客户端在之后的请求中会携带这个 Session ID,服务器通过 Session ID 来识别用户,从而获取用户的会话信息。

服务器通常会将 Session 信息存储在内存、数据库或缓存中。


2.3、安全性和用途

安全性:

  • 与 Cookie 相似,由于 Session ID 是在客户端和服务器之间传递的(通过Session ID来确认是否发送Cookie,并且数据全部存储在服务器,客户端只存Session ID),因此也存在被窃取的风险。但是一般虽然 Cookie 被盗取了,但是用户只泄漏了一个 Session ID,私密信息暂时没有被泄露的风险。

  • Session ID 便于服务端进行客户端有效性的管理,比如异地登录。可以通过 HTTPS 和设置合适的 Cookie 属性(如 HttpOnly 和 Secure)来增强安全性。

  • Session 可以设置超时时间,当超过这个时间后,Session 会自动失效。服务器也可以主动使 Session 失效,例如当用户登出时。

用途:

  • 用户认证和会话管理

  • 存储用户的临时数据(如购物车内容)

  • 实现分布式系统的会话共享(通过将会话数据存储在共享数据库或缓存中)


2.4、代码测试Session

代码

代码仅修改了第一节中的Http.hpp文件和增加了Session.hpp文件。

  • Http.hpp文件:在HttpRequest类的ParseReqHeader函数中增加了提取Cookie的语句,便于后续请求提交。
#pragma once

#include <iostream>
#include <sstream>
#include <fstream>
#include <string>
#include <vector>
#include <functional>
#include <unordered_map>

#include "Session.hpp"

const std::string SEP = "\r\n";
const std::string REQ_HEADER_SEP = ": ";
const std::string wwwroot = "wwwroot";
const std::string homepage = "index.html";
const std::string httpverison = "HTTP/1.0";
const std::string SPACE = " ";
const std::string CONF_MIME_TYPE_FILE = "conf/Mime_Type.txt";
const std::string CONF_STATUS_CODE_DESC_FILE = "conf/Status_Code_Desc.txt";
const std::string CONF_SEP = ": ";
const std::string REQ_PATH_SUFFIX_SEP = ".";
const std::string errorpage = "404.html";
const std::string URI_SEP = "?";
const std::string PATH_SEP = "/";

class HttpResponse;
class HttpRequest;
using func_t = std::function<std::shared_ptr<HttpResponse>(std::shared_ptr<HttpRequest>)>;

class HttpRequest
{
   // GET / HTTP/1.1
   // Host: 129.204.171.204:8888
   // User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.6.1 Safari/605.1.15
   // Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
   // Accept-Encoding: gzip, deflate
   // Accept-Language: zh-CN,zh-Hans;q=0.9
   // Upgrade-Insecure-Requests: 1
   // 空行
   // 请求正文

private:
   std::string GetOneLine(std::string &reqstr)
   {
       if (reqstr.empty())
           return std::string();
       auto pos = reqstr.find(SEP);
       if (pos == std::string::npos)
           return std::string();
       std::string line = reqstr.substr(0, pos); // 读到空行就是substr(0, 0),就是空
       // 删除该行,准备读取下一行
       reqstr.erase(0, pos + SEP.size());
       // 读取成功
       return line.empty() ? SEP : line;
   }

public:
   HttpRequest() : _blank_line(SEP), _path(wwwroot) {}
   bool DeSerialize(std::string &reqstr)
   {
       // 先读取第一行,是请求行
       _req_line = GetOneLine(reqstr);
       while (true)
       {
           // 这里读取请求报头行
           std::string line = GetOneLine(reqstr);
           if (line.empty())
           {
               break; // 读取不成功
           }
           else if (line == SEP)
           {
               // 读到空行
               _req_text = reqstr; // 剩下的都是正文
               break;
           }
           else
           {
               _req_header.emplace_back(line);
           }
       }
       ParseReqLine();
       ParseReqHeader();
       return true;
   }

   bool ParseReqLine()
   {
       if (_req_line.empty())
           return false;
       std::stringstream ss(_req_line); // 按空格把数据分开
       ss >> _req_method >> _uri >> _version;

       // http://129.204.171.204:8888/index.html?username=xp&password=12354
       if (strcasecmp(_req_method.c_str(), "get") == 0) // 如果请求方法是get,那可能需要分离uri,因为提交参数会放到uri中
       {
           auto pos = _uri.find(URI_SEP);
           if (pos != std::string::npos)
           {
               _args = _uri.substr(pos + 1);
               _uri.resize(pos); // 只要前面那部分
           }
           else
           {
               _args = std::string(); // 没有提交参数
           }
           LOG(INFO, "uri: %s, args : %s", _uri.c_str(), _args.c_str());
       }
       else if (strcasecmp(_req_method.c_str(), "post") == 0)
       {
       }
       _path += _uri;

       if (_path[_path.size() - 1] == '/')
       {
           _path += homepage;
       }

       auto pos = _path.rfind(REQ_PATH_SUFFIX_SEP); // 找后缀
       if (pos == std::string::npos)
       {
           return false;
       }
       _suffix = _path.substr(pos);
       return true;
   }

   std::string Path()
   {
       return _path;
   }

   std::string Suffix()
   {
       return _suffix;
   }

   std::string Method()
   {
       return _req_method;
   }

   std::string Args()
   {
       return _args;
   }

   std::string ReqText()
   {
       return _req_text;
   }

   bool ParseReqHeader()
   {
       // 这里注意:这是服务器的Request!在服务器发送了响应报头Set-Cookie: sessionid=1234后,客户端(浏览器)会对其进行处理,下一次请求就会带上Cookie: sessionid=1234
       std::string prefix = "Cookie";
       for (auto header : _req_header)
       {
           auto pos = header.find(REQ_HEADER_SEP);
           if (pos == std::string::npos)
               return false;
           _headerskv[header.substr(0, pos)] = header.substr(pos + REQ_HEADER_SEP.size()); // 添加成功
           if (header.substr(0, pos) == prefix) // 添加Cookie到vector中
           {
               _sessionid = header.substr(pos + REQ_HEADER_SEP.size());
               _cookies.push_back(_sessionid);
           }
       }
       return true;
   }

   std::string &SessionId()
   { // 这里只测试1个的情况,如果是多个,就返回vector
       return _sessionid;
   }

   bool IsExec()
   {
       return !_args.empty() || !_req_text.empty(); // 只要有提交参数或者有正文数据(post会把提交参数放正文)就执行回调函数(所以这个成员函数必须在AddText前执行,防止正文数据混淆)
   }

   void DebugPrint()
   {
       std::cout << "request: " << std::endl;
       std::cout << "$$$ " << _req_line << std::endl;
       for (auto &header : _req_header)
       {
           std::cout << "### " << header << std::endl;
       }
       std::cout << "*** " << _blank_line << std::endl;
       std::cout << "@@@ " << _req_text << std::endl;

       // std::cout << "---------------------" << std::endl;
       // std::cout << "$$$ " << _req_method << " " << _uri << " " << _version << " " << _path << std::endl;
       // for (auto &header : _headerskv)
       // {
       //     std::cout << "### " << header.first << "--" << header.second << std::endl;
       // }
       // std::cout << "*** " << _blank_line << std::endl;
       // std::cout << "@@@ " << _req_text << std::endl;
   }
   ~HttpRequest() {}

private:
   std::string _req_line;                // 请求行
   std::vector<std::string> _req_header; // 请求报头
   std::string _blank_line;              // 空行
   std::string _req_text;                // 请求正文

   // 细分
   std::string _req_method;                                 // 请求行的请求方法
   std::string _uri;                                        // 请求行的uri
   std::string _args;                                       // uri分离出来的提交参数(比如账号密码)
   std::string _path;                                       // 请求路径
   std::string _suffix;                                     // 请求后缀
   std::string _version;                                    // 请求行的版本
   std::unordered_map<std::string, std::string> _headerskv; // 请求报头的kv结构

   std::vector<std::string> _cookies; // 其实cookie可以有多个,因为Set-Cookie可以被写多条,测试,一条够了。
   std::string _sessionid;            // 请求携带的sessionid,仅仅用来测试
};

class HttpResponse
{
public:
   HttpResponse() : _version(httpverison), _blank_line(SEP) {}

   void ADDStatusLine(int status_code, std::string desc)
   {
       _status_code = status_code;
       // _desc = "OK";
       _desc = desc;
       _status_line = _version + SPACE + std::to_string(_status_code) + SPACE + _desc + SEP;
   }

   void ADDHeader(const std::string &k, const std::string &v)
   {
       _headerskv[k] = v;
   }
   void ADDText(std::string text)
   {
       _resp_text = text;
   }

   void Serialize(std::string *out)
   {
       std::string status_line = _version + SPACE + std::to_string(_status_code) + SPACE + _desc + SEP;
       for (auto &header : _headerskv)
       {
           _resp_header.emplace_back(header.first + REQ_HEADER_SEP + header.second);
       }

       // 序列化
       std::string resptr;
       resptr = status_line;
       for (auto &header : _resp_header)
       {
           resptr += header + SEP;
       }
       resptr += _blank_line;
       resptr += _resp_text;
       *out = resptr;
   }

   void DebugPrint()
   {
       std::cout << "response: " << std::endl;
       std::cout << "$$$ " << _status_line;
       for (auto &header : _resp_header)
       {
           std::cout << "### " << header << std::endl;
       }
       std::cout << "*** " << _blank_line << std::endl;
       // std::cout << "@@@ " << "_resp_text" << std::endl; // 正文是二进制
   }

   ~HttpResponse() {}

private:
   // 构建应答的必要字段
   int _status_code;     // 状态码
   std::string _desc;    // 状态描述
   std::string _version; // 版本

   std::string _status_line;              // 状态行
   std::vector<std::string> _resp_header; // 响应报头
   std::string _blank_line;               // 空行
   std::string _resp_text;                // 响应正文

   std::unordered_map<std::string, std::string> _headerskv; // 响应报头的kv结构
};

class Factory
{
public:
   // static?
   std::shared_ptr<HttpRequest> BuildRequest()
   {
       return std::make_shared<HttpRequest>();
   }

   std::shared_ptr<HttpResponse> BuildResponse()
   {
       return std::make_shared<HttpResponse>();
   }

private:
};

class HttpService
{
private:
   bool LoadMimeType()
   {
       std::ifstream in(CONF_MIME_TYPE_FILE); // 默认是读
       if (!in.is_open())
       {
           std::cerr << "load mime error" << std::endl;
           return false;
       }
       std::string line;
       while (std::getline(in, line))
       {
           auto pos = line.find(CONF_SEP);
           if (pos == std::string::npos)
           {
               std::cerr << "find CONF_SEP error" << std::endl;
               return false;
           }
           _mime_type[line.substr(0, pos)] = line.substr(pos + CONF_SEP.size());
       }
       return true;
   }

   bool LoadStatusDesc()
   {
       std::ifstream in(CONF_STATUS_CODE_DESC_FILE); // 默认是读
       if (!in.is_open())
       {
           std::cerr << "load status desc error" << std::endl;
           return false;
       }
       std::string line;
       while (std::getline(in, line))
       {
           auto pos = line.find(CONF_SEP);
           if (pos == std::string::npos)
           {
               std::cerr << "find CONF_SEP error" << std::endl;
               return false;
           }
           _status_code_to_desc[std::stoi(line.substr(0, pos))] = line.substr(pos + CONF_SEP.size());
       }
       return true;
   }

public:
   HttpService()
   {
       LoadMimeType();
       // for (auto &e : _mime_type)
       // {
       //     std::cout << e.first << "--" << e.second << std::endl;
       // }
       // std::cout << "-----------------------" << std::endl;

       LoadStatusDesc();
       // for (auto &e : _status_to_desc)
       // {
       //     std::cout << e.first << "--" << e.second << std::endl;
       // }
   }

   std::string ReadFileRequest(const std::string &path, int *size)
   {
       // 要按二进制打开
       std::ifstream in(path, std::ios::binary);
       if (!in.is_open())
       {
           std::cerr << "open file error" << std::endl;
           return std::string();
       }
       in.seekg(0, in.end);
       int filesize = in.tellg();
       in.seekg(0, in.beg);

       std::string content;
       content.resize(filesize);
       in.read((char *)content.c_str(), filesize);
       in.close();
       *size = filesize;
       return content;
   }

   void AddHandler(std::string functionname, func_t f)
   {
       // 添加方法处理 提交参数 的方法
       std::string fn = wwwroot + PATH_SEP + functionname; // wwwroot/方法名
       _funcs[fn] = f;
   }

   std::string GetMonName(int month)
   {
       std::vector<std::string> months = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
       return months[month];
   }

   std::string GetWeekname(int day)
   {
       std::vector<std::string> weekdays = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
       return weekdays[day];
   }

   std::string ExpireTimeUseRFC1123(int t) // t是秒级别
   {
       time_t timeout = time(nullptr) + t; // 当前时间加t
       struct tm *tm = gmtime(&timeout);   // 这里不能用 localtime,因为 localtime 是默认带了时区的. gmtime 获取的就是 UTC 统一时间
       char buff[1024];
       // 时间格式如: expires=Thu, 18 Dec 2024 12:00:00 UTC
       snprintf(buff, sizeof(buff), "%s, %02d %s %d %02d:%02d:%02d UTC",
                GetWeekname(tm->tm_wday).c_str(), // wday -- 一周的第几天 , mday -- 一月的第几天
                tm->tm_mday,
                GetMonName(tm->tm_mon).c_str(),
                tm->tm_year + 1900,
                tm->tm_hour,
                tm->tm_min,
                tm->tm_sec);
       return buff;
   }

   std::string ProveCookieWrite() // 证明 cookie 能被写入浏览器
   {
       return "username=xp;";
   }
   std::string ProveCookieTimeOut()
   {
       return "username=xp; expires=" + ExpireTimeUseRFC1123(2) + ";"; // 让 cookie 2秒 后过期
   }
   std::string ProvePath()
   {
       return "username=xp; path=/a/b;";
   }

   std::string ProveSession(const std::string &sessionid)
   {
       return sessionid + ";";
   }

   std::string HandlerHttpService(std::string &request)
   {
#ifdef HTTP
       std::cout << "-------------------------" << std::endl;
       std::cout << request;
       std::string response = "HTTP/1.0 200 OK\r\n"; // 404 NOT Found
       response += "\r\n";
       response += "<html><body><h1>hello ynu!</h1></body></html>";
       return response;
#else
       // std::cout << "request : " << request;

       // 对请求进行反序列化
       Factory fac; // 也可以给Build静态
       auto reqptr = fac.BuildRequest();
       reqptr->DeSerialize(request);
       reqptr->DebugPrint();
       // DebugPrint();

       std::string resource = reqptr->Path();
       std::string suffix = reqptr->Suffix();
       auto resptr = fac.BuildResponse();

       static int number = 0;

       // std::cout << "text :" << reqptr->ReqText() << std::endl;
       if (reqptr->IsExec())
       {
           // 提交参数了 --  相当于进入login了 --  用/login path向指定浏览器写入sessionid,并在服务器维护对应的session对象
           // 处理参数

           resptr = _funcs[resource](reqptr);
           
           std::string sessionid = reqptr->SessionId();
           if (sessionid.empty())
           {
               std::string user = "user-" + std::to_string(number++);
               Session *sptr = new Session(user, "logined");
               std::string sessionid = _session_manager.AddSession(sptr);
               resptr->ADDHeader("Set-Cookie", ProveSession(sessionid));
               LOG(DEBUG, "%s add done, sessionid is : %s ", user.c_str(), sessionid.c_str());
           }
       }
       else
       {
           // 当浏览器活跃在本站点任何路径中活跃,都会自动提交sessionid,我就知道谁活跃了
           std::string sessionid = reqptr->SessionId();
           if (!sessionid.empty())
           {
               Session *sptr = _session_manager.GetSession(sessionid);
               if (sptr != nullptr)
               {
                   LOG(DEBUG, "%s 正在活跃...", sptr->_username.c_str());
               }
               else
               {
                   LOG(DEBUG, "cookie : %s 已经过期, 需要清理\n", sessionid.c_str());
               }
           }

           // std::string newurl = "https://github.com/Xpccccc";
           std::string newurl = "http://129.204.171.204:8888/Image/2.png";
           int status_code = 0;

           // 对请求的地址进行判断
           if (resource == "wwwroot/redir")
           {
               cout << "test========================" << endl;
               status_code = 302;
               resptr->ADDStatusLine(status_code, _status_code_to_desc[status_code]);
               resptr->ADDHeader("Content-Type", _mime_type[suffix]);

               resptr->ADDHeader("Location", newurl);
           }
           else if (resource == "wwwroot/404.html")
           {
           }
           else
           {
               status_code = 200;
               int filesize = 0;
               // std::cout << "debug resource : " << resource << std::endl;

               std::string textcontent = ReadFileRequest(resource, &filesize); // 不存在文件会输出open file error
               // std::cout << "debug" << std::endl;
               if (textcontent.empty())
               {
                   // 空内容说明读到不存在的页面
                   status_code = 404;
                   resptr->ADDStatusLine(status_code, _status_code_to_desc[status_code]);
                   std::string text404 = ReadFileRequest(wwwroot + PATH_SEP + errorpage, &filesize);
                   // std::cout << "text404" << text404 << std::endl;
                   resptr->ADDHeader("Content-Length", std::to_string(filesize));
                   resptr->ADDHeader("Content-Type", _mime_type[".html"]);
                   resptr->ADDText(text404);
               }
               else
               {
                   resptr->ADDStatusLine(status_code, _status_code_to_desc[status_code]);
                   resptr->ADDHeader("Content-Length", std::to_string(filesize));
                   resptr->ADDHeader("Content-Type", _mime_type[suffix]);

                   resptr->ADDText(textcontent);
               }
           }

           // std::cout << "suffix : " << suffix << std::endl;

           // resptr->ADDStatusLine(200);
           // resptr->ADDStatusLine(301);
           // resptr->ADDHeader("Content-Length", std::to_string(filesize));
           // resptr->ADDHeader("Content-Type", _mime_type[suffix]);
           // resptr->ADDHeader("Location", "https://github.com/Xpccccc");
           // std::cout << "suffix Content-Type : " << _mime_type[suffix] << std::endl;

           // resptr->ADDText(textcontent);
       }

       // 添加Cookie
       // resptr->ADDHeader("Set-Cookie", ProveCookieWrite());
       // resptr->ADDHeader("Set-Cookie", ProveCookieTimeOut());
       // resptr->ADDHeader("Set-Cookie", ProvePath());

       std::string response;

       resptr->Serialize(&response);
       resptr->DebugPrint();

       return response;

#endif
   }

   ~HttpService() {}

private:
   std::unordered_map<std::string, std::string> _mime_type;   // Content-Type
   std::unordered_map<int, std::string> _status_code_to_desc; // 状态码及其描述
   std::unordered_map<std::string, func_t> _funcs;            //
   SessionManager _session_manager;                           // Session管理
};
  • Session.hpp文件:管理Session,对第一次请求某特定路径下的资源时,响应一个SessionID给客户端。
#pragma once

#include <string>
#include <sys/types.h>
#include <unistd.h>
#include <unordered_map>

class Session
{
public:
   Session(std::string username, std::string status) : _username(username), _status(status)
   {
       _create_time = time(nullptr);
   }

   ~Session() {}

public:
   std::string _username;
   std::string _status;
   uint32_t _create_time;
};

class SessionManager
{
public:
   SessionManager()
   {
       srand(time(nullptr) ^ getpid());
   }

   std::string AddSession(Session *sptr)
   {
       uint32_t randnum = rand() + time(nullptr);
       std::string sessionid = std::to_string(randnum);
       _sessions.insert(std::make_pair(sessionid, sptr));
       return sessionid;
   }

   Session *GetSession(const std::string sessionid)
   {
       if (_sessions.find(sessionid) == _sessions.end())
           return nullptr;
       return _sessions[sessionid];
   }
   ~SessionManager() {}

private:
   std::unordered_map<std::string, Session *> _sessions;
};

运行结果:



3、总结和补充

HTTP Cookie 和 Session 都是用于在 Web 应用中跟踪用户状态的机制。Cookie 是存储在客户端的,而 Session 是存储在服务器端的。它们各有优缺点,通常在实际应用中会结合使用,以达到最佳的用户体验和安全性。

补充:favicon.ico 是一个网站图标,通常显示在浏览器的标签页上、地址栏旁边或收藏夹中。这个图标的文件名 favicon 是"favorite icon" 的缩写,而 .ico 是图标的文件格式。

浏览器在发起请求的时候,也会为了获取图标而专门构建 http 请求,我们不管它。


OKOK,HTTP中的Cookie和Session就到这里,如果你对Linux和C++也感兴趣的话,可以看看我的主页哦。下面是我的github主页,里面记录了我的学习代码和leetcode的一些题的题解,有兴趣的可以看看。

Xpccccc的github主页

  • 23
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Xpccccc

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值