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

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次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


相关文章
|
24天前
|
网络协议
深入解析:TCP四次挥手断开连接的全过程及必要性
在网络通信中,TCP(传输控制协议)以其可靠性和顺序保证而闻名。然而,TCP连接的建立和终止同样重要,它们确保了网络资源的有效管理和数据传输的完整性。本文将详细描述TCP连接的四次挥手过程,并探讨为何需要四次挥手来正确终止一个TCP连接。
43 2
|
1月前
|
网络协议 网络安全 网络虚拟化
本文介绍了十个重要的网络技术术语,包括IP地址、子网掩码、域名系统(DNS)、防火墙、虚拟专用网络(VPN)、路由器、交换机、超文本传输协议(HTTP)、传输控制协议/网际协议(TCP/IP)和云计算
本文介绍了十个重要的网络技术术语,包括IP地址、子网掩码、域名系统(DNS)、防火墙、虚拟专用网络(VPN)、路由器、交换机、超文本传输协议(HTTP)、传输控制协议/网际协议(TCP/IP)和云计算。通过这些术语的详细解释,帮助读者更好地理解和应用网络技术,应对数字化时代的挑战和机遇。
87 3
|
2月前
|
弹性计算 安全 API
HTTP 405 Method Not Allowed:解析与解决
本文详细解析了HTTP 405 "Method Not Allowed" 错误,包括其定义、常见原因、示例代码及解决方案。通过检查API文档、修改请求方法或更新服务器配置,可有效解决此错误,提升Web开发效率。
639 2
|
2月前
|
Java 关系型数据库 MySQL
【编程基础知识】Eclipse连接MySQL 8.0时的JDK版本和驱动问题全解析
本文详细解析了在使用Eclipse连接MySQL 8.0时常见的JDK版本不兼容、驱动类错误和时区设置问题,并提供了清晰的解决方案。通过正确配置JDK版本、选择合适的驱动类和设置时区,确保Java应用能够顺利连接MySQL 8.0。
255 1
|
2月前
|
域名解析 存储 缓存
域名解析 DNS:连接数字世界的关键枢纽
在数字世界中,DNS(域名解析系统)如同一位至关重要的引路人,将我们输入的域名与对应的IP地址相连,使我们可以轻松访问各种网站和服务。它通过多级服务器查询,将易于记忆的域名转换为复杂的IP地址,极大提升了互联网的易用性和普及度。尽管面临网络延迟和域名数量激增等挑战,通过分布式系统和缓存技术等创新方案,DNS 系统将持续发展,为用户提供更安全、高效的网络体验。
59 2
|
2月前
|
缓存 前端开发 安全
前端开发者必备:HTTP状态码含义与用途解析,常见错误码产生原因及解决策略
前端开发者必备:HTTP状态码含义与用途解析,常见错误码产生原因及解决策略
190 0
|
2月前
|
SQL 开发框架 .NET
ASP.NET连接SQL数据库:实现过程与关键细节解析an3.021-6232.com
随着互联网技术的快速发展,ASP.NET作为一种广泛使用的服务器端开发技术,其与数据库的交互操作成为了应用开发中的重要环节。本文将详细介绍在ASP.NET中如何连接SQL数据库,包括连接的基本概念、实现步骤、关键代码示例以及常见问题的解决方案。由于篇幅限制,本文不能保证达到完整的2000字,但会确保
http数据包抓包解析课程笔记
http数据包抓包解析课程笔记
|
1月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
71 2
|
2月前
|
缓存 Java 程序员
Map - LinkedHashSet&Map源码解析
Map - LinkedHashSet&Map源码解析
76 0

推荐镜像

更多
下一篇
DataWorks