1 Star 0 Fork 1

岳彪. / http

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
HttpServer.hpp 25.96 KB
一键复制 编辑 原始数据 按行查看 历史
岳彪. 提交于 2023-10-14 21:22 . bugt
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837
#pragma once
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <string>
#include <algorithm>
#include <iostream>
#include <fstream>
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <sstream>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#include <memory>
#include "Util.hpp"
#include "Sock.hpp"
#include "TcpServer.hpp"
// #include "threadPool/Task.hpp"
#include "threadPool/ThreadPool.hpp"
using namespace std;
static const std::string HTTP_HEADER_SEP = ": ";
static const std::string CRLF = "\r\n";
static const std::string SPACE = " ";
static const std::string HOME_PAGE = "index.html";
class HttpServer;
class HttpRequest
{
friend HttpServer;
// 任务:
// 将收到的http请求报文进行解析,
// 提取:方法 url 版本
private:
std::string request_line; // 请求行
std::unordered_map<std::string, std::string> request_header; // 请求报头
std::string request_body; // 请求正文
// requeset_line的解析
std::string method;
std::string uri;
std::string version;
// 可用的方法
static const std::unordered_set<string> methods;
// 从uri中提取路径和参数
std::string path;
int fileSize;
std::string param_str;
std::unordered_map<std::string, std::string> params;
// 判断当前报文是否完整,因为TCP是流式协议,无法保证当前报文一定完整
bool is_complate_ = true;
// 判断当前报文是否合法
bool is_legal_ = true;
// 是否为cgi
bool cgi_ = false;
public:
HttpRequest(Connection *conn)
{
// 提取一个http报文并解析
if (conn->inBufferEmpty())
{
is_complate_ = false;
return;
}
getRequest(conn->getInbuffer());
}
string get_path() const
{
return path;
}
string get_param_value(const string &key) const
{
string val;
auto find = params.find(key);
if (find != params.end())
{
val = find->second;
}
return val;
}
string get_methord()
{
return method;
}
string &getParamStr()
{
return param_str;
}
string &get_body()
{
return request_body;
}
int body_size()
{
return request_body.size();
}
bool is_complate()
{
return is_complate_;
}
bool is_legal()
{
return is_legal_;
}
void setCgi(bool tf)
{
cgi_ = tf;
}
bool cgi()
{
return cgi_;
}
private:
// 解析报文
void getRequest(string &inBuffer)
{
// 提取解析请求行
int pos = 0;
pos = Util::readLine(inBuffer, request_line, pos); // 读取请求行
parseLine(); // 解析请求行
if (!legalLine())
{
// 从inBUffer将当前报文部分剪切
inBuffer.clear();
is_legal_ = false;
return;
}
// 循环提取解析报头,读到空行停止
while (true)
{
string header;
pos = Util::readLine(inBuffer, header, pos);
if (pos == 0) // 没有读到换行符,说明报文不完整
{
is_complate_ = false;
return;
}
if (header.size() == 0) // 读到空行,报头部分结束
{
break;
}
parseHeader(header);
}
// logMessage(INFO, "header:\n", inBuffer.c_str());
// 读取正文
// 先判断报文是否存在正文
// 如果不存在,从inBuffer剪切数据并返回
// 如果存在,读取正文并剪切数据返回
// 如果存在,但是正文不全,不剪切返回
is_complate_ = readBody(inBuffer, pos); // 读取正文部分,并判断报文是否完整
if (is_complate_ == false)
{
return;
}
// 报文读取完毕
// 从inBUffer将当前报文部分剪切
inBuffer = inBuffer.substr(pos);
// 从中提取路径和参数
getParams();
// 判断是否为cgi
cgiJudge();
}
void parseLine()
{
std::string uri_coded;
std::stringstream ss(request_line);
ss >> method >> uri_coded >> version;
transform(method.begin(), method.end(), method.begin(), ::toupper);
uri = Util::UrlDecode(uri_coded);
}
bool legalLine()
{
if (!methods.count(method) || uri.empty() || version.empty())
{
return false;
}
return true;
}
void parseHeader(const string &header)
{
std::string key;
std::string val;
Util::cutString(header, key, val, HTTP_HEADER_SEP);
request_header[key] = val;
}
bool hasBody(int &length)
{
if (method == "POST")
{
const auto &it = request_header.find("Content-Length");
if (it != request_header.end())
{
length = stoi(it->second);
return true;
}
}
return false;
}
bool readBody(string &inBuffer, int &pos)
{
int length = 0;
if (hasBody(length))
{
if (pos + length > inBuffer.size()) // 当前还没有收到完的报文
{
return false;
}
request_body = inBuffer.substr(pos, length);
pos += length;
}
return true;
}
void getParams()
{
// /s?ie=utf-8&f=8&rsv_bp=1&ch=&tn=baiduerr&bar=&wd=hello
size_t urlPathStart = 0;
size_t urlPathEnd = 0;
if ((urlPathEnd = uri.find('?')) != std::string::npos) // 如果有参数
{
path = uri.substr(urlPathStart, urlPathEnd - urlPathStart);
param_str = uri.substr(urlPathEnd + 1);
size_t paramEnd = param_str.find('#');
if (paramEnd != string::npos)
{
param_str = param_str.substr(0, paramEnd);
}
size_t paramStart = urlPathEnd + SPACE.size();
size_t paramCur = 0;
string key;
string val;
while (paramCur <= uri.size())
{
if (paramCur < uri.size() && uri[paramCur] == '=')
{
key = uri.substr(paramStart, paramCur - paramStart);
paramStart = paramCur + 1;
}
else if (paramCur == uri.size() || uri[paramCur] == '&' || uri[paramCur] == '#')
{
val = uri.substr(paramStart, paramCur - paramStart);
params[key] = val;
paramStart = paramCur + 1;
}
if (paramCur == uri.size() || uri[paramCur] == '#')
{
break;
}
paramCur++;
} // end while
} // end if url有参数
else
{
path = uri;
}
if (path == "/")
{
path += HOME_PAGE;
}
}
void cgiJudge()
{
if (method == "POST")
{
cgi_ = true;
}
if (params.size())
{
cgi_ = true;
}
}
};
const std::unordered_set<string> HttpRequest::methods({"GET", "POST"});
class HttpResponse
{
friend HttpServer;
enum StatusCode
{
DEFAULT = 0,
OK = 200,
Bad_Request = 400,
Forbidden = 403,
Not_Found = 404,
Server_Error = 500
};
unordered_map<StatusCode, string> code2desc = {
{OK, "OK"},
{Bad_Request, "Bad Request"},
{Forbidden, "Forbidden"},
{Not_Found, "Not Found"},
{Server_Error, "Internal Server Error"}};
unordered_map<StatusCode, string> code2page =
{
{Not_Found, "404.html"}};
private:
std::string status_line;
std::unordered_map<std::string, std::string> response_header;
// 正文可以是字符串或者一个文件
std::string response_body_str;
int response_body_fd = -1;
int response_body_size = 0;
// 状态行内容
std::string version = "HTTP/1.1";
StatusCode status_code = DEFAULT;
// web根目录
string baseDir;
public:
void setContent(const string &content, const string &type)
{
response_body_str = content;
response_header["Content-Type"] = type;
response_header["Content-Length"] = to_string(content.size());
}
void setVersion(const string &ver)
{
version = ver;
}
void setStatusCode(const StatusCode &code)
{
status_code = code;
}
void setHeader(const string &key, const string &value)
{
response_header[key] = value;
}
void setFileToSend(int fd, int fileSize)
{
response_body_fd = fd;
response_body_size = fileSize;
}
void pushToOutBuffer(Connection *conn)
{
string response;
// 响应行
makeStatusLine();
response += status_line;
response += CRLF;
if (status_code != OK)
{
setErrorResponse(status_code);
}
// 报头
for (auto kv : response_header)
{
response += kv.first;
response += HTTP_HEADER_SEP;
response += kv.second;
response += CRLF;
}
// 空行
response += CRLF;
// 正文
if (!response_body_str.empty())
{
response += response_body_str;
conn->push2OutBuffer(response);
}
else
{
// 将报头添加到发送缓冲区
conn->push2OutBuffer(response);
// 将正文信息添加到发送缓冲区
if (response_body_fd != -1 && response_body_size != 0)
{
conn->push2OutBuffer(response_body_fd, response_body_size);
}
}
}
private:
void setErrorResponse(StatusCode code)
{
#if 1
struct stat st;
string page = code2page[code];
page = baseDir + '/' + page;
response_header.clear();
if (stat(page.c_str(), &st) == 0) // 文件或路径存在
{
int fileSize = st.st_size;
int fd = open(page.c_str(), O_RDONLY);
if (fd < 0) // 打开文件失败
{
logMessage(WARNING, "%s page:%s lost", code2desc[code].c_str(), page.c_str());
if (code != Server_Error)
{
setErrorResponse(Server_Error);
}
}
else
{
setFileToSend(fd, fileSize);
setHeader("Content-Length", to_string(fileSize));
setHeader("Content-Type", "text/html");
return;
}
}
response_body_fd = -1;
response_body_size = 0;
response_body_str.clear();
#endif
}
private:
bool makeStatusLine()
{
status_line += version;
status_line += SPACE;
if (status_code == DEFAULT)
{
logMessage(WARNING, "have no status code for http response");
return false;
}
status_line += std::to_string(status_code);
status_line += SPACE;
status_line += code2desc[status_code];
return true;
}
};
typedef function<int(HttpRequest &, HttpResponse &, Connection *)> CgiTaskCb_f;
class CgiTask
{
// 由于传入的变量都是引用,所以一定要保证
private:
HttpRequest httpRequest_;
HttpResponse httpResponse_;
Connection *conn_;
CgiTaskCb_f cb_;
public:
CgiTask(HttpRequest &httpRequest,
HttpResponse &httpResponse,
Connection *conn,
CgiTaskCb_f cb)
: httpRequest_(httpRequest),
httpResponse_(httpResponse),
conn_(conn),
cb_(cb) {}
int operator()()
{
(INFO, "operator()()");
return cb_(httpRequest_, httpResponse_, conn_);
}
};
class HttpServer
{
using Handler = function<void(const HttpRequest &req, HttpResponse &rsp)>;
private:
TcpServer tcpServer_;
string baseDir_;
unordered_map<string, Handler> getWays;
unique_ptr<ThreadPool<CgiTask>> cgiHander;
public:
HttpServer(int port, const string &baseDir)
: tcpServer_(port),
baseDir_(baseDir),
cgiHander(ThreadPool<CgiTask>::getInstance())
{
cgiHander->start();
}
void setGet(const string &pattern, Handler handler)
{
getWays[pattern] = handler;
}
void run()
{
tcpServer_.setCallBack(bind(&HttpServer::HandleTcpRquest, this, placeholders::_1));
tcpServer_.Run(); // 启动tcp服务器
}
~HttpServer()
{
cgiHander->quit();
}
private:
// tcp的读处理回调
int HandleTcpRquest(Connection *conn)
{
// 循环从conn的读缓冲区中拿取http报文构建HttpRequest类,
// 然后交给HandleHttpRquest进行处理
bool is_complate = true;
while (is_complate)
{
logMessage(INFO, "HandleTcpRquest working");
HttpRequest httpRequest(conn);
is_complate = httpRequest.is_complate();
if (is_complate)
{
HandleHttpRquest(httpRequest, conn);
}
}
return 0;
}
int HandleHttpRquest(HttpRequest &httpRequest, Connection *conn)
{
logMessage(INFO, "handle http request begin");
logMessage(INFO, "%s %s", httpRequest.get_methord().c_str(), httpRequest.get_path().c_str());
HttpResponse httpResponse;
httpResponse.baseDir = baseDir_;
string filePath;
int fileSize = 0;
if (!httpRequest.is_legal()) // 当前报文不合法
{
logMessage(INFO, "http request is not legal");
httpResponse.setStatusCode(HttpResponse::Bad_Request);
}
else // 报文合法
{
httpResponse.setStatusCode(HttpResponse::OK);
// 请求的资源可能是一个文件、自定义的一个回调方法、甚至可执行程序
filePath = httpRequest.get_path();
if (getWays.count(filePath)) // 自定义的资源处理方法
{
cout << "get:" << httpRequest.get_param_value("word") << endl;
getWays[filePath](httpRequest, httpResponse);
httpResponse.pushToOutBuffer(conn);
conn->sender_(conn);
return 0;
}
else
{
filePath = baseDir_ + filePath;
struct stat st;
if (stat(filePath.c_str(), &st) == 0) // 文件或路径存在
{
if (S_ISDIR(st.st_mode)) // 说明请求的资源是个目录
{
// 则响应当前目录下的HOME_PAGE文件
filePath += "/";
filePath += HOME_PAGE;
if (stat(filePath.c_str(), &st) == 0) // 文件存在
{
fileSize = st.st_size;
}
else
{
httpResponse.setStatusCode(HttpResponse::Not_Found);
}
}
else if ((st.st_mode & S_IXGRP) ||
(st.st_mode & S_IXUSR) ||
(st.st_mode & S_IXOTH)) // 可执行程序
{
httpRequest.setCgi(true);
}
else // 一般资源
{
fileSize = st.st_size;
}
}
else // 文件不存在
{
httpResponse.setStatusCode(HttpResponse::Not_Found);
}
}
}
// 更新请求报文中的文件名和文件大小
httpRequest.path = filePath;
httpRequest.fileSize = fileSize;
// 添加报头行
httpResponse.setHeader("Content-Length", to_string(fileSize));
httpResponse.setHeader("Content-Type", getContentType(filePath));
// 如果非cgi,获取资源文件
if (!httpRequest.cgi())
{
string &filePath = httpRequest.path;
int fileSize = httpRequest.fileSize;
logMessage(INFO, "filePath:%s,fileSize:%d", filePath.c_str(), fileSize);
CgiTaskCb_f cb = std::bind(&HttpServer::processNonCgi, this, placeholders::_1, placeholders::_2, placeholders::_3);
CgiTask task(httpRequest, httpResponse, conn, cb);
cgiHander->push(task);
// processNonCgi(httpRequest, httpResponse, conn);
}
else
{
// 创建任务,将任务交给线程池进行处理
CgiTaskCb_f cb = std::bind(&HttpServer::processCgi, this, placeholders::_1, placeholders::_2, placeholders::_3);
CgiTask task(httpRequest, httpResponse, conn, cb);
cgiHander->push(task);
// processCgi(httpRequest, httpResponse, conn);
}
return 0;
}
// 从文件名获取文件类型
string getContentType(string &path)
{
static const unordered_map<string, string> suffix2type = {
{".html", "text/html"},
{".css", "text/css"},
{".js", "application/x-javascript"},
{".jpg", "image/jpeg"},
{".xml", "text/xml"}};
string suffix = Util::getFileSuffix(path);
auto it = suffix2type.find(suffix);
string type;
if (it != suffix2type.end())
{
type = it->second;
}
else
{
type = "text/html";
}
return type;
}
int processNonCgi(HttpRequest &httpRequest, HttpResponse &httpResponse, Connection *conn)
{
string &filePath = httpRequest.path;
int fileSize = httpRequest.fileSize;
// logMessage(INFO, "filePath:%s,fileSize:%d", filePath.c_str(), fileSize);
int fd = open(filePath.c_str(), O_RDONLY);
if (fd < 0) // 打开文件失败
{
logMessage(WARNING, "open file %s fail", filePath.c_str());
httpResponse.setStatusCode(HttpResponse::Not_Found);
}
else
{
httpResponse.setFileToSend(fd, fileSize);
}
// 将响应报文push到tcpServer的发送缓冲区
httpResponse.pushToOutBuffer(conn);
// 发送
conn->sender_(conn);
return 0;
}
// 处理cgi请求
int processCgi(HttpRequest &httpRequest, HttpResponse &httpResponse, Connection *conn)
{
// 正文部分,我们将数据放入reactor的Connection,非阻塞式发送
// 参数部分,通过环境变量传送
logMessage(INFO, "process cgi begin");
// 创建管道,用于父子进程通信
// 父进程向output写,从input读
// 子进程向input写,从output读
int input[2];
int output[2];
if (pipe(input) < 0)
{
logMessage(ERROR, "pipe error: FILE[%s] LINE[%d]", __FILE__, __LINE__);
return HttpResponse::Server_Error;
}
if (pipe(output) < 0)
{
logMessage(ERROR, "pipe error: FILE[%s] LINE[%d]", __FILE__, __LINE__);
return HttpResponse::Server_Error;
}
// 创建子进程处理cgi请求
pid_t pid = fork();
if (pid == 0) // 子进程
{
// 子进程向input(1)写,从output(0)读
// 关input(0),output(1)
close(input[0]);
close(output[1]);
// 进程替换后子进程的文件描述符表虽然和父进程的一样,但是input和output两个变量没有了
// 所以先进行文件描述符重定向,让input[1]代替子进程的标准输出,output[0]代替标准输入
dup2(input[1], 1);
dup2(output[0], 0);
setenv("METHOD", httpRequest.get_methord().c_str(), 0);
if (httpRequest.get_methord() == "GET")
{
setenv("QUERRY_STRING", httpRequest.getParamStr().c_str(), 0);
}
else if (httpRequest.get_methord() == "POST")
{
string bodySize = to_string(httpRequest.body_size());
setenv("CONTENT_LENGTH", bodySize.c_str(), 0);
}
// 进行程序替换
const char *path = httpRequest.path.c_str();
execl(path, path, nullptr);
logMessage(WARNING, "execl fail");
}
else if (pid < 0) // error
{
logMessage(ERROR, "fork error: FILE[%s] LINE[%d]", __FILE__, __LINE__);
return HttpResponse::Server_Error;
}
else // 父进程
{
// 父进程向output(1)写,从input(0)读
// 关input(1),output(0)
close(input[1]);
close(output[0]);
// 父进程要给子进程发送两种信息:POST的正文部分,url的参数
if (httpRequest.get_methord() == "POST")
{
// 将正文部分发给子进程
string &body = httpRequest.request_body;
int total = body.size();
while (total > 0)
{
// 阻塞式发送
int n = write(output[1], body.c_str(), body.size());
if (n > 0)
{
total -= n;
}
else if (n < 0 && errno != EINTR)
{
// 管道出问题了
httpResponse.setStatusCode(HttpResponse::Server_Error);
break;
}
}
#if 0
Connection *sendEvent = conn->R_->AddConnection(output[1], EPOLLET); // 从反应堆创建事件
sendEvent->setSender(std::bind(&HttpServer::write2pipe, this, placeholders::_1)); // 为之间设置读方法
sendEvent->push2OutBuffer(body); // 向事件缓冲区发送数据
sendEvent->sender_(sendEvent); // 发送
#endif
}
// 接收子进程发来的消息
if (httpResponse.status_code != HttpResponse::Server_Error)
{
string response_body;
char c;
while (true)
{
int n = read(input[0], &c, 1);
if (n > 0)
{
response_body += c;
}
else if (n == 0) // 对端程序退出或管道关闭,读取结束
{
break;
}
else if (errno != EINTR) // 发生了异常
{
httpResponse.setStatusCode(HttpResponse::Server_Error);
break;
}
}
httpResponse.response_body_str = response_body;
// 将响应报文push到tcpServer的发送缓冲区
httpResponse.pushToOutBuffer(conn);
// 发送
conn->sender_(conn);
}
// 父进程释放资源管道
close(input[0]);
close(output[1]);
waitpid(pid, nullptr, 0);
}
return httpResponse.status_code;
}
private:
#if 0
int write2pipe(Connection *conn)
{
logMessage(INFO, "write2pipe");
Connection::OutBuffer_str *buff = (Connection::OutBuffer_str *)conn->getOutBuffer();
string &str = buff->str;
while (str.size())
{
ssize_t n = write(conn->sock(), buff->str.c_str(), buff->str.size());
logMessage(INFO, "send: %d", n);
// send与recv不同,无法感知对端是否退出
if (n > 0) // 已经发送了的数据
{
// 从connection缓冲区去除已经发送的数据
logMessage(INFO, "str.erase(0, n),str: %d", buff->str.size());
buff->str.erase(0, n);
logMessage(INFO, "str.erase(0, n),str: %d", buff->str.size());
}
else
{
if (n == 0) // 什么都没发出去
{
// outbuffer没有数据了,传的size是0
break;
}
else if (errno == EINTR)
continue;
else if (errno == EAGAIN || errno == EWOULDBLOCK)
{
// 内核缓冲区满了,但是outbuffer还有数据,就需要再打开写事件
break;
}
else
{
conn->excepter_(conn);
logMessage(WARNING, "send error[%d]:%s", errno, strerror(errno));
return -1;
}
}
}
int epfd = tcpServer_.epfd();
// 还有数据,打开写监管,tcp的写缓冲区一有位置,Sender会再次被唤醒
if (!str.empty()) // 当前文件还有数据
{
logMessage(INFO, "!str.empty()");
Epoller::EnableReadWrite(conn->sock(), epfd, false, true);
}
else if (conn->outQueue.size() > 1) // 当前文件发完了,但是队列里还有其它文件或字符串
{
// 释放掉队列中的当前块
conn->outQueue.pop();
delete buff;
write2pipe(conn);
}
else // 写完了,且队列里没有其它数据了
{
// 发送任务已完成,调用Excepter,关闭文件描述符
tcpServer_.TcpExcepter(conn);
}
return 0;
}
#endif
};
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
C/C++
1
https://gitee.com/Y_future/http.git
git@gitee.com:Y_future/http.git
Y_future
http
http
master

搜索帮助

344bd9b3 5694891 D2dac590 5694891