🔥个人主页🔥:孤寂大仙V
🌈收录专栏🌈:计算机网络
🌹往期回顾🌹: 【Linux笔记】——进程间关系与守护进程
🔖流水不争,争的是滔滔不息
一、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=123456
LOG(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; //URI
string _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";
else
return "";
}
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=123456
LOG(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; //URI
string _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";
else
return "";
}
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类型用上面的SetHeader
和Uri2Suffix
进行设置。读取成功目标HTML文件成功写入响应正文,为了设置响应报头信息和MIME类型用上面的SetHeader
和Uri2Suffix
进行设置。
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是服务器上的一块数据空间,用来存储用户信息的。
这两者进行数据交换,为防止信息泄露,要用其他机制对信息进行加密,防止在网络传输中被泄露。