TinyWebserver的复现与改进(5):HTTP报文的解析与响应

news2024/9/22 13:32:12

GitHub - yzfzzz/MyWebServer: Linux高并发服务器项目,参考了TinyWebServer,将在此基础上进行性能改进与功能增加。为方便读者学习,附带详细注释和博客!

webserver

TinyWebserver的复现与改进(1):服务器环境的搭建与测试-CSDN博客

TinyWebserver的复现与改进(2):项目的整体框架-CSDN博客

TinyWebserver的复现与改进(3):线程同步机制类封装及线程池实现-CSDN博客

TinyWebserver的复现与改进(4):主线程的具体实现-CSDN博客

TinyWebserver的复现与改进(4):主线程的具体实现-CSDN博客

HTTP 协议是基于 TCP/IP 协议之上的应用层协议,基于 请求-响应 的模式。HTTP 协议规定,请求从客 户端发出,最后服务器端响应该请求并返回。换句话说,肯定是先从客户端开始建立通信的,服务器端 在没有接收到请求之前不会发送响应。

HTTP报文的格式

5372f4ffb55e4129bb9d3e4a303f894b

下面就是一段http协议的请求报文1️⃣

GET /index.html HTTP/1.1
Host: 124.223.141.236:10000
Content-Length: 27
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.60 Safari/537.36 Edg/100.0.1185.29
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate 
Accept-Language: zh-CN,zh;q=0.9
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1

  • GET /index.html HTTP/1.1是请求行,GET是请求方法(表示向指定的资源发出显示请求),/index.html是客户端想访问的地址(有些是http://194.118.249.130:10000/index.html),HTTP/1.1是http协议的版本

  • 每行语句结束后,都会有 '\r'(CR) '\n'(LF)作为结尾,这是我们判断一行行的重要语句

http_conn模块流程图

http_conn 模块将协议的读 read、写 write 放在了process函数里。

在 read 函数中,实现的是一个简单的有限状态机:

  1. 请求报文解析:程序通过parse_line函数,依据\r\n作为分隔符,将接收到的请求报文(如1️⃣所示)逐行切割成独立的请求行(如GET /index.html HTTP/1.1)和请求头(如Host: 124.223.141.236:10000)。

  2. 请求行与请求头解析:随后,程序按照顺序首先尝试解析请求行(通过parse_request_line函数)。若此步骤成功,则继续解析请求头(即首部行,通过parse_headers函数)。若请求行解析失败,则执行mod_fd操作,以准备下一次通过socket_fd接收数据,并结束当前解析过程,同时向客户端返回错误码。

  3. 请求处理:若请求头解析成功,则触发do_request函数的执行。该函数负责检查请求文件的权限、类型等关键信息。若在此过程中发现任何问题,将同样向客户端返回错误码。

  4. 消息实体解析的省略:理论上,程序接下来会执行parse_content函数以解析请求的消息实体部分。然而,在本代码实现中,一旦请求头解析成功,程序便直接跳转到文件检查阶段,跳过了parse_content的执行。因此,该步骤在流程图中以虚线表示,表明其在本上下文中被省略。

  5. 文件处理与响应发送:若文件检查成功,程序将以只读模式打开该文件,并对其进行内存映射,以便高效地处理数据。此时,虽然已准备好客户端所需的数据,但在发送之前,还需构建HTTP响应报文。这一任务由write函数承担,它负责将HTTP响应报文连同文件内容一起发送给客户端。若文件检查失败,则同样会向客户端返回错误码。

image-20240814161800602

write 函数:

  • http响应报文格式与请求报文格式相同,因此我们只需要 “看图说话即可”,在本项目中,我们的响应报文只写了请求行和请求头,其底层是基于 add_response 实现的,此时我们的响应报文将会存储在写缓存区中。

  • 当客户端在浏览器中输入http://194.118.249.130:10000/index.html时,说明他想发访问我们的 index.html 文件,如果一切顺利,文件会进行内存映射(将磁盘文件的数据映射到内存,用户通过修改内存就能修改磁盘文件,提高了效率),因此 index.html 文件字符串首地址和响应报文的字符串首地址不同,所以我们要使用 writev 函数分散写,writev 会将多个缓冲区写入同一个文件描述符fd中

小结

至此,最基本的代码已经完成了,当我们访问(http://123.226.142.236:10000/index.html(换上你的主机ip)后,会得到如下界面:

image-20240814172218513

我们已经实现了服务器解析、响应GET的功能,距离复现完TinyWebServer仍有较长的路要走…

http_conn模块完整代码

http_conn.h

#pragma once
#include <arpa/inet.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <cstdarg>
#include <sys/types.h>  
#include <sys/uio.h>

ssize_t writev(int fd, const struct iovec *iov, int iovcnt);

class http_conn{
public:
    static int m_epollfd;
    static int m_user_count;
    static const int FILENAME_LEN = 200;  // 文件名的最大长度
    static const  int READ_BUFFER_SIZE = 2048;
    static const  int WRITE_BUFFER_SIZE = 1024;


    // 读缓冲区
    char m_read_buf[READ_BUFFER_SIZE];
    // 标记读缓冲区空余位置的首地址
    int m_read_idx;
    // 写缓冲区
    char m_write_buf[WRITE_BUFFER_SIZE];
    int m_write_idx;

    // HTTP请求方法,这里只支持GET
    enum METHOD {GET = 0, POST, HEAD, PUT, DELETE, TRACE, OPTIONS, CONNECT};
    /*
    解析客户端请求时,主状态机的状态
    CHECK_STATE_REQUESTLINE:当前正在分析请求行
    CHECK_STATE_HEADER:当前正在分析头部字段
    CHECK_STATE_CONTENT:当前正在解析请求体
    */
    enum CHECK_STATE { CHECK_STATE_REQUESTLINE = 0, CHECK_STATE_HEADER,
    CHECK_STATE_CONTENT };
    /*
    服务器处理HTTP请求的可能结果,报文解析的结果
    NO_REQUEST : 请求不完整,需要继续读取客户数据
    GET_REQUEST : 表示获得了一个完成的客户请求
    BAD_REQUEST : 表示客户请求语法错误
    NO_RESOURCE : 表示服务器没有资源
    FORBIDDEN_REQUEST : 表示客户对资源没有足够的访问权限
    FILE_REQUEST : 文件请求,获取文件成功
    INTERNAL_ERROR : 表示服务器内部错误
    CLOSED_CONNECTION : 表示客户端已经关闭连接了
    */
    enum HTTP_CODE { NO_REQUEST, GET_REQUEST, BAD_REQUEST, NO_RESOURCE,
    FORBIDDEN_REQUEST, FILE_REQUEST, INTERNAL_ERROR, CLOSED_CONNECTION };
    // 从状态机的三种可能状态,即行的读取状态,分别表示
    // 1.读取到一个完整的行 2.行出错 3.行数据尚且不完整
    enum LINE_STATUS { LINE_OK = 0, LINE_BAD, LINE_OPEN };

public:
    http_conn(){}
    ~http_conn(){}
    void init(int sockfd, const sockaddr_in& addr);  // sockaddr_in&: 引用
    // 关闭连接
    void close_conn();
    // 非阻塞读
    bool read();
    // 非阻塞写
    bool write();
    void process();

private:
    void init();
    HTTP_CODE process_read();
    bool process_write(HTTP_CODE ret);
    LINE_STATUS parse_line();
    HTTP_CODE parse_request_line(char* text);
    HTTP_CODE parse_headers(char* text);
    HTTP_CODE parse_content(char* text);
    HTTP_CODE do_request();
    char* getline(){return m_read_buf + m_start_line;}
    void unmap();
    bool add_response(const char* format, ...);
    bool add_status_line(int status, const char* title);
    bool add_headers( int content_length );
    bool add_content_length( int content_length );
    bool add_linger();
    bool add_blank_line();
    bool add_content_type();
    bool add_content( const char* content );

private:
    int m_sockfd;
    int m_checked_idx;  // 当前正在分析的字符在读缓冲区中的位置
    sockaddr_in m_address;
    CHECK_STATE m_check_state;
    int m_start_line;           // 当前正在解析的起始地址
    char* m_url; // 客户请求的目标文件的文件名
    METHOD m_method; // 请求方法
    char* m_version; // HTTP协议版本号,我们仅支持HTTP1.1
    int m_content_length;
    bool m_linger; // HTTP请求是否要求保持连接
    char* m_host; // 主机名
    char m_real_file[FILENAME_LEN];  // 本地路径
    struct stat m_file_stat; // 目标文件的状态。通过它我们可以判断文件是否存在、是否为目录、是否可读,并获取文件大小等信息
    char* m_file_address; // 客户请求的目标文件被mmap到内存中的起始位置
    struct iovec m_iv[2];  // 从多个不同(不连续)的内存区域收集数据发送到一个网络套接字
    int m_iv_count;
    int bytes_to_send; // 将要发送的数据的字节数
    int bytes_have_send; // 已经发送的字节数
};

void addfd(int epollfd, int fd, bool one_shot);
void removefd(int epollfd, int fd);
int setnonblocking(int fd);

http_conn.c

#include "http_conn.h"

// 定义HTTP响应的一些状态信息
const char* ok_200_title = "OK";
const char* error_400_title = "Bad Request";
const char* error_400_form = "Your request has bad syntax or is inherentlyimpossible to satisfy.\n";
const char* error_403_title = "Forbidden";
const char* error_403_form = "You do not have permission to get file from thisserver.\n";
const char* error_404_title = "Not Found";
const char* error_404_form = "The requested file was not found on thisserver.\n";
const char* error_500_title = "Internal Error";
const char* error_500_form = "There was an unusual problem serving the requestedfile.\n";

// 网站的根目录
const char* doc_root = "/home/My_WebServer/resources";

int setnonblocking(int fd)
{
    int old_option = fcntl(fd, F_GETFL);
    int new_option = old_option | O_NONBLOCK;
    fcntl(fd, F_SETFL, new_option);
    return old_option;
}

// 将客户端的socket_fd加入到epoll_fd中
void addfd(int epollfd, int fd, bool one_shot)
{
    epoll_event event;
    event.data.fd = fd;
    // event.events是32位的掩码, EPOLLIN/EPOLLRDHUP表示在32bit的二进制上,只有1bit是1,其余都是0
    // event.events = EPOLLIN | EPOLLRDHUP;

    // 设为边沿触发
    event.events = EPOLLIN | EPOLLET | EPOLLRDHUP;
    // 设置该fd只能接受一次数据,再想接收需要调用modfd()函数
    if(one_shot)
    {
        event.events |= EPOLLONESHOT;
    }
    epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event);
    // 设置文件描述符为非阻塞
    setnonblocking(fd);
}

// 所有socket上的事件都被注册到同一个epoll内核事件中,所以设置成静态的
int http_conn::m_epollfd = -1;
// 所有的客户数
int http_conn::m_user_count = 0;

// 将客户端的socket_fd从epoll_fd中移除
void removefd(int epollfd, int fd)
{
    epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, 0);
    close(fd);
}

// 设置该fd只能接受一次数据,再想接收需要调用modfd()函数
void modfd(int epollfd, int fd, int ev)
{
    epoll_event event;
    event.data.fd = fd;
    event.events = ev | EPOLLET | EPOLLONESHOT | EPOLLRDHUP;
    epoll_ctl(epollfd, EPOLL_CTL_MOD, fd, &event);
}

// http_conn类的初始化, 实际上是客户端socket_fd的初始化
void http_conn::init(int sockfd, const sockaddr_in& addr)
{
    m_sockfd = sockfd;
    // 一个常量可以赋值给一个变量
    m_address = addr;
    // 设置端口复用
    int reuse = 1;
    setsockopt(m_sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
    addfd(m_epollfd, sockfd, true);
    m_user_count++;
    init();
}

// http_conn类各个成员变量的初始化
void http_conn::init()
{
    bytes_to_send = 0;
    bytes_have_send = 0;
    m_check_state = CHECK_STATE_REQUESTLINE;    // 初始状态为检查请求行
    m_linger = false;                           // 默认不保持链接 Connection : keep-alive保持连接
    m_method = GET;                             // 默认请求方式为GET
    m_url = 0;
    m_version = 0;
    m_content_length = 0;
    m_host = 0;
    m_start_line = 0;
    m_checked_idx = 0;
    m_read_idx = 0;
    m_write_idx = 0;
    // !内存地址的计算: 成员变量的存储时连续的吗
    bzero(m_read_buf, READ_BUFFER_SIZE);
    bzero(m_write_buf, WRITE_BUFFER_SIZE);
    bzero(m_real_file, FILENAME_LEN);
}

void http_conn::close_conn()
{
    if(m_sockfd != -1)
    {
        removefd(m_epollfd, m_sockfd);
        m_sockfd = -1;
        m_user_count--; // 关闭一个连接,客户总量-1
    }
}

bool http_conn::read()
{
    if(m_read_idx >= READ_BUFFER_SIZE)
    {
        // 读缓冲满了, 应用层还未处理该缓冲区数据
        return false;
    }
    int byte_read = 0;
    while(1)
    {
        
        byte_read = recv(m_sockfd, m_read_buf+m_read_idx, READ_BUFFER_SIZE-m_read_idx, 0);
        if(byte_read == -1)
        {
            if(errno == EAGAIN || errno == EWOULDBLOCK)
            {
                // 没有数据
                break;
            }
            
            return false;
        }
        else if(byte_read == 0)
        {
            // 对方关闭连接
            return false;
        }

        m_read_idx += byte_read;
    }
    
    // printf("%s\n", m_read_buf);

    return true;
}

// 解绑内存映射
void http_conn::unmap()
{
    if(m_file_address)
    {
        munmap(m_file_address, m_file_stat.st_size);
        m_file_address = 0;
    }
}

bool http_conn::write()
{
    int temp = 0;
    if(bytes_to_send == 0)
    {
        // 将要发送的字节为0,本次响应结束
        modfd(m_epollfd, m_sockfd, EPOLLIN);
        init();
        return true;
    }
    while(1)
    {
        // 分散写
        temp = writev(m_sockfd, m_iv, m_iv_count);
        if(temp <= -1)
        {
            // 如果tcp没有写缓冲的空间,则等待下一轮可写事件
            if(errno == EAGAIN)
            {
                modfd(m_epollfd, m_sockfd, EPOLLOUT);
                return true;
            }
            unmap();
            return false;
        }

        bytes_to_send -= temp;
        bytes_have_send += temp;
        // 发完m_iv[0]了
        if(bytes_have_send >= m_iv[0].iov_len)
        {
            m_iv[0].iov_len  = 0;
            m_iv[1].iov_base = m_file_address +(bytes_have_send-m_write_idx);
            m_iv[1].iov_len = bytes_to_send;
        }
        // 发完m_iv[0]的部分数据,还有一部分没发
        else
        {
            m_iv[0].iov_base = m_write_buf + bytes_have_send;
            m_iv[0].iov_len = m_iv[0].iov_len - temp;
        }

        if(bytes_to_send <= 0)
        {
            // 没有数据要发送了
            unmap();
            modfd(m_epollfd, m_sockfd, EPOLLIN);

            if(m_linger)
            {
                init();
                return true;
            }
            else
            {
                return false;
            }
        }
    }
}

// 预处理, 解析出一行一行, 判断依据: \r\n
http_conn::LINE_STATUS http_conn::parse_line()
{
    char temp;
    for(;m_checked_idx < m_read_idx; m_checked_idx++)
    {
        temp = m_read_buf[m_checked_idx];
        if(temp == '\r')
        {
            if((m_checked_idx+1) == m_read_idx)
            {
                // 数据不完整
                return LINE_OPEN;
            }
            else if(m_read_buf[m_checked_idx+1] == '\n')
            {
                m_read_buf[m_checked_idx++] = '\0';  // 先变成'\0', 再+1
                m_read_buf[m_checked_idx++] = '\0';
                return LINE_OK;
            }
            return LINE_BAD;
        }
        // 上一次末尾是'\r', 这一次开头是'\n'
        else if(temp == '\n')
        {
            if((m_checked_idx > 1)&&(m_read_buf[m_checked_idx-1]=='\r'))
            {
                m_read_buf[m_checked_idx-1] = '\0';
                m_read_buf[m_checked_idx++] = '\0';
                return LINE_OK;
            }
            return LINE_BAD;
        }
    }
    return LINE_OPEN;
}

// 对请求行进行处理, 获取请求方法、目标URL、HTTP版本号
http_conn::HTTP_CODE http_conn::parse_request_line(char* text)
{
    // 需要解析的请求行内容: GET /index.html HTTP/1.1
    m_url = strpbrk(text, " ");    // 找出第一个 " "在text出现的位置
    if(!m_url)
    {
        return BAD_REQUEST;
    }
    *m_url++ = '\0';    // m_url 当前指向的地址被解引用(*m_url),然后将这个位置的字符设置为 '\0'。然后,将 m_url 自增,使其指向下一个字符的位置。
    // 当前请求行内容: GET\0/index.html HTTP/1.1
    char* method = text;
    if(strcasecmp(method, "GET") == 0)
    {
        // 忽略大小写比较
        m_method = GET;
    }
    else
    {
        return BAD_REQUEST;
    }

    // 此时m_url经过++后,指向 /index.html HTTP/1.1
    m_version = strpbrk(m_url, " ");
    if(!m_version)
    {
        return BAD_REQUEST;
    }

    *m_version++ = '\0';
    // m_version指向 HTTP/1.1
    if(strcasecmp(m_version, "HTTP/1.1") != 0)
    {
        return BAD_REQUEST;
    }

    // 有些请求不是 /index.html, 而是 http://192.168.110.129:10000/index.html
    if(strncasecmp(m_url, "http://", 7) == 0)
    {
        m_url += 7;
        // m_url指向 192.168.110.129:10000/index.html
        m_url = strchr(m_url, '/'); // s搜索字符串中第一次出现 '/’ 的位置
    }
    // m_url指向 /index.html
    if(!m_url || m_url[0] != '/')
    {
        return BAD_REQUEST;
    }
    // 检查状态变成检查头
    m_check_state = CHECK_STATE_HEADER;
    return NO_REQUEST;
}

http_conn::HTTP_CODE http_conn::parse_headers(char* text)
{
    // 因为parse_line()预处理时,已经把\r\n替换为\0\0
    if(text[0] == '\0')
    {
        // 如果HTTP请求有消息体,则还需要读取m_content_length字节的消息体,
        // 状态机转移到CHECK_STATE_CONTENT状态
        if(m_content_length != 0)
        {
            m_check_state = CHECK_STATE_CONTENT;
            return NO_REQUEST;
        }
        // 否则得到一个完整的HTTP请求
        return GET_REQUEST;
    }
    else if(strncasecmp(text, "Connection:", 11) == 0)
    {
        // 处理Connection字段 Connection: keep-alive
        text += 11;
        text += strspn(text, " ");  // 返回text字符串中,前缀部分(' ')的长度
        if(strcasecmp(text, "keep-alive") == 0)
        {
            m_linger = true;
        }
    }
    else if(strncasecmp(text, "Content-Length:", 15) == 0)
    {
        // 处理Content-Length头部字段
        text +=15;
        text += strspn(text, " ");
        m_content_length = atoi(text);
    }
    else if(strncasecmp(text, "HOST:", 5) == 0)
    {
        // 处理HOST头部字段
        text += 5;
        text += strspn(text, " ");
        m_host = text;
    }
    else
    {
        printf("oop! unknow header %s\n", text);
    }
    return NO_REQUEST;
}

// 解析HTTP请求的消息体
http_conn::HTTP_CODE http_conn::parse_content(char* text)
{
    if(m_read_idx >= (m_content_length + m_checked_idx))
    {
        text[m_content_length] = '\0';
        return GET_REQUEST;
    }

    return NO_REQUEST;
}

// 当得到一个完整、正确的HTTP请求时,我们就分析目标文件的属性,
// 如果目标文件存在、对所有用户可读,且不是目录,则使用mmap将其
// 映射到内存地址m_file_address处,并告诉调用者获取文件成功
http_conn::HTTP_CODE http_conn::do_request()
{
    
    // "/home/WebServer/resources"
    strcpy(m_real_file, doc_root);  // 于将一个字符串(doc_root)复制到另一个字符串(m_real_file)中
    
    int len = strlen(doc_root);
    // strncpy 从源字符串m_url复制最多FILENAME_LEN-len-1字符到目标字符串m_real_file+len中。
    // 如果m_url的长度小于FILENAME_LEN-len-1,则m_real_file+len中剩余的部分会用空字符 '\0' 填充
    strncpy(m_real_file+len, m_url, FILENAME_LEN-len-1);
    // 获取文件的状态
    if(stat(m_real_file, &m_file_stat) < 0)
    {
        return NO_RESOURCE;
    }

    // 判断访问权限
    if(!(m_file_stat.st_mode & S_IROTH))
    {
        return FORBIDDEN_REQUEST;
    }

    // 判断是否为目录
    if(S_ISDIR(m_file_stat.st_mode))
    {
        return BAD_REQUEST;
    }

    // 以只读的方式打开文件
    int fd = open(m_real_file, O_RDONLY);
    // !创建内存映射
    m_file_address = (char*)mmap(0, m_file_stat.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
    close(fd);
    return FILE_REQUEST;
}

// 客户端一旦请求,服务器就发送数据给客户端
void http_conn::process()
{
    // 客户端发送请求, 服务器解析http请求
    HTTP_CODE read_ret = process_read();
    if(read_ret == NO_REQUEST)
    {
        modfd(m_epollfd, m_sockfd, EPOLLIN);
        return;
    }
    // 生成响应,服务器发送数据给客户端
    bool write_ret = process_write(read_ret);
    if(!write_ret)
    {
        close_conn();
    }
    // 表示有数据发送
    modfd(m_epollfd, m_sockfd, EPOLLOUT);
}

// 主状态机, 解析请求
http_conn::HTTP_CODE http_conn::process_read()
{
    LINE_STATUS line_status = LINE_OK;
    HTTP_CODE ret = NO_REQUEST;
    char* text = 0;
    // CHECK_STATE_CONTENT:当前正在解析请求体
    while( ((m_check_state == CHECK_STATE_CONTENT)&&(line_status == LINE_OK)) ||
    ((line_status = parse_line()) == LINE_OK) )
    {
        // 成功读到一行
        text = getline();

        // 更新下一次解析的起始位置
        m_start_line = m_checked_idx;
        // printf("got a http line: %s\n", text);

        switch(m_check_state)
        {
            // 解析请求行
            case CHECK_STATE_REQUESTLINE:
            {
                ret = parse_request_line(text);
                if(ret == BAD_REQUEST)
                {
                    return BAD_REQUEST;
                }
                break;
            }

            // 解析请求头
            case CHECK_STATE_HEADER:
            {
                ret = parse_headers(text);
                if(ret == BAD_REQUEST)
                {
                    return BAD_REQUEST;
                }
                else if(ret == GET_REQUEST)
                {
                    return do_request();
                }
                break;
            }

            // 解析消息体
            case CHECK_STATE_CONTENT:
            {
                ret = parse_content(text);
                if(ret == GET_REQUEST)
                {
                    if(ret == GET_REQUEST)
                    {
                        return do_request();
                    }
                    // 有错误,退出解析循环
                    line_status = LINE_OPEN;
                    break;
                }
            }

            default:{
                /*
                    指代系统、应用程序或服务内部发生的错误,这种错误通常是由于程序或系统内部逻辑问题、配置错误、
                    或意外状态引起的。它不是由用户操作引起的错误,而是由于代码或系统状态的缺陷所导致的。
                */
                return INTERNAL_ERROR;
            }
        }



    }
}

// !!! 往写缓冲区中发送数据
// 函数声明中的`...`(称为可变参数列表或省略号)表示该函数可以接受一个不确定数量的参数,这些参数的类型和数量在编译时是不确定的
bool http_conn::add_response(const char* format, ...)
{
    if(m_write_idx >= WRITE_BUFFER_SIZE)
    {
        return false;
    }
    va_list arg_list;  // 可变参数列表类型
    va_start(arg_list, format);  // 设置了 arg_list 变量,使其指向 format 参数之后的位置
    int len = vsnprintf(m_write_buf+m_write_idx, WRITE_BUFFER_SIZE-1-m_write_idx, format, arg_list);
    if(len >= (WRITE_BUFFER_SIZE-1-m_write_idx))
    {
        return false;
    }
    m_write_idx += len;
    va_end(arg_list);
    return true;
}

bool http_conn::add_status_line(int status, const char* title)
{
    return add_response("%s %d %s\r\n", "HTTP/1.1", status, title);
}

bool http_conn::add_headers(int content_len)
{
    add_content_length(content_len);
    add_content_type();
    add_linger();
    add_blank_line();
}

bool http_conn::add_content_length(int content_length)
{
    return add_response("Content-Length: %d\r\n", content_length);
}

bool http_conn::add_linger()
{
    return add_response("Connection: %s\r\n", (m_linger == true)? "keep-alive":"close");
}

bool http_conn::add_blank_line()
{
    return add_response("%s", "\r\n");
}

bool http_conn::add_content_type()
{
    return add_response("Content-Type:%s\r\n", "text/html");
}

bool http_conn::add_content( const char* content )
{
    return add_response( "%s", content );
}

bool http_conn::process_write(HTTP_CODE ret)
{
    switch(ret)
    {
        case INTERNAL_ERROR:
            add_status_line(500, error_500_title);
            add_headers(strlen(error_500_form));
            if(!add_content(error_500_form))
            {
                return false;
            }
            break;
        case BAD_REQUEST:
            add_status_line(400, error_400_title);
            add_headers(strlen(error_400_form));
            if(!add_content(error_400_form))
            {
                return false;
            }
            break;
        case NO_RESOURCE:
            add_status_line(404, error_404_title);
            add_headers(strlen(error_404_form));
            if(!add_content(error_404_form))
            {
                return false;
            }
            break;
        case FORBIDDEN_REQUEST:
            add_status_line(403, error_403_title);
            add_headers(strlen(error_403_form));
            if(!add_content(error_403_form))
            {
                return false;
            }
            break;
        case  FILE_REQUEST:
            add_status_line(200, ok_200_title);
            add_headers(m_file_stat.st_size);
            // iov_base 是一个指向缓冲区的指针,该缓冲区包含要读取或写入的数据。
            // iov_len 指定了 iov_base 指向的缓冲区中数据的长度(以字节为单位)
            // m_iv[0]: 响应数据报
            // m_iv[1]: 网页数据
            m_iv[0].iov_base = m_write_buf;
            m_iv[0].iov_len = m_write_idx;
            m_iv[1].iov_base = m_file_address;
            m_iv[1].iov_len = m_file_stat.st_size;
            m_iv_count = 2;
            
            bytes_to_send = m_write_idx + m_file_stat.st_size;
            return true;
        default:
            return false;
    }

    m_iv[0].iov_base = m_write_buf;
    m_iv[0].iov_len = m_write_idx;
    m_iv_count = 1;
    bytes_to_send = m_write_idx;
    return true;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2039107.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

模型训练与验证minicpm-v

minicpm-v 模型进行微调并进行验证 训练使用混合数据集进行训练&#xff0c;对minicpm-V进行lora微调&#xff0c;微调后使用llama3_1对输出结果与标签值进行比对&#xff0c;计算准确率。 验证代码为: # URL https://swift.readthedocs.io/zh-cn/latest/LLM/VLLM%E6%8E%A8%…

PMP到底有什么用?

PMP 就是项目管理证书&#xff0c;全称是项目管理专业人士资格认证&#xff0c;对于一个在项目管理岗位混迹五年的老油条来说&#xff0c;PMP 证书是敲开项目管理岗位的第一块砖&#xff0c;每年考 PMP 的人都很多&#xff0c;要是 PMP 证书没有价值&#xff0c;还会有那么多人…

Tomcat下载安装文档

简介 Tomcat服务器软件是一个免费的开源的web应用服务器。是Apache软件基金会的一个核心项目。由Apache&#xff0c;Sun和其他一些公司及个人共同开发而成。 由于Tomcat只支持Servlet/JSP少量JavaEE规范&#xff0c;所以是一个开源免费的轻量级Web服务器。 JavaEE规范&#x…

Java IO流使用方法 (常见方法)

Java系列文章目录 补充内容 Windows通过SSH连接Linux 第一章 Linux基本命令的学习与Linux历史 文章目录 Java系列文章目录一、前言二、学习内容&#xff1a;三、问题描述四、解决方案&#xff1a;4.1 File 的使用4.2 防止乱码问题 五、总结&#xff1a;5.1 学习总结&#xff1…

IPFS、IPNS 网站部署

目录 概念IPFS 网站IPNS 网站网站迁移到 IPFS/IPNS1. 连接 Github2. 选择仓库3. 配置 Build4. 绑定域名5. 绑定 IPNS 域名6. 检查 DNSLink概念 以 https://bhitdao.com/ 为例 IPFS 网站 链接为 Hash: ipfs://bafybeifxwlnnvuhbxiszvs2kkckxkxfy36chzoy2f7nrempkpznxrudbsm/…

开源AI智能名片微信小程序:以人性洞察与资源优化为驱动的社群营销新策略

摘要&#xff1a;随着科技的飞速发展&#xff0c;特别是人工智能&#xff08;AI&#xff09;技术的广泛应用&#xff0c;传统营销模式正经历着前所未有的变革。本文旨在探讨开源AI智能名片微信小程序如何凭借其独特的功能特性&#xff0c;结合人性洞察、需求解决、资源优化以及…

CLAMP-1

一、信息收集 1、主机发现 nmap 192.168.236.0/24 2、端口扫描 nmap 192.168.236.173 -p- -A 3、目录扫描 dirb http://192.168.236.173 二、漏洞探测 访问80端口 访问 /nt4stopc/ 下面有一些问题&#xff0c;提示必须收集答案 都是一些判断题&#xff0c;对与错对应1与0&…

SQL注入(原理、分类、union、POST注入)

目录 【学习目标、重难点知识】 【学习目标】 【重难点知识】 SQL注入简介 SQL注入原理 SQL注入类型 MySQL与SQL注入的相关知识 information_schema 数据库的结构 数据库查询语句 limit的用法 需要记住的几个函数 注释符号 SQL注入探测方法 SQL注入漏洞攻击流程…

gerrit的使用

配置SSH密钥 用记事本打开电脑里以下文件&#xff0c;复制内容 在gerrit代码库设置里找到菜单 SSH Keys&#xff0c;将以上复制的内容粘贴到New SSH Key处&#xff0c;点击ADD NEW SSH KEY即可。 克隆代码 git clone ssh://..... 下载commit-msg文件 复制代码下载地址里的…

java之校验QQ号是否正确以及如何用正则表达式进行优化

public class RegexDemo {public static void main(String[] args) {String qq"123456789";System.out.println(checkQQ(qq));}public static boolean checkQQ(String qq){//规则:6位到20位之内,0不能在开头,必须全部是数字//核心思想://先把异常数据过滤//下面的…

外部排序(败者树、置换-选择排序、最佳归并树)

外部排序可能会考查相关概念、方法和排序过程&#xff0c;外部排序的算法比较复杂&#xff0c;不会在算法设计上进行考查。 一、外部排序的基本概念与方法 外部排序指待排序文件较大&#xff0c;内存一次放不下&#xff0c;需存放在外存的文件的排序。 1. 基本概念 在许多应用…

python入门之命令提示符和文本创建.py文件

1.命令提示符 程序 快捷键&#xff1a;windowsR 在安装完python以后&#xff0c;可以直接在命令提示符程序上敲代码进行初步尝试。 python解释器 计算机是不认识python代码的&#xff0c;计算机只能识别0和1这个二进制的数&#xff0c;所以需要一个翻译官“python翻译器”。 …

【Linux系列】known_hosts详解

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

【Linux网络(一)】Socket编程

文章目录 1. 预备知识1.1 认识端口号1.2 初识TCP协议1.3 初识UDP协议1.4 网络字节序1.5 socket编程接口1.5.1 套接字编程的种类1.5.2 sockaddr结构体1.5.3 socket 常见API1.5.4 地址转换函数 2. 编写UDP服务器与客户端2.1 UDP服务器的创建2.2 UDP服务器接收/发送数据2.3 补充知…

动态规划——背包问题(01背包、完全背包,分组背包与二进制优化)

本蒟蒻写二进制优化开始的时候写昏了&#xff0c;并且昏了一下午。但好在有神犇救命&#xff0c;这篇博客才得以面世——躲着人群 一、01背包 概述&#xff1a; 其常见的问题形式为&#xff1a;给出n个物品&#xff0c;每个物品有对应的价值和体积。给出背包容量后求不超过背…

硬件开发流程

1.看原理图找引脚 --开发板上找到LED&#xff0c;查看丝印 --在原理图中根据丝印找到对应的器件 --找到对应的引脚 2.配置引脚功能(对应硬件的工作原理) 3.控制对应的引脚(或控制器) volatile: 易失性修饰符

qtpdfium 多平台编译

源码下载地址&#xff1a;https://codeload.github.com/kkzi/qpdf/zip/2681018e300738d6da9a9f89f06c93fc3ef17831 参考&#xff1a;https://blog.51cto.com/u_2194662/5256871 开发环境&#xff1a;QT 5.15.2 1. windows下编译&#xff1a; 编译环境&#xff1a;vs2019qt5.15…

【Python】线性规划模型(笔记)

线性规划的作用 求一个线性目标函数在线性可行域内的最值问题 线性规划的典型应用 配送运输问题&#xff1a;选大车还是小车生产规划问题&#xff1a;每种原料各买多少几何切割问题&#xff1a;切割长宽各多少买卖利润问题&#xff1a;最多能挣多少钱… 线性规划的本质 问…

6 二进制、十进制、八进制和十六进制的概念与进制转换,原码、反码、补码的概念与计算

目录 1 进制 1.1 常见的进制 1.2 不同进制的整数在 C 语言中的写法 1.3 printf 以不同进制形式输出整数 1.4 案例&#xff1a;使用不同的进制形式输出同一整数 2 进制的转换 2.1 二进制与十进制转换 2.1.1 二进制转换成十进制 2.1.2 十进制转换成二进制 2.2 八进制和…

pygame 赛亚人打怪

笔者最近学了一点pygame&#xff0c;献丑了。 代码附上&#xff0c;大佬勿喷&#xff01; import random import timeimport pygameWIDTH 410 HEIGHT 750 ENEMY_BORN pygame.USEREVENT 1 BULLET_BORN pygame.USEREVENT 10 ENEMY_BULLET_BORN pygame.USEREVENT 100 REW…