WebServer 解析HTTP 响应报文

一、基础API部分,介绍stat、mmap、iovec、writev、va_list

1.1 stat​ 

作用:获取文件信息

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>// 获取文件属性,存储在statbuf中
int stat(const char *pathname, struct stat *statbuf);struct stat {mode_t  st_mode;         /* 文件类型和权限 */off_t   st_size;         /* 文件大小,字节数 */  
};
  • 返回值:成功返回0,失败返回-1;
  • 参数:文件路径(名),struct stat 类型的结构体 

struct stat 结构体详解:

struct stat
{dev_t     st_dev;     /* ID of device containing file */文件使用的设备号ino_t     st_ino;     /* inode number */    索引节点号 mode_t    st_mode;    /* protection */  文件对应的模式,文件,目录等nlink_t   st_nlink;   /* number of hard links */    文件的硬连接数  uid_t     st_uid;     /* user ID of owner */    所有者用户识别号gid_t     st_gid;     /* group ID of owner */   组识别号  dev_t     st_rdev;    /* device ID (if special file) */ 设备文件的设备号off_t     st_size;    /* total size, in bytes */ 以字节为单位的文件容量   blksize_t st_blksize; /* blocksize for file system I/O */ 包含该文件的磁盘块的大小   blkcnt_t  st_blocks;  /* number of 512B blocks allocated */ 该文件所占的磁盘块  time_t    st_atime;   /* time of last access */ 最后一次访问该文件的时间   time_t    st_mtime;   /* time of last modification */ /最后一次修改该文件的时间   time_t    st_ctime;   /* time of last status change */ 最后一次改变该文件状态的时间   
};

stat结构体中的st_mode 则定义了下列数种情况:

    S_IFMT   0170000    文件类型的位遮罩S_IFSOCK 0140000    套接字S_IFLNK 0120000     符号连接S_IFREG 0100000     一般文件S_IFBLK 0060000     区块装置S_IFDIR 0040000     目录S_IFCHR 0020000     字符装置S_IFIFO 0010000     先进先出
​S_ISUID 04000     文件的(set user-id on execution)位S_ISGID 02000     文件的(set group-id on execution)位S_ISVTX 01000     文件的sticky位
​S_IRUSR(S_IREAD) 00400     文件所有者具可读取权限S_IWUSR(S_IWRITE)00200     文件所有者具可写入权限S_IXUSR(S_IEXEC) 00100     文件所有者具可执行权限
​S_IRGRP 00040             用户组具可读取权限S_IWGRP 00020             用户组具可写入权限S_IXGRP 00010             用户组具可执行权限
​S_IROTH 00004             其他用户具可读取权限S_IWOTH 00002             其他用户具可写入权限S_IXOTH 00001             其他用户具可执行权限
​上述的文件类型在POSIX中定义了检查这些类型的宏定义:S_ISLNK (st_mode)    判断是否为符号连接S_ISREG (st_mode)    是否为一般文件S_ISDIR (st_mode)    是否为目录S_ISCHR (st_mode)    是否为字符装置文件S_ISBLK (s3e)        是否为先进先出S_ISSOCK (st_mode)   是否为socket若一目录具有sticky位(S_ISVTX),则表示在此目录下的文件只能被该文件所有者、此目录所有者或root来删除或改名,在linux中,最典型的就是这个/tmp目录啦。
​

1.2 mmap

用于将一个文件或其他对象映射到内存,提高文件的访问速度

void* mmap(void* start,size_t length,int port,int flags,int fd,off_t offset);
int munmap(void* start,size_t length);
  • start : 映射区的开始地址,设置为0时表示由系统决定映射区的起始地址
  • length : 映射区的长度
  • prot : 期望的内存保护标志,不能与文件的打开模式冲突
    • PROT_READ 表示页内容可以读取
  • flags : 指定映射对象的类型,映射选项和映射页是否可以共享
    • MAP_PRIVATE 建立一个写入时拷贝的私有映射,内存区域的写入不会影响到原文件
  • fd : 有效的文件描述符,一般是由open()函数返回
  • off_toffset : 被映射对象内容的起点

我的往期文章:  

了解Linux 的 mmap --- 笔记_呵呵哒( ̄▽ ̄)"的博客-CSDN博客icon-default.png?t=N7T8https://heheda.blog.csdn.net/article/details/132119077

1.3 iovec

定义了一个向量元素,一般把这个结构用作一个多元素的数组

struct iovec {void    *iov_base;    /* starting address of buffer */size_t  iov_len;      /* size of buffer */
};
  • iov_base 指向数据的地址
  • iov_len 表示数据的长度
  • iovec是一个结构体,里面有两个元素,指针成员iov_base指向一个缓冲区,这个缓冲区是存放的是writev将要发送的数据
  • 成员iov_len表示实际写入的长度

1.4 writev

writev函数用于在一次函数调用中写多个非连续缓冲区,有时也将这该函数称为聚集写

#include <sys/uio.h>
ssize_t writev(int filedes,const struct iovec* iov,int iovcnt);
  • filedes 表示文件描述符
  • iov 为前述io向量机制结构体 iovec
  • iovcnt 为结构体的个数

 若成功则返回已写的字节数,若出错则返回 -1

  • writev 以顺序 iov[0],iov[1] 至 iov[iovcnt - 1] 从缓冲区中聚集输出数据
  • writev 返回输出的字节总数。通常,它应等于所有缓冲区长度之和

特别注意:循环调用writev时,需要重新处理iovec中的指针和长度,该函数不会对这两个成员做任何处理

writev()函数Linux系统中的一个系统调用,其作用是将多个缓冲区的数据一起写入到文件描述符中(各个缓冲区的数据可以是不连续的)

1.5 va_list

  • va_list 是C语言中用于解决变参问题的一组宏,它定义于标准库 stdarg.h 中
  • 通过命令 $man va_arg 也可以从手册中查询到 va_list 的用法
#include <stdarg.h>void va_start(va_list ap, last);
type va_arg(va_list ap, type);
void va_end(va_list ap);
void va_copy(va_list dest, va_list src);

对可变参数列表的处理过程:

  • va_list 定义一个可变参数列表
  • va_start 获取函数可变参数列表
  • va_arg 循环处理可变参数列表中的各个可变参数
  • va_end 结束对可变参数列表的处理 
#include <stdio.h>
#include <stdarg.h>static void show_numbers(int num,...) {va_list va;/* 初始化va,让va指向num后面的参数*/va_start(va,num);while (num--) {/* 通过while循环依次获取遍历后面的参数 */printf("%d\n",va_arg(va,int));}va_end(va);
}int main(void) {show_numbers(3,110,111,112);return 0;
}
heheda@heheda:~/Linux/webserver$ gcc va_list.cpp
heheda@heheda:~/Linux/webserver$ ./a.out
110
111
112
1.5.1 va_start
void va_start(va_list ap, last_arg);
  • ap: 这是一个 va_list 类型的对象,它用来存储通过 va_arg 获取额外参数时所必需的信息
  • 这个函数的作用是初始化 ap 变量,它与 va_arg 和 va_end 函数一起使用
  • last_arg 是最后一个传递给函数的已知的固定参数,即省略号之前的参数
#include <stdarg.h>
#include <stdio.h> // 采用可变参数,第一个参数用于标识参数数量
int sum(int num_args,...) {int val = 0;va_list ap;int i;va_start(ap,num_args);for(i=0;i<num_args;i++) {val += va_arg(ap,int);}va_end(ap);return val;
}void test1() {printf("10、20 和 30 的和 = %d\n",  sum(3, 10, 20, 30) );printf("4、20、25 和 30 的和 = %d\n",  sum(4, 4, 20, 25, 30) );
}int main() {test1();return 0;
}
heheda@heheda:~/Linux/webserver$ gcc va_list.cpp
heheda@heheda:~/Linux/webserver$ ./a.out
10、20 和 30 的和 = 60
4、20、25 和 30 的和 = 79
1.5.2 vsnprintf

作用:使用vsnprintf()用于向一个字符串缓冲区打印格式化字符串,且可以限定打印的格式化字符串的最大长度

#include <stdarg.h>
int vsnprintf(char *str, size_t size, const char *format, va_list ap);- 功能:将可变参数格式化输出到一个字符数组- 参数:- str:把生成的格式化的字符串存放在这里- size:str可接受的最大字符数- format:指定输出格式的字符串,它决定了你需要提供的可变参数的类型、个数和顺序- ap:va_list变量- 返回值:- 成功:返回最终生成字符串的长度- 失败:返回负值
  • 第一个参数:目标缓冲区(字符数组)
  • 第二个参数,限定最多打印到缓冲区的字符数量为n-1个(留位置给\0
  • 第三个参数,打印的格式(如%d:%s
  • 第四个参数,可变参数arg,需要用va_start初始化
 1.5.3 fprintf
int fprintf(FILE *stream, const char *format, ...);
#include <cassert>
#include <cstring>
#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h> 
#include <cerrno>
#include <ctime>#define DEBUG 0
#define NOTICE 1
#define WARNING 2
#define FATAL 3const char* log_level[] = {"DEBUG","NOTICE","WARNING","FATAL"};// 采用可变参数列表
void logging(int level,const char* format,...) {assert(level >= DEBUG && level <= FATAL);char* name = getenv("USER"); // 获取环境变量中的用户(执行命令的用户)char logInfo[1024];// 获取可变参数列表va_list ap;// ap -> char*va_start(ap,format);vsnprintf(logInfo,sizeof(logInfo),format,ap);va_end(ap);// ap = NULL;// 根据日志等级选择打印到 stderr/stdoutFILE *out = (level == FATAL) ? stderr:stdout;// 格式化打印到文件中fprintf(out, "%s | %u | %s | %s\n", \log_level[level], \(unsigned int)time(nullptr),\name == nullptr ? "unknow":name,\logInfo);
}int main() {logging(DEBUG, "socket create success: %d", 114514);logging(FATAL, "socket:%s:%d", strerror(errno), 11234);return 0;
}
heheda@heheda:~/Linux/webserver$ g++ 利用可变参数实现log类.cpp
heheda@heheda:~/Linux/webserver$ ./a.out
DEBUG | 1694175430 | heheda | socket create success: 114514
FATAL | 1694175430 | heheda | socket:Success:11234

二、 HTTP请求和响应步骤

2.1 HTTP 响应报文格式

响应行\r\n
响应头\r\n
空行\r\n
响应体\r\n提示: 每项信息之间都要有一个\r\n进行分割

2.2 流程图

浏览器端发出HTTP请求报文,服务器端接收该报文并调用 process_read 对其进行解析,根据解析结果 HTTP_CODE,进入相应的逻辑和模块

  • 服务器子线程完成报文的解析与响应;
  • 主线程监测读写事件,调用 read_oncehttp_conn::write 完成数据的读取与发送

2.3 HTTP_CODE 含义 

 2.4 do_request

do_request 函数的返回值是对请求的文件分析后的结果,一部分是语法错误导致的 BAD_REQUEST,一部分是 do_request 的返回结果(NO_RESOURCE、FORBIDDEN_REQUEST、FILE_REQUEST)

do_request 函数:

  • 将网络将网站根目录 和 url 文件拼接
  • 接着通过 stat 判断该文件属性
  • 为了提高访问速度,可通过mmap进行映射,将普通文件映射到内存逻辑地址
// 网站的根目录
const char* doc_root = "/home/nowcoder/webserver/resources";// 当得到一个完整、正确的HTTP请求时,我们就分析目标文件的属性,
// 如果目标文件存在、对所有用户可读,且不是目录,则使用mmap将其
// 映射到内存地址m_file_address处,并告诉调用者获取文件成功
http_conn::HTTP_CODE http_conn::do_request()
{// "/home/nowcoder/webserver/resources"// 将初始化的m_real_file赋值为网站根目录strcpy( m_real_file, doc_root );int len = strlen( doc_root );strncpy( m_real_file + len, m_url, FILENAME_LEN - len - 1 );// 获取m_real_file文件的相关的状态信息,-1失败,0成功// 通过stat获取请求资源文件信息,成功则将信息更新到m_file_stat结构体// 失败返回NO_RESOURCE状态,表示资源不存在if ( stat( m_real_file, &m_file_stat ) < 0 ) {return NO_RESOURCE;}// 判断文件的权限,是否可读,不可读则返回FORBINDDEN_REQUEST状态if ( ! ( m_file_stat.st_mode & S_IROTH ) ) {return FORBIDDEN_REQUEST;}// 判断文件类型,如果是目录,则返回BAD_REQUEST,表示请求报文有误if ( S_ISDIR( m_file_stat.st_mode ) ) {return BAD_REQUEST;}// 以只读方式获取文件描述符,通过mmap将该文件映射到内存中int fd = open( m_real_file, O_RDONLY );// 创建内存映射// 通过mmap将该文件映射到内存中m_file_address = ( char* )mmap( 0, m_file_stat.st_size, PROT_READ, MAP_PRIVATE, fd, 0 );// 避免文件描述符的浪费和占用close( fd );// 表示请求文件存在,且可以访问return FILE_REQUEST;
}

unmap 取消映射 

// 对内存映射区执行munmap操作
void http_conn::unmap() {if( m_file_address ){munmap( m_file_address, m_file_stat.st_size );m_file_address = 0;}
}

 2.5 process_write

根据 do_request 的返回状态,服务器子线程调用 process_write m_write_buf 中写入响应报文

  • add_status_line函数,添加状态行: http/1.1 状态码 状态消息
  • add_headers函数添加消息报头,内部调用add_content_length add_linger函数
    • content-length 记录响应报文长度,用于浏览器端判断服务器是否发送完的数据
    • connection 记录连接状态,用于告诉浏览器端保持长连接
  • add_blank_line添加空行

add_status_line、add_headers、add_content_length、add_linger、add_blank_line这5个函数均是内部调用 add_response 函数更新 m_write_idx 指针缓冲区 m_write_buf 中的内容

2.5.1 add_response
// 往写缓冲中写入待发送的数据
bool http_conn::add_response( const char* format, ... ) {// 如果写入内容超出m_write_buf大小则报错if( m_write_idx >= WRITE_BUFFER_SIZE ) {return false;}// 定义可变参数列表va_list arg_list;// 将变量arg_list初始化为传入参数va_start( arg_list, format );// 将数据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 ) ) {va_end( arg_list );return false;}// 更新m_write_idx位置m_write_idx += len;// 清空可变参列表va_end( arg_list );return true;
}
2.5.2 add_status_line
// 添加状态行
bool http_conn::add_status_line( int status, const char* title ) {return add_response( "%s %d %s\r\n", "HTTP/1.1", status, title );
}
2.5.3 add_headers
// 添加消息报头,具体的添加文本长度
bool http_conn::add_headers(int content_len) {add_content_length(content_len);add_content_type();add_linger();add_blank_line();
}
2.5.3 add_headers
// 添加消息报头,具体的添加文本长度
bool http_conn::add_headers(int content_len) {add_content_length(content_len);add_content_type();add_linger();add_blank_line();
}
2.5.4 add_content_length
// 添加Content-Length,表示响应报文的长度
bool http_conn::add_content_length(int content_len) {return add_response( "Content-Length: %d\r\n", content_len );
}
2.5.5 add_linger
// 添加连接状态,通知浏览器端是保持连接还是关闭
bool http_conn::add_linger()
{return add_response( "Connection: %s\r\n", ( m_linger == true ) ? "keep-alive" : "close" );
}
2.5.6 add_blank_line
// 添加空行
bool http_conn::add_blank_line()
{return add_response( "%s", "\r\n" );
}
2.5.7 add_content
// 添加文本content
bool http_conn::add_content( const char* content )
{return add_response( "%s", content );
}
2.5.8 add_content_type
// 添加文本类型,这里是html
bool http_conn::add_content_type() {return add_response("Content-Type:%s\r\n", "text/html");
}

响应报文分为两种

  • 一种是请求文件的存在,通过io向量机制iovec,声明两个iovec
    • 第一个指向m_write_buf(包含响应首行和响应头部)
    • 第二个指向mmap的地址m_file_address;(响应体)
  • 一种是请求出错,这时候只申请一个iovec,指向m_write_buf(包含响应首行、响应头部、响应体)

>>process_write : 根据服务器处理HTTP请求的结果,决定返回给客户端的内容

// 定义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 inherently impossible to satisfy.\n";
const char* error_403_title = "Forbidden";
const char* error_403_form = "You do not have permission to get file from this server.\n";
const char* error_404_title = "Not Found";
const char* error_404_form = "The requested file was not found on this server.\n";
const char* error_500_title = "Internal Error";
const char* error_500_form = "There was an unusual problem serving the requested file.\n";// 根据服务器处理HTTP请求的结果,决定返回给客户端的内容
bool http_conn::process_write(HTTP_CODE ret) {switch (ret){// 内部错误,500case INTERNAL_ERROR:// 状态行add_status_line( 500, error_500_title );// 消息头add_headers( strlen( error_500_form ) );if ( ! add_content( error_500_form ) ) {return false;}break;// 报文语法有误,400case BAD_REQUEST:add_status_line( 400, error_400_title );add_headers( strlen( error_400_form ) );if ( ! add_content( error_400_form ) ) {return false;}break;// 请求资源不存在,404case NO_RESOURCE:add_status_line( 404, error_404_title );add_headers( strlen( error_404_form ) );if ( ! add_content( error_404_form ) ) {return false;}break;// 资源没有访问权限,403case FORBIDDEN_REQUEST:add_status_line( 403, error_403_title );add_headers(strlen( error_403_form));if ( ! add_content( error_403_form ) ) {return false;}break;// 请求资源可以正常访问,200case FILE_REQUEST:add_status_line(200, ok_200_title );// 如果请求的资源存在if(m_file_stat.st_size !=0 ) {add_headers(m_file_stat.st_size);// 第一个iovec指针指向响应报文缓冲区,长度指向m_write_idxm_iv[ 0 ].iov_base = m_write_buf;m_iv[ 0 ].iov_len = m_write_idx;// 第二个iovec指针指向mmap返回的文件指针,长度指向文件大小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;} else {// 如果请求的资源大小为0,则返回空白html文件const char* ok_string = "<html><body></body></html>";add_headers(strlen(ok_string));if(!add_content(ok_string)) return false;}default:return false;}// 除FILE_REQUEST状态之外,其余状态只申请一个iovec,指向响应报文缓冲区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;
}


2.6 write 写事件

该函数实现了分散写的功能,也就是把要发送的数据分为多个部分进行传输

① 首先,在函数开始时,会判断是否存在字节需要发送,如果没有,则结束此次响应

② 通过writev()函数进行分散写操作,将 m_iv 数组中保存的多个缓冲区的数据写入到套接字中。

temp : 表示本次写入的字节数量

  • bytes_have_send : 记录已经发送的字节数  
  • bytes_to_send : 表示剩余需要发送的字节数
// 写HTTP响应
// 写事件是指服务器向客户端“写”数据,也就是服务器向客户端发送数据(即HTTP响应报文)
bool http_conn::write()
{int temp = 0;// 若要发送的数据长度为0// 表示响应报文为空,一般不会出现这种情况if ( bytes_to_send == 0 ) {// 将要发送的字节为0,这一次响应结束。modfd( m_epollfd, m_sockfd, EPOLLIN ); // 重新初始化HTTP对象init();return true;}while(1) {// 分散写// 将响应报文的状态行、消息头、空行和响应正文发送给浏览器端temp = writev(m_sockfd, m_iv, m_iv_count);if ( temp <= -1 ) {// 判断缓冲区是满了// 如果TCP写缓冲没有空间,则等待下一轮EPOLLOUT事件,虽然在此期间,// 服务器无法立即接收到同一客户的下一个请求,但可以保证连接的完整性if( errno == EAGAIN ) {// 重新注册写事件modfd( m_epollfd, m_sockfd, EPOLLOUT );return true;}// 如果发送失败,但不是缓冲区问题,取消映射unmap();return false;}bytes_have_send += temp;// 已发送的字节数// 更新已发送字节数bytes_to_send -= temp;// 剩余需要发送的字节数// 第一个iovec头部信息的数据已发送完,发送第二个iovec数据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;}// 继续发送第一个iovec头部信息的数据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();// 在epoll树上重置EPOLLONESHOT事件modfd(m_epollfd, m_sockfd, EPOLLIN);// 浏览器的请求为长连接if (m_linger){// 重新初始化HTTP对象init();return true;}else{return false;}}}
}

2.7 总结:生成响应报文的具体流程(来自这篇文章)

  1. 进行请求报文的时候,进行URL分析。使用一个文件或其他对象映射到内存,不能映射则报错!
  2. 根据状态码,若为错误状态码,则提取对应的报错介绍和报错信息,作为响应头和响应正文;若为正确的状态码,则提取成功信息作为响应头,提取的内存映射作为响应正文。根据当前的响应头和响应正文的情况,对写缓冲区进行相应的写入(状态行、响应头)
  3. 将写事件写入epoll事件表,之后主线程会自动将写缓冲区中的前半部分报文和响应正文通过分散写传回给客户端

2.8 演示效果:

heheda@heheda:~/Linux/heheda_test/webserver$ g++ main.cpp -pthread
heheda@heheda:~/Linux/heheda_test/webserver$ ./a.out 
按照如下格式运行:a.out port_number
heheda@heheda:~/Linux/heheda_test/webserver$ ./a.out 9999
create the 0th thread
create the 1th thread
create the 2th thread
create the 3th thread
create the 4th thread
create the 5th thread
create the 6th thread
create the 7th thread
heheda read
读取到了数据:GET /index.html HTTP/1.1
Host: 192.168.90.131:9999
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 Edg/116.0.1938.69
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.7
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6got 1 http line : GET /index.html HTTP/1.1
got 1 http line : Host: 192.168.90.131:9999
got 1 http line : Connection: keep-alive
got 1 http line : Upgrade-Insecure-Requests: 1
oop!unknow header Upgrade-Insecure-Requests: 1
got 1 http line : User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 Edg/116.0.1938.69
oop!unknow header User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 Edg/116.0.1938.69
got 1 http line : 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.7
oop!unknow header 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.7
got 1 http line : Accept-Encoding: gzip, deflate
oop!unknow header Accept-Encoding: gzip, deflate
got 1 http line : Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
oop!unknow header Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
got 1 http line : 
write....
heheda read
读取到了数据:GET /images/image1.jpg HTTP/1.1
Host: 192.168.90.131:9999
Connection: keep-alive
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 Edg/116.0.1938.69
Accept: image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8
Referer: http://192.168.90.131:9999/index.html
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6got 1 http line : GET /images/image1.jpg HTTP/1.1
got 1 http line : Host: 192.168.90.131:9999
got 1 http line : Connection: keep-alive
got 1 http line : User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 Edg/116.0.1938.69
oop!unknow header User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 Edg/116.0.1938.69
got 1 http line : Accept: image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8
oop!unknow header Accept: image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8
got 1 http line : Referer: http://192.168.90.131:9999/index.html
oop!unknow header Referer: http://192.168.90.131:9999/index.html
got 1 http line : Accept-Encoding: gzip, deflate
oop!unknow header Accept-Encoding: gzip, deflate
got 1 http line : Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
oop!unknow header Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
got 1 http line : 
write....
heheda read
读取到了数据:GET /favicon.ico HTTP/1.1
Host: 192.168.90.131:9999
Connection: keep-alive
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 Edg/116.0.1938.69
Accept: image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8
Referer: http://192.168.90.131:9999/index.html
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6got 1 http line : GET /favicon.ico HTTP/1.1
got 1 http line : Host: 192.168.90.131:9999
got 1 http line : Connection: keep-alive
got 1 http line : User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 Edg/116.0.1938.69
oop!unknow header User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 Edg/116.0.1938.69
got 1 http line : Accept: image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8
oop!unknow header Accept: image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8
got 1 http line : Referer: http://192.168.90.131:9999/index.html
oop!unknow header Referer: http://192.168.90.131:9999/index.html
got 1 http line : Accept-Encoding: gzip, deflate
oop!unknow header Accept-Encoding: gzip, deflate
got 1 http line : Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
oop!unknow header Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
got 1 http line : 
write....

推荐和参考文章:

超详细的HTTP协议请求报文、响应报文教程! - 知乎 (zhihu.com)

“Web 服务器” 笔记04 ------ 生成、写HTTP响应_http响应报文为啥用writev_CV发烧友的博客-CSDN博客

writev函数 - lypbendlf - 博客园 (cnblogs.com)

最新版Web服务器项目详解 - 06 http连接处理(下) (qq.com)

【webserver】 第8节 响应报文的生成_webserver中http处理类中写缓冲区大小小于读缓冲区_几日行云的博客-CSDN博客

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

CH06_第一组重构(上)

提取函数&#xff08;Extract Function |106&#xff09; 曾用名&#xff1a;提炼函数&#xff08;Extract Function&#xff09; 反向重构&#xff1a;内联函数&#xff08;115&#xff09; 示例代码 function printOwing(invoice) {printBanner();let outstanding calcul…

SpringBoot表现层数据一致性

1.定义Restful类 说明&#xff1a;使用Data注解是Lombok库提供的一个注解&#xff0c;用于自动生成类的getter、setter、equals、hashcode和toString方法。 package com.forever.controller.utils;import lombok.Data;Data public class Restful {private Boolean flag;//dat…

Redis 常用命令

目录 全局命令 1&#xff09;keys 2&#xff09;exists 3) del(delete) 4&#xff09;expire 5&#xff09;type SET命令 GET命令 MSET 和 MGET命令 其他SET命令 计数命令 redis-cli&#xff0c;进入redis 最核心的命令&#xff1a;我们这里只是先介绍 set 和 get 最简单的操作…

Java集合(Collection、Iterator、Map、Collections)概述——Java第十三讲

前言 本讲我们将继续来讲解Java的其他重要知识点——Java集合。Java集合框架是Java编程语言中一个重要的部分,它提供了一套预定义的类和接口,供程序员使用数据结构来存储和操作一组对象。Java集合框架主要包括两种类型:一种是集合(Collection),存储一个元素列表,…

MySQL入门指南:数据库操作的基础知识

当谈到关系型数据库管理系统(RDBMS)时&#xff0c;MySQL无疑是最常见和广泛使用的一个。它是一个强大的工具&#xff0c;用于存储、管理和检索数据。在这篇博客中&#xff0c;我们将介绍MySQL的基本知识&#xff0c;包括数据库的操作、数据表的操作以及数据的增删改查~~ 目录 …

嵌入式学习笔记(27)uart stdio的移植

什么是stdio&#xff1f; &#xff08;1&#xff09;#include <stdio.h> &#xff08;2&#xff09;stdio:standard input output &#xff08;3&#xff09;stdio是os定义的默认的输入和输出通道。一般在PC机的情况下&#xff0c;标准输入指的是键盘&#xff0c;标准…

VIOOVI干货分享:企业车间动作作业分析如何改善?

企业车间动作作业分析是将操作动作分解为最小的分析单位&#xff0c;我们通常称之为动素。通过对动素的定性研究&#xff0c;找出合理有效的动作&#xff0c;缩短操作时间&#xff0c;实现标准化操作。在实际应用中&#xff0c;应分析和研究操作员的各种动作&#xff0c;去除没…

深入探讨Kubernetes(K8s)在云原生架构中的关键作用和应用

文章目录 1. 容器化的应用程序管理2. 自动化扩展和负载均衡3. 容器编排和调度4. 存储管理5. 自动化滚动更新6. 多云和混合云部署7. 监控和日志8. 安全9. 社区支持和生态系统10. 未来展望案例 &#x1f388;个人主页&#xff1a;程序员 小侯 &#x1f390;CSDN新晋作者 &#x1…

Nginx部署前后端分离项目(Linux)

Nginx代理前端页面、后端接口 一、前端打包二、后端打包三、Linux部署Nginx启动、暂停、重启服务器部署文件地址&#xff1a; 一、前端打包 npm run build二、后端打包 通过Maven 使用package打包 三、Linux部署 安装Nginx 安装环境 yum -y install gcc pcre pcre-devel z…

SpringMVC系列(七)之自定义注解

目录 一. Java注解简介 1.1 Java注解分类 1.2 JDK基本注解 Override Deprecated SuppressWarnings 1.3 JDK元注解 从 Java 7 开始&#xff0c;额外添加了 3 个注解: 1.4 自定义注解 如何自定义注解&#xff1f; 二. 自定义注解示例 枚举类&#xff1a; 示例一&…

【JUC】Java并发编程从挖坑到入土全解(一)

目录 线程基础知识 作者&#xff08;拜个神&#xff09; 什么是JUC 为什么会出现多线程 硬件方面 摩尔定律失效 软件方面 弊端 & 问题 从线程启动开始 Java线程理解以及openJDK的实现 更加底层的的C源码 线程基础知识 作者&#xff08;拜个神&#xff09; Dou…

A Mathematical Framework for Transformer Circuits—(三)

A Mathematical Framework for Transformer Circuits Two-Layer Attention-Only TransformersThree Kinds of CompositionPath Expansion of LogitsPath Expansion of Attention Scores QK CircuitAnalyzing a Two-Layer ModelInduction HeadsInduction heads的功能Induction h…

CVE-2023-1454:Jeecg-Boot SQL注入漏洞复现

文章目录 Jeecg-Boot SQL注入漏洞(CVE-2023-1454)复现0x01 前言0x02 漏洞描述0x03 影响范围0x04 漏洞环境0x05 漏洞复现1.访问漏洞环境2.构造POC3.复现 0x06 修复建议 Jeecg-Boot SQL注入漏洞(CVE-2023-1454)复现 0x01 前言 免责声明&#xff1a;请勿利用文章内的相关技术从事…

Windows 性能突然打鸡血,靠 Bug 修复了多年顽疾

要说 的 Bug 集中地&#xff0c;当属资源管理器。 速度缓慢、卡顿、崩溃&#xff0c;不同设备、不同版本的用户都有不同的感受。 严格来说&#xff0c;这其实是 Windows 的传统艺能&#xff0c;要完美修复可不容易。 而作为小老弟的文件资源管理器&#xff0c;时不时来个无响…

【JUC系列-06】深入理解Semaphore底层原理和基本使用

JUC系列整体栏目 内容链接地址【一】深入理解JMM内存模型的底层实现原理https://zhenghuisheng.blog.csdn.net/article/details/132400429【二】深入理解CAS底层原理和基本使用https://blog.csdn.net/zhenghuishengq/article/details/132478786【三】熟练掌握Atomic原子系列基本…

VRTK4⭐二.VRTK4的项目基础配置

文章目录 &#x1f7e5; 硬件基本配置&#x1f7e7; 设置XR Plug-in Management&#x1f7e8; 添加项目Tilia&#x1f7e9; 配置项目Hierarchy &#x1f7e5; 硬件基本配置 解决使用OpenXR,HTC头显正常追踪,但手柄无法使用的问题. 问题如下: 当我们按照官方的标准流程配置完Op…

【HttpRunnerManager】搭建接口自动化测试平台实战

一、需要准备的知识点 1. linux: 安装 python3、nginx 安装和配置、mysql 安装和配置 2. python: django 配置、uwsgi 配置 二、我搭建的环境 1. Centos7 &#xff08;配置 rabbitmq、mysql 、Supervisord&#xff09; 2. python 3.6.8 &#xff08;配置 django、uwsgi&…

pcl--第四节 采样一致性算法RANSAC

RANSAC随机采样一致性算法简介 RANSAC是“RANdom SAmple Consensus”&#xff08;随机抽样共识或采样一致性&#xff09;的缩写&#xff0c;它是一种迭代方法&#xff0c;用于从包含异常值的一组数据中估计数学模型的参数。该算法由Fischler和Bolles于1981年发布。 RANSAC算法…

基于SpringBoot+微信小程序的智慧医疗线上预约问诊小程序

✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取项目下载方式&#x1f345; 一、项目背景介绍&#xff1a; 近年来&#xff0c;随…

关于Linux服务器.sh文件启动问题

问题描述 在linux服务器上使用文本编辑&#xff08;并非vim操作&#xff09;对.sh脚本文件进行修改后无法启动&#xff0c;显示’\r’识别错误等。 错误如下&#xff1a; 错误原因 因为.sh文件在经过这种编辑后格式产生了错误&#xff0c;由unix转为了doc格式&#xff0c;需…