开源代码学习之Tinyhttpd

时间:2024-04-23 09:35:54

想开始陆续研究一些感兴趣的开源代码于是先挑一个代码量短的来过渡一下,写这篇博客的目的是记录下自己学习的过程。Tinyhttpd算是一个微型的web服务器,浏览器与Web服务器之间的通信采用的是Http,所以一开始的切入点是HTTP协议,这里说一点如果有做HTTP通信的开发还是看一下RFC中对不同版本HTTP的定义,以下原理部分都是从《后台开发:核心技术与应用实践》中HTTP协议章节中裁剪出来的,对后台感兴趣的同学可以看一下,讲述后台开发所需要具备的技术点一本很不错的书。

一、HTTP协议

HTTP工作流程:

  在OSI七层模型中,HTTP是基于TCP上的应用层协议而我们所说的HTTPS基于同处于应用层的TLS、SSL协议层之上。HTTP默认的端口号为80,HTTPS默认的端口号为443。在HTTP1.1中(通过Connection头设置)默认在HTTP传输完成后不断开TCP连接,在此之前的HTTP版本则默认是断开连接的,也就是说这次请求与上次请求是不同的两个TCP连接。一次HTTP操作称为一个事务,其工作过程可以分为以下四步。

  (1)首先客户机与服务器需要建立连接 。 只要单击某个超级链接, HTTP 的工作即开始 。
  (2)建立连接后,客户机发送一个请求给服务器,请求方式的格式为:统一资源标识符(URL )、协议版本号,后边是 MIME 信息(包括请求修饰符、客户机信息和可能的内容) 。

  (3)服务器接到请求后,给予相应的响应信息,其格式为一个状态行,包括信息的协议版本号、一个成功或错误的代码,后边是 MTh伍信息(包括服务器信息、实体信息和可能的内容) 。

  (4)客户端接收服务器所返回的信息通过浏览器显示在用户的显示屏上,然后客户机与服务器断开连接 。

HTTP协议结构:

  HTTP 协议无论是请求报文还是回应报文,都分为以下 4 个部分 。

  (1)报文头( initial line ),上面的例子中的“ GET http://www.baidu.com/favicon.icoHTTP/1.1 ”表示用 GET 方法请求 http://www.baidu.com/favicon. ic。这个文件,用的是HTTP/1.1 协议。

  (2) 0 个或多个请求头( header line ),例如 Accept-Language: en

  (3)空行(作为 header lines 的结束) 。

  (4)可选的消息体 。

  HTTP 协议是基于行的协议,每一行 以 \r\n 作为分隔符 。 报文头通常表明报文的类型(例如请求类型),且报文头只占一行 ;请求头附带一些特殊信息,每一个请求头占一行,其格式为 name:value ,即以分号作为分隔; 空行也就以一个 \r\n 分隔;可选 body 通常包含数据,例如服务器返回的某个静态 HTML 文件的内容 。

  HTTP请求方法:

  HTTP/ 1.1 协议*定义了 9 种方法(有时也叫“动作”)来表 明 Request-URI 指定的资源的不同操作方式,如下所述。

  ( 1 ) OPTIONS :返回服务器针对特定资源所支持的 HTTP 请求方法;也可以利用向 Web服务器发送“*”的请求来测试服务器的功能性。

  ( 2) HEAD :向服务器索要与 GET 请求相一致的响应,只不过响应体将不会被返回 。 这一方法可以在不必传输整个响应内容的情况下,就可以获取包含在响应消息头中的元信息 。该方法常用于测试超链接的有效性,是否可以访问,以及最近是否更新等信息 。

  ( 3) GET :向特定的资源发出请求 。 注意 : GET 方法不应当被用于产生“副作用”的操作中,例如在 web app . 中的应用,其中一个原因是 GET 可能会被网络蜘蛛等随意访问 。

  ( 4 ) POST :向指定资源提交数据进行处理请求(例如提交表单或者上传文件) 。 数据被包含在请求体中 。 POST 请求可能会导致新的资源的建立或对已有资源的修改 。

  ( 5 ) PUT :向指定资源位置上传其最新内容 。

  ( 6) DELETE : 请求服务器删除 Request-URI 所标识的资源 。

  ( 7 ) TRACE :回显服务器收到的请求,主要用于测试或诊断。

  ( 8) CONNECT: HTTP/ 1.1 协议中预留给能够将连接改为管道方式的代理服务器 。

  ( 9) PATCH :用来将局部修改应用于某一资源,该操作添加于规范盯C5789 中 。

  HTTP 服务器至少应该实现 GET 和 HEAD 方法,其他方法都是可选的 。 此外,除了上述方法,特定的 HTTP 服务器还能够扩展自定义的方法 。

  HTTP 常见的请求头:

  在 HTTP/ l.l 协议中,所有的请求头(除 Host 外)都是可选的,因为Host主要用于请求的服务器的IP地址和端口号,请求头有Host、Connection、Accept、Accept-Encoding、User-Agent、Cookie等,请求头太多这里就不列出来了。

  HTTP回应报文:

  返回码由 3 位数字组成,第一个数字定义了响应的类别,且有 5 种可能的取值。

  ( 1 ) lxx :指示信息,表示请求已接收,继续处理。

  ( 2) 2xx :成功,表示请求已被成功接收、理解 、 接受 。

  ( 3) 3xx :重定向,要完成请求必须进行更进一步的操作 。

  ( 4 ) 4xx :客户端错误,请求有语法错误或请求无法实现。

  ( 5) 5xx :服务器端错误,服务器未能实现合法的请求 。

  Date :表示消息发送的时间,时间的描述格式由 rfc822 定义 。

  Server : 指明 Web 服务器用来处理请求的软件信息 。

  Accept-Ranges : Web 服务器表明自己是否接收获取其某个实体的一部分(比如文件的一部分)的请求 。 bytes 表示接收, none 表示不接收。

  Vary: Web 服务器用该头部的内容告诉 Cache 服务器,在什么条件下才能用本响应所返回的对象响应后续的请求 。

  Content-Encoding : Web 服务器表明自己使用了什么压缩方法( gzip, deflate)压缩响应中的对象。

  Content-Length: Web 服务器告诉浏览器自己响应的对象的长度 。

  Content-Type: Web 服务器告诉浏览器自己响应的对象的类型 。

二、CGI

  CGI ( Common Gateway Interface ,通用网关接口)是 HTTP 协议中最重要的技术之一,有着不可替代的重要地位 。 CGI 是一个 Web 服务器提供信息服务的标准接口 。 通过 CGI 接口, Web 服务器就能够获取客户端提交的信息,转交给服务器端的 CGI 程序进行处理,最后返回结果给客户端 。 组成 CGI 通信系统的是两部分: 一部分是 HTML 页面,就是在用户端浏览器上显示的页面;另一部分则是运行在服务器上的 CG I 程序 。

  浏览器只需要指定执行服务器上的哪个CGI程序就行,一般情况下,服务器和 CGI 程序之间是通过标准输入输出来进行数据传递的(就像tinyhttpd中调用CGI程序),而这个过程需要环境变茸的协作方可实现。环境变量在 CGI 中有着重要的地位,每个 CGI 程序只能处理一个用户请求,所以在激活一个CGI程序进程时也创建了属于该进程的环境变量,CGI程序能够用Python、PERL、Shell、C或C++等语言来实现。

  CGI 环境变量在 CGI 程序启动时初始化,在结束时销毁。当一个 CGI 程序不是被 HTTP 服务器调用时,它的环境变盘几乎是系统环境变量的复制 。当这个 CGI 程序被 HTTP 服务器调用时,它的环境变量就会多了以下关于HTTP 服务器、客户端、 CGI 传输过程等项目。CGI 相关的环境变量有 3 种:与请求相关的环境变量、与服务器相关的环境变量和与客户端相关的环境变量,,详细见表 12-1 。

  开源代码学习之Tinyhttpd

  CGI 工作原理:每当客户请求 CGI 的时候 Web 服务器就请求操作系统生成一个新的CGI 进程,该进程处理完请求后退出,下一个请求来时再创建新进程 。 当然,这样在访问量很少没有并发的情况可行,可是当访问量增大且并发存在时,这种方式就不适合了,于是就有了FastCGI。

  一般情况下, FastCGI 的整个工作流程如下所述。

  ( 1 ) Web Server 启动时载入 FastCGI 进程管理器( IIS ISAPI 或 Apache Module ) 。

  ( 2 )FastCGI 进程管理器自身初始化,启动多个 CGI 进程并等待来自 Web 服务器的连接 。

  ( 3 )当客户端请求到达 Web Server 时, FastCGI 进程管理器选择并连接到一个 FastCGI进程 。 Web 服务器将 CGI 环境变量和标准输入发送到 FastCGI 进程 。

  ( 4) FastCGI 子进程完成处理后将标准输出和错误信息从同一连接返回 Web Server。当FastCGI 子进程关闭连接时,请求便被告知处理完成 。 FastCGI 进程接着等待并处理来自FastCGI 进程管理器(运行在 Web 服务器中)的下一个连接 。

三、tinyhttpd解析

  关于tinyhttpd的代码解析的博客太多了,流程跟注释都描述的很详细,随便搜个tinyhttpd解析就有很多博客出现,这里还是写一下自己在看这份源码时缺乏的知识点,因为我几乎没有linux下编程的经验,所以对linux下用的Glib库调用不太熟悉,TCP协议栈也是我只看过过轻量级Lwip的源码。

  1.bind函数当传入的port为0时是会随机分配一个端口号,所以tinyhttpd中才会有显示随机的端口号,getsockname函数获取套接字的地址把动态分配的端口号值取出。

  开源代码学习之Tinyhttpd开源代码学习之Tinyhttpd

  2.int stat(const char *file_name, struct stat *buf);通过文件名filename获取文件信息,并保存在buf所指的结构体stat中,S_IXUSR:文件所有者具可执行权限,S_IXGRP:用户组具可执行权限,S_IXOTH:其他用户具可读取权限。

开源代码学习之Tinyhttpd

  3.int pipe(int filedes[2]);

  返回值:成功,返回0,否则返回-1。参数数组包含pipe使用的两个文件的描述符。fd[0]:读管道,fd[1]:写管道。

  必须在fork()中调用pipe(),否则子进程不会继承文件描述符。两个进程不共享祖先进程,就不能使用pipe。但是可以使用命名管道。

  pipe(cgi_output)执行成功后,cgi_output[0]:读通道 cgi_output[1]:写通道

  开源代码学习之Tinyhttpd

   int dup2(int oldfd,int newfd);函数的作用是复制文件描述符。

  4.fork函数,创建一个子进程,fork函数之后的部分由在fork函数执行完毕后,如果创建新进程成功,则出现两个进程,一个是子进程,一个是父进程。在子进程中,fork函数返回0,在父进程中,fork返回新创建子进程的进程ID,有时间再看fork源码是如何实现的。

  5.execl函数执行,第一参数path字符指针所指向要执行的文件路径,后面跟着可变参数,这里说一点由于GCC使用AAPCS规范,可变参数的实现小于四个的话是从r0-r3获取,大于四个的话就要从栈中获取参数。 

  6.pid_t waitpid(pid_t pid, int * status, int options);会暂时停止目前进程的执行, 直到有信号来到或子进程结束. 

  在编写博客边看tinyhttpd,目前自己的不够清楚的函数就上面几个,有空再看看它们的源码实现。