目录
前言
一HTTP协议
1认识URL
2urlencode 和 urldecode
3协议请求与响应格式
4代码实现
二HTTP常见Header
1Connection的报头
2Cookie和Session
2.1Cookie定义
2.2Cookie使用
2.3Session定义
2.4Session实现
四HTTP响应起始行
1状态码
2永久重定向与临时重定向
2.1从技术角度上
2.2从理解角度上
2.3实现
五HTTP方法
1GET与POST
编辑
2实现
前言
虽然我们说, 应用层协议是我们程序猿自己定的.但实际上, 已经有大佬们定义了一些现成的, 又非常好用的应用层协议, 供我们直接参考使用:HTTP(超文本传输协议)就是其中之一。
它是一个至关重要的协议:定义了客户端(如浏览器)与服务器之间如何通信,以交换或传输超文本(如 HTML 文档)。而且:HTTP 协议是一个无连接、 无状态的协议, 即每次请求都需要建立新的连接, 且服务器不会保存客户端的状态信息。
一HTTP协议
1认识URL
平时我们俗称的 "网址" 其实就是说的 URL(Uniform Resource Locator, 统一资源定位器
我们在访问服务器时,不是要有IP和端口号吗?怎么在URL上看不到?
那是因为域名使用时,会自动转化成IP地址(DNS);而端口号是跟协议名强关联的(如现实生活中110与报警电话是一样的类似):当服务器发起请求时,会自动拼接上端口号(80)
因此:在http中端口号是默认被忽略的
我们平时在逛淘宝时的看到的:网页,视频,图片,音频...叫做超文本资源;那么是谁来完成资源处理的呢?
这些工作是由http进行资源获取后进行推送给进行访问的用户;在没有被访问时,这些资源都是在服务器端储存的; 而我们学习Linux的都知道:Linux操作系统很适合用来作为后端服务器:在LInux中一切皆文件:http在服务器找资源时,怎么准确无误找到对应的资源?
通过路径(路径标识唯一性):在域名后的字符串就是对应访问服务器资源的路径;但这是LInux中的根目录吗?
不是:这就做web根目录(如何一个文件都是web根目录的起点);
现在:我们用了域名:标识唯一主机;端口号(忽略):表示唯一服务进程;路径:该主机上的唯一的文件资源:这三者共同组成了在互联网中唯一的文件资源!!
注:http与https两者是有很强的联系的(只不过https多了一层安全层),在学习时我们就把它们进行统一成http来学习
2urlencode 和 urldecode
像 / ? : 等这样的字符, 已经被 url 当做特殊意义理解了. 因此这些字符不能随意出现;
比如, 某个参数中需要带有这些特殊字符, 就必须先对特殊字符进行转义;
转义的规则如下:
将需要转码的字符转为 16 进制, 然后从右到左, 取 4 位(不足 4 位直接处理), 每 2 位做一位, 前面加上%, 编码成%XY 格式
"+" 被转义成了 "%2B"
urldecode 就是 urlencode 的逆过程;
转化工具:UrlEncode编码/UrlDecode解码 -工具
3协议请求与响应格式
请求格式
报文与有效载荷进行分离(封装):
空行(\r\n)
在请求头部中有描述请求正文长度的属性:Content-Length:XXX
响应格式也类似
4代码实现
HTTP协议在网络传输时也是要进行序列化与反序列化,但不同的是:它没有用第三方库(json...):它有着自己的一套格式(也就是我们上面的格式)
现在让我们用代码的形式来进行序列化与反序列化
注:以下代码时从:Linux——应用层自定义协议与序列化-CSDN博客中的实现网络计算器代码来进行修改使用的~
//Socket.hpp
#pragma once
#include <iostream>
#include <cstring>
#include <functional>
#include <unistd.h>
#include <memory>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <pthread.h>
#include "Log.hpp"
#include "InetAddr.hpp"
namespace socket_ns
{
using namespace log_ns;
class Socket; // 声明
using SockPtr = std::shared_ptr<Socket>;
enum
{
SOCK_ERROR = 1,
BIND_ERROR,
LISTEN_ERROR,
};
const static int gbacklog = 8;
// 模板方法模式
class Socket
{
public:
virtual void CreateSocket() = 0;
virtual void InitSocket(uint16_t port, int backlog = gbacklog) = 0;
virtual SockPtr AcceptSocket(InetAddr *addr) = 0; // 对象/变量
virtual bool ConnectSocket(uint16_t port, const std::string &ip) = 0; // clinet连接成功与失败
virtual int Sockfd() = 0;
virtual void Close() = 0;
virtual ssize_t Recv(std::string *out) = 0;
virtual ssize_t Send(const std::string &in) = 0;
public:
void Tcp_ServerSocket(uint16_t port)
{
CreateSocket();
InitSocket(port);
}
bool Tcp_ClientSocket(uint16_t port, const std::string &ip)
{
CreateSocket();
return ConnectSocket(port, ip);
}
};
class TcpSocket : public Socket
{
public:
TcpSocket()
{
}
TcpSocket(int sockfd)
: _sockfd(sockfd)
{
}
~TcpSocket()
{
}
virtual void CreateSocket() override
{
_sockfd = ::socket(AF_INET, SOCK_STREAM, 0);
if (_sockfd < 0)
{
LOG(FATAL, "socket fail\n");
exit(SOCK_ERROR);
}
LOG(INFO, "socket sucess sockfd: %d\n", _sockfd);
}
virtual void InitSocket(uint16_t port, int backlog) override
{
struct sockaddr_in perr;
memset(&perr, 0, sizeof(perr));
perr.sin_family = AF_INET;
perr.sin_port = htons(port);
perr.sin_addr.s_addr = INADDR_ANY;
if (::bind(_sockfd, (struct sockaddr *)&perr, sizeof(perr)) < 0)
{
LOG(FATAL, "bind fail\n");
exit(BIND_ERROR);
}
LOG(INFO, "bind sucess\n");
if (::listen(_sockfd, backlog) < 0)
{
LOG(ERROR, "listen fail\n");
exit(LISTEN_ERROR);
}
LOG(INFO, "listen sucess\n");
}
virtual SockPtr AcceptSocket(InetAddr *addr) override
{
struct sockaddr_in client;
socklen_t len = sizeof(client);
int sockfd = ::accept(_sockfd, (struct sockaddr *)&client, &len);
if (sockfd < 0)
{
LOG(ERROR, "accept fail\n");
return nullptr;
}
*addr = client;
LOG(INFO, "get a new link %s sockfd: %d\n", addr->User().c_str(), sockfd);
return std::make_shared<TcpSocket>(sockfd); // c++14
}
virtual bool ConnectSocket(uint16_t port, const std::string &ip) override
{
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(port);
inet_pton(AF_INET, ip.c_str(), &server.sin_addr);
int n = connect(_sockfd, (struct sockaddr *)&server, sizeof(server));
if (n < 0)
{
return false;
}
return true;
}
virtual int Sockfd() override
{
return _sockfd;
}
virtual void Close() override
{
if (_sockfd > 0)
{
::close(_sockfd);
}
}
virtual ssize_t Recv(std::string *out) override
{
char buffer[4096];
ssize_t n = ::recv(_sockfd, buffer, sizeof(buffer) - 1, 0);
if (n > 0)
{
buffer[n] = 0;
*out += buffer; // 细节
}
return n;
}
virtual ssize_t Send(const std::string &in) override
{
return ::send(_sockfd, in.c_str(), in.size(), 0);
}
private:
int _sockfd; // 两个角色
};
//class UdpServer:public Socket
//{}
}
//TcpServer.hpp
#pragma once
#include <functional>
#include "Socket.hpp"
#include "Log.hpp"
#include "InetAddr.hpp"
using namespace socket_ns;
static const int gport = 8888;
using service_io_t = std::function<std::string(std::string)>;
class TcpServer
{
public:
TcpServer(service_io_t server, uint16_t port = gport)
: _server(server), _port(port), _listensockfd(std::make_shared<TcpSocket>()), _isrunning(false)
{
_listensockfd->Tcp_ServerSocket(_port); // socket bind listen
}
void Start()
{
_isrunning = true;
while (_isrunning)
{
InetAddr client;
SockPtr newsock = _listensockfd->AcceptSocket(&client);
if (newsock == nullptr)
continue; // 断开连接
// 进行服务
// version 2 -- 多线程 -- 不能关fd -- 共享
pthread_t pid;
PthreadDate *date = new PthreadDate(newsock, this, client);
pthread_create(&pid, nullptr, Excute, date);
}
_isrunning = false;
}
struct PthreadDate
{
SockPtr _sockfd;
TcpServer *_self;
InetAddr _addr;
PthreadDate(SockPtr sockfd, TcpServer *self, const InetAddr &addr)
: _sockfd(sockfd),
_self(self),
_addr(addr)
{
}
};
static void *Excute(void *args)
{
pthread_detach(pthread_self());
PthreadDate *date = static_cast<PthreadDate *>(args);
std::string requeststr;
date->_sockfd->Recv(&requeststr);
std::string reponsestr=date->_self->_server(requeststr); // 进行回调
date->_sockfd->Send(reponsestr);
date->_sockfd->Close(); // 关闭 sockfd
delete date;
return nullptr;
}
private:
service_io_t _server;
uint16_t _port;
SockPtr _listensockfd;
bool _isrunning;
};
//Log.hpp
#pragma once
#include <pthread.h>
#include <fstream>
#include <syscall.h>
#include <stdarg.h>
#include <unistd.h>
#include <cstring>
namespace log_ns
{
enum
{
DEBUG = 1,
INFO,
WARNING,
ERROR,
FATAL
};
std::string Getlevel(int level)
{
switch (level)
{
case DEBUG:
return "DEBUG";
break;
case INFO:
return "INFO";
break;
case WARNING:
return "WARNING";
break;
case ERROR:
return "ERROR";
break;
case FATAL:
return "FATAL";
break;
default:
return "";
break;
}
}
std::string Gettime()
{
time_t now = time(nullptr);
struct tm *time = localtime(&now);
char buffer[128];
snprintf(buffer, sizeof(buffer), "%d-%02d-%02d %02d:%02d:%02d",
time->tm_year + 1900,
time->tm_mon + 1,
time->tm_mday,
time->tm_hour,
time->tm_min,
time->tm_sec);
return buffer;
}
struct log_message
{
std::string _level;
int _id;
std::string _filename;
int _filenumber;
std::string _cur_time;
std::string _message;
};
#define SCREAM 1
#define FILE 2
#define DEVELOP 3
#define OPERATION 4
const std::string gpath = "./log.txt";
pthread_mutex_t gmutex = PTHREAD_MUTEX_INITIALIZER;
class log
{
public:
log(const std::string &path = gpath, const int status = DEVELOP)
: _mode(SCREAM), _path(path), _status(status)
{
}
void SelectMode(int mode)
{
_mode = mode;
}
void SelectStatus(int status)
{
_status = status;
}
void PrintScream(const log_message &le)
{
printf("[%s][%d][%s][%d][%s] %s",
le._level.c_str(),
le._id,
le._filename.c_str(),
le._filenumber,
le._cur_time.c_str(),
le._message.c_str());
}
void PrintFile(const log_message &le)
{
std::fstream in(_path, std::ios::app);
if (!in.is_open())
return;
char buffer[1024];
snprintf(buffer, sizeof(buffer), "[%s][%d][%s][%d][%s] %s",
le._level.c_str(),
le._id,
le._filename.c_str(),
le._filenumber,
le._cur_time.c_str(),
le._message.c_str());
in.write(buffer, strlen(buffer)); // 不用sizeof
in.close();
}
void PrintLog(const log_message &le)
{
// 过滤
if (_status == OPERATION)
return;
// 线程安全
pthread_mutex_lock(&gmutex);
switch (_mode)
{
case SCREAM:
PrintScream(le);
break;
case FILE:
PrintFile(le);
break;
default:
break;
}
pthread_mutex_unlock(&gmutex);
}
void logmessage(const std::string &filename, int filenumber,int level, const char *message, ...)
{
log_message le;
le._level = Getlevel(level);
le._id = syscall(SYS_gettid);
le._filename = filename;
le._filenumber = filenumber;
le._cur_time = Gettime();
va_list vt;
va_start(vt, message);
char buffer[128];
vsnprintf(buffer, sizeof(buffer), message, vt);
va_end(vt);
le._message = buffer;
// 打印日志
PrintLog(le);
}
~log()
{
}
private:
int _mode;
std::string _path;
int _status;
};
// 方便上层调用
log lg;
// ##不传时可忽略参数
#define LOG(level, message, ...) \
do \
{ \
lg.logmessage(__FILE__, __LINE__, level,message, ##__VA_ARGS__); \
} while (0)
#define SleftScream() \
do \
{ \
lg.SelectMode(SCREAM_TYPE); \
} while (0)
#define SleftFile() \
do \
{ \
lg.SelectMode(FILE_TYPE); \
} while (0)
}
//InetAddr.hpp
#pragma once
#include<iostream>
#include<string>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
class InetAddr
{
void ToHost()
{
//_ip=inet_ntoa(_addr.sin_addr);//4字节地址->char*
_port=ntohs(_addr.sin_port);
char buffer[124];
inet_ntop(AF_INET,&_addr.sin_addr,buffer,sizeof(buffer));
_ip=buffer;
}
public:
InetAddr()
{
}
InetAddr(const struct sockaddr_in& addr)
:_addr(addr)
{
ToHost();
}
std::string Ip()
{
return _ip;
}
uint16_t Port()
{
return _port;
}
struct sockaddr_in Addr()
{
return _addr;
}
bool operator==(const InetAddr& ad)
{
return (this->_ip==ad._ip&&this->_port==ad._port);
}
std::string User()
{
std::string tmp=_ip+" "+std::to_string(_port)+":";
return tmp;
}
private:
std::string _ip;
uint16_t _port;
struct sockaddr_in _addr;
};
//Http.hpp
#pragma once
#include<iostream>
#include<string>
#include<sstream>
#include<vector>
#include<unordered_map>
const std::string base_sep("\r\n");
const std::string line_sep(": ");
class HttpRequest
{
private:
std::string Getline(std::string &requeststr)
{
size_t pos = requeststr.find(base_sep);
if (pos == std::string::npos) return "";
std::string line = requeststr.substr(0, pos);
requeststr.erase(0, line.size() + base_sep.size());//内容和\r\n都清除
return line.empty() ? base_sep : line;//空行使line为空要特殊判断
}
void LineDeserialize()
{
std::stringstream ss(_request_line);
ss >> _method >> _url >> _version;//stringstream插入会省略空格
}
void HeadDeserialize()
{
for (auto &head : _request_header)
{
size_t pos = head.find(line_sep);
if (pos == std::string::npos)
continue;
std::string k = head.substr(0, pos);
std::string v = head.substr(pos + line_sep.size());
if (k.empty() || v.empty())
continue;
_header.insert(std::make_pair(k, v));
}
}
public:
HttpRequest() : _null_line(base_sep)
{}
void Deserialize(std::string &requeststr)//完整请求字符串进行反序列化
{
_request_line = Getline(requeststr);
std::string line;
do
{
line = Getline(requeststr);
if (line.empty() || line == base_sep)
break;
_request_header.push_back(line);
} while (true);
if (!requeststr.empty())
_request_main = requeststr;
//更具体的反序列化
LineDeserialize();
HeadDeserialize();
}
void print()
{
std::cout << "------------------------------" << std::endl;
std::cout << "@@@" << _request_line << std::endl;
for (auto &header : _request_header)
{
std::cout << "###" << header << std::endl;
}
std::cout << "---" << _null_line;
std::cout << ">>>" << _request_main << std::endl;
std::cout << "更具体进行解析" << std::endl;
std::cout << "method: " << _method << std::endl;
std::cout << "url: " << _url << std::endl;
std::cout << "version: " << _version << std::endl;
for (auto &header : _header)
{
std::cout << header.first << "->" << header.second << std::endl;
}
}
private:
// 基本格式
std::string _request_line;
std::vector<std::string> _request_header;
std::string _null_line;
std::string _request_main;
// 具体解析
std::string _method;
std::string _url;
std::string _version;
std::unordered_map<std::string, std::string> _header;
};
class Http
{
public:
Http()
{}
~Http()
{}
std::string Server(std::string requeststr)
{
HttpRequest rt;
rt.Deserialize(requeststr);
rt.print();
return "";
}
};
接着我们来写HttpReponse:给浏览器进行应答(获取资源(wwwroot中)) 通过path来进行获取
#pragma once
#include <iostream>
#include <string>
#include <sstream>
#include <vector>
#include <unordered_map>
#include <sys/stat.h>
const std::string base_sep = "\r\n";
const std::string line_sep = ": ";
const std::string profixpath="wwwroot";
const std::string homepage = "index.html";
const std::string space_sep = " ";
class HttpRequest
{
private:
std::string Getline(std::string &requeststr)
{
size_t pos = requeststr.find(base_sep);
if (pos == std::string::npos)
return "";
std::string line = requeststr.substr(0, pos);
requeststr.erase(0, line.size() + base_sep.size()); // 内容和\r\n都清除
return line.empty() ? base_sep : line; // 空行使line为空要特殊判断
}
void LineDeserialize()
{
std::stringstream ss(_request_line);
ss >> _method >> _url >> _version; // stringstream插入会省略空格
//url进行解析
_path+=_url;
if(_url=="/")
{
_path+=homepage;
}
}
void HeadDeserialize()
{
for (auto &head : _request_header)
{
size_t pos = head.find(line_sep);
if (pos == std::string::npos)
continue;
std::string k = head.substr(0, pos);
std::string v = head.substr(pos + line_sep.size());
if (k.empty() || v.empty())
continue;
_header.insert(std::make_pair(k, v));
}
}
public:
HttpRequest() : _null_line(base_sep),_path(profixpath)
{
}
std::string Path()
{
return _path;
}
void Deserialize(std::string &requeststr) // 完整请求字符串进行反序列化
{
_request_line = Getline(requeststr);
std::string line;
do
{
line = Getline(requeststr);
if (line.empty() || line == base_sep)
break;
_request_header.push_back(line);
} while (true);
if (!requeststr.empty())
_request_main = requeststr;
// 更具体的反序列化
LineDeserialize();
HeadDeserialize();
}
void print()
{
std::cout << "------------------------------" << std::endl;
std::cout << "@@@" << _request_line << std::endl;
for (auto &header : _request_header)
{
std::cout << "###" << header << std::endl;
}
std::cout << "---" << _null_line;
std::cout << ">>>" << _request_main << std::endl;
std::cout << "更具体进行解析" << std::endl;
std::cout << "method: " << _method << std::endl;
std::cout << "url: " << _url << std::endl;
std::cout << "version: " << _version << std::endl;
for (auto &header : _header)
{
std::cout << header.first << "->" << header.second << std::endl;
}
}
private:
// 基本格式
std::string _request_line;
std::vector<std::string> _request_header;
std::string _null_line;
std::string _request_main;
// 具体解析
std::string _method;
std::string _url;
std::string _version;
std::unordered_map<std::string, std::string> _header;
std::string _path; // 访问资源路径
};
class HttpReponse
{
public:
HttpReponse():_null_line(base_sep),_version("HTTP/1.1")
{}
void AddCode(int code)
{
_code=code;
_des="OK";
}
void AddHeader(std::string k,std::string v)
{
_header.insert({k,v});
}
void AddMain(std::string main)
{
_request_main=main;
}
std::string Serialize()
{
_request_line=_version+space_sep+_code+space_sep+_des+base_sep;
for(auto& header:_header)
{
std::string key = header.first;
std::string value=header.second;
_request_header.push_back(key+line_sep+value+base_sep);
}
//拼接格式
std::string jsonstr;
jsonstr+=_request_line;
for(auto &header:_request_header)
{
jsonstr+=header;
}
jsonstr+=_null_line;
jsonstr+=_request_main;
return jsonstr;
}
private:
//基本属性
std::string _version;
std::string _code;
std::string _des;
std::unordered_map<std::string,std::string> _header;
//基本格式
std::string _request_line;
std::vector<std::string> _request_header;
std::string _null_line;
std::string _request_main;
};
class Http
{
public:
Http()
{
}
~Http()
{
}
std::string DealPath(std::string path)
{
std::ifstream in(path);
if(!in.is_open()) return "";
// in.seekg(0,in.end);
// int filesize=in.tellg();
// in.seekg(0,in.beg);
struct stat buff;
int n=::stat(path.c_str(),&buff);
int filesize=buff.st_size;
std::string content;
content.resize(filesize);
in.read((char*)content.c_str(),filesize);
in.close();
return content;
}
std::string Server(std::string requeststr)
{
//请求
HttpRequest req;
req.Deserialize(requeststr);
req.print();
//上层处理
std::string content=DealPath(req.Path());
//应答
HttpReponse rep;
rep.AddCode(20);
rep.AddHeader("Content-Length",std::to_string(content.size()));
rep.AddMain(content);
return rep.Serialize();
}
};
//wwwroot文件夹->给浏览器应答时获取的资源都在这里面
//index.html
<!DOCTYPE html>
<html>
<head>
<title>测试网站</title>
<meta charset="UTF-8">
</head>
<body>
<div id="container" style="width:800px">
<div id="header" style="background-color:#FFA500;">
<h1 style="margin-bottom:0;">网页的主标题</h1>
</div>
<div id="menu" style="background-color:#FFD700;height:200px;width:100px;float:left;">
<b>Menu</b><br>
HTML<br>
CSS<br>
JavaScript
</div>
<div id="content" style="background-color:#EEEEEE;height:200px;width:700px;float:left;">
内容就在这里</div>
<div id="footer" style="background-color:#FFA500;clear:both;text-align:center;">
Copyright © w3cschool.cn</div>
</div>
<div>
<a href="/register.html">登录页面</a>
<a href="/a.html">404</a>
<a href="/redir">重定向</a>
</div>
<div>
<form action="/inexit" method="GET">
用户名: <input type="text" name="user" value="."><br>
密码: <input type="password" name="password" value=""><br>
<input type="submit" value="提交">
</form>
</div>
<div>
<img src="/image/1.jpg" alt="图片">
</div>
</body>
</html>
获得一个完整的网页,浏览器要先得到html;根据html的标签,检测出我们还要获取其它资源,浏览器会继续发起请求!!
二HTTP常见Header
字段名 | 含义 | 样例 |
---|---|---|
Connection | 请求完后是关闭 还是保持连接 | Connection: keep-alive 或 Connection: close |
Cookie | 客户端发送给服 务器的 HTTP cookie 信息 | Cookie: session_id=abcdefg12345; user_id=123 |
Content-Type | 实体主体的媒体 类型 | Content-Type: application/x-www form-urlencoded (对于表单提交) 或 Content-Type: application/json (对于 JSON 数据) |
Content-Length | 实体主体的字节 大小 | Content-Length: 150 |
Accept | 客户端可接受的 响应内容类型 | Accept: text/html,application/xhtml+xml,app lication/xml;q=0.9,image/webp,image /apng,*/*;q=0.8 |
Accept Encoding | 客户端支持的数 据压缩格式 | Accept-Encoding: gzip, deflate, br |
Accept Language | 客户端可接受的 语言类型 | Accept-Language: zh CN,zh;q=0.9,en;q=0.8 |
Host | 请求的主机名和 端口号 | Host: www.example.com:8080 |
User-Agent | 客户端的软件环 境信息 | User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36 |
Authorization | 认证信息, 如用 户名和密码 | Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ== (Base64 编码后的用户名:密码) |
Cache-Control | 缓存控制指令 | 请求时: Cache-Control: no-cache 或 Cache-Control: max-age=3600; 响应 时: Cache-Control: public, max age=3600 |
Date | 请求或响应的日 期和时间 | Date: Wed, 21 Oct 2023 07:28:00 GMT |
Location | 重定向的目标 URL(与 3xx 状 态码配合使用) | Location: http://www.example.com/new_location .html (与 302 状态码配合使用) |
Server | 服务器类型 | Server: Apache/2.4.41 (Unix) |
Last-Modified | 资源的最后修改 时间 | Last-Modified: Wed, 21 Oct 2023 07:20:00 GMT |
ETag | 资源的唯一标识 符, 用于缓存 | ETag: "3f80f-1b6-5f4e2512a4100" |
Expires | 响应过期的日期 和时间 | Expires: Wed, 21 Oct 2023 08:28:00 GMT |
Referer | 请求的来源 URL | Referer: http://www.example.com/previous_pag e.html |
1Connection的报头
它主要用于控制和管理客户端与服务器之间的连接状态
在HTTP 1.1中它默认是长连接:允许客户端和服务器在请求/响应完成后不立即关闭 TCP 连接, 以便在同一个连接上发送多个请求和接收多个响应
这也就节省了资源:每次想客户端请求时不用再重新建立tcp连接了
而在上面的代码中,我们写的服务器是不是长连接(当然想改的话可以按照写网络版本计算机的思路:根据Content-Type来判断需不需要处理,向客户端进行返回(这个过程是循环的))
2Cookie和Session
在我们平时访问某些网站时(如b站),有些视频不用登录能观看,而有些视频要进行登录才能观看;然而我们在前面说了:HTTP协议是无连接,无状态的:网站要想识别用户身份怎么办?
登录完成后,即使后面我重新打开该网页,网站也还是能够识别我的身份?
这些问题都通过Cookie和Session技术来进行登录会话管理进行解决!
2.1Cookie定义
Cookie(也称为 Web Cookie) 是服务器发送到用户浏览器并保存在浏览器上的一小块数据(文件/内存存储), 它会在浏览器之后向同一服务器再次发起请求时被携带并发送到服务器
2.2Cookie使用
在 HTTP 响应头中添加Cookie,客户端(如浏览器) 就能获取并自行设置并保存
Set-Cookie: <name>=<value>
//其中 <name> 是 Cookie 的名称, <value> 是 Cookie 的值
//一个完整的Cookie
Set-Cookie: username=peter; expires=Thu, 18 Dec 2024 12:00:00 UTC;
path=/; domain=.example.com; secure; HttpOnly
属性 | 值 | 描述 |
---|---|---|
username(代码验证) | peter | 这是 Cookie 的名称和值, 标识用户 名为"peter"。 |
expires(代码验证) | Thu, 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报头使用(从上面的代码进行修改)
//Http.hpp
#pragma once
#include <iostream>
#include <string>
#include <sstream>
#include <vector>
#include <unordered_map>
#include <functional>
#include <sys/stat.h>
const std::string base_sep = "\r\n";
const std::string line_sep = ": ";
const std::string profixpath = "wwwroot";
const std::string homepage = "index.html";
const std::string httpversion = "HTTP/1.1";
const std::string space_sep = " ";
const std::string profix_sep = ".";
const std::string arg_sep = "?";
class HttpRequest
{
private:
std::string Getline(std::string &requeststr)
{
size_t pos = requeststr.find(base_sep);
if (pos == std::string::npos)
return "";
std::string line = requeststr.substr(0, pos);
requeststr.erase(0, line.size() + base_sep.size()); // 内容和\r\n都清除
return line.empty() ? base_sep : line; // 空行使line为空要特殊判断
}
void LineDeserialize()
{
std::stringstream ss(_request_line);
ss >> _method >> _url >> _version; // stringstream插入会省略空格
if (strcasecmp(_method.c_str(), "GET") == 0)
{
// GET获取资源放在正文 /login?user=zhangsan&password=123456
auto pos = _url.find(arg_sep);
if (pos != std::string::npos)
{
_request_main = _url.substr(pos + arg_sep.size());
_url.resize(pos);
}
}
// url进行解析
_path += _url;
if (_path[_path.size() - 1] == '/')
{
_path += homepage;
}
// wwwroot/1.jpg
auto pos = _path.rfind(profix_sep);
if (pos != std::string::npos)
{
_profix = _path.substr(pos);
}
}
void HeadDeserialize()
{
for (auto &head : _request_header)
{
size_t pos = head.find(line_sep);
if (pos == std::string::npos)
continue;
std::string k = head.substr(0, pos);
std::string v = head.substr(pos + line_sep.size());
if (k.empty() || v.empty())
continue;
_header.insert({k, v});
}
}
public:
HttpRequest() : _null_line(base_sep), _path(profixpath)
{
}
std::string Path()
{
return _path;
}
std::string Profix()
{
return _profix;
}
std::string Main()
{
std::cout << "method: " << _method << " path: " << _path << " args: " << _request_main << std::endl;
return _request_main;
}
void Deserialize(std::string &requeststr) // 完整请求字符串进行反序列化
{
_request_line = Getline(requeststr);
std::string line;
do
{
line = Getline(requeststr);
if (line.empty() || line == base_sep)
break;
_request_header.push_back(line);
} while (true);
if (!requeststr.empty())
_request_main = requeststr;
// 更具体的反序列化
LineDeserialize();
HeadDeserialize();
}
void print()
{
std::cout << "---------------request------------" << std::endl;
std::cout << "@@@" << _request_line << std::endl;
for (auto &header : _request_header)
{
std::cout << "###" << header << std::endl;
}
std::cout << "---" << _null_line;
std::cout << ">>>" << _request_main << std::endl;
std::cout << "更具体进行解析" << std::endl;
std::cout << "method: " << _method << std::endl;
std::cout << "url: " << _url << std::endl;
std::cout << "version: " << _version << std::endl;
for (auto &header : _header)
{
std::cout << header.first << "->" << header.second << std::endl;
}
}
private:
// 基本格式
std::string _request_line;
std::vector<std::string> _request_header;
std::string _null_line;
std::string _request_main;
// 具体解析
std::string _method;
std::string _url;
std::string _version;
std::unordered_map<std::string, std::string> _header;
std::string _path; // 访问资源路径
std::string _profix; // 资源后缀
};
class HttpReponse
{
public:
HttpReponse() : _null_line(base_sep), _version(httpversion)
{
}
void AddCode(int code, const std::string &des)
{
_code = code;
_des = des;
}
void AddHeader(std::string k, std::string v)
{
_header.insert({k, v});
}
void AddMain(const std::string &main)
{
_reponse_main = main;
}
std::string Serialize()
{
_reponse_line = _version + space_sep + std::to_string(_code) + space_sep + _des + base_sep;
for (auto &header : _header)
{
std::string key = header.first;
std::string value = header.second;
_reponse_header.push_back(key + line_sep + value + base_sep);
}
// 拼接格式
std::string jsonstr;
jsonstr += _reponse_line;
for (auto &header : _reponse_header)
{
jsonstr += header;
}
jsonstr += _null_line;
jsonstr += _reponse_main;
return jsonstr;
}
private:
// 基本属性
std::string _version;
int _code;
std::string _des;
std::unordered_map<std::string, std::string> _header;
// 基本格式
std::string _reponse_line;
std::vector<std::string> _reponse_header;
std::string _null_line;
std::string _reponse_main;
};
using fun_t = std::function<HttpReponse(HttpRequest &)>;
class Http
{
public:
Http()
{
_ContentType_Table.insert({".html", "text/html"});
_ContentType_Table.insert({".jpg", "image/jpeg"});
_CodeDes_Table.insert({302, "Found"});
_CodeDes_Table.insert({401, "Not Fund"});
}
~Http()
{
}
std::string DealPath(std::string path)
{
std::ifstream in(path);
if (!in.is_open())
return "";
// in.seekg(0,in.end);
// int filesize=in.tellg();
// in.seekg(0,in.beg);
struct stat buff;
int n = ::stat(path.c_str(), &buff);
int filesize = buff.st_size;
std::string content;
content.resize(filesize);
in.read((char *)content.c_str(), filesize);
in.close();
return content;
}
// #define TEST
std::string Server(std::string &requeststr)
{
#ifdef TEST
std::cout << "-------------------------------" << std::endl;
std::cout << requeststr;
std::string jsonstr = "HTTP/1.1 200 OK\r\n";
jsonstr += "Content-Type: text/html\r\n";
jsonstr += "\r\n";
jsonstr += "<html><h1> Hello Http </h1></html>";
return jsonstr;
#else
// 请求
HttpRequest req;
req.Deserialize(requeststr);
req.print();
req.Main();
// 应答
HttpReponse rep;
if (req.Path() == "wwwroot/redir")
{
std::string redir_path = "https://www.qq.com";
rep.AddCode(302, _CodeDes_Table[302]);
rep.AddHeader("Location", redir_path);
}
std::string content=DealPath(req.Path());
rep.AddCode(20, "OK");
rep.AddHeader("Content-Length", std::to_string(content.size()));
rep.AddHeader("Content-Type", _ContentType_Table[req.Profix()]);
rep.AddHeader("Set-Cookie", "username=zhangsan; path=/a/b;");
rep.AddMain(content);
return rep.Serialize();
#endif
}
void InsertServer(const std::string &path, fun_t server)
{
// path: /login
std::string s = profixpath + path;
_Server[s] = server;
}
bool IsServerExist(std::string path)
{
auto pos = _Server.find(path);
if (pos == _Server.end())
return false;
return true;
}
std::string GetMon(int pos)
{
std::vector<std::string> Mon = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
return Mon[pos];
}
std::string GetDay(int pos)
{
std::vector<std::string> Week = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
return Week[pos];
}
std::string ExpireTime(int t)
{
// Thu, 18 Dec 2024 12:00:00 UTC
time_t timout = time(nullptr) + t; // 超时时间
struct tm *tm = gmtime(&timout);
char timebuffer[1024];
snprintf(timebuffer, sizeof(timebuffer), "%s, %02d %s %d %02d:%02d:%02d UTC",
GetDay(tm->tm_wday).c_str(),
tm->tm_mday,
GetMon(tm->tm_mon).c_str(),
tm->tm_year + 1900,
tm->tm_hour,
tm->tm_min,
tm->tm_sec);
return timebuffer;
}
private:
std::unordered_map<std::string, std::string> _ContentType_Table;
std::unordered_map<int, std::string> _CodeDes_Table;
std::unordered_map<std::string, fun_t> _Server;
};
请求/时
请求/a/b时
如果你想往Cookie添加用户和密码:就需要添加两个Cookie(一个用户名,一个密码)
我们写入的是测试数据, 如果写入的是用户的私密数据呢? 比如, 用户名密码,浏览痕迹等。
Cookie会在浏览器(用户端)保存,非常容易被人盗取,更重要的是用户的私密数据;为了解决问题,我们引入HTTP Session
2.3Session定义
HTTP Session 是服务器用来跟踪用户与服务器交互期间用户状态的机制。 由于 HTTP协议是无状态的(每个请求都是独立的) , 因此服务器需要通过 Session 来记住用户的信息
2.4Session实现
原理:
a.当用户首次访问网站时, 服务器会为用户创建一个唯一的 Session ID, 并通过Cookie 将其发送到客户端。
b.客户端在之后的请求中会携带这个 Session ID, 服务器通过 Session ID 来识别用户, 从而获取用户的会话信息。
c.服务器通常会将 Session 信息存储在内存、 数据库或缓存中。(如Redis)
//主要代码
//Httprotocol.hpp
#pragma once
#include <iostream>
#include <string>
#include <sstream>
#include <vector>
#include <memory>
#include <ctime>
#include <functional>
#include "TcpServer.hpp"
#include "Session.hpp" // 引入session
const std::string HttpSep = "\r\n";
// 可以配置的
const std::string homepage = "index.html";
const std::string wwwroot = "./wwwroot";
class HttpRequest
{
public:
HttpRequest() : _req_blank(HttpSep), _path(wwwroot)
{
}
bool GetLine(std::string &str, std::string *line)
{
auto pos = str.find(HttpSep);
if (pos == std::string::npos)
return false;
*line = str.substr(0, pos); // \r\n
str.erase(0, pos + HttpSep.size());
return true;
}
void Parse()
{
// 解析出来url
std::stringstream ss(_req_line);
ss >> _method >> _url >> _http_version;
// 查找cookie
std::string prefix = "Cookie: "; // 写入: Set-Cookie: sessionid=1234 提交: Cookie: sessionid=1234
for (auto &line : _req_header)
{
std::string cookie;
if (strncmp(line.c_str(), prefix.c_str(), prefix.size()) == 0) // 找到了
{
cookie = line.substr(prefix.size()); // 截取"Cookie: "之后的就行了
_cookies.emplace_back(cookie);
break;
}
}
// 查找sessionid
// sessionid=1234
prefix = "sessionid=";
for (const auto &cookie : _cookies)
{
if (strncmp(cookie.c_str(), prefix.c_str(), prefix.size()) == 0)
{
_sessionid = cookie.substr(prefix.size()); // 截取"sessionid="之后的就行了
// std::cout << "_sessionid: " << _sessionid << std::endl;
}
}
}
std::string Url()
{
return _url;
}
std::string SessionId()
{
return _sessionid;
}
bool Deserialize(std::string &request)
{
std::string line;
bool ok = GetLine(request, &line);
if (!ok)
return false;
_req_line = line;
while (true)
{
bool ok = GetLine(request, &line);
if (ok && line.empty())
{
_req_content = request;
break;
}
else if (ok && !line.empty())
{
_req_header.push_back(line);
}
else
{
break;
}
}
return true;
}
void DebugHttp()
{
std::cout << "_req_line: " << _req_line << std::endl;
for (auto &line : _req_header)
{
std::cout << "---> " << line << std::endl;
}
}
~HttpRequest()
{
}
private:
// http报文自动
std::string _req_line; // method url http_version
std::vector<std::string> _req_header;
std::string _req_blank;
std::string _req_content;
// 解析之后的内容
std::string _method;
std::string _url; // / /dira/dirb/x.html /dira/dirb/XX?usrname=100&&password=1234 /dira/dirb
std::string _http_version;
std::string _path; // "./wwwroot"
std::string _suffix; // 请求资源的后缀
std::vector<std::string> _cookies; // 其实cookie可以有多个,因为Set-Cookie可以被写多条,测试,一条够了。
std::string _sessionid; // 请求携带的sessionid,仅仅用来测试
};
const std::string BlankSep = " ";
const std::string LineSep = "\r\n";
class HttpResponse
{
public:
HttpResponse() : _http_version("HTTP/1.0"), _status_code(200), _status_code_desc("OK"), _resp_blank(LineSep)
{
}
void SetCode(int code)
{
_status_code = code;
}
void SetDesc(const std::string &desc)
{
_status_code_desc = desc;
}
void MakeStatusLine()
{
_status_line = _http_version + BlankSep + std::to_string(_status_code) + BlankSep + _status_code_desc + LineSep;
}
void AddHeader(const std::string &header)
{
_resp_header.push_back(header + LineSep);
}
void AddContent(const std::string &content)
{
_resp_content = content;
}
std::string Serialize()
{
MakeStatusLine();
std::string response_str = _status_line;
for (auto &header : _resp_header)
{
response_str += header;
}
response_str += _resp_blank;
response_str += _resp_content;
return response_str;
}
~HttpResponse() {}
private:
std::string _status_line;
std::vector<std::string> _resp_header;
std::string _resp_blank;
std::string _resp_content; // body
// httpversion StatusCode StatusCodeDesc
std::string _http_version;
int _status_code;
std::string _status_code_desc;
};
class Http
{
private:
std::string GetMonthName(int month)
{
std::vector<std::string> months = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
return months[month];
}
std::string GetWeekDayName(int day)
{
std::vector<std::string> weekdays = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
return weekdays[day];
}
std::string ExpireTimeUseRfc1123(int t) // 秒级别的未来UTC时间
{
time_t timeout = time(nullptr) + t;
struct tm *tm = gmtime(&timeout); // 这里不能用localtime,因为localtime是默认带了时区的. gmtime获取的就是UTC统一时间
char timebuffer[1024];
// 时间格式如: expires=Thu, 18 Dec 2024 12:00:00 UTC
snprintf(timebuffer, sizeof(timebuffer), "%s, %02d %s %d %02d:%02d:%02d UTC",
GetWeekDayName(tm->tm_wday).c_str(),
tm->tm_mday,
GetMonthName(tm->tm_mon).c_str(),
tm->tm_year + 1900,
tm->tm_hour,
tm->tm_min,
tm->tm_sec);
return timebuffer;
}
public:
Http(uint16_t port)
{
_tsvr = std::make_unique<TcpServer>(port, std::bind(&Http::HandlerHttp, this, std::placeholders::_1));
_tsvr->Init();
_session_manager = std::make_unique<SessionManager>();
}
std::string ProveCookieWrite() // 证明cookie能被写入浏览器
{
return "Set-Cookie: username=zhangsan;";
}
std::string ProveCookieTimeOut()
{
return "Set-Cookie: username=zhangsan; expires=" + ExpireTimeUseRfc1123(60) + ";"; // 让cookie 1min后过期
}
std::string ProvePath()
{
return "Set-Cookie: username=zhangsan; path=/a/b;";
}
std::string ProveSession(const std::string &session_id)
{
return "Set-Cookie: sessionid=" + session_id + ";";
}
std::string HandlerHttp(std::string request)
{
HttpRequest req;
HttpResponse resp;
req.Deserialize(request);
req.Parse();
// req.DebugHttp();
// std::cout << req.Url() << std::endl;
// 下面的代码就用来测试,如果你想更优雅,可以回调出去处理
static int number = 0;
if (req.Url() == "/login") // 用/login path向指定浏览器写入sessionid,并在服务器维护对应的session对象
{
std::string sessionid = req.SessionId();
if (sessionid.empty()) // 说明历史没有登陆过
{
std::string user = "user-" + std::to_string(number++);
session_ptr s = std::make_shared<Session>(user, "logined");
std::string sessionid = _session_manager->AddSession(s);
lg.LogMessage(Debug, "%s 被添加, sessionid是: %s\n", user.c_str(), sessionid.c_str());
resp.AddHeader(ProveSession(sessionid));
}
}
else
{
// 当浏览器在本站点任何路径中活跃,都会自动提交sessionid, 我们就能知道谁活跃了.
std::string sessionid = req.SessionId();
if (!sessionid.empty())
{
session_ptr s = _session_manager->GetSession(sessionid);
// 这个地方有坑,一定要判断服务器端session对象是否存在,因为可能测试的时候
// 浏览器还有历史sessionid,但是服务器重启之后,session对象没有了.
if(s != nullptr)
lg.LogMessage(Debug, "%s 正在活跃.\n", s->_username.c_str());
else
lg.LogMessage(Debug, "cookie : %s 已经过期, 需要清理\n", sessionid.c_str());
}
}
resp.SetCode(200);
resp.SetDesc("OK");
resp.AddHeader("Content-Type: text/html");
// resp.AddHeader(ProveCookieWrite()); //测试cookie被写入与自动提交
// resp.AddHeader(ProveCookieTimeOut()); //测试过期时间的写入
// resp.AddHeader(ProvePath()); // 测试路径
resp.AddContent("<html><h1>helloworld</h1></html>");
return resp.Serialize();
}
void Run()
{
_tsvr->Start();
}
~Http()
{
}
private:
std::unique_ptr<TcpServer> _tsvr;
std::unique_ptr<SessionManager> _session_manager;
};
//Session.hpp
#pragma once
#include <iostream>
#include <string>
#include <memory>
#include <ctime>
#include <unistd.h>
#include <unordered_map>
// 用来进行测试说明
class Session
{
public:
Session(const std::string &username, const std::string &status)
:_username(username), _status(status)
{
_create_time = time(nullptr); // 获取时间戳就行了,后面实际需要,就转化就转换一下
}
~Session()
{}
public:
std::string _username;
std::string _status;
uint64_t _create_time;
uint64_t _time_out; // 60*5
std::string vip; // vip
int active; //
std::string pos;
//当然还可以再加任何其他信息,看你的需求
};
using session_ptr = std::shared_ptr<Session>;
class SessionManager
{
public:
SessionManager()
{
srand(time(nullptr) ^ getpid());
}
std::string AddSession(session_ptr s)
{
uint32_t randomid = rand() + time(nullptr); // 随机数+时间戳,实际有形成sessionid的库,比如boost uuid库,或者其他第三方库等
std::string sessionid = std::to_string(randomid);
_sessions.insert(std::make_pair(sessionid, s));
return sessionid;
}
session_ptr GetSession(const std::string sessionid)
{
if(_sessions.find(sessionid) == _sessions.end()) return nullptr;
return _sessions[sessionid];
}
~SessionManager()
{}
private:
std::unordered_map<std::string, session_ptr> _sessions;
};
虽然Session比起Cookie来说相对安全,但还是有可能被盗取!(没有绝对的安全)
所以在服务器端就要设置一些方案来减低身份被冒人的风险:通过位置或者活跃度来判断用户身份,如果身份不对就要让服务器储存的对应sessionid失效即可!(通过具体业务来实现)
四HTTP响应起始行
1状态码
状态码 | 含义 | 应用样例 |
100 | Continue | 上传大文件时, 服务器告诉客户端可以 继续上传 |
200 | OK | 访问网站首页, 服务器返回网页内容 |
201 | Created | 发布新文章, 服务器返回文章创建成功 的信息 |
204 | No Content | 删除文章后, 服务器返回“无内容”表示操 作成功 |
400 | Bad Request | 填写表单时, 格式不正确导致提交失败 |
401 | Unauthorized | 访问需要登录的页面时, 未登录或认证 失败 |
403 | Forbidden | 尝试访问你没有权限查看的页面 |
404 | Not Found | 访问不存在的网页链接 |
500 | Internal Server Error | 服务器崩溃或数据库错误导致页面无法 加载 |
502 | Bad Gateway | 使用代理服务器时, 代理服务器无法从 上游服务器获取有效响应 |
503 | Service Unavailable | 服务器维护或过载, 暂时无法处理请求 |
以下是重定向相关状态码的表格:使用时要搭配Header的Location一起使用!
状态码 | 含义 | 是否为临时重定向 | 应用样例 |
301 | Moved Permanently | 否(永久重定向) | 网站换域名后, 自 动跳转到新域名; 搜索引擎更新网站 链接时使用 |
302 | Found 或 See Other | 是(临时重定向) | 用户登录成功后, 重定向到用户首页 |
307 | Temporary Redirect | 是(临时重定向) | 临时重定向资源到 新的位置(较少使 用) |
308 | Permanent Redirect | 否(永久重定向) | 永久重定向资源到 新的位置(较少使 用) |
2永久重定向与临时重定向
2.1从技术角度上
client端访问server端时:如果server端识别client所访问的资源要进行跳转,它就会把状态码(301/302)与要跳转的链接作为响应发给用户,让用户能够跳转到该地址处
2.2从理解角度上
举例子来理解:
在你们学校的南门,有家xxx鱼店;每次中午你都会去那里吃;但由于这家店附件的马路最近在施工,在店门口贴了张告示:店临时搬到学校北门;所以接下来你要到那里去吃的话就直接去学校北门吃鱼;过了两个月后,你是会去南门(老店地址)还是北门(临时地址)去用餐呢?
因为你不确定路是路是修没修好的,所以你会先去南门看看,没开就去北门那里用餐(临时重定向)
如果路是一直修不好的,店老板在店门口贴了:本店永久搬到学校北门;接下来你要去吃的话就直接到北门的店不取南门的店了(永久重定向)
但在实际中,临时重定向我们是见过的:页面跳转;但永久重定向我们没见过:正常,因为永久重定向是给搜索引擎看的!
你实现处理的站点:www.hello.com:xxx搜索引擎要想获取到;它就会通过爬虫等方式获取该站点的信息从而保存到自己的数据库中:用户在进行搜索时就能看到该网站从而进行跳转访问到;此时由于某种原因你需要更换网址为:www.world.com;那这时怎么告诉用户我更换了网址呢?
这就需要xxx搜索引擎下一次爬取该网站信息时,你需要告诉它网站要进行永久重定向(301),它就会更新网站信息,让用户下一次访问时直接就跳转到新网站了!
2.3实现
#pragma once
#include <iostream>
#include <string>
#include <sstream>
#include <vector>
#include <unordered_map>
#include <sys/stat.h>
const std::string base_sep = "\r\n";
const std::string line_sep = ": ";
const std::string profixpath = "wwwroot";
const std::string homepage = "index.html";
const std::string httpversion = "HTTP/1.1";
const std::string space_sep = " ";
const std::string profix_sep = ".";
class HttpRequest
{
private:
std::string Getline(std::string &requeststr)
{
size_t pos = requeststr.find(base_sep);
if (pos == std::string::npos)
return "";
std::string line = requeststr.substr(0, pos);
requeststr.erase(0, line.size() + base_sep.size()); // 内容和\r\n都清除
return line.empty() ? base_sep : line; // 空行使line为空要特殊判断
}
void LineDeserialize()
{
std::stringstream ss(_request_line);
ss >> _method >> _url >> _version; // stringstream插入会省略空格
// url进行解析
_path += _url;
if (_url == "/")
{
_path += homepage;
}
// wwwroot/1.jpg
auto pos = _path.rfind(profix_sep);
if (pos != std::string::npos)
{
_profix = _path.substr(pos);
}
}
void HeadDeserialize()
{
for (auto &head : _request_header)
{
size_t pos = head.find(line_sep);
if (pos == std::string::npos)
continue;
std::string k = head.substr(0, pos);
std::string v = head.substr(pos + line_sep.size());
if (k.empty() || v.empty())
continue;
_header.insert(std::make_pair(k, v));
}
}
public:
HttpRequest() : _null_line(base_sep), _path(profixpath)
{
}
std::string Path()
{
return _path;
}
std::string Profix()
{
return _profix;
}
void Deserialize(std::string &requeststr) // 完整请求字符串进行反序列化
{
_request_line = Getline(requeststr);
std::string line;
do
{
line = Getline(requeststr);
if (line.empty() || line == base_sep)
break;
_request_header.push_back(line);
} while (true);
if (!requeststr.empty())
_request_main = requeststr;
// 更具体的反序列化
LineDeserialize();
HeadDeserialize();
}
void print()
{
std::cout << "------------------------------" << std::endl;
std::cout << "@@@" << _request_line << std::endl;
for (auto &header : _request_header)
{
std::cout << "###" << header << std::endl;
}
std::cout << "---" << _null_line;
std::cout << ">>>" << _request_main << std::endl;
std::cout << "更具体进行解析" << std::endl;
std::cout << "method: " << _method << std::endl;
std::cout << "url: " << _url << std::endl;
std::cout << "version: " << _version << std::endl;
for (auto &header : _header)
{
std::cout << header.first << "->" << header.second << std::endl;
}
}
private:
// 基本格式
std::string _request_line;
std::vector<std::string> _request_header;
std::string _null_line;
std::string _request_main;
// 具体解析
std::string _method;
std::string _url;
std::string _version;
std::unordered_map<std::string, std::string> _header;
std::string _path; // 访问资源路径
std::string _profix; // 资源后缀
};
class HttpReponse
{
public:
HttpReponse() : _null_line(base_sep), _version(httpversion)
{
}
void AddCode(int code, const std::string &des)
{
_code = code;
_des = des;
}
void AddHeader(std::string k, std::string v)
{
_header.insert({k, v});
}
void AddMain(std::string main)
{
_request_main = main;
}
std::string Serialize()
{
_request_line = _version + space_sep + std::to_string(_code) + space_sep + _des + base_sep;
for (auto &header : _header)
{
std::string key = header.first;
std::string value = header.second;
_request_header.push_back(key + line_sep + value + base_sep);
}
// 拼接格式
std::string jsonstr;
jsonstr += _request_line;
for (auto &header : _request_header)
{
jsonstr += header;
}
jsonstr += _null_line;
jsonstr += _request_main;
return jsonstr;
}
private:
// 基本属性
std::string _version;
int _code;
std::string _des;
std::unordered_map<std::string, std::string> _header;
// 基本格式
std::string _request_line;
std::vector<std::string> _request_header;
std::string _null_line;
std::string _request_main;
};
class Http
{
public:
Http()
{
_ContentType_Table.insert({".html", "text/html"});
_ContentType_Table.insert({".jpg", "image/jpeg"});
_CodeDes_Table.insert({302, "Found"});
_CodeDes_Table.insert({401, "Not Fund"});
}
~Http()
{
}
std::string DealPath(std::string path)
{
std::ifstream in(path);
if (!in.is_open())
return "";
// in.seekg(0,in.end);
// int filesize=in.tellg();
// in.seekg(0,in.beg);
struct stat buff;
int n = ::stat(path.c_str(), &buff);
int filesize = buff.st_size;
std::string content;
content.resize(filesize);
in.read((char *)content.c_str(), filesize);
in.close();
return content;
}
// #define TEST
std::string Server(std::string requeststr)
{
#ifdef TEST
std::cout << "-------------------------------" << std::endl;
std::cout << requeststr;
std::string jsonstr = "HTTP/1.1 200 OK\r\n";
jsonstr += "Content-Type: text/html\r\n";
jsonstr += "\r\n";
jsonstr += "<html><h1> Hello Http </h1></html>";
return jsonstr;
#else
// 请求
HttpRequest req;
req.Deserialize(requeststr);
req.print();
// 应答
HttpReponse rep;
if (req.Path() == "wwwroot/redir")
{
std::string redir_path = "https://www.qq.com";
rep.AddCode(302, _CodeDes_Table[302]);
rep.AddHeader("Location", redir_path);
}
else
{
// 上层处理
std::string content = DealPath(req.Path());
if (content.empty())
{
content = DealPath("wwwroot/404.html");
rep.AddCode(404, _CodeDes_Table[404]);
rep.AddHeader("Content-Length", std::to_string(content.size()));
rep.AddHeader("Content-Type", _ContentType_Table[req.Profix()]);
rep.AddMain(content);
}
else
{
rep.AddCode(20, "OK");
rep.AddHeader("Content-Length", std::to_string(content.size()));
rep.AddHeader("Content-Type", _ContentType_Table[req.Profix()]);
rep.AddMain(content);
}
}
return rep.Serialize();
#endif
}
private:
std::unordered_map<std::string, std::string> _ContentType_Table;
std::unordered_map<int, std::string> _CodeDes_Table;
};
五HTTP方法
其中最常用的就是 GET 方法和 POST 方法
1GET与POST
两者之间的对比我们要借助form表单来完成(前端)
<form action="/login" method="POST">
用户名: <input type="text" name="user" value="."><br>
密码: <input type="password" name="password" value=""><br>
<input type="submit" value="提交">
</form>
现象
1.GET一般用来获取静态资源,也可以通过url来传递参数
2.POST可以通过htrp request的正文来传递参数
3.url传递参数:体量一般不大;正文传递参数:体量可以很大
4.用POST方法传递参数更私密,但是都不安全!
2实现
实现:表单中的数据填完后进行跳转到另一个页面中(我们要通过上层做处理)
(如果时GET方法:要把url中的我们向form表单填的数据给截取到request正文中再处理!)
//ServerMain.cc
#include "TcpServer.hpp"
#include "Http.hpp"
HttpReponse Login(HttpRequest& req)
{
req.Main();
HttpReponse rep;
rep.AddCode(200,"OK");
rep.AddMain("<html><h1> Hello Http </h1></html>");
return rep;
//fork,dup2,pipe,exec* ->pthon,Jova,PHP
//接下来的写业务就交给其它语言了,c++已经完成使命了
}
int main(int args, char *argv[])
{
if (args != 2)
{
std::cerr << "./Server Localport" << std::endl;
exit(0);
}
uint16_t port = std::stoi(argv[1]);
Http http;
http.InsertServer("/login",Login);
std::unique_ptr<TcpServer> svr=std::make_unique<TcpServer>(
std::bind(&Http::Server, &http,
std::placeholders::_1),
port);
svr->Start();
return 0;
}
//Http.hpp
#pragma once
#include <iostream>
#include <string>
#include <sstream>
#include <vector>
#include <unordered_map>
#include <functional>
#include <sys/stat.h>
const std::string base_sep = "\r\n";
const std::string line_sep = ": ";
const std::string profixpath = "wwwroot";
const std::string homepage = "index.html";
const std::string httpversion = "HTTP/1.1";
const std::string space_sep = " ";
const std::string profix_sep = ".";
const std::string arg_sep = "?";
class HttpRequest
{
private:
std::string Getline(std::string &requeststr)
{
size_t pos = requeststr.find(base_sep);
if (pos == std::string::npos)
return "";
std::string line = requeststr.substr(0, pos);
requeststr.erase(0, line.size() + base_sep.size()); // 内容和\r\n都清除
return line.empty() ? base_sep : line; // 空行使line为空要特殊判断
}
void LineDeserialize()
{
std::stringstream ss(_request_line);
ss >> _method >> _url >> _version; // stringstream插入会省略空格
if (strcasecmp(_method.c_str(), "GET") == 0)
{
// GET获取资源放在正文 /login?user=zhangsan&password=123456
auto pos = _url.find(arg_sep);
if (pos != std::string::npos)
{
_request_main = _url.substr(pos + arg_sep.size());
_url.resize(pos);
}
}
// url进行解析
_path += _url;
if (_path[_path.size()-1] == '/')
{
_path += homepage;
}
// wwwroot/1.jpg
auto pos = _path.rfind(profix_sep);
if (pos != std::string::npos)
{
_profix = _path.substr(pos);
}
}
void HeadDeserialize()
{
for (auto &head : _request_header)
{
size_t pos = head.find(line_sep);
if (pos == std::string::npos)
continue;
std::string k = head.substr(0, pos);
std::string v = head.substr(pos + line_sep.size());
if (k.empty() || v.empty())
continue;
_header.insert({k,v});
}
}
public:
HttpRequest() : _null_line(base_sep), _path(profixpath)
{
}
std::string Path()
{
return _path;
}
std::string Profix()
{
return _profix;
}
std::string Main()
{
std::cout << "method: " << _method << " path: " << _path << " args: " << _request_main << std::endl;
return _request_main;
}
void Deserialize(std::string &requeststr) // 完整请求字符串进行反序列化
{
_request_line = Getline(requeststr);
std::string line;
do
{
line = Getline(requeststr);
if (line.empty() || line == base_sep)
break;
_request_header.push_back(line);
} while (true);
if (!requeststr.empty())
_request_main = requeststr;
// 更具体的反序列化
LineDeserialize();
HeadDeserialize();
}
void print()
{
std::cout << "------------------------------" << std::endl;
std::cout << "@@@" << _request_line << std::endl;
for (auto &header : _request_header)
{
std::cout << "###" << header << std::endl;
}
std::cout << "---" << _null_line;
std::cout << ">>>" << _request_main << std::endl;
std::cout << "更具体进行解析" << std::endl;
std::cout << "method: " << _method << std::endl;
std::cout << "url: " << _url << std::endl;
std::cout << "version: " << _version << std::endl;
for (auto &header : _header)
{
std::cout << header.first << "->" << header.second << std::endl;
}
}
private:
// 基本格式
std::string _request_line;
std::vector<std::string> _request_header;
std::string _null_line;
std::string _request_main;
// 具体解析
std::string _method;
std::string _url;
std::string _version;
std::unordered_map<std::string, std::string> _header;
std::string _path; // 访问资源路径
std::string _profix; // 资源后缀
};
class HttpReponse
{
public:
HttpReponse() : _null_line(base_sep), _version(httpversion)
{
}
void AddCode(int code, const std::string &des)
{
_code = code;
_des = des;
}
void AddHeader(std::string k, std::string v)
{
_header.insert({k, v});
}
void AddMain(const std::string &main)
{
_request_main = main;
}
std::string Serialize()
{
_request_line = _version + space_sep + std::to_string(_code) + space_sep + _des + base_sep;
for (auto &header : _header)
{
std::string key = header.first;
std::string value = header.second;
_request_header.push_back(key + line_sep + value + base_sep);
}
// 拼接格式
std::string jsonstr;
jsonstr += _request_line;
for (auto &header : _request_header)
{
jsonstr += header;
}
jsonstr += _null_line;
jsonstr += _request_main;
return jsonstr;
}
private:
// 基本属性
std::string _version;
int _code;
std::string _des;
std::unordered_map<std::string, std::string> _header;
// 基本格式
std::string _request_line;
std::vector<std::string> _request_header;
std::string _null_line;
std::string _request_main;
};
using fun_t = std::function<HttpReponse(HttpRequest&)>;
class Http
{
public:
Http()
{
_ContentType_Table.insert({".html", "text/html"});
_ContentType_Table.insert({".jpg", "image/jpeg"});
_CodeDes_Table.insert({302, "Found"});
_CodeDes_Table.insert({401, "Not Fund"});
}
~Http()
{
}
std::string DealPath(std::string path)
{
std::ifstream in(path);
if (!in.is_open())
return "";
// in.seekg(0,in.end);
// int filesize=in.tellg();
// in.seekg(0,in.beg);
struct stat buff;
int n = ::stat(path.c_str(), &buff);
int filesize = buff.st_size;
std::string content;
content.resize(filesize);
in.read((char *)content.c_str(), filesize);
in.close();
return content;
}
// #define TEST
std::string Server(std::string &requeststr)
{
#ifdef TEST
std::cout << "-------------------------------" << std::endl;
std::cout << requeststr;
std::string jsonstr = "HTTP/1.1 200 OK\r\n";
jsonstr += "Content-Type: text/html\r\n";
jsonstr += "\r\n";
jsonstr += "<html><h1> Hello Http </h1></html>";
return jsonstr;
#else
// 请求
HttpRequest req;
req.Deserialize(requeststr);
req.print();
req.Main();
// 应答
HttpReponse rep;
if (req.Path() == "wwwroot/redir")
{
std::string redir_path = "https://www.qq.com";
rep.AddCode(302, _CodeDes_Table[302]);
rep.AddHeader("Location", redir_path);
}
// 处理动态资源
else if (!req.Main().empty())
{
if (IsServerExist(req.Path()))
{
// 回调
rep = _Server[req.Path()](req);
}
}
else
{
// 处理静态资源
std::string content = DealPath(req.Path());
if (content.empty())
{
content = DealPath("wwwroot/404.html");
rep.AddCode(404, _CodeDes_Table[404]);
rep.AddHeader("Content-Length", std::to_string(content.size()));
rep.AddHeader("Content-Type", _ContentType_Table[req.Profix()]);
rep.AddMain(content);
}
else
{
rep.AddCode(20, "OK");
rep.AddHeader("Content-Length", std::to_string(content.size()));
rep.AddHeader("Content-Type", _ContentType_Table[req.Profix()]);
rep.AddMain(content);
}
}
return rep.Serialize();
#endif
}
void InsertServer(const std::string &path, fun_t server)
{
// path: /login
std::string s = profixpath + path;
_Server[s]=server;
}
bool IsServerExist(std::string path)
{
auto pos = _Server.find(path);
if (pos == _Server.end())
return false;
return true;
}
private:
std::unordered_map<std::string, std::string> _ContentType_Table;
std::unordered_map<int, std::string> _CodeDes_Table;
std::unordered_map<std::string, fun_t> _Server;
};
以上便是全部内容:有错误欢迎指正,感谢观看!