【后台开发】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


相关文章
|
缓存 监控 搜索推荐
301重定向实现原理全面解析:从HTTP协议到SEO最佳实践
301重定向是HTTP协议中的永久重定向状态码,用于告知客户端请求的资源已永久移至新URL。它在SEO中具有重要作用,能传递页面权重、更新索引并提升用户体验。本文详解其工作原理、服务器配置方法(如Apache、Nginx)、对搜索引擎的影响及最佳实践,帮助实现网站平稳迁移与优化。
1492 68
|
网络协议 API Python
解析http.client与requests在Python中的性能比较和改进策略。
最后,需要明确的是,这两种库各有其优点和适用场景。`http.client` 更适合于基础且并行的请求,`requests` 则因其易用且强大的功能,更适用于复杂的 HTTP 场景。对于哪种更适合你的应用,可能需要你自己进行实际的测试来确定。
323 10
|
缓存 网络协议 UED
深度解析HTTP协议从版本0.9至3.0的演进和特性。
总的来说,HTTP的演进是互联网技术不断发展和需求日益增长的结果。每一次重要更新都旨在优化性能,增进用户体验,适应新的应用场景,而且保证了向后兼容,让互联网的基础架构得以稳定发展。随着网络技术继续进步,我们可以预期HTTP协议在未来还会继续演化。
1169 0
|
数据采集 JSON Go
Go语言实战案例:实现HTTP客户端请求并解析响应
本文是 Go 网络与并发实战系列的第 2 篇,详细介绍如何使用 Go 构建 HTTP 客户端,涵盖请求发送、响应解析、错误处理、Header 与 Body 提取等流程,并通过实战代码演示如何并发请求多个 URL,适合希望掌握 Go 网络编程基础的开发者。
|
JavaScript 前端开发 开发者
Node学习笔记:HTTP模块
总的来说,Node.js的HTTP模块是一个强大的工具,可以帮助你处理HTTP协议的各种需求。无论你是想开设自己的餐厅(创建服务器),还是想去别的餐厅点菜(发出请求),HTTP模块都能满足你的需求。
421 18
|
域名解析 网络协议 网络安全
SSL证书验证全攻略:DNS/HTTP/手动解析怎么选?
SSL证书在网络安全中至关重要,1Panel提供三种验证方式:DNS验证、HTTP验证和手动解析。DNS验证便捷,适合CDN网站;HTTP验证快速,需服务器在线;手动解析灵活,但操作复杂。根据需求选择合适确认方式,定期检查证书状态。
1463 2
|
安全 网络协议 算法
HTTP/HTTPS与SOCKS5协议在隧道代理中的兼容性设计解析
本文系统探讨了构建企业级双协议隧道代理系统的挑战与实现。首先对比HTTP/HTTPS和SOCKS5协议特性,分析其在工作模型、连接管理和加密方式上的差异。接着提出兼容性架构设计,包括双协议接入层与统一隧道内核,通过协议识别模块和分层设计实现高效转换。关键技术部分深入解析协议转换引擎、连接管理策略及加密传输方案,并从性能优化、安全增强到典型应用场景全面展开。最后指出未来发展趋势将更高效、安全与智能。
708 1
|
算法 测试技术 C语言
深入理解HTTP/2:nghttp2库源码解析及客户端实现示例
通过解析nghttp2库的源码和实现一个简单的HTTP/2客户端示例,本文详细介绍了HTTP/2的关键特性和nghttp2的核心实现。了解这些内容可以帮助开发者更好地理解HTTP/2协议,提高Web应用的性能和用户体验。对于实际开发中的应用,可以根据需要进一步优化和扩展代码,以满足具体需求。
1582 29
|
网络安全
网络问题解析:如何解决CondaHTTPError HTTP 000 CONNECTION FAILED错误。
以上就是斯诺普为你准备的解决Conda出现HTTP连接错误的手术室。希望这辆小车可以顺利驶出棘手的泥潭,再次在自由的大路上疾驰。一切的尝试和努力,只为更好的探索与开发。
586 17
|
安全 网络安全 数据安全/隐私保护
HTTP 与 HTTPS 协议及 SSL 证书解析-http和https到底有什么区别?-优雅草卓伊凡
HTTP 与 HTTPS 协议及 SSL 证书解析-http和https到底有什么区别?-优雅草卓伊凡
851 3

推荐镜像

更多
  • DNS