首页 > 其他分享 >WebServer构建响应 && 发送响应

WebServer构建响应 && 发送响应

时间:2024-10-15 15:21:34浏览次数:9  
标签:status request 响应 WebServer && path line response size

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);
    }
}

标签:status,request,响应,WebServer,&&,path,line,response,size
From: https://blog.csdn.net/zz130428/article/details/142954553

相关文章

  • 应急响应处置流程Windows篇
    一、服务流程沟通确认安全事件在与客户第一次沟通时,应及时提醒客户对受害机器及时进行断网隔离操作。已知的安全事件包括但不限于如下:1. 恶意代码威胁,即僵尸网络、恶意木马、蠕虫病毒、勒索病毒等恶意代码导致的安全事件2. 高级持续性威胁(APT)攻击事件,即具有潜伏性、隐......
  • 对vue响应式数据的理解(vue基础,面试,源码级讲解)
    首先我们要知道哪些数据可以劫持。  是否可以劫持:在JavaScript等动态语言中,字符串和数字虽然是基本数据类型(也称为原始数据类型),但它们可以包装成对象(如String对象和Number对象)进行处理。当它们被包装成对象后,就可以使用对象的方法,包括Object.defineProperty等方法进行数据......
  • proxy代理机制和工作原理,reactive是怎么通过proxy实现响应式的
    1.什么是ProxyProxy是JavaScript中一个用于创建代理对象的构造函数,允许你定义基本操作(如属性查找、赋值、枚举、函数调用等)的自定义行为。通过Proxy,你可以对一个对象进行拦截,并在该对象的操作上添加自定义逻辑。在Vue3中,Proxy被广泛用于实现响应式系统。2.代理......
  • 看不懂来打我!让性能提升56%的Vue3.5响应式重构
    前言在Vue3.5版本中最大的改动就是响应式重构,重构后性能竟然炸裂的提升了56%。之所以重构后的响应式性能提升幅度有这么大,主要还是归功于:双向链表和版本计数。这篇文章我们来讲讲使用双向链表后,Vue内部是如何实现依赖收集和依赖触发的。搞懂了这个之后你就能掌握Vue3.5重构后的响......
  • 未发表的原创模型!三类典型需求响应负荷的标准化建模+共享储能提升灵活性(Matlab代码实
      ......
  • 【应急响应+Linux】常见的rootkit隐藏手段:前言
    原文首发在:奇安信攻防社区https://forum.butian.net/share/3796本文主要针对黑灰产相关的蠕木僵毒等恶意软件在Linux上常用的rootkit手段做一些总结,以及详细分析常见应急响应中遇到的进程、文件隐藏手段的原理以及排查和恢复方法;前言本文主要针对黑灰产、以及蠕木僵毒等恶意软......
  • 【应急响应+Linux】常见的rootkit隐藏手段:通过用户层劫持加载器/连接器隐藏进程pld(用
    原理linux在进程启动后,和windows加载dll一样会按照一定的顺序加载动态链接库,相关顺序如下:加载环境变量LD_PRELOAD指定的动态库加载文件/etc/ld.so.preload指定的动态库搜索环境变量LD_LIBRARY_PATH指定的动态库搜索路径搜索路径/lib64下的动态库文件攻击者常见使用的劫......
  • 【应急响应+Linux】常见的rootkit隐藏手段:通过劫持shell环境,实现文件、进程名隐藏等操
    原理修改或构造/etc/profile.d/下sh文件,劫持环境变量,从而实现覆盖常见的命令,如:ps、ls、lsof等;实现:1、配置环境变量shell脚本:重新登录用户之后;或者使用命令source/etc/profile更新配置,使生效;2、根目录下存在的myshell.sh文件被隐藏:执行ls命令效果:排查方法:使用strace......
  • 【应急响应+Linux】常见的rootkit隐藏手段:通过挂载/proc/pid实现pid隐藏
    原理ps、netstat是遍历/proc来显示pid的原理,通过隐藏相关/proc/pid文件夹来实现pid隐藏实现运行如下命令,将pid对应文件夹挂载到隐藏目录上面mount-obind/home/.hidden/proc/9212现象:如下图,使用root权限调用netstat发现PID和Programname都是空:排查方法1、ca......
  • flaks 请求 与 响应 重定向
    请求Request请求 属性ur1完整请求地址base_url去掉GET参数的URLhosturl只有主机和端口号的URLpath路由中的路径method请求方法remote_addr请求的客户端地址argsGET请求参数formPOST请求参数files文件上传headers......