一直很好奇web的工作原理,加之这阵子也在学习Python爬虫,就有想法了解这部分的知识,所以买了一本图解HTTP。
这本书简洁清晰也很形象地介绍了HTTP协议的工作流程,对零基础了解HTTP协议有着不错的引导作用。
书也很薄,可以很快看完。不过纯粹通过看书学习一个协议难免会浮于表面,因此,我找了TinyHttpd的source code来了解http协议的实现和实际工作场景。
1. 背景
一直很好奇web的工作原理,加之这阵子也在学习Python爬虫,就有想法了解这部分的知识,所以买了一本图解HTTP。这本书简洁清晰也很形象地介绍了HTTP协议的工作流程,对零基础了解HTTP协议有着不错的引导作用。书也很薄,可以很快看完。不过纯粹通过看书学习一个协议难免会浮于表面,因此,我找了TinyHttpd的source code来了解http协议的实现和实际工作场景。
2. 源码解析
声明:这篇里面的代码并不是TinyHttpd的源码,是我自己手动临摹一遍的代码,实测跑通了。一直相信代码自己码一遍会比纯看加注释收获多一些。同时,TinyHttpd只有几百行,自己码一遍也不算什么。关于阅读tinyhttpd的source code,个人觉得可以以如下顺序展开:main –> startup –> accept_request –> execute_cgi –>了解cgi实现,因此本文就按照此顺序展开分享。
主体框架 -> main()
main函数是整个httpd的工作框架,具体的实现流程如下, startup创建socket通信并建立端口监听 –> accept等待客户端连接请求 –> accept_request处理客户端http请求 –> cleanup释放资源
1 | int main(int argc,char *argv[]) |
基础通讯实现 -> startup()
HTTP是一个应用层协议,通过TCP/IP进行传输的。HTTP协议规定,连接请求从客户端发起,服务端提供资源响应。在客户端无请求的情况下,服务端不会主动发送响应。服务端通讯建立过程: socket创建套接字 –> bind绑定套接字 –> listen监听套接字 –> accept等待客户端连接请求。
1 | int startup(u_short *port) |
下文我们用来分析的报头首部是用wireshark抓chrome访问httpd时发出的,只有报文首部,没有报文主体。不同浏览器可能有所差异,具体可用wireshark尝试分析。
TinyHttpd主要是针对请求起始行进行处理。请求起始行由Method、Request-Url和Http版本信息组成,三者通过空格隔开。如下请求起始行中”GET”就是method,表示请求访问服务器的类型,用于告知服务器访问意图。”/“为URL,表示请求访问的资源,也称作Request-URL,”HTTP/1.1”表示http版本信息,用来提示客户端使用的http协议功能。
下面的内容为请求首部字段,是可选的,在accept_request的execute_cgi中,我们只有在处理POST请求时才会去解析这部分的内容,对于GET,我们解析请求起始行后会去清除buf中的这部分数据,避免对后续处理或者下次通讯请求造成影响。
1 | GET / HTTP/1.1 // 请求起始行 |
解析请求的具体实现。
1 | void *accept_request(void *pclient) |
执行CGI
1 | void execute_cgi( int client, const char *path, const char *method, const char *query_string ) |
文件发送实现
1 | void cat( int client, FILE *resource ) |
相关函数实现
1、获取客户端请求报文的一行内容
1 | int sock_getline(int sock, char *buf, unsigned int size) |
2、服务器响应报文实现
为方便代码编写和阅读,我在tinyhttpd的基础上实现了下面这个函数,专门用于发送字符到socket
1 | void send_str(int client, const char *str) |
1 | /*发送文件前的响应头*/ |
v1.5.2