[项目][WebServer][构建响应 && 发送响应]详细讲解

前端 0

目录

  • 1.构建响应
  • 2.发送响应


1.构建响应

  • 构建响应流程如下
    • 确认方法
    • 根据不同方法,以不同方法提参
    • 确认访问资源
  • 如果用户的URL没有指明要访问的某种资源(路径),虽然浏览器默认会添加/,但是依旧没有告知服务器,要访问什么资源
    • 此时,默认返回对应服务的首页
    • 这里的/不是Linux服务器的根目录,通常是http服务器设置的自己的WEB根目录
  • 关于stat
    • stat()返回一个文件的信息,信息都存在一个struct stat结构体变量中
      • 细节可以: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);    }}

也许您对下面的内容还感兴趣: