1 Star 0 Fork 4

ximen_ww/HttpServer

forked from 林慢慢/HttpServer 
加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
Protocol.hpp 24.16 KB
一键复制 编辑 原始数据 按行查看 历史
林慢慢 提交于 2023-03-11 10:45 . Done
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677
#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <unordered_map>
#include <sstream>
#include <algorithm>
#include <cstdlib>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/sendfile.h>
#include <sys/wait.h>
#include <fcntl.h>
#include "Util.hpp"
#include "Log.hpp"
#define SEP ": "
#define WEB_ROOT "wwwroot"
#define HOME_PAGE "index.html"
#define HTTP_VERSION "HTTP/1.0"
#define LINE_END "\r\n"
#define PAGE_400 "400.html"
#define PAGE_404 "404.html"
#define PAGE_500 "500.html"
#define OK 200
#define BAD_REQUEST 400
#define NOT_FOUND 404
#define INTERNAL_SERVER_ERROR 500
//根据状态码获取状态码描述
static std::string CodeToDesc(int code)
{
std::string desc;
switch(code){
case 200:
desc = "OK";
break;
case 400:
desc = "Bad Request";
break;
case 404:
desc = "Not Found";
break;
case 500:
desc = "Internal Server Error";
break;
default:
break;
}
return desc;
}
//根据后缀获取资源类型
static std::string SuffixToDesc(const std::string& suffix)
{
static std::unordered_map<std::string, std::string> suffix_to_desc = {
{".html", "text/html"},
{".css", "text/css"},
{".js", "application/x-javascript"},
{".jpg", "application/x-jpg"},
{".xml", "text/xml"}
};
auto iter = suffix_to_desc.find(suffix);
if(iter != suffix_to_desc.end()){
return iter->second;
}
return "text/html"; //所给后缀未找到则默认该资源为html文件
}
// Http请求
class HttpRequest{
public:
// Http请求内容
std::string _request_line; // 请求行
std::vector<std::string> _request_header; // 请求报头
std::string _blank; // 空行
std::string _request_body; // 请求正文
// 存放解析结果
std::string _method; // 请求方法
std::string _uri; // URI
std::string _version; // 版本号
std::unordered_map<std::string, std::string> _header_kv; // 请求报头的内容是以键值对的形式存在的,用hash保存
int _content_length; // 正文长度
std::string _path; // 请求资源的路径
std::string _query_string; // URI携带的参数
// 是否使用CGI
bool _cgi;
public:
HttpRequest()
:_content_length(0) // 默认请求正文长度为0
,_cgi(false) // 默认不适用CGI模式
{}
~HttpRequest()
{}
};
class HttpResponse{
public:
// Http响应内容
std::string _status_line; // 状态行
std::vector<std::string> _response_header; // 响应报头
std::string _blank; // 空行
std::string _response_body; // 响应正文(如果CGI为true(即Get带_query_string或者Post),响应正文才存在)
// 所需数据
int _status_code; // 状态码
int _fd; // 响应文件的fd
int _size; // 响应文件的大小
std::string _suffix; // 响应文件的后缀
public:
HttpResponse()
:_blank(LINE_END)
,_status_code(OK)
,_fd(-1)
,_size(0)
{}
~HttpResponse()
{}
};
class EndPoint{
private:
int _sock; //通信的套接字
HttpRequest _http_request; //HTTP请求
HttpResponse _http_response; //HTTP响应
bool _stop; //是否停止本次处理
private:
// 读取请求行 读取出错返回true
bool RecvHttpRequestLine()
{
auto& line = _http_request._request_line;
if (Util::ReadLine(_sock, line) > 0) // 读取成功
{
line.resize(line.size() - 1);//去掉读取上来的\n
}
else // 读取出错
{
_stop = true;
}
return _stop;
}
// 读取请求报头和空行 读取出错返回true
bool RecvHttpRequestHeader()
{
std::string line;
while(true)
{
line.clear();// 一行行读取
if (Util::ReadLine(_sock, line) <= 0)// 读取出错,则停止本次处理
{
_stop = true;
break;
}
if (line == "\n") // 读取到了空行
{
_http_request._blank = line;
break;
}
// 读取到一行请求报头,记得去掉尾巴的\n
line.resize(line.size() - 1);
_http_request._request_header.push_back(line);
}
return _stop;
}
// 解析请求行
void ParseHttpRequestLine()
{
auto& line = _http_request._request_line;
// 字符串操作小技巧:stringstream根据空格拆分字符串
std::stringstream ss(line);
ss >> _http_request._method >> _http_request._uri >> _http_request._version;
// 将请求方法统一转换为全大写
auto& method = _http_request._method;
std::transform(method.begin(), method.end(), method.begin(), toupper);
}
// 解析请求报头
void ParseHttpRequestHeader()
{
std::string key;
std::string value;
for (auto& iter : _http_request._request_header)
{
if (Util::CutString(iter, key, value, SEP))//将每行请求报头以“kv键值对形式”插入到unordered_map中
{
_http_request._header_kv.insert({key, value});
}
}
}
// 判断是否需要读取请求正文
bool IsNeedRecvHttpRequestBody()
{
auto& method = _http_request._method;
if (method == "POST")// 请求方法为POST则需要读取正文
{
auto& header_kv = _http_request._header_kv;
auto iter = header_kv.find("Content-Length"); // 通过Content-Length获取请求正文长度
if (iter != header_kv.end())
{
_http_request._content_length = atoi(iter->second.c_str());
return true;
}
}
return false;
}
// 读取请求正文 (POST才有必要看正文)
bool RecvHttpRequestBody()
{
if (IsNeedRecvHttpRequestBody())// 先判断是否需要读取正文
{
int content_length = _http_request._content_length;// 正文长度
auto& body = _http_request._request_body;
char ch = 0;
while(content_length)
{
ssize_t size = recv(_sock, &ch, 1, 0);
if (size > 0)
{
body.push_back(ch);
content_length--;
}
else// 读取出错或对端关闭,则停止本次处理
{
_stop = true;
break;
}
}
}
return _stop;
}
// CGI = true,处理cgi
int ProcessCgi()
{
int code = OK; // 要返回的状态码,默认设置为200
auto& bin = _http_request._path; // 需要执行的CGI程序
auto& method = _http_request._method; // 请求方法
//需要传递给CGI程序的参数
auto& query_string = _http_request._query_string; // GET
auto& request_body = _http_request._request_body; // POST
int content_length = _http_request._content_length; // 请求正文的长度
auto& response_body = _http_response._response_body; // CGI程序的处理结果放到响应正文当中
// 1、创建两个匿名管道(管道命名站在父进程角度)
// 在调用 pipe 函数创建管道成功后,pipefd[0] 用于读取数据,pipefd[1] 用于写入数据。
// 1.1 创建从子进程到父进程的通信信道
int input[2];
if(pipe(input) < 0){ // 管道创建失败,pipe()返回-1
LOG(ERROR, "pipe input error!");
code = INTERNAL_SERVER_ERROR;
return code;
}
// 1.2 创建从父进程到子进程的通信信道
int output[2];
if(pipe(output) < 0){ // 管道创建失败,pipe()返回-1
LOG(ERROR, "pipe output error!");
code = INTERNAL_SERVER_ERROR;
return code;
}
//2、创建子进程
pid_t pid = fork();
if(pid == 0){ //child
// 子进程关闭两个管道对应的读写端
close(input[0]);
close(output[1]);
//将请求方法通过环境变量传参
std::string method_env = "METHOD=";
method_env += method;
putenv((char*)method_env.c_str());
if(method == "GET"){ //将query_string通过环境变量传参
std::string query_env = "QUERY_STRING=";
query_env += query_string;
putenv((char*)query_env.c_str());
LOG(INFO, "GET Method, Add Query_String env");
}
else if(method == "POST"){ //将正文长度通过环境变量传参
std::string content_length_env = "CONTENT_LENGTH=";
content_length_env += std::to_string(content_length);
putenv((char*)content_length_env.c_str());
LOG(INFO, "POST Method, Add Content_Length env");
}
else{
//Do Nothing
}
//3、将子进程的标准输入输出进行重定向,子进程会继承了父进程的所有文件描述符
dup2(output[0], 0); //标准输入重定向到管道的输入
dup2(input[1], 1); //标准输出重定向到管道的输出
//4、将子进程替换为对应的CGI程序,代码、数据全部替换掉
execl(bin.c_str(), bin.c_str(), nullptr);
exit(1); // 替换失败则exit(1)
}
else if(pid < 0){ //创建子进程失败,则返回对应的错误码
LOG(ERROR, "fork error!");
code = INTERNAL_SERVER_ERROR;
return code;
}
else{ //father
//父进程关闭两个管道对应的读写端
close(input[1]);
close(output[0]);
if(method == "POST") // 将正文中的参数通过管道传递给CGI程序
{
const char* start = request_body.c_str();
int total = 0;
int size = 0;
while(total < content_length && (size = write(output[1], start + total, request_body.size() - total)) > 0)
{
total += size;
}
}
// 读取CGI程序的处理结果
char ch = 0;
while(read(input[0], &ch, 1) > 0)// 不会一直读,当另一端关闭后会继续往下执行
{
response_body.push_back(ch);
}
// 等待子进程(CGI程序)退出
// status 保存退出状态
int status = 0;
pid_t ret = waitpid(pid, &status, 0);
if(ret == pid){
if(WIFEXITED(status)){ // 子进程正常退出
if(WEXITSTATUS(status) == 0){ // 子进程退出码结果正确
LOG(INFO, "CGI program exits normally with correct results");
code = OK;
}
else{
LOG(INFO, "CGI program exits normally with incorrect results");
code = BAD_REQUEST;
}
}
else{
LOG(INFO, "CGI program exits abnormally");
code = INTERNAL_SERVER_ERROR;
}
}
//关闭两个管道对应的文件描述符
close(input[0]);
close(output[1]);
}
return code; //返回状态码
}
// CGI = false
int ProcessNonCgi()
{
// 打开客户端请求的资源文件,以供后续发送
_http_response._fd = open(_http_request._path.c_str(), O_RDONLY);
if(_http_response._fd >= 0){ // 打开文件成功
return OK;
}
return INTERNAL_SERVER_ERROR; // 打开文件失败
}
void BuildOkResponse()
{
//构建响应报头
std::string content_type = "Content-Type: ";
content_type += SuffixToDesc(_http_response._suffix);
content_type += LINE_END;
_http_response._response_header.push_back(content_type);
std::string content_length = "Content-Length: ";
if(_http_request._cgi){ //以CGI方式请求
content_length += std::to_string(_http_response._response_body.size());
}
else{ //以非CGI方式请求
content_length += std::to_string(_http_response._size);
}
content_length += LINE_END;
_http_response._response_header.push_back(content_length);
}
void HandlerError(std::string page)
{
_http_request._cgi = false; //需要返回对应的错误页面(非CGI返回)
//打开对应的错误页面文件,以供后续发送
_http_response._fd = open(page.c_str(), O_RDONLY);
if(_http_response._fd > 0){ //打开文件成功
//构建响应报头
struct stat st;
stat(page.c_str(), &st); //获取错误页面文件的属性信息
std::string content_type = "Content-Type: text/html";
content_type += LINE_END;
_http_response._response_header.push_back(content_type);
std::string content_length = "Content-Length: ";
content_length += std::to_string(st.st_size);
content_length += LINE_END;
_http_response._response_header.push_back(content_length);
_http_response._size = st.st_size; //重新设置响应文件的大小
}
}
public:
EndPoint(int sock)
:_sock(sock)
,_stop(false)
{}
~EndPoint()
{}
bool IsStop()
{
return _stop;
}
// 读取请求:如果请求行和请求报头正常读取,那先解析请求行和请求报头,然后读取请求正文
void RecvHttpRequest()
{
if (!RecvHttpRequestLine() && !RecvHttpRequestHeader())// 请求行与请求报头读取均正常读取
{
ParseHttpRequestLine();
ParseHttpRequestHeader();
RecvHttpRequestBody();
}
}
// 处理请求
void HandlerHttpRequest()
{
auto& code = _http_response._status_code;
//非法请求
if (_http_request._method != "GET" && _http_request._method != "POST")
{
LOG(WARNING, "method is not right");
code = BAD_REQUEST;
return;
}
// 判断请求是get还是post,设置cgi,_path,_query_string
if (_http_request._method == "GET")
{
size_t pos = _http_request._uri.find('?');
if (pos != std::string::npos)// uri中携带参数
{
// 切割uri,得到客户端请求资源的路径和uri中携带的参数
Util::CutString(_http_request._uri, _http_request._path, _http_request._query_string, "?");
LOG(INFO, "GET方法分割路径和参数");
_http_request._cgi = true;// 上传了参数,需要使用CGI模式
}
else // uri中没有携带参数
{
_http_request._path = _http_request._uri;// uri即是客户端请求资源的路径
}
}
else if (_http_request._method == "POST")
{
_http_request._path = _http_request._uri;// uri即是客户端请求资源的路径
_http_request._cgi = true; // 上传了参数,需要使用CGI模式
}
else
{
// 只是为了代码完整性
}
// 为请求资源路径拼接web根目录
std::string path = _http_request._path;
_http_request._path = WEB_ROOT;
_http_request._path += path;
// 请求资源路径以/结尾,说明请求的是一个目录
if (_http_request._path[_http_request._path.size() - 1] == '/')
{
_http_request._path += HOME_PAGE; // 拼接上该目录下的index.html
}
LOG(INFO, _http_request._path);
//获取请求资源文件的属性信息
struct stat st;
if (stat(_http_request._path.c_str(), &st) == 0) // 属性信息获取成功,说明该资源存在
{
if (S_ISDIR(st.st_mode)) // 该资源是一个目录
{
_http_request._path += "/"; // 以/结尾的目录前面已经处理过了,这里处理不是以/结尾的目录情况,需要拼接/
_http_request._path += HOME_PAGE; // 拼接上该目录下的index.html
stat(_http_request._path.c_str(), &st); // 重新获取资源文件的属性信息
}
else if (st.st_mode&S_IXUSR||st.st_mode&S_IXGRP||st.st_mode&S_IXOTH) // 该资源是一个可执行程序
{
_http_request._cgi = true; //需要使用CGI模式
}
_http_response._size = st.st_size; //设置请求资源文件的大小
}
else // 属性信息获取失败,可以认为该资源不存在
{
LOG(WARNING, _http_request._path + "NOT_FOUND");
code = NOT_FOUND;
return;
}
// 获取请求资源文件的后缀
size_t pos = _http_request._path.rfind('.');
if (pos == std::string::npos)
{
_http_response._suffix = ".html";
}
else
{
_http_response._suffix = _http_request._path.substr(pos);// 把'.'也带上
}
// 进行CGI或非CGI处理
// CGI为true就三种情况,GET方法的uri带参(_query_string),或者POST方法,又或者请求的资源是一个可执行程序
if (_http_request._cgi == true)
{
code = ProcessCgi(); // 以CGI的方式进行处理
}
else
{
code = ProcessNonCgi(); // 简单的网页返回,返回静态网页
}
}
// 构建响应
void BulidHttpResponse()
{
int code = _http_response._status_code;
//构建状态行
auto& status_line = _http_response._status_line;
status_line += HTTP_VERSION;
status_line += " ";
status_line += std::to_string(code);
status_line += " ";
status_line += CodeToDesc(code);
status_line += LINE_END;
//构建响应报头
std::string path = WEB_ROOT;
path += "/";
switch(code){
case OK:
BuildOkResponse();
break;
case NOT_FOUND:
path += PAGE_404;
HandlerError(path);
break;
case BAD_REQUEST:
path += PAGE_400;
HandlerError(path);
break;
case INTERNAL_SERVER_ERROR:
path += PAGE_500;
HandlerError(path);
break;
default:
break;
}
}
// 发送响应
bool SendHttpResponse()
{
//发送状态行
if(send(_sock, _http_response._status_line.c_str(), _http_response._status_line.size(), 0) <= 0)
{
_stop = true; //发送失败,设置_stop
}
//发送响应报头
if(!_stop){
for(auto& iter : _http_response._response_header)
{
if(send(_sock, iter.c_str(), iter.size(), 0) <= 0)
{
_stop = true; //发送失败,设置_stop
break;
}
}
}
//发送空行
if(!_stop)
{
if(send(_sock, _http_response._blank.c_str(), _http_response._blank.size(), 0) <= 0)
{
_stop = true; //发送失败,设置_stop
}
}
//发送响应正文
if(_http_request._cgi)
{
if(!_stop)
{
auto& response_body = _http_response._response_body;
const char* start = response_body.c_str();
size_t size = 0;
size_t total = 0;
while(total < response_body.size()&&(size = send(_sock, start + total, response_body.size() - total, 0)) > 0){
total += size;
}
}
}
else
{
if(!_stop)
{
// sendfile:这是一个系统调用,用于高效地从文件传输数据到套接字中。它避免了在内核空间和用户空间之间复制数据的需求,从而实现更快的数据传输。
if(sendfile(_sock, _http_response._fd, nullptr, _http_response._size) <= 0)
{
_stop = true; //发送失败,设置_stop
}
}
//关闭请求的资源文件
close(_http_response._fd);
}
return _stop;
}
};
class CallBack{
public:
CallBack()
{}
~CallBack()
{}
// 重载运算符 ()
void operator()(int sock)
{
HandlerRequest(sock);
}
void HandlerRequest(int sock)
{
LOG(INFO, "HandlerRequest begin");
EndPoint* ep = new EndPoint(sock);
ep->RecvHttpRequest(); //读取请求
if (!ep->IsStop())
{
LOG(INFO, "RecvHttpRequest Success");
ep->HandlerHttpRequest(); //处理请求
ep->BulidHttpResponse(); //构建响应
ep->SendHttpResponse(); //发送响应
if (ep->IsStop())
{
LOG(WARNING, "SendHttpResponse Error, Stop Send HttpResponse");
}
}
else
{
LOG(WARNING, "RecvHttpRequest Error, Stop handler Response");
}
close(sock); //响应完毕,关闭与该客户端建立的套接字
delete ep;
LOG(INFO, "handler request end");
}
};
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
C++
1
https://gitee.com/ximen-ww/HttpServer.git
git@gitee.com:ximen-ww/HttpServer.git
ximen-ww
HttpServer
HttpServer
master

搜索帮助