1 Star 0 Fork 0

杭电码农-NEO / HTTP-Web Server Project

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
Protocol.hpp 18.76 KB
一键复制 编辑 原始数据 按行查看 历史
杭电码农-NEO 提交于 2024-05-03 10:41 . 提交修改,云服务器过期
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573
#pragma once
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <vector>
#include <string>
#include <sstream>
#include <cstdlib>
#include <unordered_map>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/sendfile.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <arpa/inet.h>
#include <algorithm>
#include <sys/wait.h>
#include <unistd.h>
#include "util.hpp"
#include "log.hpp"
using namespace std;
#define SEP ": "
#define SEP_LEN strlen(SEP)
#define OK 200
#define NOT_FOUND 404
#define BAD_REQUEST 400
#define SERVER_ERROR 500
#define WEB_ROOT "./wwwroot" // 根目录
#define HOME_PAGE "index.html" // 默认首页
#define HTTP_VERSION "HTTP/1.0" // HTTP的版本
#define LINE_END "\r\n" // 行分隔符
#define PAGE_404 "404.html"
#define PAGE_500 "500.html"
#define PAGE_400 "400.html"
// 协议的读取,分析,处理都在这个.hpp中
static string CodeToEsc(int code)
{
switch (code)
{
case 200:
return "OK";
case 404:
return "Not Found";
case 204:
return "No Content";
case 301:
return "Moved Permanently";
case 302:
return "Moved Temporarily";
case 400:
return "Bad Request";
case 500:
return "Server Error";
default:
break;
}
return "";
}
static string SuffixtoDesc(const string &suffix)
{
static unordered_map<string, string> suffix_desc =
{{".html", "text/html"},
{".css", "text/css"},
{".js", "application/javascript"},
{".jpg", "application/x-jpg"},
{".xml", "application/xml"}};
auto iter = suffix_desc.find(suffix);
if (iter != suffix_desc.end())
return iter->second;
else
return "text/html";
}
class HttpRequest
{
public:
string _req_line; // 请求行
vector<string> _req_hander; // 请求报头
string _blank; // 空行
string _req_body; // 请求正文
string _method; // 请求方法
string _uri; // 请求的资源
string _version; // http的版本
string _path; // url请求的资源
string _query; // url后面的参数
bool _cgi = false; // 用户是否上传了数据,上传了数据就是CGI模式
unordered_map<string, string> _hander_kv; // 保存解析完req_hander后的Kv结构
int _content_length = 0; // 正文的字节数
string _suffix;
int _size; // 对方要访问的资源的大小
};
class HttpResponse
{
public:
string _status_line; // 状态行
vector<string> _resp_hander; // 属性行
string _blank = LINE_END; // 空行
string _resp_body; // 响应正文
int _status_code = OK; // 状态码标识这次请求是否非法
int _fd = -1; // 被访问的文件,打开后的文件描述符
public:
};
// 读取请求.分析请求,构建相应,IO通信
class EndPoint
{
public:
EndPoint(int sock)
: _sock(sock)
{}
bool RecvRequest() // 读取请求,解析请求
{
if((!RecvReqLine()) && (!RecvReqHander()))// 读取请求行 读取报头
{
ParseReqLine(); // 解析请求行
ParseReqHander(); // 解析报头
// 将报头打散后,确认是否是有正文,正文有多少个字节(GET方法无正文,POST才有,报头中content-length字段存储了正文的字节数)
RecvReqBody(); // 读取正文
}
}
void BulidResponse() // 构建响应
{
string path;
auto &code = _httpResp._status_code;
size_t found = 0; // 用来找文件后缀的点
if (_httpReq._method != "GET" && _httpReq._method != "POST") // 需要这两种方法,如果不是这两种方法,则是非法请求
{
// 状态码可以直接决定报文的处理方式
logMessage(WARNING, "method 不合法, [%s], [%d]", __FILE__, __LINE__);
code = BAD_REQUEST;
goto END;
}
if (_httpReq._method == "GET")
{
ssize_t pos = _httpReq._uri.find('?');//查看get方法有无参数
if (pos != string::npos)
{
Util::CutString(_httpReq._uri, _httpReq._path, _httpReq._query, "?");
_httpReq._cgi = true;//有参数证明它要上传数据,要是哟个cgi方法处理
}
else
{
_httpReq._path = _httpReq._uri; //若没有参数,则请求资源的路径就是uri本身
}
}
else if (_httpReq._method == "POST") // POST方法
{
_httpReq._cgi = true;
_httpReq._path = _httpReq._uri;
}
// 设置默认路径
path = _httpReq._path;
_httpReq._path = WEB_ROOT;
_httpReq._path += path;
if (_httpReq._path[_httpReq._path.size() - 1] == '/')
_httpReq._path += HOME_PAGE;
logMessage(DEBUG,"path: %s",path.c_str());
// 确认请求的资源是否存在
struct stat st;
if (stat(_httpReq._path.c_str(), &st) == 0)
{
// 获取资源成功,可能访问的是一个特定的文件,也可能是单纯的访问一个文件夹,也有可能请求的是一个可执行程序
if (S_ISDIR(st.st_mode)) // 条件成立说明请求的资源是一个目录,web根目录中的一个目录,需要将这个目录下的默认网页返回
{
_httpReq._path += "/"; // a/b/c 还要加上一个/
_httpReq._path += HOME_PAGE;
stat(_httpReq._path.c_str(), &st);
}
if ((st.st_mode & S_IXUSR) || (st.st_mode & S_IXGRP) || (st.st_mode & S_IXOTH)) // 任何一个条件成立,代表对方请求了一个可执行程序
{
_httpReq._cgi = true;
}
// 获取要访问的资源的大小
_httpReq._size = st.st_size;
}
else // 资源不存在
{
logMessage(WARNING, "申请的资源不存在, [%s], [%d]", __FILE__, __LINE__);
code = NOT_FOUND;
goto END;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
found = _httpReq._path.rfind(".");
if (found == string::npos)
_httpReq._suffix = ".html";
else
_httpReq._suffix = _httpReq._path.substr(found);
// 走到这儿了,证明没有执行goto语句,所以此时的请求是合法的,并且申请的资源也是存在的
if (_httpReq._cgi) // 用户上传了数据
{
logMessage(DEBUG,"cgi方法");
// 以CGI的方式处理请求
code = ProcessCGI(); // 执行目标程序,拿到结果到_http_resp.body中
}
else // 用户只是获取了数据
{
logMessage(DEBUG,"非cgi方法");
// 以非CGI的方式处理请求
code = ProcessNonCGI(); // 只需要将网页打开即可
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
END:
BulidResponseHelper();
}
void SendResponse() // 发送响应
{
send(_sock, _httpResp._status_line.c_str(), _httpResp._status_line.size(), 0);
for (auto &iter : _httpResp._resp_hander)
send(_sock, iter.c_str(), iter.size(), 0);
send(_sock, _httpResp._blank.c_str(), _httpResp._blank.size(), 0);
if(_httpReq._cgi)//cgi方法的发送正文
{
auto& resp_body = _httpResp._resp_body;
int size = 0;
int total = 0;
const char* start = resp_body.c_str();
while( total < resp_body.size() && (size = send(_sock, start + total, resp_body.size() - total,0))>0)
{
total += size;
}
close(_httpResp._fd);
}
else //非cgi方法的发送正文
{
sendfile(_sock, _httpResp._fd, nullptr, _httpReq._size);
close(_httpResp._fd);
}
}
~EndPoint()
{
close(_sock);
}
private:
bool RecvReqLine() // 读取请求报文的第一行
{
if(Util::ReadLine(_sock, _httpReq._req_line)>0)
{
_httpReq._req_line.resize(_httpReq._req_line.size()-1);
}
else _stop = true;
return _stop;
}
bool RecvReqHander() // 读取请求报文的报头
{
string line;
while (1) // 读到空行时,line就是"\n",其他情况下line都是正文
{
line.clear();
if(Util::ReadLine(_sock, line)<=0)
{
_stop = true;
break;
}
if (line == "\n")
{
_httpReq._blank = "\n";
break;
}
line.resize(line.size() - 1);
_httpReq._req_hander.push_back(line);
}
return _stop;
}
void ParseReqLine() // 解析请求
{
auto &line = _httpReq._req_line;
stringstream ss(line);
ss >> _httpReq._method >> _httpReq._uri >> _httpReq._version; // 解析 请求方法,URL,HTTP版本
for (auto &ch : _httpReq._method) // 将请求方法全部大写
ch = toupper(ch);
}
void ParseReqHander() // 将读取到的一行一行的放在vector中的请求报头打散放在KV结构中
{
string key;
string value;
for (auto &iter : _httpReq._req_hander)
{
if (Util::CutString(iter, key, value, SEP))
_httpReq._hander_kv[key] = value;
}
}
bool IfHasBody()
{
auto &method = _httpReq._method;
if (method == "POST")
{
auto &hander_kv = _httpReq._hander_kv;
auto iter = hander_kv.find("Content-Length");
if (iter != hander_kv.end())
_httpReq._content_length = atoi(iter->second.c_str());
return true;
}
return false;
}
bool RecvReqBody()
{
if (IfHasBody())
{
int content_length = _httpReq._content_length;
auto &body = _httpReq._req_body; // 将正文读取到req_body
char ch = 0;
while (content_length)
{
ssize_t s = recv(_sock, &ch, 1, 0);
if (s > 0)
{
body.push_back(ch);
content_length--;
}
else
{
_stop = true;
break;
}
}
}
return _stop;
}
// 返回并不是简单的返回网页,而是要构建HTTP响应
int ProcessNonCGI() // 换回静态网页
{
_httpResp._fd = open(_httpReq._path.c_str(), O_RDONLY);
if (_httpResp._fd >= 0)
{
// 添加状态行
/* _httpResp._status_line = HTTP_VERSION;
_httpResp._status_line += " ";
_httpResp._status_line += to_string(_httpResp._status_code);
_httpResp._status_line += " ";
_httpResp._status_line += CodeToEsc(_httpResp._status_code);
_httpResp._status_line += LINE_END; */
/* string hander_line = "Content-Length: ";
hander_line += to_string(_httpResp._size);
hander_line += LINE_END;
_httpResp._resp_hander.push_back(hander_line);
hander_line = "Content-Type: ";
hander_line += SuffixtoDesc(_httpReq._suffix);
hander_line += LINE_END;
_httpResp._resp_hander.push_back(hander_line); */
return OK;
}
return 404;
}
int ProcessCGI()
{
auto &query = _httpReq._query; // 一定是GET方法
auto &body = _httpReq._req_body; // 一定是POST方法
auto &bin = _httpReq._path; // 要执行的可执行程序的位置
int code = OK;
int content_length = _httpReq._content_length;
string content_length_env;
string env;
string method_env;
int input[2];
int output[2]; // 站在父进程角度的in和out,intput进行读取,output进行写入
if (pipe(input) < 0)
{
logMessage(ERROR, "创建管道失败(input)");
code = SERVER_ERROR;
return code;
}
if (pipe(output) < 0)
{
logMessage(ERROR, "创建管道失败(output)");
code = SERVER_ERROR;
return code;
}
// 是一个线程进入到这个CGI中,从头到尾都只有进程,那就是httpserver
pid_t pid = fork();
if (pid == 0) // 让子进程执行exec系列函数
{
close(input[0]);
close(output[1]);
// 替换成功后,子进程如何知道对应的读写描述符?做一个约定,让目标倍进程替换后,读取管道等价于读取标准输入,写入管道等价于写到便准输出
// 在exec函数执行前,进行重定向,子进程角度,input[1]写出,output[0]读入
method_env = "METHOD=";
method_env += _httpReq._method;
putenv((char *)method_env.c_str());
if (_httpReq._method == "GET") // 如果是GET方法,则导入环境变量
{
env = "QUERY_STRING=";
env += query;
putenv((char *)env.c_str());
logMessage(DEBUG, "将GET方法的参数导入到环境变量成功");
}
else if (_httpReq._method == "POST") // post方法要将要读取数据的长度传入
{
content_length_env = "CONTENT_LENGTH=";
content_length_env += to_string(content_length);
putenv((char *)content_length_env.c_str());
logMessage(DEBUG, "添加content-length到环境变量成功");
}
dup2(output[0], 0);
dup2(input[1], 1);
execl(bin.c_str(), bin.c_str(), nullptr);
exit(1);
}
else if (pid < 0)
{
logMessage(ERROR, "创建子进程失败 [%s] , [%d]", __FILE__, __LINE__);
code = SERVER_ERROR;
}
else // 父进程
{
close(input[1]);
close(output[0]);
if (_httpReq._method == "POST") // 说明请求的数据在body中,将数据写入管道
{
const char *start = body.c_str();
int total = 0;
int size = 0;
while (total < content_length && (size = write(output[1], start + total, body.size() - total)) > 0)
total += size;
}
char ch = 0;
while (read(input[0], &ch, 1) > 0) // 当子进程退出,文件描述符也关闭,此时就会读到0终止循环
{
// CGI执行完之后的结果不能立马send到客户端,因为这部分内容只是响应正文
_httpResp._resp_body.push_back(ch);
}
int status = 0;
pid_t ret = waitpid(pid, &status, 0);
if (ret == pid)
{
if (!WIFEXITED(status))
{
logMessage(ERROR, "子进程退出异常");
code = BAD_REQUEST;
}
if (WEXITSTATUS(status) != 0)
{
logMessage(ERROR, "子进程退出异常");
code = BAD_REQUEST;
}
}
close(input[0]);
close(output[1]);
}
return code;
}
void BulidResponseHelper() // 处理存在错误时的响应应该怎样构建
{
auto &code = _httpResp._status_code;
// 构建状态行
auto &status_line = _httpResp._status_line;
status_line += HTTP_VERSION;
status_line += " ";
status_line += to_string(code);
status_line += " ";
status_line += CodeToEsc(code);
status_line += LINE_END;
// 构建响应正文,可能包括响应报头
string path = WEB_ROOT;
path += "/";
switch (code)
{
case NOT_FOUND:
path += PAGE_404;
HanderError(path);
break;
//case 500:
//HanderError(PAGE_500);
case OK:
BulidOKResponse();
break;
case BAD_REQUEST:
path += PAGE_400;
HanderError(path);
break;
case SERVER_ERROR:
path += PAGE_500;
HanderError(path);
break;
default:
break;
}
}
void HanderError(string page)// 处理404错误,返回404的响应,要给用户返回404页面
{
logMessage(DEBUG,"返回错误的网页信息");
_httpReq._cgi = false;
_httpResp._fd = open(page.c_str(), O_RDONLY);
if (_httpResp._fd > 0)
{
struct stat st;
stat(page.c_str(), &st);
_httpReq._size = st.st_size;
string line = "Content-Type: text/html";
line += LINE_END;
_httpResp._resp_hander.push_back(line);
line = "Content-Length: ";
line += to_string(st.st_size);
line += LINE_END;
_httpResp._resp_hander.push_back(line);
}
}
void BulidOKResponse()
{
logMessage(DEBUG,"构建正常的响应");
string line = "Content-Type: ";
line += SuffixtoDesc(_httpReq._suffix);
line += LINE_END;
_httpResp._resp_hander.push_back(line);
line = "Content-Length: ";
if(_httpReq._cgi) line += to_string(_httpResp._resp_body.size()); //GET方法
else line += to_string(_httpReq._size); //POST方法
line += LINE_END;
_httpResp._resp_hander.push_back(line);
}
public:
bool _stop = false;
private:
int _sock;
HttpRequest _httpReq;
HttpResponse _httpResp;
};
class CallBack
{
public:
CallBack(){}
void operator()(int sock)
{
HandlerRequest(sock);
}
~CallBack(){}
void HandlerRequest(int sock)
{
// delete (int*)args;
#ifdef DEBUGP
char buffer[10240];
recv(sock, buffer, sizeof(buffer), 0);
cout << "----------------------" << endl;
cout << buffer << endl;
cout << "----------------------" << endl;
#else
EndPoint* ep = new EndPoint(sock);
ep->RecvRequest();
if(!ep->_stop){
ep->BulidResponse();
ep->SendResponse();
}
else logMessage(WARNING,"recv error, stop bulid");
delete ep;
#endif
}
};
C++
1
https://gitee.com/NEO_kou/http-web-server-project.git
git@gitee.com:NEO_kou/http-web-server-project.git
NEO_kou
http-web-server-project
HTTP-Web Server Project
master

搜索帮助