1.HTTP Cookie
定义
认识cookie
HTTP存在一个报头选项:Set-cookie,可以用来进行给浏览器设置cookie值。
在HTTP响应头中添加,客服端获取并自行设置并保存。
完整的Set-Cookie实例
C++Set-Cookie: username=peter; expires=Thu, 18 Dec 2024 12:00:00UTC; path=/; domain=.example.com; secure; HttpOnly
2.HTTP Session
没有Session时的隐私风险
如果没有Session机制,Web应用可能需要依赖其他方式(如Cookie)来跟踪用户状态。Cookie存储在客户端,容易被恶意程序获取和篡改,从而导致隐私泄露。此外,没有Session的加密通信工具可能无法提供端到端加密或去中心化存储,从而增加数据被窃取或监控的风险。
数据存储位置:Session数据存储在服务器端,而不是客户端。即使客户端的Session ID被窃取,攻击者也无法直接获取到存储在服务器上的会话数据。
安全性:服务器可以更好地控制Session的生命周期,例如定期更新Session ID、限制Session的使用范围等,从而降低被攻击的风险。
3.cookie实现
HttpProtocol.hpp文件
第一个类是实现网络请求的,GetLine函数用来截取一行,以HttpSep(\r\n)来作为分隔符,则0到pos位置就是一行的内容了,把读取到的内容在删除掉,读取下一行内容。Deserialize函数是序列化,request是网络请求,用getline函数获取一行出来,第一行是请求行,单独存储起来,然后就是一直死循环读取剩下的报头数据,直到读到了空行,如果是空行的话ok为true且empty也会true则就会把剩下的内容全都放到req_content里,这里就是文本内容了,也是要单独存储的,empty不为空就说明还是在读取报头信息,就把报头信息放到vector里面存储,这里是把键和值一起放到string里面的。
#pragma once
#include <iostream>
#include <string>
#include <sstream>
#include <vector>
#include <memory>
#include <ctime>
#include "TcpServer.hpp"
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);
str.erase(0,pos+HttpSep.size());
return true;
}
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:
std::string _req_line;
std::vector<std::string> _req_header;
std::string _req_blank;
std::string _req_content;
std::string _method;
std::string _url;
std::string _http_version;
std::string _path;
std::string _suffix;
};
第二个代码是网络响应的,构造函数设置网络版本和状态码和状态信息。SetCode函数设置状态码的值,SetDesc函数设置状态信息,MakeStatusLine函数构造状态行,网络版本+空格号+状态码+空格号+状态信息+换行拼接在一起。AddHeader函数把报头信息+换行符插入到vector中。AddContent函数设置文内容。Serialize函数先构造出状态行,然后把报头信息一一取出拼接在状态行的后面,for结束后标识状态行和报头信息已经填好了,接下来就是空行和文本内容的填充,把填充完整的响应返回。
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;
std::string _http_version;
int _status_code;
std::string _status_code_desc;
};
这个类就是网络处理部分。构造函数要接收一个端口号,make_unique构造一个TcpServer对象出来,参数是端口号和一个bind对象,再调用Init函数。ProveCookieWrite函数和ProveCookieTimeOut函数都是添加报头信息,一个是用户名一个是用户名和过期时间(这里设置一分钟),ProvePath函数添加了路径,ProveOtherCookie添加了密码。HandlerHttp函数就是处理部分了,创建请求对象,调用请求的反序列化方法,显示报头信息,创建响应对象,设置状态码,状态信息,添加报头内容,添加文本内容helloworld(前端格式写的,最后网页访问会看到helloworld),最后返回完整的且序列化后的信息。
class Http
{
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();
}
std::string ProveCookieWrite()
{
return "Set-Cookie: username=zhangsan;";
}
std::string ProveCookieTimeOut()
{
return "Set-Cookie: username=zhangsan;expires="+ExpireTimeUseRfc1123(60)+";";
}
std::string ProvePath()
{
return "Set-Cookie: username=zhangsan; path=/a/b";
}
std::string ProveOtherCookie()
{
return "Set-Cookie: passwd=1234567890; path=/a/b;";
}
std::string HandlerHttp(std::string request)
{
HttpRequest req;
req.Deserialize(request);
req.DebugHttp();
lg.LogMessage(Debug,"%s\n",ExpireTimeUseRfc1123(60).c_str());
HttpResponse resp;
resp.SetCode(200);
resp.SetCode(200);
resp.SetDesc("OK");
resp.AddHeader("Content-Type: text/html");
resp.AddHeader(ProvePath());
resp.AddHeader(ProveOtherCookie());
resp.AddContent("<html><h1>helloworld</h1></html>");
return resp.Serialize();
}
void Run()
{
_tsvr->Start();
}
~Http()
{}
private:
std::unique_ptr<TcpServer> _tsvr;
};
TcpServer.hpp文件
构造函数接收端口号和回调函数,以及套接字的值初始化等。ProcessConnection函数参数为Socket对象和InetAddr对象,调用sock方法recv在创建的套接字中读取内容,调用handler处理读取到的信息,把响应信息发送回去,关闭套接字(关闭后就不能传入命令了 telnet指令)。Start函数则会调用AcceptConnection函数进行连接(Tcp协议需要双方连接才能通信),创建task_t对象赋值为bind对象,bind是第一个是函数地址,但是类的话不是这样,类函数需要&来获取函数地址,后面的都是参数,而类函数还需要传递this指针,因为类函数隐式参数this。接着再创建实例并调用插入函数把task插入进去。
#pragma once
#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <functional>
#include <memory>
#include "ThreadPool.hpp"
#include "Log.hpp"
#include "Comm.hpp"
#include "Socket.hpp"
#include "InetAddr.hpp"
using namespace Net_Work;
const static int default_backlog = 6;
using task_t = std::function<void()>;
using handler_t = std::function<std::string(std::string)>;
class TcpServer
{
public:
TcpServer(uint16_t port,handler_t handler):_port(port),_isrunning(false),_listensock(new TcpSocket()),_handler(handler)
{}
void Init()
{
_listensock->BuildListenSocketMethod(_port,default_backlog);
}
void ProcessConnection(std::shared_ptr<Socket> sock,InetAddr addr)
{
std::string request_str;
if(sock->Recv(&request_str,4096))
{
std::string response=_handler(request_str);
sock->Send(response);
sock->CloseSocket();
}
}
void Start()
{
_isrunning=true;
while(_isrunning)
{
std::string clientip;
uint16_t clientport;
std::shared_ptr<Socket> sock=_listensock->AcceptConnection(&clientip,&clientport);
if(sock==nullptr)
{
continue;
}
InetAddr addr(clientip,clientport);
task_t task=std::bind(&TcpServer::ProcessConnection,this,sock,addr);
ThreadPool<task_t>::GetInstance()->Push(task);
}
_isrunning=false;
}
~TcpServer()
{}
private:
uint16_t _port;
std::unique_ptr<Socket> _listensock;
bool _isrunning;
handler_t _handler;
};
成员函数与非成员函数的区别
非成员函数 :对于非成员函数(即全局函数或命名空间内的函数),函数名本身可以直接作为函数的指针。例如,对于一个非成员函数
void foo()
,可以将foo
作为函数指针使用。成员函数 :成员函数属于类的一部分,它们的调用需要一个对象上下文(即通过对象来调用)。因此,要获取成员函数的地址,必须使用
&
操作符。例如,对于类A
中的成员函数void bar()
,要获取其地址,必须写成&A::bar
。
Main.cc文件
#include "HttpProtocol.hpp"
#include <memory>
using namespace std;
void Usage(std::string proc)
{
std::cout<<"Usage: \n\t"<<proc<<" local_port\n"<<std::endl;
}
int main(int argc,char* argv[])
{
if(argc!=2)
{
Usage(argv[0]);
return Usage_Err;
}
uint16_t port=stoi(argv[1]);
std::unique_ptr<Http> http=make_unique<Http>(port);
http->Run();
return 0;
}
ThreadPool.hpp文件
#pragma once
#include <iostream>
#include <queue>
#include <vector>
#include <pthread.h>
#include <functional>
#include "Log.hpp"
#include "Thread.hpp"
#include "LockGuard.hpp"
static const int defaultnum=5;
class ThreadData
{
public:
ThreadData(const std::string& name):threadname(name)
{}
~ThreadData()
{}
public:
std::string threadname;
};
template<class T>
class ThreadPool
{
private:
ThreadPool(int thread_num=defaultnum):_thread_num(thread_num)
{
pthread_mutex_init(&_mutex,nullptr);
pthread_cond_init(&_cond,nullptr);
for(int i=0;i<_thread_num;i++)
{
std::string threadname="thread-";
threadname+=std::to_string(i+1);
ThreadData td(threadname);
_threads.emplace_back(threadname,std::bind(&ThreadPool<T>::ThreadRun,this,std::placeholders::_1),td);
lg.LogMessage(Info,"%s is created...\n", threadname.c_str());
}
}
ThreadPool(const ThreadPool<T>& tp)=delete;
const ThreadPool<T>& operator=(const ThreadPool<T>)=delete;
public:
static ThreadPool<T>* GetInstance()
{
if(instance==nullptr)
{
LockGuard lockguard(&sig_lock);
if(instance==nullptr)
{
lg.LogMessage(Info, "创建单例成功...\n");
instance =new ThreadPool<T>();
instance->Start();
}
}
return instance;
}
bool Start()
{
for(auto& thread:_threads)
{
thread.Start();
lg.LogMessage(Info, "%s is running ...\n", thread.ThreadName().c_str());
}
return true;
}
void ThreadWait(const ThreadData& td)
{
lg.LogMessage(Debug, "no task, %s is sleeping...\n", td.threadname.c_str());
pthread_cond_wait(&_cond,&_mutex);
}
void ThreadWakeup()
{
pthread_cond_signal(&_cond);
}
void checkSelf()
{
}
void ThreadRun(ThreadData &td)
{
while(true)
{
T t;
{
LockGuard lockguard(&_mutex);
while(_q.empty())
{
ThreadWait(td);
lg.LogMessage(Debug, "thread %s is wakeup\n", td.threadname.c_str());
}
t=_q.front();
_q.pop();
}
t();
}
}
void Push(T& in)
{
LockGuard Lockguard(&_mutex);
_q.push(in);
ThreadWakeup();
}
~ThreadPool()
{
pthread_mutex_destroy(&_mutex);
pthread_cond_destroy(&_cond);
}
void Wait()
{
for(auto& thread: _threads)
{
thread.Join();
}
}
private:
std::queue<T> _q;
std::vector<Thread<ThreadData>> _threads;
int _thread_num;
pthread_mutex_t _mutex;
pthread_cond_t _cond;
static ThreadPool<T>* instance;
static pthread_mutex_t sig_lock;
};
template <class T>
ThreadPool<T> *ThreadPool<T>::instance = nullptr;
template <class T>
pthread_mutex_t ThreadPool<T>::sig_lock = PTHREAD_MUTEX_INITIALIZER;
thread.hpp文件
#pragma once
#include <iostream>
#include <string>
#include <functional>
#include <pthread.h>
// 设计方的视角
//typedef std::function<void()> func_t;
template<class T>
using func_t = std::function<void(T&)>;
template<class T>
class Thread
{
public:
Thread(const std::string &threadname, func_t<T> func, T &data)
:_tid(0), _threadname(threadname), _isrunning(false), _func(func), _data(data)
{}
static void *ThreadRoutine(void *args) // 类内方法,
{
// (void)args; // 仅仅是为了防止编译器有告警
Thread *ts = static_cast<Thread *>(args);
ts->_func(ts->_data);
return nullptr;
}
bool Start()
{
int n = pthread_create(&_tid, nullptr, ThreadRoutine, this/*?*/);
if(n == 0)
{
_isrunning = true;
return true;
}
else return false;
}
bool Join()
{
if(!_isrunning) return true;
int n = pthread_join(_tid, nullptr);
if(n == 0)
{
_isrunning = false;
return true;
}
return false;
}
std::string ThreadName()
{
return _threadname;
}
bool IsRunning()
{
return _isrunning;
}
~Thread()
{}
private:
pthread_t _tid;
std::string _threadname;
bool _isrunning;
func_t<T> _func;
T _data;
};
代码流程
main中unique_ptr创建了Http对象的智能指针,就会调用Http类的构造函数,会给Http的tsvr赋值上一个make出来的TcpServer对象,参数是端口号和bind对象,Tcp类也会调用构造函数确定了端口号和回调函数handler,接着调用init函数,里面会调用BuildListenSocketMethod创建套接字并初始化。这时走完了main的第20行代码,接着21调用http的run函数,里面调用tsvr的Start函数,又到了Tcpserver部分,这里就会开始连接然后调用GetInstacnce函数,此时会跳到threadPool部分,执行threadpool的构造函数,锁和条件变量初始化,然后开始往_threads里面插入线程名字和bind对象(ThreadRun函数),因为_thread<Thread<ThreadData>>,所以就会调用thread构造函数,确定了回调函数是Threadrun函数,然后再继续执行GetInstance函数,开辟空间调用线程池的Start函数,start函数会调用所有线程的start函数(thread.start()),就会跳到thread部分,这里就会创建线程执行routine函数,然后调用回调函数threadrun,参数是线程对象ThreadData型,就会回到线程池部分,run会一直检查任务队列是否为空,为空就把这个线程放到条件变量中,不为空就取出任务队列并执行,这里是全部到条件变量中等待,还没有插入。此时才到Push(task)地方,开始插入任务,然后唤醒线程,唤醒的线程就会执行routine函数走回调函数func(Threadrun函数),run函数while队列不是空,就可以取出任务执行了,而这个任务就是httphandler函数,处理请求,构造响应返回。
4.session实现
HttpProcotol.hpp文件
请求类方法实现,GetLine把套接字读取的内容进行行划分,以间隔号作为基准划分多行。Parse函数把提取的行进行分离,stringstream字符串流,会以空格号为间隔把方法,uri和版本号提取出来存储在成员变量中,接着定义prefix,然后范围for遍历报头信息,如果报头信息中有与prefix一样的字符串就把跟prefix一样的部分之后的地方提取出来存储在cookie中,在插入到vector中管理,接着改变prefix值,在来一次,这次找的就是sessionid=,会提取=后面的值存储到vector中管理。
#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);
str.erase(0,pos+HttpSep.size());
return true;
}
void Parse()
{
std::stringstream ss(_req_line);
ss>>_method>>_url>>_http_version;
std::string prefix="Cookie:";
for(auto& line:_req_header)
{
std::string cookie;
if(strncmp(line.c_str(),prefix.c_str(),prefix.size())==0)
{
cookie=line.substr(prefix.size());
_cookies.emplace_back(cookie);
break;
}
}
prefix="sessionid=";
for(const auto& cookie:_cookies)
{
if(strncmp(cookie.c_str(),prefix.c_str(),prefix.size())==0)
{
_sessionid=cookie.substr(prefix.size());
}
}
}
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:
std::string _req_line;
std::vector<std::string> _req_header;
std::string _req_blank;
std::string _req_content;
std::string _method;
std::string _url;
std::string _http_version;
std::string _path;
std::string _suffix;
std::vector<std::string> _cookies;
std::string _sessionid;
};
处理请求的地方,构建请求和应答类,反序列化请求并提取请求行的信息,定义number变量,走if判断,如果为login就说明要登陆,然后提取sessionid值,如果为空说明没有登陆过,定义用户名为number+1,创建session对象,会走session构造函数,得到名字和状态,调用Sessionmanager类方法Addsession插入创建的s对象,会创建一个sessionid值出来,通过随机数加时间戳,并把创建好的sessionid和s存储到map里面管理,把sessionid信息添加到报头中。如果不是login则会查看sessionid值,不为空就调用Getsession函数,在Session.hpp文件中的SessionManger类中成员变量_sessions中查看是否存在这个sessionid,存在就打印正在活跃,没有就标识过期,后面就是设置状态码状态信息等内容。
std::string HandlerHttp(std::string request)
{
HttpRequest req;
HttpResponse resp;
req.Deserialize(request);
req.Parse();
static int number=0;
if(req.Url()=="/login")
{
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
{
std::string sessionid=req.SessionId();
if(!sessionid.empty())
{
session_ptr s=_session_manager->GetSession(sessionid);
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.AddContent("<html><h1>helloworld</h1></html>");
return resp.Serialize();
}
Session.hpp文件
Session类主要是定义用户名字和访问什么文件,SessionManger则是管理session的类,添加session和检验并返回session。
#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;
};