06 HTTP(下)

06 HTTP(下)

介绍服务器如何响应请求报文,并将该报文发送给浏览器端。介绍一些基础API,然后结合流程图和代码对服务器响应请求报文进行详解。
基础API部分,介绍stat、mmap、iovec、writev。
流程图部分,描述服务端响应请求报文的逻辑,各模块间的关系。
代码部分,结合代码对服务器响应请求报文进行详解。

基础API

stat

stat函数用于取得指定文件的文件属性,并将文件属性存储在结构体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; /*文件大小,字节数*/
};

mmap

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

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

iovec

定义了一个向量元素,通常这个结构体用作一个多元素的数组。

struct iovec{void *iov_base;//指向数据的地址size_t iov_len; //数据的长度
};

writev

writev函数用于在一次函数调用中写多个非连续缓冲区,聚集写。

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

若成功则返回已写的字节数,若出错则返回-1。writev以顺序iov[0],iov[1]至iov[iovcnt-1]从缓冲区聚集输出数据。writev返回输出的字节总数。通常,它应等于所有缓冲区长度之和。
特别注意:循环调用writev时,需要重新处理iovec中的指针和长度,该函数不会对这个成员做任何处理。writev的返回值为已写的字节数,但这个返回值“实用性”并不高,因为参数传入的是iovec数组,计量单位是iovcnt,而不是字节数,我们仍然需要通过遍历iovec来计算新的地址,另外写入数据的“结束点”可能位于一个iovec的中间某个位置,因此需要调整临界iovec的io_base和io_len。

流程图

浏览器端发出HTTP请求报文,服务器端接收报文并调用process_read对其进行解析,根据解析结果HTTP_CODE,进入相应的逻辑和模块。
其中,服务器子线程完成报文的解析与响应;主线程检测读写事件,调用read_once和http_conn::write完成数据的读取与发送。
在这里插入图片描述

HTTP_CODE含义

表示HTTP请求的处理结果,在头文件中初始化了8种情形,在报文解析与响应中只用到了7种。

NO_REQUEST

  • 请求不完整,需要继续读取请求报文数据
  • 跳转主线程继续监测读事件

GET_REQUEST

  • 获得了完整的HTTP请求
  • 调用do_request完成请求资源映射

NO_RESOURCE

  • 请求资源不存在
  • 跳转process_write完成响应报文

BAD_REQUEST

  • HTTP请求报文有语法错误或请求资源为目录
  • 跳转process_write完成响应报文

FILE_REQUEST

  • 请求资源可以正常访问
  • 跳转process_write完成响应报文

INTERNAL_ERROR

  • 服务器内部错误,该结果在主状态机逻辑switch的default下,一般不会触发

代码分析

do_request

process_read函数的返回值是对请求的文件分析后的结果,一部分是语法错误导致的BAD_REQUEST,一部分是do_request的返回结果。该函数将网站根目录和url文件拼接,然后通过stat判断该文件属性。另外,为了提高访问速度,通过mmap进行映射,将普通文件映射到内存逻辑地址。
为了更好的理解请求资源的访问流程,这里对各种页面跳转机制进行简要介绍。浏览器网址栏中的字符,即url,可以将其抽象成ip:port/xxx/,xxx通过html文件的action属性进行设置。
m_url为请求报文解析出的请求资源,以/开头,也就是/xxx,项目中解析后的m_url有8种情况。

/

  • GET请求,跳转到judge.html,即欢迎访问页面

/0

  • POST请求,跳转到register.html,即注册页面

/1

  • POST请求,跳转到log.thml,即登录页面

/2CGISQL.cgi

  • POST请求,进行登录校验
  • 验证成功跳转到welcome.html,即资源请求成功页面
  • 验证失败跳转到logError.html,即登录失败页面

/3CGISQL.cgi

  • POST请求,进行注册校验
  • 注册成功跳转到log.html,即登录页面
  • 注册失败跳转到registerError.html,即注册失败页面

/5

  • POST请求,跳转到picture.thml,即图片请求页面

/6

  • POST请求,跳转到video.html,即视频请求页面

/7

  • POST请求,跳转到fans.html,即关注页面

具体的登录和注册校验功能会在第12节进行详解。

//网站根目录,文件夹内存放请求的资源和跳转的html文件
const char* doc_root="/home/qgy/github/ini_tinywebserver/root";http_conn::HTTP_CODE http::do_request()
{//将初始化的m_real_file赋值为网站根目录strcpy(m_real_file,doc_root);int len=strlen(doc_root);//找到m_url中/的位置const char *p=strchr(m_url,'/');//实现登录和注册校验if(cgi==1&&(*(p+1)=='2'||*(p+1)=='3')){//根据标志判断是登录校验还是注册校验//同步线程登录校验//CGI多线程登录校验}//如果请求资源为/0,表示跳转注册页面if(*(p+1)=='0'){char *m_url_real=(char *)malloc(sizeof(char)*200);strcpy(m_url_real,"/register.html");//将网站目录和/register.html进行拼接,更新到m_real_file中strncpy(m_real_file+len,m_url_real,strlen(m_url_real));free(m_url_real);}//如果请求资源为/1,表示跳转登录页面if(*(p+1)=='1'){char *m_url_real=(char *)malloc(sizeof(char)*200);strcpy(m_url_real,"/log.html");//将网站目录和/register.html进行拼接,更新到m_real_file中strncpy(m_real_file+len,m_url_real,strlen(m_url_real));free(m_url_real);}else//如果以上均不符合,既不是登录和注册,直接将url与网站目录拼接//这里的情况是welcome界面,请求服务上的一个图片strncpy(m_real_file,m_url,FILENAME_LEN-len-1);//通过stat获取请求资源文件信息,成功则将信息更新到m_file_stat结构体//失败则返回NO_RESOURCE状态,表示资源不存在if(stat(m_real_file,&m_file_stat)<0)return NO_RESOURCE;//判断文件的权限,是否可读,不可读则返回FORBIDDEN_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);m_file_address=(char*)mmap(0,m_file_stat.st_size,PROT_READ,MAP_PRIVATE,fd,0);//避免文件描述符的浪费和占用close(fd);//表示请求文件存在,且可以访问return FILE_REQUEST;	
}

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添加空行

上述涉及的5个函数,均是内部调用add_response函数更新m_write_idx指针和缓冲区m_write_buf中的内容。

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;
}//添加状态行
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_linger();add_blank_line();
}//添加Content-Length,表示响应报文的长度
bool http_conn::add_content_length(int content_len)
{return add_response("Content-Length:%d\r\n",content_len);
}//添加文本类型,这里是html
bool http_conn::add_content_type()
{return add_response("Content-Type:%s\r\n","text/html");
}//添加连接状态,通知浏览器端是保持连接还是关闭
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");
}//添加文本content
bool http_conn::add_content(const char* content)
{return add_response("%s",content);
}

响应报文分为两种,一种是请求文件的存在,通过io向量机制iovec,声明两个iovec,第一个指向m_write_buf,第二个指向mmap的地址m_file_address;一种是请求出错,这时候只申请一个iovec,指向m_write_buf.

  • iovec是一个结构体,里面有两个元素,指针成员iov_base指向一个缓冲区,这个缓冲区是存放的是write将要发送的数据
  • 成员iov_len表示实际写入的长度
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;}//报文语法有误,404case BAD_REQUEST:{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_header(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].iob_len=m_write_idx;m_iv_count=1;return true;
}

http_conn::write

服务器子线程调用process_write完成响应报文,随后注册epollout事件。服务器主线程检测写事件,并调用http_conn::write函数将响应报文发送给浏览器端
该函数具体逻辑如下:
在生成响应报文时初始化byte_to_send,包括头部信息和文件数据大小。通过writev函数循环发送响应报文数据,根据返回值更新byte_have_send和iovec结构体的指针和长度,并判断响应报文整体是否发送成功。

  • 若writev单次发送成功,更新byte_to_send和byte_have_send的大小,若响应报文整体发送成功,则取消mmap映射,并判断是否是长连接。
    长连接重置http类实例,注册读事件,不关闭连接。
    短连接直接关闭连接
  • 若writev单次发送不成功,判断是否是写缓冲区满了
    若不是因为缓冲区满了而失败,取消mmap映射,关闭连接
    若eagain则满了,更新iovec结构体的指针和长度,并注册写事件,等待下一次写事件触发(当写缓冲区从不可写变为可写,触发epollout),因此在此期间无法立即接收到同一用户的下一请求,但可以保证连接的完整性。
bool http_conn::write()
{int temp=0;int newadd=0;//若要发送的数据长度为0//表示响应报文为空,一般不会出现这种情况if(bytes_to_send==0){modfd(m_epollfd,m_sockfd,EPOLLIN);init();return true;}while(1){//将响应报文的状态行、消息体、空行和响应正文发送给浏览器temp=writev(m_sockfd,m_iv,m_iv_count);//正常发送,temp为发送的字节数if(temp>0){//更新已发送字节bytes_have_send+=temp;//偏移文件iovec的指针newadd=bytes_have_send-m_write_idx;}if(temp<=-1){//判断缓冲区是否满了if(errno==EAGAIN){//第一个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+newadd;m_iv[1].iov_len=bytes_to_send;}//继续发送第一个iovec头部信息的数据else{m_iv[0].iov_base=m_write_buf+bytes_to_send;m_iv[0].iov_len=m_iv[0].iov_len-bytes_have_send;}//重写注册写事件modfd(m_epollfd,m_sockfd,EPOLLOUT);return true;}如果发送失败,但不是缓冲区问题,取消映射unmap();return false;}//更新需要发送字节数bytes_to_send-=send;//判断条件,数据已全部发送完if(bytes_to_send<=0){unmap();//在epoll树上重置EPOLLIN事件modfd(m_epollfd,m_sockfd,EPOLLIN);//浏览器的请求为长连接if(m_linger){//重新初始化HTTP对象init();return true;}else{return false;}}}
}

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

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

相关文章

linux 安装FTP

检查是否已经安装 $] rpm -qa |grep vsftpd vsftpd-3.0.2-29.el7_9.x86_64出现 vsftpd 信息表示已经安装&#xff0c;无需再次安装 yum安装 $] yum -y install vsftpd此命令需要root执行或有sudo权限的账号执行 /etc/vsftpd 目录 ftpusers # 禁用账号列表 user_list # 账号列…

集成kisso框架springboot解决登录不了问题

前端使用 VUE 框架&#xff0c;要求请求头中传入 token值&#xff0c;获取设置token使用cookie操作 import Cookies from js-cookieconst TokenKey token-c export function getToken() {return Cookies.get(TokenKey) } export function setToken(token) {Cookies.set(Token…

html学习3(表格table、列表list)

1、html表格由<table>标签来定义。 <thead>用来定义表格的标题部分&#xff0c;其内部用 <th > 元素定义列的标题&#xff0c;可以使其在表格中以粗体显示&#xff0c;与普通单元格区分开来。<tbody>用来定义表格的主体部分&#xff0c;其内部用<t…

无涯教程-jQuery - Tabs组件函数

窗口小部件选项卡函数可与JqueryUI中的窗口小部件一起使用。选项卡用于在分成逻辑部分的内容之间交换。 Tabs - 语法 $( "#tabs" ).tabs(); Tabs - 示例 以下是显示Tab用法的简单示例- <!doctype html> <html lang"en"><head><m…

【文生图系列】Runaway Gen-2试用体验

文章目录 风景示例动物示例人物动作示例 Runway旗下的视频生成产品Gen-1和Gen-2已彻底开放&#xff0c;任何人都可注册一个账号免费尝试。免费的时长是105s&#xff0c;每个视频生成4s。 看gen-2官网和各公众号放出来的示例&#xff0c;非常震撼&#xff0c;不禁感慨现在文生视…

Ubuntu Server版 之 apache系列 常用配置 以及 隐藏 版本号 IP、Port 搭建服务案例

查看版本 旧的 用 httpd -v 新的 用 apache2 -v 配置检测 旧的 httpd -t 新的 apachectl configtest window用的apache 是 httpd -t Linux 中 apachectl configtest 主配置文件 之前旧版apache 是httpd 现在都改成 apache2 /etc/apache2/apache2.conf window中 httpd.con…

Git全栈体系(三)

第六章 GitHub 操作 一、创建远程仓库 二、远程仓库操作 命令名称作用git remote -v查看当前所有远程地址别名git remote add 别名 远程地址起别名git push 别名 分支推送本地分支上的内容到远程仓库git clone 远程地址将远程仓库的内容克隆到本地git pull 远程库地址别名 远…

【C++】模板进阶(模板的特化,非类型模板参数,模板的分离编译)

文章目录 一、模板使用时一定要加typename的情况二、 非类型模板参数三、模板的特化1.函数模板特化2.类模板特化1.全特化&#xff1a;2. 偏特化&#xff1a;1. 部分特化2.参数更一步限制 四、模板的分离编译1.Stack.h2.Stack.cpp(定义)3.test.cpp 一、模板使用时一定要加typena…

TSINGSEE青犀视频汇聚平台EasyCVR多种视频流播放协议介绍

众所周知&#xff0c;TSINGSEE青犀视频汇聚平台EasyCVR可支持多协议方式接入&#xff0c;包括主流标准协议GB28181、RTSP/Onvif、RTMP等&#xff0c;以及厂家私有协议与SDK接入&#xff0c;包括海康Ehome、海大宇等设备的SDK等。今天我们来说一说&#xff0c;EasyCVR平台支持分…

maven本地仓库地址修改+maven国内镜像设置+maven运行所需pos.xml文件配置基本写法

1&#xff0c;maven本地仓库地址修改 maven在使用过程中&#xff0c;本地项目仓库其空间占用会越来越大&#xff0c;但是其默认仓库位置往往是以C盘为主&#xff0c;C盘作为系统盘常常会遇到所在盘空间占满的情况&#xff0c;所以我们将其改至其他硬盘空间位置为适合做法&#…

二十三种设计模式第二十三篇--状态模式

状态模式&#xff0c;是一种行为模式&#xff0c;在软件开发过程中&#xff0c;对象按照不同的情况做出不同的行为&#xff0c;我们把这样的对象称为具有状态的对象&#xff0c;而把影响对象行为的一个或者多个动态变化的属性称为状态。 对这种具有状态的对象变成&#xff0c;…

【kubernetes】k8s单master集群环境搭建及kuboard部署

k8s入门学习环境搭建 学习于许大仙: https://www.yuque.com/fairy-era k8s官网 https://kubernetes.io/ kuboard官网 https://kuboard.cn/ 基于k8s 1.21.10版本 前置环境准备 一主两从&#xff0c;三台虚拟机 CPU内存硬盘角色主机名IPhostname操作系统4C16G50Gmasterk8s-mast…

JVM面试题--JVM组成

JVM是什么 Java Virtual Machine Java程序的运行环境&#xff08;java二进制字节码的运行环境&#xff09; 运行流程 什么是程序计数器&#xff1f; 程序计数器&#xff1a;线程私有的&#xff0c;内部保存的字节码的行号。用于记录正在执行的字节码指令的地址。 我们知道ja…

第4章 .NetCore程序通过花生壳实现内网穿透

.NetCore程序在调试环境中都是基于内网的&#xff0c;但是.NetCore程序如果需要集成微信第3方认证程序&#xff0c;由于微信第3方认证程序需要外网把认证后的数据信息&#xff0c;通过外网传输到.NetCore程序中&#xff0c;所以必须先实现把内网地址映射为外网地址这样的操作叫…

【电影推荐系统】数据爬取、数据加载进MongoDB数据库

概览 本篇主要介绍数据来源、数据加载进数据库过程 1 数据获取 使用Scrapy爬取豆瓣电影数据&#xff0c;然后利用movielens数据集来造一份rating数据。 1.1 数据集获取 数据集获取&#xff1a;选取movielens 数据集&#xff1a;movielens官网数据集包括&#xff1a;movies…

【数据挖掘torch】 基于LSTM电力系统负荷预测分析(Python代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

Istio 安全 授权管理AuthorizationPolicy

这个和cka考试里面的网络策略是类似的。它是可以实现更加细颗粒度限制的。 本质其实就是设置谁可以访问&#xff0c;谁不可以访问。默认命名空间是没有AuthorizationPolicy---允许所有的客户端访问。 这里是没有指定应用到谁上面去&#xff0c;有没有指定使用哪些客户端&#…

SE-Net注意力机制

📌本次任务:了解SE-Net原理 SE-Net 是 ImageNet 2017(ImageNet 收官赛)的冠军模型,是由WMW团队发布。具有复杂度低,参数少和计算量小的优点。且SENet 思路很简单,很容易扩展到已有网络结构如 Inception 和 ResNet 中。(这篇论文是2019年的,应该是后续做了更新) 一…

12-3_Qt 5.9 C++开发指南_创建和使用静态链接库

第12章中的静态链接库和动态链接库介绍&#xff0c;都是以UI操作的方式进行&#xff0c;真正在实践中&#xff0c;可以参考UI操作产生的代码来实现同样的功能。 文章目录 1. 创建静态链接库1.1 创建静态链接库过程1.2 静态链接库代码1.2.1 静态链接库可视化UI设计框架1.2.2 qw…

Netty3 和Netty4区别

Netty3 和Netty4区别 目录概述需求&#xff1a; 设计思路实现思路分析1.Netty3和Netty4区别2.demo 拓展实现 参考资料和推荐阅读 Survive by day and develop by night. talk for import biz , show your perfect code,full busy&#xff0c;skip hardness,make a better resul…