目录
- 🚀前言
- 🚃HTTP协议
- 🚄1、URL网址
- 🚅2、URL的编码和解码
- 🚇3、HTTP协议格式
- 🚈4、HTTP请求
- 🚉4.1、 HTTP GET和POST方法
- 🚋4.2、HTTP状态码
- 🚊4.3、HTTP常见Header
🚀前言
本篇文章进行应用层中HTTP协议的学习!!!
🚃HTTP协议
前言:应用层协议都是人定的,在以前有一些大佬定的好用的应用层协议,供我们直接参考使用,后面就变成了标准,HTTP就在其中
-
应用层的作用主要是向用户提供应用服务时的通信活动
-
http协议:又名超文本传输协议,是一个简单的网络请求和响应协议,它通常运行在传输层之上的应用层
-
它指定了客户端可能发送给服务器什么样的消息以及得到什么样的响应
-
HTTP协议主要是在网络中传输图片、音频、视频等网络资源,把这些丰富的资源都呈现在一个网站上,正是因为它可以传输任何资源,所以才叫超文本传输协议
🚄1、URL网址
前言:平时我们经常说的访问某某某网址,其实就是说访问某某某URL!!!
URL中的字段
-
第一个字段是协议名
-
第二个字段中的登录信息(用户名和密码)一般不在URL中明文传输,一般都是通过请求正文加密进行传输
-
第三个字段是网址的域名,域名可以通过DNS服务转换成IP地址,然后通过对应的端口号标定网络中唯一的进程
-
第四个字段中的第一个斜杠是Web根目录,它可以被设置为根目录,也可以设置为其他路径作为web根目录(相对路径),后面访问的文件是这个网址要在服务器获取的资源(index.html)
-
查询字符串用于向服务器传递参数,格式为?key=value&key2=value2,其中?key=value表示为第一个参数,&用于分隔多个参数,GET方法请求可以直接将参数以明文方式添加到URL中
-
片段标识符用于指定文档中特定的部分,格式为#fragment,其中#表示片段标识符开始,fragment表示文档中特定的部分。主要用于跳转到文档的特定位置
-
总结:URL就是通过某种协议获取某个主机中的某种资源,拉取到网站本地中,如果是html,浏览器可以自动解析
🚅2、URL的编码和解码
【URL编码解码在线网页工具】
-
编码
-
- 当使用URL进行传参时,如果存在特殊字符或汉字时,会对其进行转义(编码)【url特殊字符】
-
- 比如问号和加号等特殊字符,它们已经被URL当做特殊意义理解了,因此这些字符不能随意出现
-
- 编码规则:将需要转码的字符转为16进制,然后从右到左,取4位(不足4位直接处理),每2位做一位,前面加上%,编码成%XY格式
搜关键字通过utl传参,“+” 被转义成了 %2B
汉字通过url进行传参后,也会对其进行编码,每个汉字转换成三个16进制的数据
-
解码
-
- 解码就是编码的逆过程,首先去掉%,然后将16进制数据转换成十进制,然后通过ASCII表转换成对应的特殊字符即可!!!
🚇3、HTTP协议格式
-
HTTP请求
-
- HTTP请求包含:请求行、请求报头、空行和请求正文(有效载荷)这四个部分,空行是用来分离请求报头和请求正文(有效载荷)的分隔符
-
- 首行:[方法] + [url] + [HTTP版本]
-
- 请求报头(Header):请求报头内的属性可以有多个,请求的属性是冒号分割的键值对;每组属性之间使用“\r\n”分隔;遇到空行(“\r\n”)表示Header部分结束
-
- 请求正文(Body):空行后面的内容都是Body,Body允许为空字符串;如果Body存在, 则在Header中会有一个Content-Length属性来标识Body的长度
-
HTTP响应
-
- HTTP响应包含:状态行、响应报头、空行和响应正文(有效载荷)这四个部分,空行是用来分离请求报头和响应正文(有效载荷)的分隔符
-
- 首行:[HTTP版本号] + [状态码] + [状态码描述]
-
- 请求报头(Header):响应报头内的属性可以有多个,响应的属性是冒号分割的键值对;每组属性之间使用“\r\n”分隔;遇到空行(“\r\n”)表示Header部分结束
-
- 响应正文(Body):空行后面的内容都是Body,Body允许为空字符串;如果Body存在, 则在Header中会有一个Content-Length属性来标识Body的长度;如果服务器返回了一个html页面,那么html页面内容就是在body中
HTTP的请求和响应如何保证自己的报头和有效载荷被全部读取呢?
-
读取完整报头:按行读取,直到读取到空行即读取完了报头
-
读取完整有效载荷:报头读取完毕后,请求或响应的报头属性中,有一个属性(Content-Length)可以标识有效载荷的数据长度,按照给定的长度进行读取,就能读取到完整的正文
HTTP请求行:GET / HTTP/1.1
-
请求行中的第二个字段是URL,而URL除了资源路径以外其他的相关字段都省略了
-
第二个字段下的斜杠,它不是服务器的根目录,而是一个Web根目录,但是它也可以设置成为根目录,一般为服务器内相对路径下的Web根目录
-
举个例子,我们访问baidu.com时,请求资源路径只写一个斜杠,但是baidu会给我们自动添加主页资源的路径(/index.html),随后通过网络传输到客户端显示给用户
代码感受一下:首个http服务器代码
- 该服务器只是简单的构建了http响应报头,多线程的方式进行读取请求和响应请求
#include <iostream>
#include <string>
#include <cerrno>
#include <cstdlib>
#include <cstring>
#include <cassert>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <thread>
#include <fstream>
#define ROOT_PATH "./wwwroot"
#define HOME "index.html"
#define CRLF "\r\n"
#define CRLF_LEN 2
#define SPACE " "
#define SPACE_LEN 1
class HttpServer
{
public:
HttpServer(uint16_t Port, std::string Ip = "")
: ServerIp_(Ip), ServerPort_(Port), ListenSockfd_(-1)
{
}
void Init()
{
assert(ServerPort_ > 1024);
ListenSockfd_ = socket(AF_INET, SOCK_STREAM, 0);
if (ListenSockfd_ < 0)
{
std::cout << "have socket error: " << strerror(errno) << std::endl;
exit(1);
}
sockaddr_in ServerData;
socklen_t len = sizeof(ServerData);
memset(&ServerData, 0, len);
ServerData.sin_family = AF_INET;
ServerData.sin_port = htons(ServerPort_);
ServerData.sin_addr.s_addr = ServerIp_.empty() ? htonl(INADDR_ANY) : inet_addr(ServerIp_.c_str());
if (bind(ListenSockfd_, (const sockaddr *)&ServerData, len))
{
std::cout << "bind inet data error: " << strerror(errno) << std::endl;
exit(2);
}
if (listen(ListenSockfd_, 2) < 0)
{
std::cout << "Set listen status error: " << strerror(errno) << std::endl;
exit(3);
}
}
void Start()
{
while (true)
{
int sersockfd = accept(ListenSockfd_, nullptr, nullptr);
if (sersockfd < 0)
{
std::cout << "accept error: " << strerror(errno) << std::endl;
exit(4);
}
// 使用线程并行的去执行服务,不影响获取连接请求
std::thread Thread(&HttpServer::RunThread, this, sersockfd);
Thread.detach();
}
}
private:
void RunThread(int sersockfd)
{
std::string inbuffer;
inbuffer.resize(1024, 0);
// 读http请求
ssize_t Index = read(sersockfd, (void *)inbuffer.c_str(), inbuffer.size());
if (Index > 0)
{
std::cout << inbuffer << std::endl;
}
// 构建响应报文
std::string RequestPath = ROOT_PATH;
RequestPath += GetRequestPath(inbuffer);
std::string resources = ReadFile(RequestPath);
std::string Response;
Response += "HTTP/1.1 200 OK\r\n"; // 请求行
Response += "Content-Type: text/html\r\n";
Response += ("Content-Length: ") + (std::to_string(resources.size()) + "\r\n");
Response += "\r\n";
Response += resources;
// 发送响应报文
send(sersockfd, Response.c_str(), Response.size(), 0);
close(sersockfd); // 服务完成后关闭服务套接字
}
private:
std::string ReadFile(const std::string &Path)
{
// 读取请求资源路径下的文件
std::ifstream in(Path.c_str());
if (!in.is_open())
{
std::cout << "open file error: " << strerror(errno) << std::endl;
return "404";
}
std::string buffer;
std::string result;
while (std::getline(in, buffer))
{
result += buffer;
}
std::cout << result << std::endl;
return result;
}
std::string GetRequestPath(std::string &inbuffer)
{
// "GET /a/b/c HTTP/1.1\r\n" --> "/a/b/c"
size_t pos = inbuffer.find(CRLF);
if (pos == std::string::npos)
return "4xx";
std::string RequestLine = inbuffer.substr(0, pos + CRLF_LEN);
size_t SpaceOne = RequestLine.find(SPACE);
size_t SpaceTwo = RequestLine.rfind(SPACE);
if (SpaceOne == std::string::npos && SpaceTwo == std::string::npos)
return "4xx";
// 如果请求为'/'那么自动添加******主页的资源******
std::string Path = RequestLine.substr(SpaceOne + SPACE_LEN, SpaceTwo - (SpaceOne + SPACE_LEN));
if (Path.size() == 1 && Path[0] == '/')
{
Path += HOME;
}
return Path;
}
private:
std::string ServerIp_;
uint16_t ServerPort_;
int ListenSockfd_;
};
int main(int argc, char *argv[])
{
if (argc < 2)
{
std::cout << "Format: ./可执行程序 [ip] port" << std::endl;
exit(5);
}
std::string ip;
uint16_t port;
if (argc == 3)
{
ip = argv[1];
port = std::stoi(argv[2]);
}
else
{
port = std::stoi(argv[1]);
}
HttpServer hs(port, ip);
hs.Init();
hs.Start();
return 0;
}
运行结果
🚈4、HTTP请求
HTTP请求行中的请求方法有如下图所示:
🚉4.1、 HTTP GET和POST方法
我们的网络行为无非有两种:
-
我想把远端服务器的某种资源拿到你的本地(GET /index.html)
-
我们想把我们的属性字段(比如搜索关键字或用户名和密码等等),提交到远端服务器中
-
传参时,是使用html中一个表单的东西进行传递参数的
GET方法
-
GET方法是用于获取服务器中某个路径下的某种资源,可以向服务器进行传参
-
我们想把我们的属性字段提交到远端,可以使用GET方法进行传参
-
在HTTP协议中GET方法会以明文的方式将我们对应的参数信息,拼接到URL中传参
-
前面说的URL中查询字符串字段,GET方法就是把参数拼接到这里,格式为?key=value&key2=value2,&为参数间的分隔符,参数的格式是以键值对的方式进行传参
POST方法
-
POST方法也可以获取服务器中某个路径下的某种资源,也可以向服务器进行传参
-
POST方法传参时,会将参数以明文的方式,拼接到http请求正文中进行提交
-
提交参数时也是以?key=value&key2=value2这样的格式进行传参,&为参数间的分隔符,参数的格式是以键值对的方式进行传参
GET vs POST
-
GET方法通过URL进行传参
-
POST通过请求正文(有效载荷)进行传参
-
GET方法传参不私密,因为懂些网络的人,就知道你传的参数是什么了
-
POST方法通过正文传参,相对比较私密一些(有些人不会抓包)
-
GET通过URL传参,POST通过正文传参,一般传一些比较大的内容都是通过POST的方式进行传参的,因为正文有报头属性(Content-Type)来标定正文数据类型是什么
-
GET方法以前是会限定传参内容大小的,现在已经可以无限制了,但是传输的数据类型只能是文本(text/html),所以还是使用POST以正文的方式进行传参,还能识别数据类型
验证是否正确:
当前路径下wwwroot下的index.html文件
- 这里使用html语法中的表单进行传参,不会的可以学一下html的语法
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>测试网页</title>
</head>
<body>
<h3>hello my server!</h3>
<p>我终于测试完了我的代码</p>
<form action="/a/b/c.html" method="post">
Username: <input type="text" name="user"><br>
Password: <input type="password" name="passwd"><br>
<input type="submit" value="Submit">
</form>
<!-- <img border="0" src="https://img1.baidu.com/it/u=1691233364,820181697&fm=253&fmt=auto&app=138&f=JPEG?w=889&h=500" alt="Pulpit rock" width="304" height="228"> -->
</body>
</html>
HttpServer.cc
#include "Util.h"
#include <thread>
#include <fstream>
#define ROOT_PATH "./wwwroot"
#define HOME "index.html"
#define CRLF "\r\n"
#define CRLF_LEN 2
#define SPACE " "
#define SPACE_LEN 1
class HttpServer
{
public:
HttpServer(uint16_t Port, std::string Ip = "")
: ServerIp_(Ip), ServerPort_(Port), ListenSockfd_(-1)
{
}
void Init()
{
assert(ServerPort_ > 1024);
ListenSockfd_ = socket(AF_INET, SOCK_STREAM, 0);
if (ListenSockfd_ < 0)
{
std::cout << "have socket error: " << strerror(errno) << std::endl;
exit(1);
}
sockaddr_in ServerData;
socklen_t len = sizeof(ServerData);
memset(&ServerData, 0, len);
ServerData.sin_family = AF_INET;
ServerData.sin_port = htons(ServerPort_);
ServerData.sin_addr.s_addr = ServerIp_.empty() ? htonl(INADDR_ANY) : inet_addr(ServerIp_.c_str());
if (bind(ListenSockfd_, (const sockaddr *)&ServerData, len))
{
std::cout << "bind inet data error: " << strerror(errno) << std::endl;
exit(2);
}
if (listen(ListenSockfd_, 4) < 0)
{
std::cout << "Set listen status error: " << strerror(errno) << std::endl;
exit(3);
}
}
void Start()
{
while (true)
{
int sersockfd = accept(ListenSockfd_, nullptr, nullptr);
if (sersockfd < 0)
{
std::cout << "accept error: " << strerror(errno) << std::endl;
exit(4);
}
// 使用线程并行的去执行服务,不影响获取连接请求
std::thread Thread(&HttpServer::RunThread, this, sersockfd);
Thread.detach();
}
}
private:
void RunThread(int sersockfd)
{
// char inbuffer[1024];
std::string inbuffer;
inbuffer.resize(1024, 0);
ssize_t Index = read(sersockfd, (void *)inbuffer.c_str(), inbuffer.size());
if (Index > 0)
{
std::cout << inbuffer << std::endl;
}
// 发响应报文
std::string RequestPath = ROOT_PATH;
RequestPath += GetRequestPath(inbuffer);
std::string resources = ReadFile(RequestPath);
std::string Response;
Response += "HTTP/1.1 200 OK\r\n";
Response += "Host: " + (ServerIp_.empty() ? "0.0.0.0" : ServerIp_) + ":" + \
std::to_string(ServerPort_) + "\r\n";
Response += "Content-Type: text/html\r\n";
Response += ("Content-Length: ") + (std::to_string(resources.size()) + "\r\n");
Response += "\r\n";
Response += resources;
send(sersockfd, Response.c_str(), Response.size(), 0);
close(sersockfd);
}
private:
std::string ReadFile(const std::string &Path)
{
std::ifstream in(Path.c_str());
if (!in.is_open())
{
std::cout << "open file error: " << strerror(errno) << std::endl;
return "404";
}
std::string buffer;
std::string result;
while (std::getline(in, buffer))
{
result += buffer;
}
return result;
}
std::string GetRequestPath(std::string &inbuffer)
{
// "GET /a/b/c HTTP/1.1\r\n" --> "/a/b/c"
size_t pos = inbuffer.find(CRLF);
if (pos == std::string::npos)
return "4xx";
std::string RequestLine = inbuffer.substr(0, pos + CRLF_LEN);
size_t SpaceOne = RequestLine.find(SPACE);
size_t SpaceTwo = RequestLine.rfind(SPACE);
if (SpaceOne == std::string::npos && SpaceTwo == std::string::npos)
return "4xx";
// 如果请求为'/'那么自动添加主页的资源
std::string Path = RequestLine.substr(SpaceOne + SPACE_LEN, SpaceTwo - (SpaceOne + SPACE_LEN));
if (Path.size() == 1 && Path[0] == '/')
{
Path += HOME;
}
return Path;
}
private:
std::string ServerIp_;
uint16_t ServerPort_;
int ListenSockfd_;
};
int main(int argc, char *argv[])
{
if (argc < 2)
{
std::cout << "Format: ./可执行程序 [ip] port" << std::endl;
exit(5);
}
std::string ip;
uint16_t port;
if (argc == 3)
{
ip = argv[1];
port = std::stoi(argv[2]);
}
else
{
port = std::stoi(argv[1]);
}
HttpServer hs(port, ip);
hs.Init();
hs.Start();
return 0;
}
GET方法
POST方法
其他方法
【摘至☞developer.mozilla.org】
-
PUT:PUT请求方法是一种将数据添加到指定URL中的操作,可以用于更新服务器上的数据或创建新的资源
-
HEAD:HEAD请求方法用于获取服务端的请求行和报头
-
DELETE:DELETE请求方法用于删除服务端的指定资源
-
OPTIONS:OPTIONS请求方法用于获取服务端所支持的请求方法
-
TRACE:TRACE请求方法沿着通往目标资源的路径进行信息回环测试,提供一个有用的调试机制
-
CONNECT:CONNECT请求方法可以开启与所请求资源之间的双向沟通的通道;它可以用来创建隧道(tunnel)
🚋4.2、HTTP状态码
【HTT状态码 – 百度百科】
-
状态码:状态码是服务器响应客户端请求时在状态行填充的字段,包含状态码和状态码描述
-
1xx:该状态码表示接收的请求报文正在处理中,稍后会响应一个完整的响应报文。100的状态码描述为Continue,101状态码描述为Switching Protocols
-
2xx:该状态码表示请求已成功被服务器接收、理解、并接受(但不代表已经处理)。200状态码描述为OK;201状态码描述为Created;202状态码描述为Accepted
-
3xx:该状态码需要客户端采取进一步的操作才能完成请求,用于进行重定向(请求的URL可能临时进行维护或永久失效了),后续通过请求报头属性中的Location可以重定向到新的URL。300状态码描述为Multiple Choices(临时重定向);301状态码描述为Moved Permanently(永久重定向)
-
4xx:该状态码表示客户端发送请求有问题,可能请求报文没有按照要求进行编写或请求的资源不存在等等。403状态码描述为Forbidden(有权限访问此网站),404状态码描述为Not Found(请求资源不存在)
-
5xx:该状态码表示服务器在处理请求的过程中有错误或者异常状态发生(内存不足或程序异常等),500状态码描述为Internal Server Error,502状态码描述为Bad Gateway
响应报文中状态行格式如下:
🚊4.3、HTTP常见Header
HTTP常见报头:
-
Content-Type:用于标定正文的数据类型(text/html等)【Content-Type对照表】
-
Content-Length:用于标定正文(Body)的长度,方便正确的读取正文数据大小
-
Host:客户端告知服务器,所请求的资源是在哪个主机的哪个端口上(格式ip:port)
-
User-Agent:声明(获取)用户的操作系统和浏览器版本信息
-
Referer:当前页面是从哪个页面跳转过来的
-
Location:搭配3xx状态码使用,告诉客户端接下来要去哪里访问重定向后的url
-
Cookie: 用于在客户端存储少量信息(用户名和密码),通常用于实现会话(session)保持的功能
状态码3xx和响应报头属性Location字段的关系
-
状态码告诉用户要访问的网站正在进行维护或永久下架了
-
客户端收到响应报头属性Location字段后,会根据Location字段的值进行重定向或跳转操作,如果需要进行重定向操作,客户端会发送新的请求,请求的URL地址为Location字段的值
-
注意:Location字段的值必须是完整的URL,不能是baidu.com这样,必须携带使用的协议名(https://www.baidu.com/)
代码看构建响应报文的部分即可
#include "Util.h"
#include <thread>
#include <fstream>
#define ROOT_PATH "./wwwroot"
#define HOME "index.html"
#define CRLF "\r\n"
#define CRLF_LEN 2
#define SPACE " "
#define SPACE_LEN 1
class HttpServer
{
public:
HttpServer(uint16_t Port, std::string Ip = "")
: ServerIp_(Ip), ServerPort_(Port), ListenSockfd_(-1)
{
}
void Init()
{
assert(ServerPort_ > 1024);
ListenSockfd_ = socket(AF_INET, SOCK_STREAM, 0);
if (ListenSockfd_ < 0)
{
std::cout << "have socket error: " << strerror(errno) << std::endl;
exit(1);
}
sockaddr_in ServerData;
socklen_t len = sizeof(ServerData);
memset(&ServerData, 0, len);
ServerData.sin_family = AF_INET;
ServerData.sin_port = htons(ServerPort_);
ServerData.sin_addr.s_addr = ServerIp_.empty() ? htonl(INADDR_ANY) : inet_addr(ServerIp_.c_str());
if (bind(ListenSockfd_, (const sockaddr *)&ServerData, len))
{
std::cout << "bind inet data error: " << strerror(errno) << std::endl;
exit(2);
}
if (listen(ListenSockfd_, 4) < 0)
{
std::cout << "Set listen status error: " << strerror(errno) << std::endl;
exit(3);
}
}
void Start()
{
while (true)
{
int sersockfd = accept(ListenSockfd_, nullptr, nullptr);
if (sersockfd < 0)
{
std::cout << "accept error: " << strerror(errno) << std::endl;
exit(4);
}
// 使用线程并行的去执行服务,不影响获取连接请求
std::thread Thread(&HttpServer::RunThread, this, sersockfd);
Thread.detach();
}
}
private:
void RunThread(int sersockfd)
{
// char inbuffer[1024];
std::string inbuffer;
inbuffer.resize(1024, 0);
ssize_t Index = read(sersockfd, (void *)inbuffer.c_str(), inbuffer.size());
if (Index > 0)
{
std::cout << inbuffer << std::endl;
}
// 发响应报文
std::string RequestPath = ROOT_PATH;
RequestPath += GetRequestPath(inbuffer);
std::string resources = ReadFile(RequestPath);
std::string Response;
Response += "HTTP/1.1 300 Multiple Choices\r\n";
Response += "Host: " + (ServerIp_.empty() ? "0.0.0.0" : ServerIp_) + ":" + \
std::to_string(ServerPort_) + "\r\n";
Response += "Content-Type: text/html\r\n";
Response += ("Content-Length: ") + (std::to_string(resources.size()) + "\r\n");
Response += "Location: https://www.baidu.com/\r\n";
Response += "\r\n";
Response += resources;
send(sersockfd, Response.c_str(), Response.size(), 0);
close(sersockfd);
}
private:
std::string ReadFile(const std::string &Path)
{
std::ifstream in(Path.c_str());
if (!in.is_open())
{
std::cout << "open file error: " << strerror(errno) << std::endl;
return "404";
}
std::string buffer;
std::string result;
while (std::getline(in, buffer))
{
result += buffer;
}
return result;
}
std::string GetRequestPath(std::string &inbuffer)
{
// "GET /a/b/c HTTP/1.1\r\n" --> "/a/b/c"
size_t pos = inbuffer.find(CRLF);
if (pos == std::string::npos)
return "4xx";
std::string RequestLine = inbuffer.substr(0, pos + CRLF_LEN);
size_t SpaceOne = RequestLine.find(SPACE);
size_t SpaceTwo = RequestLine.rfind(SPACE);
if (SpaceOne == std::string::npos && SpaceTwo == std::string::npos)
return "4xx";
// 如果请求为'/'那么自动添加主页的资源
std::string Path = RequestLine.substr(SpaceOne + SPACE_LEN, SpaceTwo - (SpaceOne + SPACE_LEN));
if (Path.size() == 1 && Path[0] == '/')
{
Path += HOME;
}
return Path;
}
private:
std::string ServerIp_;
uint16_t ServerPort_;
int ListenSockfd_;
};
int main(int argc, char *argv[])
{
if (argc < 2)
{
std::cout << "Format: ./可执行程序 [ip] port" << std::endl;
exit(5);
}
std::string ip;
uint16_t port;
if (argc == 3)
{
ip = argv[1];
port = std::stoi(argv[2]);
}
else
{
port = std::stoi(argv[1]);
}
HttpServer hs(port, ip);
hs.Init();
hs.Start();
return 0;
}
关于Cookie字段和Session详解
-
HTTP协议的特定之一:它是无状态的(客户端发送请求和服务器响应请求时的报文都不会被保存起来,发完后就直接丢弃了)
-
如果我们要看电影,访问了一个网站,这些电影要登录账号才能免费观看,如果HTTP是无状态的话,请求报文中传参的用户名和密码都会被丢弃,那用户每请求一个新的电影资源都要进行重新登录!!
-
解法方法就是在这个网站中保持用户的登录状态(会话保持),但是保持登录数据就要将请求的报文存储起来,每次请求和响应都要保存,会很费内存
-
Cookie:它是一个在客户端存储数据的小型文本文件,由服务器发给客户端(响应属性字段Set-Cookie),然后客户端将其存储在本地(浏览器维护,可能存储在磁盘或内存)。每次客户端向服务器发送请求时,Cookie会随请求一起发送到服务器
-
Session:由于Cookie存储在客户端,因此很容易被篡改和盗取,而Session是一种在服务端存储数据的机制,当用户第一次访问网站时,会为其创建一个唯一的Session ID,并将其存储在Cookie中(Cookie其他数据都保存在服务器端中,后面通过Session ID进行校验),后面客户端向服务器发送请求时,服务器会根据Session ID查找对应的Session数据
看代码中的构建报文部分
#include "Util.h"
#include <thread>
#include <fstream>
#define ROOT_PATH "./wwwroot"
#define HOME "index.html"
#define CRLF "\r\n"
#define CRLF_LEN 2
#define SPACE " "
#define SPACE_LEN 1
class HttpServer
{
public:
HttpServer(uint16_t Port, std::string Ip = "")
: ServerIp_(Ip), ServerPort_(Port), ListenSockfd_(-1)
{
}
void Init()
{
assert(ServerPort_ > 1024);
ListenSockfd_ = socket(AF_INET, SOCK_STREAM, 0);
if (ListenSockfd_ < 0)
{
std::cout << "have socket error: " << strerror(errno) << std::endl;
exit(1);
}
sockaddr_in ServerData;
socklen_t len = sizeof(ServerData);
memset(&ServerData, 0, len);
ServerData.sin_family = AF_INET;
ServerData.sin_port = htons(ServerPort_);
ServerData.sin_addr.s_addr = ServerIp_.empty() ? htonl(INADDR_ANY) : inet_addr(ServerIp_.c_str());
if (bind(ListenSockfd_, (const sockaddr *)&ServerData, len))
{
std::cout << "bind inet data error: " << strerror(errno) << std::endl;
exit(2);
}
if (listen(ListenSockfd_, 4) < 0)
{
std::cout << "Set listen status error: " << strerror(errno) << std::endl;
exit(3);
}
}
void Start()
{
while (true)
{
int sersockfd = accept(ListenSockfd_, nullptr, nullptr);
if (sersockfd < 0)
{
std::cout << "accept error: " << strerror(errno) << std::endl;
exit(4);
}
// 使用线程并行的去执行服务,不影响获取连接请求
std::thread Thread(&HttpServer::RunThread, this, sersockfd);
Thread.detach();
}
}
private:
void RunThread(int sersockfd)
{
// char inbuffer[1024];
std::string inbuffer;
inbuffer.resize(1024, 0);
ssize_t Index = read(sersockfd, (void *)inbuffer.c_str(), inbuffer.size());
if (Index > 0)
{
std::cout << inbuffer << std::endl;
}
// 发响应报文
std::string RequestPath = ROOT_PATH;
RequestPath += GetRequestPath(inbuffer);
std::string resources = ReadFile(RequestPath);
std::string Response;
Response += "HTTP/1.1 200 OK\r\n";
Response += "Host: " + (ServerIp_.empty() ? "0.0.0.0" : ServerIp_) + ":" + \
std::to_string(ServerPort_) + "\r\n";
Response += "Content-Type: text/html\r\n";
Response += ("Content-Length: ") + (std::to_string(resources.size()) + "\r\n");
Response += "Location: https://www.baidu.com/\r\n";
Response += "Set-Cookie: This is my cookie\r\n";
Response += "\r\n";
Response += resources;
send(sersockfd, Response.c_str(), Response.size(), 0);
close(sersockfd);
}
private:
std::string ReadFile(const std::string &Path)
{
std::ifstream in(Path.c_str());
if (!in.is_open())
{
std::cout << "open file error: " << strerror(errno) << std::endl;
return "404";
}
std::string buffer;
std::string result;
while (std::getline(in, buffer))
{
result += buffer;
}
return result;
}
std::string GetRequestPath(std::string &inbuffer)
{
// "GET /a/b/c HTTP/1.1\r\n" --> "/a/b/c"
size_t pos = inbuffer.find(CRLF);
if (pos == std::string::npos)
return "4xx";
std::string RequestLine = inbuffer.substr(0, pos + CRLF_LEN);
size_t SpaceOne = RequestLine.find(SPACE);
size_t SpaceTwo = RequestLine.rfind(SPACE);
if (SpaceOne == std::string::npos && SpaceTwo == std::string::npos)
return "4xx";
// 如果请求为'/'那么自动添加主页的资源
std::string Path = RequestLine.substr(SpaceOne + SPACE_LEN, SpaceTwo - (SpaceOne + SPACE_LEN));
if (Path.size() == 1 && Path[0] == '/')
{
Path += HOME;
}
return Path;
}
private:
std::string ServerIp_;
uint16_t ServerPort_;
int ListenSockfd_;
};
int main(int argc, char *argv[])
{
if (argc < 2)
{
std::cout << "Format: ./可执行程序 [ip] port" << std::endl;
exit(5);
}
std::string ip;
uint16_t port;
if (argc == 3)
{
ip = argv[1];
port = std::stoi(argv[2]);
}
else
{
port = std::stoi(argv[1]);
}
HttpServer hs(port, ip);
hs.Init();
hs.Start();
return 0;
}
服务器发送Cookie后,会在网站上的cookie中找到相关数据