[项目][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);

}

}



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。