HTTP 协议
虽然我们说, 应用层协议是我们程序猿自己定的. 但实际上, 已经有大佬们定义了一些现成的, 又非常好用的应用层协议, 供我们直接参考使用. HTTP(超文本传输协议)就是其中之一。
在互联网世界中,HTTP(HyperText Transfer Protocol,超文本传输协议)是一个至关重要的协议。它定义了客户端(如浏览器)与服务器之间如何通信,以交换或传输超文本(如 HTML 文档)。
HTTP 协议是客户端与服务器之间通信的基础。客户端通过 HTTP 协议向服务器发送请求,服务器收到请求后处理并返回响应。HTTP 协议是一个无连接、无状态的协议,即每次请求都需要建立新的连接,且服务器不会保存客户端的状态信息。
认识 URL
平时我们俗称的 "网址" 其实就是说的 URL
urlencode 和 urldecode
像 / ? : 等这样的字符, 已经被 url 当做特殊意义理解了. 因此这些字符不能随意出现.比如, 某个参数中需要带有这些特殊字符, 就必须先对特殊字符进行转义.
转义的规则如下:
将需要转码的字符转为 16 进制,然后从右到左,取 4 位(不足 4 位直接处理),每 2 位做一位,前面加上%,编码成%XY 格式
例如:
"+" 被转义成了 "%2B",urldecode 就是 urlencode 的逆过程;
HTTP 协议请求与响应格式
HTTP 请求
首行: [方法] + [url] + [版本]
Header: 请求的属性, 冒号分割的键值对;每组属性之间使用\r\n 分隔;遇到空行表示 Header 部分结束
Body: 空行后面的内容都是 Body. Body 允许为空字符串. 如果 Body 存在, 则在Header 中会有一个 Content-Length 属性来标识 Body 的长度;
基本的应答格式
所以http请求是一个大号的字符串
编写 HTTP 请求的代码 - 验证 http 请求
公共代码
Socket.hpp
#pragma once
#include <iostream>
#include <string>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstdlib>
#include <unistd.h>
#include "Log.hpp"
#include "Common.hpp"
#include "InetAddr.hpp"
namespace SocketModule
{
using namespace LogModule;
const static int gbacklog =16;
const static int defaultfd =-1;
// 基类socket
class Socket
{
public:
virtual ~Socket() {}
virtual void SocketOrDie() = 0;
virtual void BindOrDie(uint16_t port) = 0;
virtual void ListenOrDie(int backlog) = 0;
virtual std::shared_ptr<Socket> Accept(InetAddr * client)= 0;
virtual void Close()=0;
virtual int Recv(std::string * out) = 0;
virtual int Send(std::string &message) = 0;
virtual int Connect(const std::string &server_ip ,uint16_t port) =0;
public:
void BuildTcpClientSocketMethod()
{
SocketOrDie();
}
void BUildTcpLIstenSocketMethod(uint16_t port,int backlog = gbacklog)
{
SocketOrDie();
BindOrDie(port);
ListenOrDie(backlog);
}
// void BUildUdpSocketMethod()
// {
// SocketOrDie();
// BindOrDie();
// }
};
class TcpSocket : public Socket
{
public:
TcpSocket()
:_sockfd(defaultfd)
{}
TcpSocket(int fd): _sockfd(fd) {}
~TcpSocket() {}
void SocketOrDie() override
{
_sockfd = ::socket(AF_INET, SOCK_STREAM, 0);
if (_sockfd < 0)
{
LOG(Loglevel::FATAL) << "创建套接字失败!";
exit(SOCKET_ERR);
}
LOG(Loglevel::INIF) << "创建套接字成功!";
}
void BindOrDie(uint16_t port) override
{
InetAddr localaddr(port);
int n = ::bind(_sockfd, localaddr.NetAddrPtr(), localaddr.NetAddrLen());
if (n < 0)
{
LOG(Loglevel::FATAL) << "绑定失败!";
exit(BIND_ERR);
}
LOG(Loglevel::INIF) << "绑定成功!";
}
void ListenOrDie(int backlog) override
{
int n = ::listen(_sockfd, backlog);
if (n < 0)
{
LOG(Loglevel::FATAL) << "监听失败!";
exit(LISTEN_ERR);
}
LOG(Loglevel::INIF) << "监听成功!";
}
std::shared_ptr<Socket> Accept(InetAddr * client) override
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int fd =::accept(_sockfd,CONV(peer),&len);
if (fd < 0)
{
LOG(Loglevel::WARNING)<<"连接失败!";
exit(-1);
}
LOG(Loglevel::WARNING)<<"连接成功!";
client->SetAddr(peer);
return std::make_shared<TcpSocket>(fd);
}
void Close() override
{
if(_sockfd >=0)
{
::close(_sockfd);
}
}
int 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;
}
return n;
}
int Send(std::string &message) override
{
return send(_sockfd,message.c_str(),message.size(),0);
}
int Connect(const std::string &server_ip ,uint16_t port) override
{
InetAddr server(server_ip,port);
return ::connect(_sockfd,server.NetAddrPtr(),server.NetAddrLen()) ;
}
private:
int _sockfd; //
};
// class UdpSocket : public Socket
// {
// };
}
Mutex.hpp
#pragma once
#include <pthread.h>
#include <iostream>
namespace MutexModule
{
class Mutex
{
public:
Mutex()
{
pthread_mutex_init(&_mutex, nullptr);
}
void Lock()
{
int n = pthread_mutex_lock(&_mutex);
(void)n;
}
void Unlock()
{
int n = pthread_mutex_unlock(&_mutex);
(void)n;
}
~Mutex()
{
pthread_mutex_destroy(&_mutex);
}
pthread_mutex_t *get()
{
return &_mutex;
}
private:
pthread_mutex_t _mutex;
};
class LockGuard
{
public:
LockGuard(Mutex &mutex):_mutex(mutex)
{
_mutex.Lock();
}
~LockGuard()
{
_mutex.Unlock();
}
private:
Mutex &_mutex;
};
}
Log.hpp
#ifndef __LOG_HPP__
#define __LOG_HPP__
#include <iostream>
#include <string>
#include "Mutex.hpp"
#include <filesystem>
#include <fstream>
#include <memory>
#include <unistd.h>
#include <sstream>
#include<ctime>
namespace LogModule
{
const std::string sep = "\r\n";
using namespace MutexModule ;
// 2.刷新策略
class LogStrategy
{
public:
~LogStrategy() = default;
virtual void SyncLog(const std::string &message) = 0;
};
// 显示器刷新日志的策略
class ConsoleLogStrategy : public LogStrategy
{
public:
ConsoleLogStrategy() {}
~ConsoleLogStrategy() {}
void SyncLog(const std::string &message) override
{
LockGuard lockguard(_mutex);
std::cout << message << sep;
}
private:
Mutex _mutex;
};
// 缺省文件路径以及文件本身
const std::string defaultpath = "./log";
const std::string defaultfile = "my.log";
// 文件刷新日志的策略
class FileLogStrategy : public LogStrategy
{
public:
FileLogStrategy(const std::string &path = defaultpath, const std::string &file = defaultfile)
: _path(path), _file(file)
{
LockGuard lockguard(_mutex);
if (std::filesystem::exists(_path)) // 判断路径是否存在
{
return;
}
try
{
std::filesystem::create_directories(_path);
}
catch (const std::filesystem::filesystem_error &e)
{
std::cerr << e.what() << '\n';
}
}
void SyncLog(const std::string &message) override
{
LockGuard lockguard(_mutex);
std::string filename = _path + (_path.back() == '/' ? "" : "/") + _file;
std::ofstream out(filename, std::ios::app); // 追加写入
if (!out.is_open())
{
return;
}
out << message << sep;
out.close();
}
~FileLogStrategy() {}
private:
Mutex _mutex;
std::string _path; // 日志文件的路径
std::string _file; // 要打印的日志文件
};
// 形成日志等级
enum class Loglevel
{
DEBUG,
INIF,
WARNING,
ERROR,
FATAL
};
std::string Level2Str(Loglevel level)
{
switch (level)
{
case Loglevel::DEBUG:
return "DEBUG";
case Loglevel::INIF:
return "INIF";
case Loglevel::WARNING:
return "WARNING";
case Loglevel::ERROR:
return "ERROR";
case Loglevel::FATAL:
return "FATAL";
default:
return "UNKNOWN";
}
}
std::string GetTimeStamp()
{
time_t cuur =time(nullptr);
struct tm curr_tm;
localtime_r(&cuur,&curr_tm);
char buffer[128];
snprintf(buffer,sizeof(buffer),"%4d-%02d-%02d %02d:%02d:%02d",
curr_tm.tm_year+1900,
curr_tm.tm_mon+1,
curr_tm.tm_mday,
curr_tm.tm_hour,
curr_tm.tm_min,
curr_tm.tm_sec
);
return buffer;
}
class Logger
{
public:
Logger()
{
EnableConsoleLogStrategy();
}
// 选择某种策略
// 1.文件
void EnableFileLogStrategy()
{
_ffush_strategy = std::make_unique<FileLogStrategy>();
}
// 显示器
void EnableConsoleLogStrategy()
{
_ffush_strategy = std::make_unique<ConsoleLogStrategy>();
}
// 表示的是未来的一条日志
class LogMessage
{
public:
LogMessage(Loglevel &level, std::string &src_name, int line_number, Logger &logger)
: _curr_time(GetTimeStamp())
, _level(level)
, _pid(getpid())
, _src_name(src_name)
, _line_number(line_number)
, _logger(logger)
{
// 合并左半部分
std::stringstream ss;
ss << "[" << _curr_time << "] "
<< "[" << Level2Str(_level) << "] "
<< "[" << _pid << "] "
<< "[" << _src_name << "] "
<< "[" << _line_number << "] "
<< "- ";
_loginfo = ss.str();
}
template <typename T>
LogMessage &operator<<(const T &info)
{
// 右半部分,可变
std::stringstream ss;
ss << info;
_loginfo += ss.str();
return *this;
}
~LogMessage()
{
if (_logger._ffush_strategy)
{
_logger._ffush_strategy->SyncLog(_loginfo);
}
}
private:
std::string _curr_time; // 日志时间
Loglevel _level; // 日志状态
pid_t _pid; // 进程pid
std::string _src_name; // 文件名称
int _line_number; // 对应的行号
std::string _loginfo; // 合并之后的一条完整信息
Logger &_logger;
};
LogMessage operator()(Loglevel level, std::string src_name, int line_number)
{
return LogMessage(level, src_name, line_number, *this);
}
~Logger() {}
private:
std::unique_ptr<LogStrategy> _ffush_strategy;
};
//全局日志对象
Logger logger;
//使用宏,简化用户操作,获取文件名和行号
// __FILE__ 一个宏,替换完成后目标文件的文件名
// __LINE__ 一个宏,替换完成后目标文件对应的行号
#define LOG(level) logger(level,__FILE__,__LINE__)
#define Enable_Console_Log_Strategy() logger.EnableConsoleLogStrategy()
#define Enable_File_Log_Strategy() logger.EnableFileLogStrategy()
}
#endif
Main.cc
#include"Http.hpp"
// http port
int main(int argc,char* argv[])
{
if(argc != 2)
{
std::cout << "Usage: "<<argv[0]<<" port"<<std::endl;
exit(USAGE_ERR);
}
uint16_t port = std::stoi(argv[1]);
std::unique_ptr<Http> httpsvr = std::make_unique<Http>(port);
httpsvr->Start();
return 0;
}
Common.hpp
#pragma once
#include <iostream>
#include<functional>
#include <string>
#include <cstring>
#include <memory>
#include<unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
enum ExitCode
{
OK = 0,
USAGE_ERR,
SOCKET_ERR,
BIND_ERR,
LISTEN_ERR,
CONNECT_ERR,
FORK_ERR
};
class NoCopy
{
public:
NoCopy(){}
~NoCopy(){}
NoCopy(const NoCopy&)=delete;
const NoCopy& operator= (const NoCopy &)=delete;
};
#define CONV(addr) ((struct sockaddr *)&addr)
InetAddr.hpp
#pragma once
#include "Common.hpp"
class InetAddr
{
public:
InetAddr()
{
}
InetAddr(struct sockaddr_in &addr)
{
SetAddr(addr);
}
InetAddr(const std::string &ip,uint16_t port)
:_ip(ip)
,_port(port)
{
//主机转网络
memset(&_addr,0,sizeof(_addr));
_addr.sin_family=AF_INET;
inet_pton(AF_INET,_ip.c_str(),&_addr.sin_addr);
_addr.sin_port=htons(_port);
}
InetAddr(uint16_t port)
:_port(port),_ip("0")
{ //端口转
memset(&_addr,0,sizeof(_addr));
_addr.sin_family=AF_INET;
_addr.sin_addr.s_addr=INADDR_ANY;
_addr.sin_port = htons(_port);
}
uint16_t Port() { return _port; }
std::string Ip() { return _ip; }
void SetAddr(struct sockaddr_in &addr)
{ //网络转主机
_addr=addr;
_port = ntohs(addr.sin_port);
//_ip = inet_ntoa(addr.sin_addr);
char ipbuffer[64];
inet_ntop(AF_INET,&_addr.sin_addr,ipbuffer,sizeof(_addr));
_ip=ipbuffer;
}
const struct sockaddr_in &NetAddr() { return _addr; }
const struct sockaddr *NetAddrPtr() { return CONV(_addr); }
socklen_t NetAddrLen() { return sizeof(_addr); }
bool operator==(const InetAddr &addr)
{
return addr._ip == _ip && addr._port == _port;
}
std::string StringAddr()
{
return _ip + ":" + std::to_string(_port);
}
~InetAddr() {}
private:
struct sockaddr_in _addr;
std::string _ip;
uint16_t _port;
};
Tcpserver.hpp
#include "Socket.hpp"
#include<iostream>
#include<memory>
#include <sys/wait.h>
#include <functional>
using namespace LogModule;
using namespace SocketModule;
using ioservice_t = std::function<void(std::shared_ptr<Socket>&sock,InetAddr &client)>;
class TcpServer
{
public:
TcpServer(uint16_t port)
:_port(port)
,_listensockptr(std::make_unique<TcpSocket>())
,_isrunning(false)
{
_listensockptr->BUildTcpLIstenSocketMethod(_port);
}
void Start(ioservice_t callback)
{
_isrunning =true;
while(_isrunning)
{
InetAddr client;
auto sock = _listensockptr->Accept(&client);
if(sock == nullptr)
{
continue;
}
LOG(Loglevel::DEBUG)<<"accept is running"<<client.StringAddr();
//sock && client
pid_t id =fork();
if (id < 0)
{
LOG(Loglevel::FATAL)<<"创建子进程失败";
exit(FORK_ERR);
}
else if(id == 0)
{
//子进程 关闭listensock
_listensockptr->Close();
if(fork()>0)
{
exit(0);
}
//孙子进程是孤儿进程
callback(sock,client);
exit(OK);
}
else
{
//父进程 关闭sock
sock->Close();
pid_t rid = ::waitpid(id,nullptr,0);
(void)rid;
}
}
_isrunning =false;
}
~TcpServer(){}
private:
uint16_t _port;
std::unique_ptr<Socket>_listensockptr;
bool _isrunning;
};
Makefile
myhttp:Main.cc
g++ -o $@ $^ -std=c++17
.PHONY:clean
clean:
rm -f myhttp
Http.hpp
#pragma once
#include "TcpServer.hpp"
#include <memory>
#include <iostream>
#include <string>
#include <unordered_map>
using namespace SocketModule;
const std::string gspace = " ";
const std::string glinespace = "\r\n";
const std::string glinesep = ": ";
class HttpRequest
{
public:
std::string Serialize()
{
return std::string();
}
bool Deserialize(std::string &reqstr)
{
return true;
}
HttpRequest() {}
~HttpRequest() {}
private:
std::string _method;
std::string _url;
std::string _version;
std::unordered_map<std::string, std::string> _headers;
std::string _blankline;
std::string _text;
};
class HttpResponse
{
public:
std::string Serialize()
{
std::string status_line = _version +" "+gspace+std::to_string(_code)+gspace+_desc+glinespace;
std::string resp_header;
for(auto &header : _headrs)
{
std::string line = header.first+glinesep+header.second+glinespace;
resp_header+=line;
}
return status_line+resp_header+_blankline+_text;
}
bool Deserialize(std::string &reqstr)
{
return true;
}
HttpResponse()
:_blankline(glinespace)
{}
~HttpResponse(){}
std::string _version; //版本
int _code; //状态码
std::string _desc; //状态码描述
std::unordered_map<std::string,std::string> _headrs;//相应报头
std::string _blankline;//空行
std::string _text;//正文
};
class Http
{
public:
Http(uint16_t port)
: tsvrp(std::make_unique<TcpServer>(port))
{
}
void HandlenHttpRequest(std::shared_ptr<Socket> &sock, InetAddr &client)
{
#ifndef DEBUG
#define DEBUG
std::string httprequest;
sock->Recv(&httprequest);
std::cout<<httprequest<<std::endl;
//构建应答,内存级 + 固定
HttpResponse resp;
resp._version = "HTTP?1.1";
resp._code = 200;//正常
resp._desc ="OK";
resp._text ="<!DOCTYPE html>\
<html lang=\"en\">\
<head>\
<meta charset=\"UTF-8\">\
<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\
<title>Hello World</title>\
</head>\
<body>\
<h1>Hello World</h1>\
</body>\
</html>";
std::string response_str = resp.Serialize();
sock->Send(response_str);
#endif
}
void Start()
{
tsvrp->Start([this](std::shared_ptr<Socket> &sock, InetAddr &client)
{ this->HandlenHttpRequest(sock, client); });
}
~Http() {}
private:
std::unique_ptr<TcpServer> tsvrp;
};
这是浏览器返回的部分http
respones返回一个html
HTTP 的方法
其中最常用的就是 GET 方法和 POST 方法.
HTTP 常见方法
1.GET 方法(重点)
用途:用于请求 URL 指定的资源。
示例:GET /index.html HTTP/1.1
特性:指定资源经服务器端解析后返回响应内容。
form 表单:https://www.runoob.com/html/html-forms.html
2.POST 方法(重点)
用途:用于传输实体的主体,通常用于提交表单数据。
示例:POST /submit.cgi HTTP/1.1
特性:可以发送大量的数据给服务器,并且数据包含在请求体中。
form 表单:https://www.runoob.com/html/html-forms.html
3.PUT 方法(不常用)
用途:用于传输文件,将请求报文主体中的文件保存到请求 URL 指定的位置。
示例:PUT /example.html HTTP/1.1
特性:不太常用,但在某些情况下,如 RESTful API 中,用于更新资源。
4. HEAD 方法
用途:与 GET 方法类似,但不返回报文主体部分,仅返回响应头。
示例:HEAD /index.html HTTP/1.1
特性:用于确认 URL 的有效性及资源更新的日期时间等。
5. DELETE 方法(不常用)
用途:用于删除文件,是 PUT 的相反方法。
示例:DELETE /example.html HTTP/1.1
特性:按请求 URL 删除指定的资源。
6. OPTIONS 方法
用途:用于查询针对请求 URL 指定的资源支持的方法。
示例:OPTIONS * HTTP/1.1
特性:返回允许的方法,如 GET、POST 等。
HttpDamon 1
Util.hpp
#pragma once
#include <iostream>
#include <string>
#include <fstream>
class Util
{
public:
//打开指定文件
static bool ReadFileContent(const std::string &filename,std::string *out)
{
//version1
std::ifstream in(filename);
if(!in.is_open())
{
return false;
}
std::string line;
while(std::getline(in,line))
{
*out+=line;
}
in.close();
return true;
}
};
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello World</title>
</head>
<body>
<h1>Hello World !</h1>
<h1>Are you OK ?</h1>
</body>
</html>
Common.hpp
#pragma once
#include <iostream>
#include<functional>
#include <string>
#include <cstring>
#include <memory>
#include<unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
enum ExitCode
{
OK = 0,
USAGE_ERR,
SOCKET_ERR,
BIND_ERR,
LISTEN_ERR,
CONNECT_ERR,
FORK_ERR
};
class NoCopy
{
public:
NoCopy(){}
~NoCopy(){}
NoCopy(const NoCopy&)=delete;
const NoCopy& operator= (const NoCopy &)=delete;
};
#define CONV(addr) ((struct sockaddr *)&addr)
Http.hpp
#pragma once
#include "TcpServer.hpp"
#include <memory>
#include <iostream>
#include <string>
#include <unordered_map>
#include"Util.hpp"
using namespace SocketModule;
const std::string gspace = " ";
const std::string glinespace = "\r\n";
const std::string glinesep = ": ";
const std::string webroot = "./wwwroot";
const std::string homepage = "/index.html";
class HttpRequest
{
public:
std::string Serialize()
{
return std::string();
}
bool Deserialize(std::string &reqstr)
{
return true;
}
HttpRequest() {}
~HttpRequest() {}
private:
std::string _method;
std::string _url;
std::string _version;
std::unordered_map<std::string, std::string> _headers;
std::string _blankline;
std::string _text;
};
class HttpResponse
{
public:
std::string Serialize()
{
std::string status_line = _version +" "+gspace+std::to_string(_code)+gspace+_desc+glinespace;
std::string resp_header;
for(auto &header : _headrs)
{
std::string line = header.first+glinesep+header.second+glinespace;
resp_header+=line;
}
return status_line+resp_header+_blankline+_text;
}
bool Deserialize(std::string &reqstr)
{
return true;
}
HttpResponse()
:_blankline(glinespace)
{}
~HttpResponse(){}
std::string _version; //版本
int _code; //状态码
std::string _desc; //状态码描述
std::unordered_map<std::string,std::string> _headrs;//相应报头
std::string _blankline;//空行
std::string _text;//正文
};
class Http
{
public:
Http(uint16_t port)
: tsvrp(std::make_unique<TcpServer>(port))
{
}
void HandlenHttpRequest(std::shared_ptr<Socket> &sock, InetAddr &client)
{
#ifndef DEBUG
#define DEBUG
std::string httprequest;
sock->Recv(&httprequest);
std::cout<<httprequest<<std::endl;
//构建应答,内存级 + 固定
HttpResponse resp;
resp._version = "HTTP/1.1";
resp._code = 200;//正常
resp._desc ="OK";
std::string filename= webroot+homepage;// ./wwwroot/index.html
bool res = Util::ReadFileContent(filename,&(resp._text));
(void)res;
std::string response_str = resp.Serialize();
sock->Send(response_str);
#endif
}
void Start()
{
tsvrp->Start([this](std::shared_ptr<Socket> &sock, InetAddr &client)
{ this->HandlenHttpRequest(sock, client); });
}
~Http() {}
private:
std::unique_ptr<TcpServer> tsvrp;
};
InerAddr.hpp
#pragma once
#include "Common.hpp"
class InetAddr
{
public:
InetAddr()
{
}
InetAddr(struct sockaddr_in &addr)
{
SetAddr(addr);
}
InetAddr(const std::string &ip,uint16_t port)
:_ip(ip)
,_port(port)
{
//主机转网络
memset(&_addr,0,sizeof(_addr));
_addr.sin_family=AF_INET;
inet_pton(AF_INET,_ip.c_str(),&_addr.sin_addr);
_addr.sin_port=htons(_port);
}
InetAddr(uint16_t port)
:_port(port),_ip("0")
{ //端口转
memset(&_addr,0,sizeof(_addr));
_addr.sin_family=AF_INET;
_addr.sin_addr.s_addr=INADDR_ANY;
_addr.sin_port = htons(_port);
}
uint16_t Port() { return _port; }
std::string Ip() { return _ip; }
void SetAddr(struct sockaddr_in &addr)
{ //网络转主机
_addr=addr;
_port = ntohs(addr.sin_port);
//_ip = inet_ntoa(addr.sin_addr);
char ipbuffer[64];
inet_ntop(AF_INET,&_addr.sin_addr,ipbuffer,sizeof(_addr));
_ip=ipbuffer;
}
const struct sockaddr_in &NetAddr() { return _addr; }
const struct sockaddr *NetAddrPtr() { return CONV(_addr); }
socklen_t NetAddrLen() { return sizeof(_addr); }
bool operator==(const InetAddr &addr)
{
return addr._ip == _ip && addr._port == _port;
}
std::string StringAddr()
{
return _ip + ":" + std::to_string(_port);
}
~InetAddr() {}
private:
struct sockaddr_in _addr;
std::string _ip;
uint16_t _port;
};
Log.hpp
#ifndef __LOG_HPP__
#define __LOG_HPP__
#include <iostream>
#include <string>
#include "Mutex.hpp"
#include <filesystem>
#include <fstream>
#include <memory>
#include <unistd.h>
#include <sstream>
#include<ctime>
namespace LogModule
{
const std::string sep = "\r\n";
using namespace MutexModule ;
// 2.刷新策略
class LogStrategy
{
public:
~LogStrategy() = default;
virtual void SyncLog(const std::string &message) = 0;
};
// 显示器刷新日志的策略
class ConsoleLogStrategy : public LogStrategy
{
public:
ConsoleLogStrategy() {}
~ConsoleLogStrategy() {}
void SyncLog(const std::string &message) override
{
LockGuard lockguard(_mutex);
std::cout << message << sep;
}
private:
Mutex _mutex;
};
// 缺省文件路径以及文件本身
const std::string defaultpath = "./log";
const std::string defaultfile = "my.log";
// 文件刷新日志的策略
class FileLogStrategy : public LogStrategy
{
public:
FileLogStrategy(const std::string &path = defaultpath, const std::string &file = defaultfile)
: _path(path), _file(file)
{
LockGuard lockguard(_mutex);
if (std::filesystem::exists(_path)) // 判断路径是否存在
{
return;
}
try
{
std::filesystem::create_directories(_path);
}
catch (const std::filesystem::filesystem_error &e)
{
std::cerr << e.what() << '\n';
}
}
void SyncLog(const std::string &message) override
{
LockGuard lockguard(_mutex);
std::string filename = _path + (_path.back() == '/' ? "" : "/") + _file;
std::ofstream out(filename, std::ios::app); // 追加写入
if (!out.is_open())
{
return;
}
out << message << sep;
out.close();
}
~FileLogStrategy() {}
private:
Mutex _mutex;
std::string _path; // 日志文件的路径
std::string _file; // 要打印的日志文件
};
// 形成日志等级
enum class Loglevel
{
DEBUG,
INIF,
WARNING,
ERROR,
FATAL
};
std::string Level2Str(Loglevel level)
{
switch (level)
{
case Loglevel::DEBUG:
return "DEBUG";
case Loglevel::INIF:
return "INIF";
case Loglevel::WARNING:
return "WARNING";
case Loglevel::ERROR:
return "ERROR";
case Loglevel::FATAL:
return "FATAL";
default:
return "UNKNOWN";
}
}
std::string GetTimeStamp()
{
time_t cuur =time(nullptr);
struct tm curr_tm;
localtime_r(&cuur,&curr_tm);
char buffer[128];
snprintf(buffer,sizeof(buffer),"%4d-%02d-%02d %02d:%02d:%02d",
curr_tm.tm_year+1900,
curr_tm.tm_mon+1,
curr_tm.tm_mday,
curr_tm.tm_hour,
curr_tm.tm_min,
curr_tm.tm_sec
);
return buffer;
}
class Logger
{
public:
Logger()
{
EnableConsoleLogStrategy();
}
// 选择某种策略
// 1.文件
void EnableFileLogStrategy()
{
_ffush_strategy = std::make_unique<FileLogStrategy>();
}
// 显示器
void EnableConsoleLogStrategy()
{
_ffush_strategy = std::make_unique<ConsoleLogStrategy>();
}
// 表示的是未来的一条日志
class LogMessage
{
public:
LogMessage(Loglevel &level, std::string &src_name, int line_number, Logger &logger)
: _curr_time(GetTimeStamp())
, _level(level)
, _pid(getpid())
, _src_name(src_name)
, _line_number(line_number)
, _logger(logger)
{
// 合并左半部分
std::stringstream ss;
ss << "[" << _curr_time << "] "
<< "[" << Level2Str(_level) << "] "
<< "[" << _pid << "] "
<< "[" << _src_name << "] "
<< "[" << _line_number << "] "
<< "- ";
_loginfo = ss.str();
}
template <typename T>
LogMessage &operator<<(const T &info)
{
// 右半部分,可变
std::stringstream ss;
ss << info;
_loginfo += ss.str();
return *this;
}
~LogMessage()
{
if (_logger._ffush_strategy)
{
_logger._ffush_strategy->SyncLog(_loginfo);
}
}
private:
std::string _curr_time; // 日志时间
Loglevel _level; // 日志状态
pid_t _pid; // 进程pid
std::string _src_name; // 文件名称
int _line_number; // 对应的行号
std::string _loginfo; // 合并之后的一条完整信息
Logger &_logger;
};
LogMessage operator()(Loglevel level, std::string src_name, int line_number)
{
return LogMessage(level, src_name, line_number, *this);
}
~Logger() {}
private:
std::unique_ptr<LogStrategy> _ffush_strategy;
};
//全局日志对象
Logger logger;
//使用宏,简化用户操作,获取文件名和行号
// __FILE__ 一个宏,替换完成后目标文件的文件名
// __LINE__ 一个宏,替换完成后目标文件对应的行号
#define LOG(level) logger(level,__FILE__,__LINE__)
#define Enable_Console_Log_Strategy() logger.EnableConsoleLogStrategy()
#define Enable_File_Log_Strategy() logger.EnableFileLogStrategy()
}
#endif
Main.cc
#include"Http.hpp"
// http port
int main(int argc,char* argv[])
{
if(argc != 2)
{
std::cout << "Usage: "<<argv[0]<<" port"<<std::endl;
exit(USAGE_ERR);
}
uint16_t port = std::stoi(argv[1]);
std::unique_ptr<Http> httpsvr = std::make_unique<Http>(port);
httpsvr->Start();
return 0;
}
Makefile
myhttp:Main.cc
g++ -o $@ $^ -std=c++17
.PHONY:clean
clean:
rm -f myhttp
Mutex.hpp
#pragma once
#include <pthread.h>
#include <iostream>
namespace MutexModule
{
class Mutex
{
public:
Mutex()
{
pthread_mutex_init(&_mutex, nullptr);
}
void Lock()
{
int n = pthread_mutex_lock(&_mutex);
(void)n;
}
void Unlock()
{
int n = pthread_mutex_unlock(&_mutex);
(void)n;
}
~Mutex()
{
pthread_mutex_destroy(&_mutex);
}
pthread_mutex_t *get()
{
return &_mutex;
}
private:
pthread_mutex_t _mutex;
};
class LockGuard
{
public:
LockGuard(Mutex &mutex):_mutex(mutex)
{
_mutex.Lock();
}
~LockGuard()
{
_mutex.Unlock();
}
private:
Mutex &_mutex;
};
}
Socket.hpp
#pragma once
#include <iostream>
#include <string>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstdlib>
#include <unistd.h>
#include "Log.hpp"
#include "Common.hpp"
#include "InetAddr.hpp"
namespace SocketModule
{
using namespace LogModule;
const static int gbacklog =16;
const static int defaultfd =-1;
// 基类socket
class Socket
{
public:
virtual ~Socket() {}
virtual void SocketOrDie() = 0;
virtual void BindOrDie(uint16_t port) = 0;
virtual void ListenOrDie(int backlog) = 0;
virtual std::shared_ptr<Socket> Accept(InetAddr * client)= 0;
virtual void Close()=0;
virtual int Recv(std::string * out) = 0;
virtual int Send(std::string &message) = 0;
virtual int Connect(const std::string &server_ip ,uint16_t port) =0;
public:
void BuildTcpClientSocketMethod()
{
SocketOrDie();
}
void BUildTcpLIstenSocketMethod(uint16_t port,int backlog = gbacklog)
{
SocketOrDie();
BindOrDie(port);
ListenOrDie(backlog);
}
// void BUildUdpSocketMethod()
// {
// SocketOrDie();
// BindOrDie();
// }
};
class TcpSocket : public Socket
{
public:
TcpSocket()
:_sockfd(defaultfd)
{}
TcpSocket(int fd): _sockfd(fd) {}
~TcpSocket() {}
void SocketOrDie() override
{
_sockfd = ::socket(AF_INET, SOCK_STREAM, 0);
if (_sockfd < 0)
{
LOG(Loglevel::FATAL) << "创建套接字失败!";
exit(SOCKET_ERR);
}
LOG(Loglevel::INIF) << "创建套接字成功!";
}
void BindOrDie(uint16_t port) override
{
InetAddr localaddr(port);
int n = ::bind(_sockfd, localaddr.NetAddrPtr(), localaddr.NetAddrLen());
if (n < 0)
{
LOG(Loglevel::FATAL) << "绑定失败!";
exit(BIND_ERR);
}
LOG(Loglevel::INIF) << "绑定成功!";
}
void ListenOrDie(int backlog) override
{
int n = ::listen(_sockfd, backlog);
if (n < 0)
{
LOG(Loglevel::FATAL) << "监听失败!";
exit(LISTEN_ERR);
}
LOG(Loglevel::INIF) << "监听成功!";
}
std::shared_ptr<Socket> Accept(InetAddr * client) override
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int fd =::accept(_sockfd,CONV(peer),&len);
if (fd < 0)
{
LOG(Loglevel::WARNING)<<"连接失败!";
exit(-1);
}
LOG(Loglevel::WARNING)<<"连接成功!";
client->SetAddr(peer);
return std::make_shared<TcpSocket>(fd);
}
void Close() override
{
if(_sockfd >=0)
{
::close(_sockfd);
}
}
int 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;
}
return n;
}
int Send(std::string &message) override
{
return send(_sockfd,message.c_str(),message.size(),0);
}
int Connect(const std::string &server_ip ,uint16_t port) override
{
InetAddr server(server_ip,port);
return ::connect(_sockfd,server.NetAddrPtr(),server.NetAddrLen()) ;
}
private:
int _sockfd; //
};
// class UdpSocket : public Socket
// {
// };
}
TcpServer.hpp
#include "Socket.hpp"
#include<iostream>
#include<memory>
#include <sys/wait.h>
#include <functional>
using namespace LogModule;
using namespace SocketModule;
using ioservice_t = std::function<void(std::shared_ptr<Socket>&sock,InetAddr &client)>;
class TcpServer
{
public:
TcpServer(uint16_t port)
:_port(port)
,_listensockptr(std::make_unique<TcpSocket>())
,_isrunning(false)
{
_listensockptr->BUildTcpLIstenSocketMethod(_port);
}
void Start(ioservice_t callback)
{
_isrunning =true;
while(_isrunning)
{
InetAddr client;
auto sock = _listensockptr->Accept(&client);
if(sock == nullptr)
{
continue;
}
LOG(Loglevel::DEBUG)<<"accept is running"<<client.StringAddr();
//sock && client
pid_t id =fork();
if (id < 0)
{
LOG(Loglevel::FATAL)<<"创建子进程失败";
exit(FORK_ERR);
}
else if(id == 0)
{
//子进程 关闭listensock
_listensockptr->Close();
if(fork()>0)
{
exit(0);
}
//孙子进程是孤儿进程
callback(sock,client);
exit(OK);
}
else
{
//父进程 关闭sock
sock->Close();
pid_t rid = ::waitpid(id,nullptr,0);
(void)rid;
}
}
_isrunning =false;
}
~TcpServer(){}
private:
uint16_t _port;
std::unique_ptr<Socket>_listensockptr;
bool _isrunning;
};
HTTP 的状态码
最常见的状态码, 比如 200(OK), 404(Not Found), 403(Forbidden), 302(Redirect, 重定向), 504(Bad Gateway)
状态码 | 含义 | 应用样例 |
100 | Continue | 上传大文件时,服务器告诉客户端可以 继续上传 |
200 | OK | 访问网站首页,服务器返回网页内容 |
201 | Created | 发布新文章,服务器返回文章创建成功的信息 |
204 | No Content | 删除文章后,服务器返回“无内容”表示操作成功 |
301 | Moved Permanently | 网站换域名后,自动跳转到新域名;搜索引擎更新网站链接时使用 |
302 | Found 或 See Other | 用户登录成功后,重定向到用户首页 |
304 | Not Modified | 浏览器缓存机制,对未修改的资源返回304 状态码 |
400 | Bad Request | 填写表单时,格式不正确导致提交失败 |
401 | Unauthorized | 访问需要登录的页面时,未登录或认证失败 |
403 | Forbidden | 尝试访问你没有权限查看的页面 |
404 | Not Found | 访问不存在的网页链接 |
500 | Internal Server Error | 服务器崩溃或数据库错误导致页面无法加载 |
502 | Bad Gateway | 使用代理服务器时,代理服务器无法从上游服务器获取有效响应 |
503 | Service Unavailable | 服务器维护或过载,暂时无法处理请 |
以下是仅包含重定向相关状态码的表格:
状态码 | 含义 | 是否为临时重定向 | 应用样例 |
301 | Moved Permanently | 否(永久重定向) | 网站换域名后,自动跳转到新域名;搜索引擎更新网站链接时使用 |
302 | Found 或 See Other | 是(临时重定向) | 用户登录成功后,重定向到用户首页 |
307 | Temporary Redirect | 是(临时重定向) | 临时重定向资源到新的位置(较少使用) |
308 | Permanent Redirect | 否(永久重定向) | 永久重定向资源到新的位置(较少使用) |
关于重定向的验证,以 301 为代表 ,HTTP 状态码 301(永久重定向)和 302(临时重定向)都依赖 Location 选项。以下是关于两者依赖 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)的功能
HTTPDamon 2
404.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>404 - Page Not Found</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f4f4f4;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
flex-direction: column;
text-align: center;
}
.error-container {
max-width: 600px;
text-align: center;
}
.error-container h1 {
font-size: 3em;
color: #d9534f;
margin-bottom: 10px;
}
.error-container p {
font-size: 1.2em;
color: #333;
margin-bottom: 20px;
}
.error-container a {
display: inline-block;
padding: 10px 20px;
background-color: #007bff;
color: #fff;
text-decoration: none;
border-radius: 4px;
font-size: 1em;
}
.error-container a:hover {
background-color: #0056b3;
}
</style>
</head>
<body>
<div class="error-container">
<h1>404 - Page Not Found</h1>
<p>Sorry, the page you are looking for does not exist.</p>
<a href="/">Go to Home Page</a>
</div>
</body>
</html>
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Home Page</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f4f4f4;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
flex-direction: column;
}
.home-container {
text-align: center;
}
.home-container h1 {
font-size: 2.5em;
margin-bottom: 20px;
}
.button-group {
display: flex;
justify-content: center;
gap: 20px;
}
.button-group a {
display: inline-block;
padding: 10px 20px;
background-color: #007bff;
color: #fff;
text-decoration: none;
border-radius: 4px;
font-size: 1em;
}
.button-group a:hover {
background-color: #0056b3;
}
</style>
</head>
<body>
<div class="home-container">
<h1>Welcome to Our Website</h1>
<div class="button-group">
<a href="Login.html">Login</a>
<a href="Register.html">Register</a>
<img src="/image/1.png" alt="图片1">
<img src="/image/2.jpg" alt="图片2">
</div>
</div>
</body>
</html>
Login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login Page</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f4f4f4;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
}
.login-container {
background-color: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
width: 300px;
text-align: center;
}
.login-container h2 {
margin-bottom: 20px;
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 5px;
text-align: left;
}
.form-group input {
width: 100%;
padding: 10px;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
}
.form-group input:focus {
outline: none;
border-color: #007bff;
}
.form-group button {
width: 100%;
padding: 10px;
border: none;
background-color: #007bff;
color: #fff;
border-radius: 4px;
cursor: pointer;
}
.form-group button:hover {
background-color: #0056b3;
}
</style>
</head>
<body>
<div class="login-container">
<h2>Login</h2>
<form action="#" method="post">
<div class="form-group">
<label for="username">Username</label>
<input type="text" id="username" name="username" required>
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" id="password" name="password" required>
</div>
<div class="form-group">
<button type="submit">Login</button>
</div>
</form>
</div>
</body>
</html>
Register.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Registration Page</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f4f4f4;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
}
.register-container {
background-color: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
width: 350px;
text-align: center;
}
.register-container h2 {
margin-bottom: 20px;
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 5px;
text-align: left;
}
.form-group input {
width: 100%;
padding: 10px;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
}
.form-group input:focus {
outline: none;
border-color: #007bff;
}
.form-group button {
width: 100%;
padding: 10px;
border: none;
background-color: #007bff;
color: #fff;
border-radius: 4px;
cursor: pointer;
}
.form-group button:hover {
background-color: #0056b3;
}
.form-group .error {
color: red;
font-size: 0.8em;
text-align: left;
margin-top: 5px;
}
</style>
</head>
<body>
<div class="register-container">
<h2>Register</h2>
<form action="#" method="post" id="registrationForm">
<div class="form-group">
<label for="username">Username</label>
<input type="text" id="username" name="username" required>
</div>
<div class="form-group">
<label for="email">Email</label>
<input type="email" id="email" name="email" required>
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" id="password" name="password" required>
</div>
<div class="form-group">
<label for="confirm-password">Confirm Password</label>
<input type="password" id="confirm-password" name="confirm-password" required>
<div class="error" id="passwordError"></div>
</div>
<div class="form-group">
<button type="submit">Register</button>
</div>
</form>
</div>
<script>
document.getElementById('registrationForm').addEventListener('submit', function(event) {
const password = document.getElementById('password').value;
const confirmPassword = document.getElementById('confirm-password').value;
const passwordError = document.getElementById('passwordError');
if (password !== confirmPassword) {
passwordError.textContent = 'Passwords do not match';
event.preventDefault(); // Prevent form submission
} else {
passwordError.textContent = '';
}
});
</script>
</body>
</html>
Common.hpp
#pragma once
#include <iostream>
#include<functional>
#include <string>
#include <cstring>
#include <memory>
#include<unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
enum ExitCode
{
OK = 0,
USAGE_ERR,
SOCKET_ERR,
BIND_ERR,
LISTEN_ERR,
CONNECT_ERR,
FORK_ERR
};
class NoCopy
{
public:
NoCopy(){}
~NoCopy(){}
NoCopy(const NoCopy&)=delete;
const NoCopy& operator= (const NoCopy &)=delete;
};
#define CONV(addr) ((struct sockaddr *)&addr)
Http.hpp
#pragma once
#include "TcpServer.hpp"
#include <memory>
#include <iostream>
#include <string>
#include <unordered_map>
#include <sstream>
#include "Util.hpp"
using namespace SocketModule;
const std::string gspace = " ";
const std::string glinespace = "\r\n";
const std::string glinesep = ": ";
const std::string webroot = "./wwwroot";
const std::string homepage = "index.html";
const std::string page_404 = "/404.html";
class HttpRequest
{
public:
std::string Serialize()
{
return std::string();
}
bool Deserialize(std::string &reqstr)
{
//1.提取请求行
std::string reqline;
bool res = Util::ReadOneLine(reqstr,&reqline,glinespace);
LOG(Loglevel::DEBUG)<<"reqline: "<<reqline;
//2.对请求行进行反序列化 缺省对报文完整性判断
ParseReqLine(reqline);
if(_uri=="/")
_uri = webroot +_uri +homepage; //./wwwroot/index.html
else
_uri= webroot+_uri;//./wwwroot/a/b/c
return true;
}
void ParseReqLine(std::string &reqline)
{
std::stringstream ss(reqline);
ss >> _method >> _uri >> _version;
}
HttpRequest() {}
~HttpRequest() {}
std::string Uri()
{
return _uri;
}
private:
std::string _method;
std::string _uri;
std::string _version;
std::unordered_map<std::string, std::string> _headers;
std::string _blankline;
std::string _text;
};
class HttpResponse
{
public:
HttpResponse()
:_version("HTTP/1.1")
,_blankline(glinespace)
{}
public:
std::string Serialize()
{
std::string status_line = _version +" "+gspace+std::to_string(_code)+gspace+_desc+glinespace;
std::string resp_header;
for(auto &header : _headrs)
{
std::string line = header.first+glinesep+header.second+glinespace;
resp_header+=line;
}
return status_line+resp_header+_blankline+_text;
}
void SetHeader(const std::string &key,const std::string &value)
{
auto iter = _headrs.find(key);
if(iter != _headrs.end())
return ;
_headrs.insert(std::make_pair(key,value));
}
bool MakeResponse()
{
if(_targetfile== "./wwwroot/favicon.ico")
{
LOG(Loglevel::DEBUG)<<"用户请求:"<<_targetfile<<"忽略他";
return false;
}
int filesize=0;
bool res = Util::ReadFileContent(_targetfile,&_text);
if(!res)
{
_text = "";
LOG(Loglevel::WARNING) << "client want get : " << _targetfile << " but not found";
SetCode(404);
_targetfile = webroot+page_404;
filesize = Util::FileSize(_targetfile);
Util::ReadFileContent(_targetfile,&_text);
std::string suffix = Uri2Suffix(_targetfile);
SetHeader("Conent-Type",suffix);
SetHeader("Content-Length",std::to_string(filesize));
}
else
{
LOG(Loglevel::DEBUG) << "读取文件: " << _targetfile;
SetCode(200);
filesize = Util::FileSize(_targetfile);
std::string suffix = Uri2Suffix(_targetfile);
SetHeader("Conent-Type",suffix);
SetHeader("Content-Length",std::to_string(filesize));
}
return true;
}
std::string Uri2Suffix(const std::string &targetfile)
{
// ./wwwroot/a/b/c.html
auto pos = targetfile.rfind(".");
if(pos == std::string::npos)
{
return "text/html";
}
std::string suffix = targetfile.substr(pos);
if(suffix == ".html"||suffix == ".htm")
{
return "text/html";
}
else if(suffix ==".png")
{
return "image/png";
}
else if (suffix == ".jpg")
{
return "image/jpeg";
}
else
return "";
}
void SetCode(int code)
{
_code=code;
switch (_code)
{
case 200:
_desc="OK";
break;
case 404:
_desc="Not Found";
break;
default:
break;
}
}
void SetTargeFile(const std::string &target)
{
_targetfile = target;
}
bool Deserialize(std::string &reqstr)
{
std::string reqline;
bool res = Util::ReadOneLine(reqstr,&reqline,glinespace);
return true;
}
~HttpResponse(){}
std::string _version; //版本
int _code; //状态码
std::string _desc; //状态码描述
std::unordered_map<std::string,std::string> _headrs;//相应报头
std::string _blankline;//空行
std::string _text;//正文
std::string _targetfile;
};
class Http
{
public:
Http(uint16_t port)
: tsvrp(std::make_unique<TcpServer>(port))
{
}
void HandlenHttpRequest(std::shared_ptr<Socket> &sock, InetAddr &client)
{
std::string httpreqstr;
//浏览器发给我的是一个大号的http字符串,其实我们的recv也是有问题的。tcp是面向字节流的
int n = sock->Recv(&httpreqstr);
if( n > 0)
{
HttpRequest req;
req.Deserialize(httpreqstr);
HttpResponse resp;
resp. SetTargeFile(req.Uri());
if(resp.MakeResponse())
{
std::string response_str = resp.Serialize();
sock->Send(response_str);
}
// std::string filename = req.Uri();
// HttpResponse resp;
// resp._version = "HTTP/1.1";
// resp._code = 200;//正常
// resp._desc ="OK";
// LOG(Loglevel::DEBUG)<<"用户请求: "<<filename;
// bool res = Util::ReadFileContent(filename,&(resp._text));
// (void)res;
}
#ifdef DEBUG
std::string httprequest;
sock->Recv(&httprequest);
std::cout<<httprequest<<std::endl;
//构建应答,内存级 + 固定
HttpResponse resp;
resp._version = "HTTP/1.1";
resp._code = 200;//正常
resp._desc ="OK";
std::string filename= webroot+homepage;// ./wwwroot/index.html
bool res = Util::ReadFileContent(filename,&(resp._text));
(void)res;
std::string response_str = resp.Serialize();
sock->Send(response_str);
#endif
}
void Start()
{
tsvrp->Start([this](std::shared_ptr<Socket> &sock, InetAddr &client)
{ this->HandlenHttpRequest(sock, client); });
}
~Http() {}
private:
std::unique_ptr<TcpServer> tsvrp;
};
InetAddr.hpp
#pragma once
#include "Common.hpp"
class InetAddr
{
public:
InetAddr()
{
}
InetAddr(struct sockaddr_in &addr)
{
SetAddr(addr);
}
InetAddr(const std::string &ip,uint16_t port)
:_ip(ip)
,_port(port)
{
//主机转网络
memset(&_addr,0,sizeof(_addr));
_addr.sin_family=AF_INET;
inet_pton(AF_INET,_ip.c_str(),&_addr.sin_addr);
_addr.sin_port=htons(_port);
}
InetAddr(uint16_t port)
:_port(port),_ip("0")
{ //端口转
memset(&_addr,0,sizeof(_addr));
_addr.sin_family=AF_INET;
_addr.sin_addr.s_addr=INADDR_ANY;
_addr.sin_port = htons(_port);
}
uint16_t Port() { return _port; }
std::string Ip() { return _ip; }
void SetAddr(struct sockaddr_in &addr)
{ //网络转主机
_addr=addr;
_port = ntohs(addr.sin_port);
//_ip = inet_ntoa(addr.sin_addr);
char ipbuffer[64];
inet_ntop(AF_INET,&_addr.sin_addr,ipbuffer,sizeof(_addr));
_ip=ipbuffer;
}
const struct sockaddr_in &NetAddr() { return _addr; }
const struct sockaddr *NetAddrPtr() { return CONV(_addr); }
socklen_t NetAddrLen() { return sizeof(_addr); }
bool operator==(const InetAddr &addr)
{
return addr._ip == _ip && addr._port == _port;
}
std::string StringAddr()
{
return _ip + ":" + std::to_string(_port);
}
~InetAddr() {}
private:
struct sockaddr_in _addr;
std::string _ip;
uint16_t _port;
};
Log.hpp
#ifndef __LOG_HPP__
#define __LOG_HPP__
#include <iostream>
#include <string>
#include "Mutex.hpp"
#include <filesystem>
#include <fstream>
#include <memory>
#include <unistd.h>
#include <sstream>
#include<ctime>
namespace LogModule
{
const std::string sep = "\r\n";
using namespace MutexModule ;
// 2.刷新策略
class LogStrategy
{
public:
~LogStrategy() = default;
virtual void SyncLog(const std::string &message) = 0;
};
// 显示器刷新日志的策略
class ConsoleLogStrategy : public LogStrategy
{
public:
ConsoleLogStrategy() {}
~ConsoleLogStrategy() {}
void SyncLog(const std::string &message) override
{
LockGuard lockguard(_mutex);
std::cout << message << sep;
}
private:
Mutex _mutex;
};
// 缺省文件路径以及文件本身
const std::string defaultpath = "./log";
const std::string defaultfile = "my.log";
// 文件刷新日志的策略
class FileLogStrategy : public LogStrategy
{
public:
FileLogStrategy(const std::string &path = defaultpath, const std::string &file = defaultfile)
: _path(path), _file(file)
{
LockGuard lockguard(_mutex);
if (std::filesystem::exists(_path)) // 判断路径是否存在
{
return;
}
try
{
std::filesystem::create_directories(_path);
}
catch (const std::filesystem::filesystem_error &e)
{
std::cerr << e.what() << '\n';
}
}
void SyncLog(const std::string &message) override
{
LockGuard lockguard(_mutex);
std::string filename = _path + (_path.back() == '/' ? "" : "/") + _file;
std::ofstream out(filename, std::ios::app); // 追加写入
if (!out.is_open())
{
return;
}
out << message << sep;
out.close();
}
~FileLogStrategy() {}
private:
Mutex _mutex;
std::string _path; // 日志文件的路径
std::string _file; // 要打印的日志文件
};
// 形成日志等级
enum class Loglevel
{
DEBUG,
INIF,
WARNING,
ERROR,
FATAL
};
std::string Level2Str(Loglevel level)
{
switch (level)
{
case Loglevel::DEBUG:
return "DEBUG";
case Loglevel::INIF:
return "INIF";
case Loglevel::WARNING:
return "WARNING";
case Loglevel::ERROR:
return "ERROR";
case Loglevel::FATAL:
return "FATAL";
default:
return "UNKNOWN";
}
}
std::string GetTimeStamp()
{
time_t cuur =time(nullptr);
struct tm curr_tm;
localtime_r(&cuur,&curr_tm);
char buffer[128];
snprintf(buffer,sizeof(buffer),"%4d-%02d-%02d %02d:%02d:%02d",
curr_tm.tm_year+1900,
curr_tm.tm_mon+1,
curr_tm.tm_mday,
curr_tm.tm_hour,
curr_tm.tm_min,
curr_tm.tm_sec
);
return buffer;
}
class Logger
{
public:
Logger()
{
EnableConsoleLogStrategy();
}
// 选择某种策略
// 1.文件
void EnableFileLogStrategy()
{
_ffush_strategy = std::make_unique<FileLogStrategy>();
}
// 显示器
void EnableConsoleLogStrategy()
{
_ffush_strategy = std::make_unique<ConsoleLogStrategy>();
}
// 表示的是未来的一条日志
class LogMessage
{
public:
LogMessage(Loglevel &level, std::string &src_name, int line_number, Logger &logger)
: _curr_time(GetTimeStamp())
, _level(level)
, _pid(getpid())
, _src_name(src_name)
, _line_number(line_number)
, _logger(logger)
{
// 合并左半部分
std::stringstream ss;
ss << "[" << _curr_time << "] "
<< "[" << Level2Str(_level) << "] "
<< "[" << _pid << "] "
<< "[" << _src_name << "] "
<< "[" << _line_number << "] "
<< "- ";
_loginfo = ss.str();
}
template <typename T>
LogMessage &operator<<(const T &info)
{
// 右半部分,可变
std::stringstream ss;
ss << info;
_loginfo += ss.str();
return *this;
}
~LogMessage()
{
if (_logger._ffush_strategy)
{
_logger._ffush_strategy->SyncLog(_loginfo);
}
}
private:
std::string _curr_time; // 日志时间
Loglevel _level; // 日志状态
pid_t _pid; // 进程pid
std::string _src_name; // 文件名称
int _line_number; // 对应的行号
std::string _loginfo; // 合并之后的一条完整信息
Logger &_logger;
};
LogMessage operator()(Loglevel level, std::string src_name, int line_number)
{
return LogMessage(level, src_name, line_number, *this);
}
~Logger() {}
private:
std::unique_ptr<LogStrategy> _ffush_strategy;
};
//全局日志对象
Logger logger;
//使用宏,简化用户操作,获取文件名和行号
// __FILE__ 一个宏,替换完成后目标文件的文件名
// __LINE__ 一个宏,替换完成后目标文件对应的行号
#define LOG(level) logger(level,__FILE__,__LINE__)
#define Enable_Console_Log_Strategy() logger.EnableConsoleLogStrategy()
#define Enable_File_Log_Strategy() logger.EnableFileLogStrategy()
}
#endif
Main.cc
#include"Http.hpp"
// http port
int main(int argc,char* argv[])
{
if(argc != 2)
{
std::cout << "Usage: "<<argv[0]<<" port"<<std::endl;
exit(USAGE_ERR);
}
uint16_t port = std::stoi(argv[1]);
std::unique_ptr<Http> httpsvr = std::make_unique<Http>(port);
httpsvr->Start();
return 0;
}
Mutex.hpp
#pragma once
#include <pthread.h>
#include <iostream>
namespace MutexModule
{
class Mutex
{
public:
Mutex()
{
pthread_mutex_init(&_mutex, nullptr);
}
void Lock()
{
int n = pthread_mutex_lock(&_mutex);
(void)n;
}
void Unlock()
{
int n = pthread_mutex_unlock(&_mutex);
(void)n;
}
~Mutex()
{
pthread_mutex_destroy(&_mutex);
}
pthread_mutex_t *get()
{
return &_mutex;
}
private:
pthread_mutex_t _mutex;
};
class LockGuard
{
public:
LockGuard(Mutex &mutex):_mutex(mutex)
{
_mutex.Lock();
}
~LockGuard()
{
_mutex.Unlock();
}
private:
Mutex &_mutex;
};
}
Socket.hpp
#pragma once
#include <iostream>
#include <string>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstdlib>
#include <unistd.h>
#include "Log.hpp"
#include "Common.hpp"
#include "InetAddr.hpp"
namespace SocketModule
{
using namespace LogModule;
const static int gbacklog =16;
const static int defaultfd =-1;
// 基类socket
class Socket
{
public:
virtual ~Socket() {}
virtual void SocketOrDie() = 0;
virtual void BindOrDie(uint16_t port) = 0;
virtual void ListenOrDie(int backlog) = 0;
virtual std::shared_ptr<Socket> Accept(InetAddr * client)= 0;
virtual void Close()=0;
virtual int Recv(std::string * out) = 0;
virtual int Send(std::string &message) = 0;
virtual int Connect(const std::string &server_ip ,uint16_t port) =0;
public:
void BuildTcpClientSocketMethod()
{
SocketOrDie();
}
void BUildTcpLIstenSocketMethod(uint16_t port,int backlog = gbacklog)
{
SocketOrDie();
BindOrDie(port);
ListenOrDie(backlog);
}
// void BUildUdpSocketMethod()
// {
// SocketOrDie();
// BindOrDie();
// }
};
class TcpSocket : public Socket
{
public:
TcpSocket()
:_sockfd(defaultfd)
{}
TcpSocket(int fd): _sockfd(fd) {}
~TcpSocket() {}
void SocketOrDie() override
{
_sockfd = ::socket(AF_INET, SOCK_STREAM, 0);
if (_sockfd < 0)
{
LOG(Loglevel::FATAL) << "创建套接字失败!";
exit(SOCKET_ERR);
}
LOG(Loglevel::INIF) << "创建套接字成功!";
}
void BindOrDie(uint16_t port) override
{
InetAddr localaddr(port);
int n = ::bind(_sockfd, localaddr.NetAddrPtr(), localaddr.NetAddrLen());
if (n < 0)
{
LOG(Loglevel::FATAL) << "绑定失败!";
exit(BIND_ERR);
}
LOG(Loglevel::INIF) << "绑定成功!";
}
void ListenOrDie(int backlog) override
{
int n = ::listen(_sockfd, backlog);
if (n < 0)
{
LOG(Loglevel::FATAL) << "监听失败!";
exit(LISTEN_ERR);
}
LOG(Loglevel::INIF) << "监听成功!";
}
std::shared_ptr<Socket> Accept(InetAddr * client) override
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int fd =::accept(_sockfd,CONV(peer),&len);
if (fd < 0)
{
LOG(Loglevel::WARNING)<<"连接失败!";
exit(-1);
}
LOG(Loglevel::WARNING)<<"连接成功!";
client->SetAddr(peer);
return std::make_shared<TcpSocket>(fd);
}
void Close() override
{
if(_sockfd >=0)
{
::close(_sockfd);
}
}
int Recv(std::string * out) override
{
//流式读取,不关心读到的是什么
char buffer[4096*2];
ssize_t n =::recv(_sockfd,buffer,sizeof(buffer)-1,0);
if (n >0)
{
buffer[n]=0;
*out+=buffer;
return n;
}
return n;
}
int Send(std::string &message) override
{
return send(_sockfd,message.c_str(),message.size(),0);
}
int Connect(const std::string &server_ip ,uint16_t port) override
{
InetAddr server(server_ip,port);
return ::connect(_sockfd,server.NetAddrPtr(),server.NetAddrLen()) ;
}
private:
int _sockfd; //
};
// class UdpSocket : public Socket
// {
// };
}
TcpServer.hpp
#include "Socket.hpp"
#include<iostream>
#include<memory>
#include <sys/wait.h>
#include <functional>
using namespace LogModule;
using namespace SocketModule;
using ioservice_t = std::function<void(std::shared_ptr<Socket>&sock,InetAddr &client)>;
class TcpServer
{
public:
TcpServer(uint16_t port)
:_port(port)
,_listensockptr(std::make_unique<TcpSocket>())
,_isrunning(false)
{
_listensockptr->BUildTcpLIstenSocketMethod(_port);
}
void Start(ioservice_t callback)
{
_isrunning =true;
while(_isrunning)
{
InetAddr client;
auto sock = _listensockptr->Accept(&client);
if(sock == nullptr)
{
continue;
}
LOG(Loglevel::DEBUG)<<"accept is running"<<client.StringAddr();
//sock && client
pid_t id =fork();
if (id < 0)
{
LOG(Loglevel::FATAL)<<"创建子进程失败";
exit(FORK_ERR);
}
else if(id == 0)
{
//子进程 关闭listensock
_listensockptr->Close();
if(fork()>0)
{
exit(0);
}
//孙子进程是孤儿进程
callback(sock,client);
exit(OK);
}
else
{
//父进程 关闭sock
sock->Close();
pid_t rid = ::waitpid(id,nullptr,0);
(void)rid;
}
}
_isrunning =false;
}
~TcpServer(){}
private:
uint16_t _port;
std::unique_ptr<Socket>_listensockptr;
bool _isrunning;
};
Util.hpp
#pragma once
#include <iostream>
#include <string>
#include <fstream>
class Util
{
public:
//打开指定文件
static bool ReadFileContent(const std::string &filename,std::string *out)
{
//version1 默认文本方式读取
// std::ifstream in(filename);
// if(!in.is_open())
// {
// return false;
// }
// std::string line;
// while(std::getline(in,line))
// {
// *out+=line;
// }
// in.close();
//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 bool ReadOneLine(std::string &bigstr,std::string *out,const std::string &sep)
{
auto pos = bigstr.find(sep);
if(pos == std::string::npos)
{
return false;
}
*out = bigstr.substr(0,pos);
bigstr.erase(0,pos+sep.size());
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;
}
};
Makefile
myhttp:Main.cc
g++ -o $@ $^ -std=c++17
.PHONY:clean
clean:
rm -f myhttp
游览器首页效果
查看首页
查看图片
查看不存在资源
其他前端效果不展示,主要学习http相关知识