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

【计算机网络】应用层协议Http——构建Http服务服务器

🔥个人主页🔥:孤寂大仙V
🌈收录专栏🌈:计算机网络
🌹往期回顾🌹: 【Linux笔记】——进程间关系与守护进程
🔖流水不争,争的是滔滔不息


  • 一、Http协议
    • URL
    • urlencode 和 urldecode
    • HTTP协议请求与响应格式
    • HTTP常见方法
    • Http状态码
      • 关于重定向
    • HTTP常见Header
  • 二、构建http服务器
    • Util.hpp 工具类
    • Http.hpp应用层Http协议的代码
      • **HttpRequest 处理客户端http请求**
      • HttpRespose.hpp服务端做应答
      • Http类最核心调度逻辑
    • **main.cc**
  • 三、Cookie和Session

一、Http协议

HTTP(HyperText Transfer Protocol,超文本传输协议)是一种用于分布式、协作式和超媒体信息系统的应用层协议。它是万维网(WWW)数据通信的基础,设计用于客户端与服务器之间的请求-响应交互。
HTTP 协议是客户端与服务器之间通信的基础。 客户端通过 HTTP 协议向服务器发送请求, 服务器收到请求后处理并返回响应。 HTTP 协议是一个无连接、 无状态的协议, 即每次请求都需要建立新的连接, 且服务器不会保存客户端的状态信息。

URL

http://www.example.com:8080/index.html

我们平常所俗称的“网址”就是说的URL。上面的url,https是采用的协议,www.example.com是域名也就是ip,端口一般是:后面的,index.html是路径,也就是用户要访问的超文本文件所处在目标主机的位置。域名是ip地址具有唯一性,路径是目标主机上特定路径的一个文件,这两个就表示了全网内唯一的文件。

但是我们发现好多URL没有端口号啊

http://www.example.com/index.html

实际上就是访问:http://www.example.com/index.html,因为有些协议有默认的端口号如http默认端口号是80.https默认端口号是443。

urlencode 和 urldecode

像 / ? : 等这样的字符, 已经被 url 当做特殊意义理解了,因此这些字符不能随意出现。比如, 某个参数中需要带有这些特殊字符, 就必须先对特殊字符进行转义。
将需要转码的字符转为 16 进制, 然后从右到左, 取 4 位(不足 4 位直接处理), 每 2 位做一位, 前面加上%, 编码成%XY 格式。

HTTP协议请求与响应格式

HTTP请求
在这里插入图片描述
在这里插入图片描述
首行:协议+url+协议版本
请求报头(Header):请求的属性,冒号分割的键值对;每组属性之间用\r\n分割,遇到空行表示Header部分结束了,如上图。
请求正文(Body):空行后面的内容都是Body,Body允许为空字符串,如果Body存在,则在Header 中会有一个 Content-Length 属性来标识 Body 的长度。

换行符和空行等特殊字符,是http能够做到报头和有效载荷的分离。http协议,序列化和反序列化用的是特殊字符进行子串拼接,不依赖第三方库


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

HTTP常见方法

GET方法
你访问网址的时候,其实就是发了个 GET 请求。
用于请求URL指定的资源。

POST方法
用于传输实体的主体, 通常用于提交表单数据。
就是提交数据,参数放在请求正文中。

PUT 方法
用于传输文件, 将请求报文主体中的文件保存到请求 URL 指定的位置。

HEAD 方法
与 GET 方法类似, 但不返回报文主体部分, 仅返回响应头。

DELETE 方法
用于删除文件, 是 PUT 的相反方法

OPTIONS 方法
用于查询针对请求 URL 指定的资源支持的方法。

Http状态码

在这里插入图片描述
最常见的状态码, 比如 200(OK), 404(Not Found), 403(Forbidden), 302(Redirect, 重定向), 504(Bad Gateway)。
在这里插入图片描述

关于重定向

HTTP 状态码 301(永久重定向) 和 302(临时重定向) 都依赖 Location 选项。

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

HTTP/1.1 301 Moved Permanently\r\n
Location: https://www.new-url.com\r\n

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

HTTP/1.1 302 Found\r\n
Location: https://www.new-url.com\r\n

无论是 HTTP 301 还是 HTTP 302 重定向, 都需要依赖 Location 选项来指定资源的新位置。 这个 Location 选项是一个标准的 HTTP 响应头部, 用于告诉浏览器应该将请求重定向到哪个新的 URL 地址。

临时重定向不是永久的,用在比如登录调整语言切换。永久重定向是永久的,用在域名变更。理解一下这两个用途的不同,就可以窥见两个重定向的不同了。

HTTP常见Header

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

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

核心作用

  • 管理持久连接: Connection 字段还用于管理持久连接(也称为长连接) 。 持久连接允许客户端和服务器在请求/响应完成后不立即关闭 TCP 连接, 以便在同一个连接上发送多个请求和接收多个响应。

持久连接(长连接)

  • HTTP/1.1: 在 HTTP/1.1 协议中, 默认使用持久连接。 当客户端和服务器都不明确指定关闭连接时, 连接将保持打开状态, 以便后续的请求和响应可以复用同一个连接。
  • HTTP/1.0: 在 HTTP/1.0 协议中, 默认连接是非持久的。 如果希望在 HTTP/1.0上实现持久连接, 需要在请求头中显式设置 Connection: keep-alive。

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

二、构建http服务器

基于TCP通信,服务器的传输层是用之前写的模版方法进行构造。

Util.hpp 工具类

#pragma once
#include <iostream>
#include <string>
#include <fstream>using namespace std;class Util
{
public:static bool Getoneline(string& in,string* out,const string& sep) //获取报文请求行{auto pos=in.find(sep);if(pos==string::npos){return false;}*out+=in.substr(0,pos);in.erase(0, pos + sep.size());return true;}static bool ReadFileConet(string &filename, string *out) // 把html文件写入应答正文{// version2 : 以二进制方式进行读取int filesize = FileSize(filename);if (filesize > 0){std::ifstream in(filename);if (!in.is_open())return false;out->resize(filesize);in.read((char *)(out->c_str()), filesize);in.close();}else{return false;}return true;}static int FileSize(const std::string &filename)  //获取正文长度{std::ifstream in(filename, std::ios::binary);if (!in.is_open())return -1;in.seekg(0, in.end);int filesize = in.tellg();in.seekg(0, in.beg);in.close();return filesize;}
};

Getoneline,是解析一行字符串,比如提取请求行。
ReadFileConet,读取HTML、图片等资源文件。
FileSize,获取文件大小,设置Content-Length。

下面具体用到再具体问题具体分析。

Http.hpp应用层Http协议的代码

#pragma once#include "Tcpserver.hpp"
#include "Util.hpp"
#include "Log.hpp"
#include <unordered_map>
#include <sstream>using namespace std;
using namespace SocketModule;
using namespace LogModule;const std::string gspace = " ";
const std::string glinespace = "\r\n";
const std::string glinesep = ": ";const string wwwroot = "./wwwroot";
const string homepage = "index.html";
const string page_404 = "/404.html";class HttpRequest   //处理客户端http请求
{
public:HttpRequest(): _is_interact(false){}string Serialize(){return string();}void ParseReqLin(string &req_line){stringstream ss(req_line);ss >> _method >> _uri >> _version;}bool Deserialize(string &in)    //反序列化{string req_line;bool r = Util::Getoneline(in, &req_line, glinespace);   //获取请求行ParseReqLin(req_line);if (_uri == "/"){_uri = wwwroot + _uri + homepage;}else{_uri = wwwroot + _uri;}//  _uri: ./wwwroot/login?username=zhangsan&password=123456LOG(LogLevel::DEBUG) << "method -> " << _method;LOG(LogLevel::DEBUG) << "uri -> " << _uri;LOG(LogLevel::DEBUG) << "version -> " << _version;const string temp = "?";    //url请求前面有?,代表浏览器要查询auto pos = _uri.find(temp);if (pos == string::npos){return true;}_args = _uri.substr(pos + temp.size());_uri = _uri.substr(0, pos);_is_interact = true;return true;}string Uri() { return _uri; }bool isInteract() { return _is_interact; }std::string Args() { return _args; }~HttpRequest(){}public:string _method;                         //请求方法string _uri;                            //URIstring _version;                        //http 版本unordered_map<string, string> _headers; //请求报头string _blankline;                      //空行string _text;                           //正文std::string _args; // 请求bool _is_interact; // 动态还是静态
};class HttpRespose   //服务端做应答
{
public:HttpRespose(): _blankline(glinespace){}string Serialize()  //序列化{string status_line = _version + gspace + to_string(_code) + gspace + _desc + glinespace;string kv_line;for (auto &head : _headers){string head_line = head.first + glinesep + gspace + head.second + glinespace;kv_line += head_line;}string resstr = status_line + kv_line + _blankline + _text;return resstr;}bool Deserialize(string &in){return true;}void SetTargetFile(const string &target){_targetfile = target;}void SetCode(int code){_code = code;switch (_code){case 200:_desc = "OK";break;case 404:_desc = "Not Found";break;case 302:_desc = "See Other";break;default:break;}}void SetHeader(const string &key, const string &value){auto iter = _headers.find(key);if (iter != _headers.end()){return;}_headers.insert(make_pair(key, value));}string Uri2Suffix(const string &targetfile){auto pos = targetfile.rfind('.');if (pos == string::npos){return "text/html";}string suffix = targetfile.substr(pos);if (suffix == ".html" || suffix == ".htm")return "text/html";else if (suffix == ".jpg")return "image/jpeg";else if (suffix == ".png")return "image/png";elsereturn "";}bool MakeResponse(){_version = "HTTP/1.1";if (_targetfile == "./wwwroot/redir_test") // 测试重定向{SetCode(302);SetHeader("Location", "https://www.qq.com/");return true;}int filesize;string suffix;bool re = Util::ReadFileConet(_targetfile, &_text);//读取目标文件的内容if (!re){// SetCode(302);// SetHeader("Location","http://120.46.84.37:8080/404.html"); //通过重定向方式访问404页面SetCode(404);_targetfile = wwwroot + page_404;Util::ReadFileConet(_targetfile, &_text);filesize = Util::FileSize(_targetfile); // 拿到正文大小suffix = Uri2Suffix(_targetfile);SetHeader("Content-Length", to_string(filesize));SetHeader("Content-Type", suffix);}else{SetCode(200);filesize = Util::FileSize(_targetfile); // 拿到正文大小suffix = Uri2Suffix(_targetfile);SetHeader("Content-Length", to_string(filesize));SetHeader("Content-Type", suffix);}return true;}void SetText(const std::string &t){_text = t;}~HttpRespose(){}public:string _version;int _code;string _desc;unordered_map<string, string> _headers;string _blankline;string _text;string _targetfile;
};using http_func_t = function<void(HttpRequest &req, HttpRespose &resp)>;
class Http
{
public:Http(uint16_t port): _tserver(make_unique<Tcpserver>(port)){}void HttpRequestserver(shared_ptr<Socket> &sockfd, InetAddr &client){string reqstr;int n = sockfd->Recv(&reqstr);if (n > 0){cout << "#############################" << endl;cout << reqstr << endl;cout << "#############################" << endl;HttpRequest req;  // 请求对象HttpRespose resq; // 应答对象req.Deserialize(reqstr);if (req.isInteract()) // 动态{if (_route.find(req.Uri()) == _route.end()){}else{_route[req.Uri()](req, resq);string response_str = resq.Serialize();sockfd->Send(response_str);}}else // 静态{resq.SetTargetFile(req.Uri());if (resq.MakeResponse()) // 封装报文{string resq_str = resq.Serialize();sockfd->Send(resq_str);}}}}void Start(){_tserver->Start([this](shared_ptr<Socket> &sockfd, InetAddr &client){ this->HttpRequestserver(sockfd, client); });}void RegisterService(const string name, http_func_t h){string key = wwwroot + name;auto iter = _route.find(key);if (iter == _route.end()){_route.insert(make_pair(key, h));}}~Http(){}private:unique_ptr<Tcpserver> _tserver;unordered_map<string, http_func_t> _route;
};

HttpRequest 处理客户端http请求

class HttpRequest   //处理客户端http请求
{
public:HttpRequest(): _is_interact(false){}string Serialize(){return string();}void ParseReqLin(string &req_line){stringstream ss(req_line);ss >> _method >> _uri >> _version;}bool Deserialize(string &in)    //反序列化{string req_line;bool r = Util::Getoneline(in, &req_line, glinespace);   //获取请求行ParseReqLin(req_line);if (_uri == "/"){_uri = wwwroot + _uri + homepage;}else{_uri = wwwroot + _uri;}//  _uri: ./wwwroot/login?username=zhangsan&password=123456LOG(LogLevel::DEBUG) << "method -> " << _method;LOG(LogLevel::DEBUG) << "uri -> " << _uri;LOG(LogLevel::DEBUG) << "version -> " << _version;const string temp = "?";    //url请求前面有?,代表浏览器要查询auto pos = _uri.find(temp);if (pos == string::npos){return true;}_args = _uri.substr(pos + temp.size());_uri = _uri.substr(0, pos);_is_interact = true;return true;}string Uri() { return _uri; }bool isInteract() { return _is_interact; }std::string Args() { return _args; }~HttpRequest(){}public:string _method;                         //请求方法string _uri;                            //URIstring _version;                        //http 版本unordered_map<string, string> _headers; //请求报头string _blankline;                      //空行string _text;                           //正文std::string _args; // 请求bool _is_interact; // 动态还是静态
};

私有成员变量中,根据http协议请求,请求行有请求方法、URL、http版本,然后是请求报头,空行正文,根据这些定义这些成员变量。请求字符串这个成员变量是从url中提取出来的用户的需求。
成员变量中还有一个判断是是否是动态还是静态的,所谓静态资源就是内容是固定的,不需要数据库的,动态资源就是根据用户的请求内容变化,通常需要数据库,如登录、注册等。

服务器要想拿到客户端的http请求,要对http通过网络发来的http报文做出解析,注意http报文从客户端发到主机不用序列化,服务器的应答发到客户端不用反序列化(HTTP 请求和响应虽然是“文本格式”,但本质上已经是一种“通用序列化格式”了。HTTP 的本质,就是一种“轻量文本格式”的协议,天然具备自解释性,这就是它“看起来不用手动序列化/反序列化”的根本原因)。
其实我代码中处理客户端http请求的反序列化,更像是解析。

解析请求过程,通过工具类中的Getoneline是解析一行字符串,来提取请求行。把请求行分割为三部分,请求方法、URL、版本。(在这里说一下,请求的资源,比如网页内容必须是服务器特定路径下的文件,放在web根目录中)如果URL是根目录(web根目录)就把路径拼接为网页的首页,如果URL用户申请的特定网页就拼接上用户请求的url。有时候urll后面带一个?,后面就是用户提交的查询参数了,做一下判断。截取一下 _uri 是用户请求的页面,比如 /login.html_args 是查询参数,比如 username=han&password=123。有查询参数是动态页面。

上面的GET方法通常在获取静态资源使用,POST方法通常在获取动态资源使用。

客户端发来的请求,服务器第一时间是拆第一行,搞清楚是 GET 还是 POST,要访问哪个资源(URL),是静态页面还是带查询的动态页面,然后拼成服务器本地路径,再判断是否有参数 —— 这就完成了解析的第一步。


HttpRespose.hpp服务端做应答

class HttpRespose   //服务端做应答
{
public:HttpRespose(): _blankline(glinespace){}string Serialize()  //序列化{string status_line = _version + gspace + to_string(_code) + gspace + _desc + glinespace;string kv_line;for (auto &head : _headers){string head_line = head.first + glinesep + gspace + head.second + glinespace;kv_line += head_line;}string resstr = status_line + kv_line + _blankline + _text;return resstr;}bool Deserialize(string &in){return true;}void SetTargetFile(const string &target){_targetfile = target;}void SetCode(int code){_code = code;switch (_code){case 200:_desc = "OK";break;case 404:_desc = "Not Found";break;case 302:_desc = "See Other";break;default:break;}}void SetHeader(const string &key, const string &value){auto iter = _headers.find(key);if (iter != _headers.end()){return;}_headers.insert(make_pair(key, value));}string Uri2Suffix(const string &targetfile){auto pos = targetfile.rfind('.');if (pos == string::npos){return "text/html";}string suffix = targetfile.substr(pos);if (suffix == ".html" || suffix == ".htm")return "text/html";else if (suffix == ".jpg")return "image/jpeg";else if (suffix == ".png")return "image/png";elsereturn "";}bool MakeResponse(){_version = "HTTP/1.1";if (_targetfile == "./wwwroot/redir_test") // 测试重定向{SetCode(302);SetHeader("Location", "https://www.qq.com/");return true;}int filesize;string suffix;bool re = Util::ReadFileConet(_targetfile, &_text);//读取目标文件的内容if (!re){// SetCode(302);// SetHeader("Location","http://120.46.84.37:8080/404.html"); //通过重定向方式访问404页面SetCode(404);_targetfile = wwwroot + page_404;Util::ReadFileConet(_targetfile, &_text);filesize = Util::FileSize(_targetfile); // 拿到正文大小suffix = Uri2Suffix(_targetfile);SetHeader("Content-Length", to_string(filesize));SetHeader("Content-Type", suffix);}else{SetCode(200);filesize = Util::FileSize(_targetfile); // 拿到正文大小suffix = Uri2Suffix(_targetfile);SetHeader("Content-Length", to_string(filesize));SetHeader("Content-Type", suffix);}return true;}void SetText(const std::string &t){_text = t;}~HttpRespose(){}public:string _version;int _code;string _desc;unordered_map<string, string> _headers;string _blankline;string _text;string _targetfile;
};

http协议应答中,状态行包含hettp版本、状态码、找状态码描述。私有成员变量要设置相应的变量。响应报头、空行、响应正文和上面处理请求的私有成员变量一样。

先说序列化Serialize(),不是真正的序列化,是搞成Http协议标准应答报文的格式。第一层状态行,给_version,_code,_desc拼接起来。封装响应报头,用范围for把多个响应报头拿到对其按照标准报文格式进行拼接。最后在拼接上空行和响应正文。

设置状态码和描述SetCode,根据规定的状态码对应的方案,写了几个重要的状态码以及描述。

添加响应报头信息,就是在响应报头中贴标签,比如这个服务器叫啥。就是告诉浏览器一些关系的信息,比如返回的是HTML还是图片,SetHeader函数把这些信息一条条加进去。

Uri2Suffix函数根据请求的URL去判断是什么文件,然后根据它的MIME类型,比如“image/jpeg”这个结果会被放到响应报头的的Content-Type字段里。响应报头里的 Content-Type 字段标明了服务器返回的内容类型(MIME 类型),这就像给浏览器打个招呼:“哥们,我这是一张图,你别当成网页来渲染哈”。

MakeResponse主逻辑根据目标文件生成应答。设置版本,加个重定向主要是为了测试一下重定向,如果路径是xxx,就跳转到设定的目标网页,这里是临时重定向。现在我们要做的是把目标 HTML 文件的内容读出来,作为响应报文的正文(body)发回去给浏览器,通过工具类ReadFileConet把目标HTML文件写入响应正文。如果读取错误,返回404错误页面,这个错误页面的html也是在web根目录中,目标路径_targetfile重新设置为404页面重新写入正文。为了设置响应报头信息和MIME类型用上面的SetHeaderUri2Suffix进行设置。读取成功目标HTML文件成功写入响应正文,为了设置响应报头信息和MIME类型用上面的SetHeaderUri2Suffix进行设置。


Http类最核心调度逻辑

using http_func_t = function<void(HttpRequest &req, HttpRespose &resp)>;
class Http
{
public:Http(uint16_t port): _tserver(make_unique<Tcpserver>(port)){}void HttpRequestserver(shared_ptr<Socket> &sockfd, InetAddr &client){string reqstr;int n = sockfd->Recv(&reqstr);if (n > 0){cout << "#############################" << endl;cout << reqstr << endl;cout << "#############################" << endl;HttpRequest req;  // 请求对象HttpRespose resq; // 应答对象req.Deserialize(reqstr);if (req.isInteract()) // 动态{if (_route.find(req.Uri()) == _route.end()){}else{_route[req.Uri()](req, resq);string response_str = resq.Serialize();sockfd->Send(response_str);}}else // 静态{resq.SetTargetFile(req.Uri());if (resq.MakeResponse()) // 封装报文{string resq_str = resq.Serialize();sockfd->Send(resq_str);}}}}void Start(){_tserver->Start([this](shared_ptr<Socket> &sockfd, InetAddr &client){ this->HttpRequestserver(sockfd, client); });}void RegisterService(const string name, http_func_t h){string key = wwwroot + name;auto iter = _route.find(key);if (iter == _route.end()){_route.insert(make_pair(key, h));}}~Http(){}private:unique_ptr<Tcpserver> _tserver;unordered_map<string, http_func_t> _route;
};

私有成员变量_tserver是Tcpserver对象,负责TCP监听和连接。_route是unordered_map对象,用于注册动态接口。

void Start(){_tserver->Start([this](shared_ptr<Socket> &sockfd, InetAddr &client){ this->HttpRequestserver(sockfd, client); });}

启动Tcpserver,用lambda回调函数,调用HttpRequestserver收客户端的请求。

RegisterService(),注册服务路径->对应处理函数。

HttpRequestserver调用Recv接收请求信息,构造HttpRequest请求对象和 HttpRespose应答对象。对请求进行解析提取客户端用户要访问的目标html,根据是否是动态请求分发,动态查找_route,调用注册回调,静态构造目标文件路径,调用MakeResponse()生成响应报文。将序列化后的响应报文Send给客户端。


main.cc


#include "Http.hpp"
void Login(HttpRequest& req, HttpRespose& resp)
{LOG(LogLevel::DEBUG) << req.Args() << ", 成功进入到了处理数据的逻辑";std::string text = "hello: " + req.Args();// 登录认证resp.SetCode(200);resp.SetHeader("Content-Type","text/plain");resp.SetHeader("Content-Length", std::to_string(text.size()));resp.SetText(text);LOG(LogLevel::DEBUG) <<"返回了啊";
}
// http port
int main(int argc, char *argv[])
{uint16_t port = std::stoi(argv[1]);Enable_Console_Log_Strategy(); // 启用控制台输出std::unique_ptr<Http> httpsvr = std::make_unique<Http>(port);httpsvr->RegisterService("/login", Login); // httpsvr->Start();return 0;
}

浏览器访问 /login 这个路径时,我们希望服务器:不是去找静态文件(因为根本没有有/login.html),而是执行一个 函数,比如去数据库查用户是否存在,返回一段 JSON 或重定向。Http类中成员变量 unordered_map<string, http_func_t> _route;key是路由路径,value是处理这个路径的处理函数。处理函数是

void Login(HttpRequest& req, HttpRespose& resp)
{LOG(LogLevel::DEBUG) << req.Args() << ", 成功进入到了处理数据的逻辑";std::string text = "hello: " + req.Args();// 登录认证resp.SetCode(200);resp.SetHeader("Content-Type","text/plain");resp.SetHeader("Content-Length", std::to_string(text.size()));resp.SetText(text);LOG(LogLevel::DEBUG) <<"返回了啊";
}

注册函数,我们告诉 Http:以后只要有人请求这个路径 /login,你就调用 handler 这个函数去处理。

void RegisterService(const string name, http_func_t h){string key = wwwroot + name;auto iter = _route.find(key);if (iter == _route.end()){_route.insert(make_pair(key, h));}}

main.cc中使用方式,httpsvr->RegisterService(“/login”, Login);

在这里插入图片描述


测试结果
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
Http服务器:源码

三、Cookie和Session

HTTP 协议是一个无连接、 无状态的协议, 即每次请求都需要建立新的连接, 且服务器不会保存客户端的状态信息。
无连接是每次请求响应之后,客户端与服务器就断开了。
无状态是服务器不会记住上一次请求的任何信息。

在这里插入图片描述

cookie是浏览器保存的一小段文本信息,随请求发给客户端,这种机制是为了应对上述情况的一种浏览器实现的机制,cookie会存储用户的账号和密码等信息。如果没有cookie那么每次访问网站都要重新登录。cookie是在客户端浏览器实现的。你打开淘宝登录了一次,下次刷新网页还能记住你是谁,就是因为 cookie 帮你“记住”了登录信息。

session是服务器上的一块数据空间,用来存储用户信息的。

这两者进行数据交换,为防止信息泄露,要用其他机制对信息进行加密,防止在网络传输中被泄露。

相关文章:

【计算机网络】应用层协议Http——构建Http服务服务器

&#x1f525;个人主页&#x1f525;&#xff1a;孤寂大仙V &#x1f308;收录专栏&#x1f308;&#xff1a;计算机网络 &#x1f339;往期回顾&#x1f339;&#xff1a; 【Linux笔记】——进程间关系与守护进程 &#x1f516;流水不争&#xff0c;争的是滔滔不息 一、Http协…...

【求A类B类月】2022-2-9

缘由编程求解&#xff0c;如内容所示题-Python-CSDN问答只写示例及注释 每月工作日只考虑周末情况&#xff0c;即只有周六、周日放假。每月第一个工作日如果是星期一则该月是A类月&#xff0c;每月最后一个工作日如果是星期五则该月是B类月。一个月可能是A类月也可能是B类月。…...

信息安全之为什么引入公钥密码

在对称密码中&#xff0c;由于加密和解密的密钥是相同的&#xff0c;因此必须向接收者配送密钥&#xff0c;这里就涉及到密钥配送问题 那么什么时候密钥配送问题呢&#xff1f;举个简单的例子大家就清楚了&#xff0c; Alice 前几天在网上认识了Bob&#xff0c;现在她想给Bob…...

linux版本vmware修改ubuntu虚拟机为桥接模式

1、先打开linux版本vmware操作界面 2、设置虚拟路由编辑器的桥接模式 输入账号密码 自动模式 不需要进行任何操作 3、修改虚拟机设置网络模式为桥接模式 然后save保存一下配置 4、现在进入虚拟机查看ens33配置 网卡启动但是没有ip 5、自己进行设置修改ubuntu网络配置文件 cd …...

pytest 常见问题解答 (FAQ)

pytest 常见问题解答 (FAQ) 1. 基础问题 Q1: 如何让 pytest 发现我的测试文件&#xff1f; 测试文件命名需符合 test_*.py 或 *_test.py 模式测试函数/方法需以 test_ 开头测试类需以 Test 开头(且不能有__init__方法) Q2: 如何运行特定测试&#xff1f; pytest path/to/t…...

从0到1上手Trae:开启AI编程新时代

摘要&#xff1a;字节跳动 2025 年 1 月 19 日发布的 Trae 是一款 AI 原生集成开发环境工具&#xff0c;3 月 3 日国内版推出。它具备 AI 问答、代码自动补全、基于 Agent 编程等功能&#xff0c;能自动化开发任务&#xff0c;实现端到端开发。核心功能包括智能代码生成与补全、…...

HTTPS 协议:数据传输安全的坚实堡垒

在互联网技术飞速发展的今天&#xff0c;数据在网络中的传输无处不在。从日常浏览网页、在线购物&#xff0c;到企业间的数据交互&#xff0c;每一次信息传递都关乎着用户隐私、企业利益和网络安全。HTTP 协议作为互联网应用层的基础协议&#xff0c;曾经承担着数据传输的重任&…...

Spring Boot中使用@JsonAnyGetter和@JsonAnySetter处理动态JSON属性

Spring Boot 中使用 @JsonAnyGetter 和 @JsonAnySetter 处理动态 JSON 属性 在实际的后端开发中,尤其是使用 Spring Boot 构建 API 时,我们经常会遇到需要处理动态 JSON 属性的场景。例如,前端传递过来的 JSON 数据结构不固定,或者业务需求变更频繁,导致实体类无法预先定…...

Spring Boot测试框架全面解析

Spring Boot测试框架基础 Spring Boot通过增强Spring测试框架的能力,为开发者提供了一系列简化测试流程的新注解和特性。该框架建立在成熟的Spring测试基础之上,通过自动化配置和专用注解显著提升了测试效率。 核心依赖配置 要使用Spring Boot的全部测试功能,只需在项目中…...

Linux之MySQL安装篇

1.确保Yum环境是否能正常使用 使用yum环境进行软件的安装 yum -y install mysql-server mysql2.确保软件包已正常完成安装 3.设置防火墙和selinux配置 ## 关闭防火墙 systemctl stop firewalld## 修该selinux配置 vim /etc/selinux/config 将seliuxenforcing修改为sel…...

Asp.Net Core 如何配置在Swagger中带JWT报文头

文章目录 前言一、配置方法二、使用1、运行应用程序并导航到 /swagger2、点击右上角的 Authorize 按钮。3、输入 JWT 令牌&#xff0c;格式为 Bearer your_jwt_token。4、后续请求将自动携带 Authorization 头。 三、注意事项总结 前言 配置Swagger支持JWT 一、配置方法 在 …...

第12讲、Odoo 18 权限控制机制详解

目录 引言权限机制概述权限组&#xff08;Groups&#xff09;访问控制列表&#xff08;ACL&#xff09;记录规则&#xff08;Record Rules&#xff09;字段级权限控制按钮级权限控制菜单级权限控制综合案例&#xff1a;多层级权限控制最佳实践与注意事项总结 引言 Odoo 18 提…...

8086 处理器 Flags 标志位全解析:CPU 的 “晴雨表” 与 “遥控器”总结:

引入&#xff1a; 你是否好奇&#xff0c;当 CPU 执行一条加法指令时&#xff0c;如何自动判断结果是否超出范围&#xff1f;当程序跳转时&#xff0c;如何快速决定走哪条分支&#xff1f;甚至在调试程序时&#xff0c;为何能让 CPU “一步一停”&#xff1f;这一切的答案&…...

具有离散序列建模的统一多模态大语言模型【AnyGPT】

第1章 Instruction 在人工智能领域、多模态只语言模型的发展正迎来新的篇章。传统的大型语言模型(LLM)在理解和生成人类语言方面展现出了卓越的能力&#xff0c;但这些能力通常局限于 文本处理。然而&#xff0c;现实世界是一个本质上多模态的环境&#xff0c;生物体通过视觉、…...

PHP HTTP 完全指南

PHP HTTP 完全指南 引言 PHP 作为一种流行的服务器端脚本语言,广泛应用于各种Web开发项目中。HTTP(超文本传输协议)是互联网上应用最为广泛的网络协议之一,用于在Web服务器和客户端之间传输数据。本文将详细介绍 PHP 在 HTTP 通信中的应用,帮助开发者更好地理解和利用 P…...

物流项目第九期(MongoDB的应用之作业范围)

本项目专栏&#xff1a; 物流项目_Auc23的博客-CSDN博客 建议先看这期&#xff1a; MongoDB入门之Java的使用-CSDN博客 需求分析 在项目中&#xff0c;会有两个作业范围&#xff0c;分别是机构作业范围和快递员作业范围&#xff0c;这两个作业范围的逻辑是一致的&#xf…...

系统思考:经营决策沙盘

今年是我为黄浦区某国有油漆涂料企业提供经营决策沙盘培训的第二年。在这段时间里&#xff0c;我越来越感受到&#xff0c;企业的最大成本往往不在生产环节&#xff0c;而是在决策错误上所带来的长远影响。尤其是在如今这个复杂多变的环境下&#xff0c;企业面临的挑战愈发严峻…...

[网页五子棋][对战模块]实现游戏房间页面,服务器开发(创建落子请求/响应对象)

实现游戏房间页面 创建 css/game_room.css #screen 用于显示当前的状态&#xff0c;例如“等待玩家连接中…”&#xff0c;“轮到你落子”&#xff0c;“轮到对方落子”等 #screen { width: 450px; height: 50px; margin-top: 10px; color: #8f4e19; font-size: 28px; …...

数据结构-代码总结

下面代码自己上完课写着玩的,除了克鲁斯卡尔那里完全ai&#xff0c;其他基本上都是自己写的&#xff0c;具体请参考书本,同时也欢迎各位大佬来纠错 线性表 //线性表--顺序存储结构 #include<iostream> using namespace std; template<typename T> …...

快速掌握 GO 之 RabbitMQ

更多个人笔记见&#xff1a; github个人笔记仓库 gitee 个人笔记仓库 个人学习&#xff0c;学习过程中还会不断补充&#xff5e; &#xff08;后续会更新在github和 gitee上&#xff09; 文章目录 作用经典例子生产者&#xff08;发送端&#xff09;消费者&#xff08;接收端&a…...

SQL Server 事务详解:概念、特性、隔离级别与实践

一、事务的基本概念 事务&#xff08;Transaction&#xff09;是数据库操作的基本单位&#xff0c;它是由一组SQL语句组成的逻辑工作单元。事务具有以下关键特性&#xff0c;通常被称为ACID特性&#xff1a; ​​原子性&#xff08;Atomicity&#xff09;​​&#xff1a;事务…...

MAC软件游戏打开提示已损坏

打开「终端.app」&#xff0c;输入以下命令并回车&#xff0c;输入开机密码回车 sudo spctl --master-disable 按照上述步骤操作完成后&#xff0c;打开「系统偏好设置」-「安全与隐私」-「通用」&#xff0c;确保已经修改为「任何来源」。 打开「终端.app」&#xff0c;输入…...

React基础教程(13):路由的使用

文章目录 1、什么是路由?2、路由安装3、路由使用(1)路由方法导入和使用(2)定义路由以及重定向(3)嵌套路由(4)路由跳转方式(5)动态路由动态路由写法一动态路由写法二4、实现效果5、完整代码下载1、什么是路由? 路由是根据不同的url地址展示不同的内容或页面。 一个…...

力扣刷题(第四十三天)

灵感来源 - 保持更新&#xff0c;努力学习 - python脚本学习 解题思路 1. 逐位检查法&#xff1a;通过右移操作逐位检查每一位是否为1&#xff0c;统计计数 2. 位运算优化法&#xff1a;利用 n & (n-1) 操作消除最低位的1&#xff0c;减少循环次数 3. 内置函数法&…...

Centos环境下安装/重装MySQL完整教程

目录 一、卸载残留的MySQL环境&#xff1a; 二、安装MySQL&#xff1a; 1、下载MySQL官方的yum源&#xff1a; 2、更新系统yum源&#xff1a; 3、确保系统中有了对应的MySQL安装包&#xff1a; 4、安装MySQL服务&#xff1a; 5、密钥问题安装失败解决方法&#xff1a; …...

【Linux】环境变量完全解析

9.环境变量 文章目录 9.环境变量一、命令行参数二、获取环境变量程序中获取环境变量1. 使用命令行参数2. 使用系统调用函数getenv("字符串");3. 使用系统提供的全局变量environ 命令行中查询环境变量 三、常见环境变量1. HOME2. OLDPWD3. PATH4. SHELL 四、环境变量与…...

【Java】mybatis-plus乐观锁-基本使用

乐观锁&#xff08;Optimistic Locking&#xff09;是解决并发问题的重要机制。它通过在数据更新时验证数据版本来确保数据的一致性&#xff0c;从而避免并发冲突。与悲观锁不同&#xff0c;乐观锁并不依赖数据库的锁机制&#xff0c;而是通过检查数据的版本或标志字段来判断数…...

力扣每日一题——找到离给定两个节点最近的节点

目录 题目链接&#xff1a;2359. 找到离给定两个节点最近的节点 - 力扣&#xff08;LeetCode&#xff09; 题目描述 解法一&#xff1a;双指针路径交汇法​ 基本思路 关键步骤 为什么这样可行呢我请问了&#xff1f; 举个例子 特殊情况 Java写法&#xff1a; C写法&a…...

机器学习与深度学习03-逻辑回归01

目录 上集回顾1. 逻辑回归与线性回归的区别2.逻辑回归的常见目标函数3.逻辑回归如何分类4.Sigmoid函数详解5.逻辑回归模型的参数 上集回顾 上一节文章地址&#xff1a;链接 1. 逻辑回归与线性回归的区别 应用领域 线性回归通常⽤于解决回归问题&#xff0c;其中⽬标是预测⼀…...

卷积神经网络(CNN)入门学习笔记

什么是 CNN&#xff1f; CNN&#xff0c;全称 卷积神经网络&#xff08;Convolutional Neural Network&#xff09;&#xff0c;是一种专门用来处理图片、语音、文本等结构化数据的神经网络。 它模仿人眼识别图像的方式&#xff1a; 从局部到整体&#xff0c;一步步提取特征&a…...