HTTP协议
一、三个预备知识
1、域名
2、URL(统一资源定位符)
3、编码(encode)和解码(decode)
二、HTTP请求和响应
1、画图解析
HTTP请求
HTTP响应
2、请求抓包工具fiddler和postman
百度的请求和响应报文信息(粗略一看):
3、最简单的HTTP服务器
代码部分:
前面的Socket和Log可以复制前面的代码:
httpserver.hpp:
#pragma once
#include <iostream>
#include <pthread.h>
#include "Socket.hpp"
#include "Log.hpp"
static const uint16_t defaultport = 8080;
struct ThreadData
{
int sockfd;
};
class HttpServer
{
public:
HttpServer(uint16_t port = defaultport)
: _port(port)
{
}
bool Start()
{
_listensockfd.Socket();
_listensockfd.Bind(_port);
_listensockfd.Listen();
for ( ; ; )
{
std::string clientip;
uint16_t clientport;
int sockfd = _listensockfd.Accept(&clientip, &clientport);
lg(Info, "get a new link sucessary...");
pthread_t tid;
ThreadData* td = new ThreadData;
td->sockfd = sockfd;
pthread_create(&tid, nullptr, ThreadRun, td);
}
}
static void* ThreadRun(void* args)
{
pthread_detach(pthread_self());
ThreadData* td = static_cast<ThreadData*>(args);
char buffer[10240];
ssize_t n = recv(td->sockfd, buffer, sizeof(buffer) - 1, 0);
if (n > 0)
{
buffer[n] = 0;
std::cout << buffer;
}
close(td->sockfd);
return nullptr;
}
~HttpServer()
{
}
private:
Sock _listensockfd;
uint16_t _port;
};
httpserver.cc:
#include "httpserver.hpp"
#include <iostream>
#include <memory>
int main(int argc, char* argv[])
{
if (argc != 2)
{
exit(1);
}
uint16_t port = std::stoi(argv[1]);
std::unique_ptr<HttpServer> svr(new HttpServer(port));
svr->Start();
return 0;
}
现象:
4、处理函数及请求现象
利用recv收到消息,再做封装后send发送出去!
5、设计个好看的网页(前端)
前端代码路径问题,web路径问题:
注意看上面的实例,利用/区分出目录的地址,所以我们可以用一个目录文件来存放这些web前端的代码。
6、http请求
7、访问根目录跳转到wwwroot文件中的index.html中
我们为了实现安全性,那么就要设计一下,当我们访问根目录的时候,我们就自动的跳转到我们指定的目录文件下的指定的index.html文件中,那么其实很简单了,我们只需要建立一个拼接字符串即可,遇到根目录或者是index.html目录我们就跳转到wwwroot中的index.html中,以后有人想访问我的根目录自动跳转,以确保安全性。
class HttpRequest
{
public:
void Deserialize(std::string req)
{
while (true)
{
auto pos = req.find(sep);
if (pos == std::string::npos)
{
break;
}
std::string tmp = req.substr(0, pos);
if (tmp.empty()) // 找到空串
{
break;
}
_req_header.push_back(tmp); // push一行
req.erase(0, pos + sep.size()); // 移除一行
}
_text = req;
}
void Parse()
{
std::stringstream ss(_req_header[0]); // 请求行
ss >> method >> url >> http_version; // 把三个字符串放到三个string中
_req_path = wwwroot; // ./wwwroot
if (url == "/" || url == "/index.html")
{
_req_path += "/";
_req_path += homepage; // ./wwwroot/index.html
}
else
{
_req_path += url; // ./wwwroot/a/b/c/d.html
}
}
void DebugPrint()
{
std::cout << "----------------" << std::endl;
for (auto &line : _req_header)
{
std::cout << line << "\n\n";
}
std::cout << "method: " << method << std::endl;
std::cout << "url: " << url << std::endl;
std::cout << "http_version: " << http_version << std::endl;
std::cout << "_req_path: " << _req_path << std::endl;
std::cout << _text << std::endl;
}
public:
std::vector<std::string> _req_header;
std::string _text;
// 解析后的成员
std::string method;
std::string url;
std::string http_version;
std::string _req_path;
};
8、通过根目录加/的方式访问其他文件
三、HTTP的方法
最常用的就是GET方法和POST方法
1、GET方法
GET方法通过url来进行提参数。一般参数数量受限,不私密。
数据都是通过表单来提参的。get方法提参数是自动将路径拼接到url上面的。
如果我们要提交参数给我们的服务器的时候,我们使用get方法时候,我们提交的参数是通过url提交的。
2、POST方法
POST方法也支持参数提交,提交方式默认采用请求的正文提交参数,比GET私密,因为不会在url回显。
四、HTTP状态码
最常见的状态码, 比如 200(OK), 404(Not Found), 403(Forbidden),302(Redirect, 重定向), 504(Bad Gateway)。
1、404 Not Found
wwwroot下的error.html:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>404 Not Found</title>
<style>
body {
text-align: center;
padding: 150px;
}
h1 {
font-size: 50px;
}
body {
font-size: 20px;
}
a {
color: #008080;
text-decoration: none;
}
a:hover {
color: #005F5F;
text-decoration: underline;
}
</style>
</head>
<body>
<div>
<h1>404</h1>
<p>页面未找到<br></p>
<p>
您请求的页面可能已经被删除、更名或者您输入的网址有误。<br>
请尝试使用以下链接或者自行搜索:<br><br>
<a href="https://www.baidu.com">百度一下></a>
</p>
</div>
</body>
</html>
HttpServer.hhp中的Handler函数改装,只需要改装成为判断是否要走到404的时候走到404就用error.html页面,没走到就正常页面。
fiddler抓包显示:
2、302重定向
谁掌握了浏览器谁就有了流量密码。
永久性重定向和临时性重定向。
httpserver.hpp的Handler函数:
五、HTTP请求属性
Content-Type: 数据类型(text/html等)
Content-Length: Body的长度
Host: 客户端告知服务器, 所请求的资源是在哪个主机的哪个端口上;
User-Agent: 声明用户的操作系统和浏览器版本信息;
referer: 当前页面是从哪个页面跳转过来的;
location: 搭配3xx状态码使用, 告诉客户端接下来要去哪里访问;
Cookie: 用于在客户端存储少量信息. 通常用于实现会话(session)的功能;
1、短连接和长连接
一次请求响应一个资源,关闭连接,是短连接。http/1.0
建立一个TCP连接,发送和返回多个http的request和response,就是长连接。http/1.1
我们的计算机上会有一个比较常见的现象,老版本的服务器响应新版本的客户端,新版本的客户端去请求老版本的服务器,这就会导致了服务器和客户端不匹配的情况,但我们要让他们匹配,因为是协商同意得,所以报文信息中有一个Connection: keep-alive的信息,表示的是服务端和客户端都是长连接。
2、加上图片二进制流的代码编写
index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!-- <form action="/a/b/hello.html" method="post">
name: <input type="text" name="name"><br>
password: <input type="password" name="passwd"><br>
<input type="submit" value="提交">
</form> -->
<h1>这个是我们的首页</h1>
<img src="/image/1.png" alt="俺牛爷爷来了">
<img src="/image/2.png" alt="俺牛爷爷带着野牛来了">
</body>
</html>
HttpServer.hpp:
#pragma once
#include <iostream>
#include <pthread.h>
#include <fstream>
#include <sstream>
#include <vector>
#include <unordered_map>
#include "Socket.hpp"
#include "Log.hpp"
const std::string wwwroot = "./wwwroot"; // web根目录
const std::string homepage = "index.html";
const std::string sep = "\r\n";
static const uint16_t defaultport = 8080;
class HttpServer;
class ThreadData
{
public:
ThreadData(int fd, HttpServer* ts) : sockfd(fd), tsv(ts)
{
}
public:
int sockfd;
HttpServer* tsv; // 对象,具体是static类内成员不支持this指针
};
class HttpRequest
{
public:
void Deserialize(std::string req)
{
while (true)
{
auto pos = req.find(sep);
if (pos == std::string::npos)
{
break;
}
std::string tmp = req.substr(0, pos);
if (tmp.empty()) // 找到空串
{
break;
}
_req_header.push_back(tmp); // push一行
req.erase(0, pos + sep.size()); // 移除一行
}
_text = req;
}
void Parse()
{
std::stringstream ss(_req_header[0]); // 请求行
ss >> method >> url >> http_version; // 把三个字符串放到三个string中
_req_path = wwwroot; // ./wwwroot
if (url == "/" || url == "/index.html")
{
_req_path += "/";
_req_path += homepage; // ./wwwroot/index.html
}
else
{
_req_path += url; // ./wwwroot/a/b/c/d.html
}
auto pos = _req_path.rfind(".");
if (pos == std::string::npos)
{
suffix = ".html";
}
else suffix = _req_path.substr(pos);
}
void DebugPrint()
{
std::cout << "----------------" << std::endl;
for (auto &line : _req_header)
{
std::cout << line << "\n\n";
}
std::cout << "method: " << method << std::endl;
std::cout << "url: " << url << std::endl;
std::cout << "http_version: " << http_version << std::endl;
std::cout << "_req_path: " << _req_path << std::endl;
std::cout << _text << std::endl;
}
public:
std::vector<std::string> _req_header;
std::string _text;
// 解析后的成员
std::string method;
std::string url;
std::string http_version;
std::string _req_path;
std::string suffix; // 文件后缀
};
class HttpServer
{
public:
HttpServer(uint16_t port = defaultport)
: _port(port)
{
// 解析后缀对应表
content_type.insert({".html", "text/html"});
content_type.insert({".png", "image/png"});
content_type.insert({".jpg", "image/jpg"});
}
bool Start()
{
_listensockfd.Socket();
_listensockfd.Bind(_port);
_listensockfd.Listen();
for (;;)
{
std::string clientip;
uint16_t clientport;
int sockfd = _listensockfd.Accept(&clientip, &clientport);
if (sockfd < 0)
continue;
lg(Info, "get a new link successary...sockfd: %d", sockfd);
pthread_t tid;
ThreadData *td = new ThreadData(sockfd, this); // 传对象上去
pthread_create(&tid, nullptr, ThreadRun, td);
}
}
static std::string ReadHtmlContent(const std::string &path)
{
std::ifstream in(path, std::ios::binary); // 二进制文件读取 主要是读取图片
if (!in.is_open())
{
return "";
}
// 读二进制流文件
in.seekg(0, std::ios_base::end);
auto len = in.tellg();
in.seekg(0, std::ios_base::beg);
std::string content;
content.resize(len);
in.read((char*)content.c_str(), content.size());
// std::string content;
// std::string line;
// while (std::getline(in, line))
// {
// content += line;
// }
in.close();
return content;
}
std::string SuffixToDesc(const std::string& suffix)
{
auto it = content_type.find(suffix);
if (it == content_type.end())
{
return content_type[".html"];
}
else
{
return content_type[suffix];
}
}
void Handler(int sockfd)
{
char buffer[10240];
ssize_t n = recv(sockfd, buffer, sizeof(buffer) - 1, 0);
if (n > 0)
{
buffer[n] = 0;
std::cout << buffer;
HttpRequest req;
req.Deserialize(buffer); // 请求的反序列化
req.Parse();
req.DebugPrint();
// // 返回响应
std::string text;
bool ok = true;
text = ReadHtmlContent(req._req_path); // 要发送的字符串
if (text.empty())
{
ok = false;
std::string errhtml = wwwroot;
errhtml += "/";
errhtml += "error.html";
text = ReadHtmlContent(errhtml);
}
std::string response_line;
if (ok)
response_line = "HTTP/1.0 200 OK\r\n"; // 命令行
else
response_line = "HTTP/1.0 404 Not Found\r\n";
// response_line = "HTTP/1.0 302 Found\r\n";
std::string response_header = "Content-Length: "; // 头部
response_header += std::to_string(text.size()); // 加上长度
response_header += "\r\n";
response_header += "Content-Type: ";
response_header += SuffixToDesc(req.suffix);
response_header += "\r\n";
// response_header += "Location: https://www.qq.com\r\n"; // 新的地址
std::string blank_line = "\r\n"; // 多了一个空行所以多加一个\r\n
std::string response = response_line;
response += response_header;
response += blank_line;
response += text;
send(sockfd, response.c_str(), response.size(), 0);
}
close(sockfd);
}
static void *ThreadRun(void *args)
{
pthread_detach(pthread_self());
ThreadData *td = static_cast<ThreadData *>(args);
td->tsv->Handler(td->sockfd);
delete td;
return nullptr;
}
~HttpServer()
{
}
private:
Sock _listensockfd;
uint16_t _port;
std::unordered_map<std::string, std::string> content_type;
};
短连接:
3、Cookie
http协议是无状态的,http对登录用户的会话保持功能,就比如我们访问浏览器登录一次以后隔10分钟再访问一次不用登录了。
这种情况cookie被盗取并且个人信息泄露。
cookie有文件级和用户级。当cookie文件放到文件级中的时候,就是放到磁盘中了,所以下次打开浏览器依旧可以免登录了。而设置了自动消除时间,cookie就会自动消亡了。内存级的则浏览器不会免登录。
4、session
这种做法是将客户端信息统一放到服务端去进行管理,这样子个人信息泄露的风险比较小,但仍旧避免不了cookie被盗取的风险。而为了避免风险,那么就需要服务端那边对session id进行管理来减少风险。