[项目][WebServer][构建响应 && 发送响应]详细讲解
DieSnowK 2024-09-17 13:03:01 阅读 58
目录
1.构建响应2.发送响应
1.构建响应
构建响应流程如下
确认方法根据不同方法,以不同方法提参确认访问资源 如果用户的URL没有指明要访问的某种资源(路径),虽然浏览器默认会添加/,但是依旧没有告知服务器,要访问什么资源
此时,默认返回对应服务的首页这里的/不是Linux服务器的根目录,通常是http服务器设置的自己的WEB根目录 关于stat
stat()返回一个文件的信息,信息都存在一个struct stat结构体变量中
细节可以:<code>man 2 stat struct stat可以判断
是否是一个可执行程序该文件的大小… 判断文件是否是可执行程序
若不是,则走ProcessNonCgi
逻辑,返回静态网页若是,则走ProcessCgi
逻辑,将参数传递给子程序处理 请求资源是什么类型,取决于资源的后缀为什么要使用goto?
分析协议的每一步,都有可能出错所以在做出错处理的时候,如果没有goto,会导致使用大量的if判断
void BuildResponse()
{
size_t pos = 0;
if (_request.method != "GET" && _request.method != "POST")
{
// 非法请求
_response.status_code = BAD_REQUEST; // TODO
LOG(WARNING, "Method is not right");
goto END;
}
if (_request.method == "GET")
{
// GET可能带参数,也可能不带参数,要区分出来
if (_request.uri.find('?') != std::string::npos)
{
Util::CutString(_request.uri, _request.path, _request.arg, "?");
_request.cgi = true;
}
else
{
_request.path = _request.uri;
}
}
else if(_request.method == "POST")
{
_request.cgi = true;
_request.path = _request.uri;
}
else
{
// Do Nothing
}
_request.path.insert(0, WEB_ROOT); // 从根目录开始
// 如果访问的是根目录,则默认访问主页
if(_request.path[_request.path.size() - 1] == '/')
{
_request.path += HOME_PAGE;
}
// 确认访问资源是否存在
struct stat st;
if(stat(_request.path.c_str(), &st) == 0) // TODO 待整理stat()
{
// 访问的是否是一个具体资源?
if(S_ISDIR(st.st_mode))
{
// 请求的是一个目录,需要特殊处理 -- 改为访问该目录下主页
// 虽然是目录,但是绝对不会以/结尾
_request.path += "/";
_request.path += HOME_PAGE;
// 上面更改了path指向,所以重新获取文件状态
stat(_request.path.c_str(), &st);
}
// 是否是一个可程序程序?
if (st.st_mode & S_IXUSR || st.st_mode & S_IXGRP || st.st_mode & S_IXOTH)
{
_request.cgi = true; // TODO CGI标志位感觉有多余
}
_response.fSize = st.st_size;
}
else
{
// 资源不存在
LOG(WARNING, _request.path + " Not Found");
_response.status_code = NOT_FOUND;
goto END;
}
// 读取文件后缀
pos = _request.path.rfind('.');
if (pos== std::string::npos)
{
_request.suffix = ".html"; // 没找到就默认设置为html
}
else
{
_request.suffix = _request.path.substr(pos);
}
if(_request.cgi)
{
_response.status_code = ProcessCgi(); // 执行目标程序,拿到结果到_response.response_body
}
else
{
// 1.至此,目标网页一定是存在的
// 2.返回并不是单单返回网页,而是要构建HTTP响应
_response.status_code = ProcessNonCgi(); // 简单的网页返回,返回静态网页,只需要打开即可
}
END:
// 最后统一构建响应
BuildResponseHelper();
return;
}
void BuildResponseHelper()
{
// 此处status_line是干净的,没有内容的
// 构建状态行
_response.status_line += HTTP_VERSION;
_response.status_line += " ";
_response.status_line += std::to_string(_response.status_code);
_response.status_line += " ";
_response.status_line += Util::Code2Desc(_response.status_code);
_response.status_line += LINE_END;
// 构建响应正文,可能包括响应报头
std::string path = WEB_ROOT;
path += '/';
switch (_response.status_code)
{
case OK:
BuildOKResponse();
break;
case NOT_FOUND:
path += PAGE_404;
HandlerError(path);
break;
case BAD_REQUEST: // 暂时先404处理,后面有需要再改
path += PAGE_404;
HandlerError(path);
break;
case SERVER_ERROR:
path += PAGE_404;
HandlerError(path);
break;
default:
break;
}
}
void BuildOKResponse()
{
// Header
std::string header_line = "Content-Type: ";
header_line += Util::Suffix2Desc(_request.suffix);
header_line += LINE_END;
_response.response_header.push_back(header_line);
header_line = "Content-Length: ";
if(_request.cgi)
{
header_line += std::to_string(_response.response_body.size());
}
else
{
header_line += std::to_string(_response.fSize);
}
header_line += LINE_END;
_response.response_header.push_back(header_line);
}
2.发送响应
注意:
分多次发和把所有内容合成一个字符串一次性发送是没区别的send只是把内容拷贝到发送缓冲区中具体什么时候发,一次性发多少,是由TCP决定的 此处_response.response_body
发送逻辑确保了_response.response_body
能全部发完此次请求为ProcessNonCgi
时,需要从磁盘读取文件内容,再把内容拷贝到发送缓冲区中,这样是效率低下的
sendfile()
可以在两个文件描述符之间拷贝数据
即:sendfile()
可以把文件内容,直接拷贝到Tcp的发送缓冲区中,提高了效率 这个拷贝实在内核态完成的,所以比read() / write()
高效的多
void SendResponse()
{
send(_sock, _response.status_line.c_str(), _response.status_line.size(), 0);
for(auto& str : _response.response_header)
{
send(_sock, str.c_str(), str.size(), 0);
}
send(_sock, _response.blank.c_str(), _response.blank.size(), 0);
if(_request.cgi)
{
const char *start = _response.response_body.c_str();
size_t size = 0, total = 0;
while (total < _response.response_body.size() &&
(size = send(_sock, start + total, _response.response_body.size() - total, 0) > 0))
{
total += size;
}
}
else
{
sendfile(_sock, _response.fd, nullptr, _response.fSize);
close(_response.fd);
}
}
声明
本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。