1.构建响应
- 构建响应流程如下
- 如果用户的URL没有指明要访问的某种资源(路径),虽然浏览器默认会添加/,但是依旧没有告知服务器,要访问什么资源
- 此时,默认返回对应服务的首页
- 这里的/不是Linux服务器的根目录,通常是http服务器设置的自己的WEB根目录
- 关于stat
- stat()返回一个文件的信息,信息都存在一个struct 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; LOG(WARNING, "Method is not right"); goto END; } if (_request.method == "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 { } _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) { if(S_ISDIR(st.st_mode)) { _request.path += "/"; _request.path += HOME_PAGE; 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; } _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"; } else { _request.suffix = _request.path.substr(pos); } if(_request.cgi) { _response.status_code = ProcessCgi(); } else { _response.status_code = ProcessNonCgi(); } END: BuildResponseHelper(); return;}void BuildResponseHelper(){ _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: path += PAGE_404; HandlerError(path); break; case SERVER_ERROR: path += PAGE_404; HandlerError(path); break; default: break; }}void BuildOKResponse(){ 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); }}