【后台开发】TinyWebser学习笔记(3)HTTP连接与解析

简介: 【后台开发】TinyWebser学习笔记(3)HTTP连接与解析

之前学习了有关线程池、数据库连接池的相关知识,接下来进行重头戏——HTTP连接的相关知识。

这段时间在准备暑期实习的面试,参加了腾讯、阿里、CVTE、深信服等公司的面试发现自己对之前的这个开源项目还有很多细节不够了解,特此回来学习一波,如果有什么纰漏还请大家谅解、指正。

一、http的详细连接过程

http需要结合epoll/select/poll来理解,所以可以点此跳转回学习笔记2,复习相关知识。

1.1 Http报文

有关HTTP报文格式,HTTP报文分为请求报文和响应报文两种,每种报文必须按照特有格式生成,才能被浏览器端识别。

其中,浏览器端向服务器发送的为请求报文,服务器处理后返回给浏览器端的为响应报文。

·请求报文=请求行(request line)、请求头部(header)、空行和请求数据四个部分组成。又分为两种,GET/POST

请求行,用来说明请求类型,要访问的资源以及所使用的HTTP版本。

GET说明请求类型为GET,/562f25980001b1b106000338.jpg(URL)为要访问的资源,该行的最后一部分说明使用的是HTTP1.1版本。

请求头部,紧接着请求行(即第一行)之后的部分,用来说明服务器要使用的附加信息,常见如下:

HOST,给出请求资源所在服务器的域名。
User-Agent,HTTP客户端程序的信息,该信息由你发出请求使用的浏览器来定义,并且在每个请求中自动发送等。
Accept,说明用户代理可处理的媒体类型。
Accept-Encoding,说明用户代理支持的内容编码。
Accept-Language,说明用户代理能够处理的自然语言集。
Content-Type,说明实现主体的媒体类型。
Content-Length,说明实现主体的大小。
Connection,连接管理,可以是Keep-Alive或close。

·空行,请求头部后面的空行是必须的即使第四部分的请求数据为空,也必须有空行。

·请求数据也叫主体,可以添加任意的其他数据。

·响应报文=状态行+消息报头+空行+响应正文四个部分组成

·状态行,由HTTP协议版本号,状态码,状态消息 三部分组成。第一行为状态行,(HTTP/1.1)表明HTTP版本为1.1版本,状态码为200,状态消息为OK。

·消息报头,用来说明客户端要使用的一些附加信息。

第二行和第三行为消息报头,Date:生成响应的日期和时间;Content-Type:指定了MIME类型的HTML(text/xml),编码类型是UTF-8。

·空行,消息报头后面的空行是必须的。

·响应正文,服务器返回给客户端的文本信息。空行后面的html部分为响应正文。

有关http的状态码:

HTTP有5种类型的状态码,具体的:

·1xx:指示信息--表示请求已接收,继续处理。
·2xx:成功--表示请求正常处理完毕。
200 OK:客户端请求被正常处理。
206 Partial content:客户端进行了范围请求。
·3xx:重定向--要完成请求必须进行更进一步的操作。
301 Moved Permanently:永久重定向,该资源已被永久移动到新位置,将来任何对该资源的访问都要使用本响应返回的若干个URI之一。
302 Found:临时重定向,请求的资源现在临时从不同的URI中获得。
·4xx:客户端错误--请求有语法错误,服务器无法处理请求。
400 Bad Request:请求报文存在语法错误。
403 Forbidden:请求被服务器拒绝。
404 Not Found:请求不存在,服务器上找不到请求的资源。
·5xx:服务器端错误--服务器处理请求出错。
500 Internal Server Error:服务器在执行请求时出现错误。

1.2 http报文处理流程

·浏览器端发出http连接请求,服务器端主线程创建http对象接收请求并将所有数据读入对应buffer,将该对象插入任务队列,工作线程从任务队列中取出一个任务进行处理。 参见1.3节

·工作线程取出任务后,调用process_read函数,通过主、从状态机对请求报文进行解析。参见1.4节

·解析完之后,跳转do_request函数生成响应报文,通过process_write写入buffer,返回给浏览器端。

1.3 http类

1class http_conn{
  2    public:
  3        //设置读取文件的名称m_real_file大小
  4        static const int FILENAME_LEN=200;
  5        //设置读缓冲区m_read_buf大小
  6        static const int READ_BUFFER_SIZE=2048;
  7        //设置写缓冲区m_write_buf大小
  8        static const int WRITE_BUFFER_SIZE=1024;
  9        //报文的请求方法,本项目只用到GET和POST
 10        enum METHOD{GET=0,POST,HEAD,PUT,DELETE,TRACE,OPTIONS,CONNECT,PATH};
 11        //主状态机的状态
 12        enum CHECK_STATE{CHECK_STATE_REQUESTLINE=0,CHECK_STATE_HEADER,CHECK_STATE_CONTENT};
 13        //报文解析的结果
 14        enum HTTP_CODE{NO_REQUEST,GET_REQUEST,BAD_REQUEST,NO_RESOURCE,FORBIDDEN_REQUEST,FILE_REQUEST,INTERNAL_ERROR,CLOSED_CONNECTION};
 15        //从状态机的状态
 16        enum LINE_STATUS{LINE_OK=0,LINE_BAD,LINE_OPEN};
 17
 18    public:
 19        http_conn(){}
 20        ~http_conn(){}
 21
 22    public:
 23        //初始化套接字地址,函数内部会调用私有方法init
 24        void init(int sockfd,const sockaddr_in &addr);
 25        //关闭http连接
 26        void close_conn(bool real_close=true);
 27        void process();
 28        //读取浏览器端发来的全部数据
 29        bool read_once();
 30        //响应报文写入函数
 31        bool write();
 32        sockaddr_in *get_address(){
 33            return &m_address;  
 34        }
 35        //同步线程初始化数据库读取表
 36        void initmysql_result();
 37        //CGI使用线程池初始化数据库表
 38        void initresultFile(connection_pool *connPool);
 39
 40    private:
 41        void init();
 42        //从m_read_buf读取,并处理请求报文
 43        HTTP_CODE process_read();
 44        //向m_write_buf写入响应报文数据
 45        bool process_write(HTTP_CODE ret);
 46        //主状态机解析报文中的请求行数据
 47        HTTP_CODE parse_request_line(char *text);
 48        //主状态机解析报文中的请求头数据
 49        HTTP_CODE parse_headers(char *text);
 50        //主状态机解析报文中的请求内容
 51        HTTP_CODE parse_content(char *text);
 52        //生成响应报文
 53        HTTP_CODE do_request();
 54
 55        //m_start_line是已经解析的字符
 56        //get_line用于将指针向后偏移,指向未处理的字符
 57        char* get_line(){return m_read_buf+m_start_line;};
 58
 59        //从状态机读取一行,分析是请求报文的哪一部分
 60        LINE_STATUS parse_line();
 61
 62        void unmap();
 63
 64        //根据响应报文格式,生成对应8个部分,以下函数均由do_request调用
 65        bool add_response(const char* format,...);
 66        bool add_content(const char* content);
 67        bool add_status_line(int status,const char* title);
 68        bool add_headers(int content_length);
 69        bool add_content_type();
 70        bool add_content_length(int content_length);
 71        bool add_linger();
 72        bool add_blank_line();
 73
 74    public:
 75        static int m_epollfd;
 76        static int m_user_count;
 77        MYSQL *mysql;
 78
 79    private:
 80        int m_sockfd;
 81        sockaddr_in m_address;
 82
 83        //存储读取的请求报文数据
 84        char m_read_buf[READ_BUFFER_SIZE];
 85        //缓冲区中m_read_buf中数据的最后一个字节的下一个位置
 86        int m_read_idx;
 87        //m_read_buf读取的位置m_checked_idx
 88        int m_checked_idx;
 89        //m_read_buf中已经解析的字符个数
 90        int m_start_line;
 91
 92        //存储发出的响应报文数据
 93        char m_write_buf[WRITE_BUFFER_SIZE];
 94        //指示buffer中的长度
 95        int m_write_idx;
 96
 97        //主状态机的状态
 98        CHECK_STATE m_check_state;
 99        //请求方法
100        METHOD m_method;
101
102        //以下为解析请求报文中对应的6个变量
103        //存储读取文件的名称
104        char m_real_file[FILENAME_LEN];
105        char *m_url;
106        char *m_version;
107        char *m_host;
108        int m_content_length;
109        bool m_linger;
110
111        char *m_file_address;        //读取服务器上的文件地址
112        struct stat m_file_stat;
113        struct iovec m_iv[2];        //io向量机制iovec
114        int m_iv_count;
115        int cgi;                    //是否启用的POST
116        char *m_string;                //存储请求头数据
117        int bytes_to_send;          //剩余发送字节数
118        int bytes_have_send;        //已发送字节数
119};

1.3.1 read_once

1 //循环读取客户数据,直到无数据可读或对方关闭连接
 2bool http_conn::read_once()
 3{
 4    if(m_read_idx>=READ_BUFFER_SIZE)
 5    {
 6        return false;
 7    }
 8    int bytes_read=0;
 9    while(true)
10    {
11        //从套接字接收数据,存储在m_read_buf缓冲区
12        bytes_read=recv(m_sockfd,m_read_buf+m_read_idx,READ_BUFFER_SIZE-m_read_idx,0);
13        if(bytes_read==-1)
14        {    
15            //非阻塞ET模式下,需要一次性将数据读完
16            if(errno==EAGAIN||errno==EWOULDBLOCK)
17                break;
18            return false;
19        }
20        else if(bytes_read==0)
21        {
22            return false;
23        }
24        //修改m_read_idx的读取字节数
25        m_read_idx+=bytes_read;
26    }
27    return true;
28}

1.3.2 epoll部分

① 文件描述符的设置

② epoll的epollshot注册

//将内核事件表注册读事件,ET模式,选择开启EPOLLONESHOT
void addfd(int epollfd, int fd, bool one_shot, int TRIGMode)
{
    epoll_event event;
    event.data.fd = fd;

    if (1 == TRIGMode)
        event.events = EPOLLIN | EPOLLET | EPOLLRDHUP;
    else
        event.events = EPOLLIN | EPOLLRDHUP;

    if (one_shot)
        event.events |= EPOLLONESHOT;
    epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event);
    setnonblocking(fd);
}

PS:epoll_onesheot主要是为了防止同一socket连续接收到数据,造成解析异常,详见博客:https://blog.csdn.net/liuhengxiao/article/details/46911129

③ 重置epollshot事件

void modfd(int epollfd, int fd, int ev, int TRIGMode)
{
    epoll_event event;
    event.data.fd = fd;

    if (1 == TRIGMode)
        event.events = ev | EPOLLET | EPOLLONESHOT | EPOLLRDHUP;
    else
        event.events = ev | EPOLLONESHOT | EPOLLRDHUP;

    epoll_ctl(epollfd, EPOLL_CTL_MOD, fd, &event);
}

1.3.3 服务器接收http请求

主要由函数://事件回环(即服务器主线程)

void WebServer::eventLoop()进行处理。

1.4 http的状态机与解析请求报文

从状态机负责读取报文的一行,主状态机负责对该行数据进行解析,主状态机内部调用从状态机,从状态机驱动主状态机。流程图如下:(该部分最好的理解途径就是借助流程图进行理解)

  主状态机有三种状态:
·CHECK_STATE_REQUESTLINE,解析请求行
·CHECK_STATE_HEADER,解析请求头
·CHECK_STATE_CONTENT,解析消息体,仅用于解析POST请求
  从状态机的三种状态:
·LINE_OK,完整读取一行
·LINE_BAD,报文语法有误
·LINE_OPEN,读取的行不完整

解析报文整体流程

process_read通过while循环,将主从状态机进行封装,对报文的每一行进行循环处理。

1.5 服务器相应报文

浏览器端发出HTTP请求报文,服务器端接收该报文并调用process_read对其进行解析,根据解析结果HTTP_CODE,进入相应的逻辑和模块。其中,服务器子线程完成报文的解析与响应;主线程监测读写事件,调用read_once和http_conn::write完成数据的读取与发送。(参考:https://mp.weixin.qq.com/s/451xNaSFHxcxfKlPBV3OCg

PS:关于这个部分的http资源请求和登陆,如果还想进一步了解其细节,可以直接参考博客:https://mp.weixin.qq.com/s/451xNaSFHxcxfKlPBV3Ocg以及https://mp.weixin.qq.com/s/wAQHU-QZiRt1VACMZZjNlw


相关文章
|
JavaScript 前端开发 开发者
Node学习笔记:HTTP模块
总的来说,Node.js的HTTP模块是一个强大的工具,可以帮助你处理HTTP协议的各种需求。无论你是想开设自己的餐厅(创建服务器),还是想去别的餐厅点菜(发出请求),HTTP模块都能满足你的需求。
360 18
|
12月前
|
域名解析 网络协议 网络安全
SSL证书验证全攻略:DNS/HTTP/手动解析怎么选?
SSL证书在网络安全中至关重要,1Panel提供三种验证方式:DNS验证、HTTP验证和手动解析。DNS验证便捷,适合CDN网站;HTTP验证快速,需服务器在线;手动解析灵活,但操作复杂。根据需求选择合适确认方式,定期检查证书状态。
1176 2
|
算法 测试技术 C语言
深入理解HTTP/2:nghttp2库源码解析及客户端实现示例
通过解析nghttp2库的源码和实现一个简单的HTTP/2客户端示例,本文详细介绍了HTTP/2的关键特性和nghttp2的核心实现。了解这些内容可以帮助开发者更好地理解HTTP/2协议,提高Web应用的性能和用户体验。对于实际开发中的应用,可以根据需要进一步优化和扩展代码,以满足具体需求。
1315 29
|
监控 Shell Linux
Android调试终极指南:ADB安装+多设备连接+ANR日志抓取全流程解析,覆盖环境变量配置/多设备调试/ANR日志分析全流程,附Win/Mac/Linux三平台解决方案
ADB(Android Debug Bridge)是安卓开发中的重要工具,用于连接电脑与安卓设备,实现文件传输、应用管理、日志抓取等功能。本文介绍了 ADB 的基本概念、安装配置及常用命令。包括:1) 基本命令如 `adb version` 和 `adb devices`;2) 权限操作如 `adb root` 和 `adb shell`;3) APK 操作如安装、卸载应用;4) 文件传输如 `adb push` 和 `adb pull`;5) 日志记录如 `adb logcat`;6) 系统信息获取如屏幕截图和录屏。通过这些功能,用户可高效调试和管理安卓设备。
|
JSON 监控 网络协议
Bilibili直播信息流:连接方法与数据解析
本文详细介绍了自行实现B站直播WebSocket连接的完整流程。解析了基于WebSocket的应用层协议结构,涵盖认证包构建、心跳机制维护及数据包解析步骤,为开发者定制直播数据监控提供了完整技术方案。
1991 9
|
缓存 安全 网络安全
代理协议解析:如何根据需求选择HTTP、HTTPS或SOCKS5?
本文详细介绍了HTTP、HTTPS和SOCKS5三种代理协议的特点、优缺点以及适用场景。通过对比和分析,可以根据具体需求选择最合适的代理协议。希望本文能帮助您更好地理解和应用代理协议,提高网络应用的安全性和性能。
1246 17
|
安全 网络协议 网络安全
解析HTTP代理服务器不稳定致使掉线的关键原因
随着数字化发展,网络安全和隐私保护成为核心需求。HTTP代理服务器掉线原因主要包括:1. 网络问题,如本地网络不稳定、路由复杂;2. 服务器质量差、IP资源不稳定;3. 用户配置错误、超时或请求频率异常;4. IP失效或协议不兼容。这些问题会影响连接稳定性。
806 8
|
网络协议
深入解析:TCP四次挥手断开连接的全过程及必要性
在网络通信中,TCP(传输控制协议)以其可靠性和顺序保证而闻名。然而,TCP连接的建立和终止同样重要,它们确保了网络资源的有效管理和数据传输的完整性。本文将详细描述TCP连接的四次挥手过程,并探讨为何需要四次挥手来正确终止一个TCP连接。
641 2
|
前端开发 数据安全/隐私保护 CDN
二次元聚合短视频解析去水印系统源码
二次元聚合短视频解析去水印系统源码
533 4
|
JavaScript 算法 前端开发
JS数组操作方法全景图,全网最全构建完整知识网络!js数组操作方法全集(实现筛选转换、随机排序洗牌算法、复杂数据处理统计等情景详解,附大量源码和易错点解析)
这些方法提供了对数组的全面操作,包括搜索、遍历、转换和聚合等。通过分为原地操作方法、非原地操作方法和其他方法便于您理解和记忆,并熟悉他们各自的使用方法与使用范围。详细的案例与进阶使用,方便您理解数组操作的底层原理。链式调用的几个案例,让您玩转数组操作。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~

热门文章

最新文章

推荐镜像

更多
  • DNS