注:tiny.c csapp.c csapp.h等示例代码均可在Code Examples获取
11.6
A.
书上写的示例代码已经完成了大部分工作:<code>doit</code>函数中的<code>printf("%s", buf);</code>语句打印出了请求行;<code>read_requesthdrs</code>函数打印出了剩下的请求报头,但是要注意书上写的是:
如果按照这个打印的话,第一个请求抱头Host将无法输出,所以我们应该在while循环前输出一个报头:
B.
我们在A中是将请求行和请求报头输出到标准输出,在命令行启动的时候用“>”重定向到一个文件即可。
但是要注意一点:由于我们输出到文件而非一个交互设备,所以流缓冲默认是满缓冲的,如果我们在一个静态请求后按下“CRTL+C”终止tiny,那么由于缓冲内容没有输出,则文件内将没有内容,所以我们应该在<code>read_requesthdrs</code>函数最后加上一个<code>fflush</code> :
关于流缓冲的问题,可以参考我之前写的这篇文章:文件描述符 流 流缓冲的一些概念与问题
运行输出:
C.
由B中的请求行:GET /test.c HTTP/1.1可知使用的是HTTP/1.1
D.
注意: 书上说查RFC 2616来确认各个请求报头的功能,但是有一些报头我没有在该文档中找到,根据这篇文章:
HTTP/1.1协议更新:RFC2616遭废弃
“原来的RFC 2616拆分为六个单独的协议说明,并重点对原来语义模糊的部分进行了解释,新的协议说明更易懂、易读。新的协议说明包括以下六部分:”
RFC7230 - HTTP/1.1: Message Syntax and Routing - low-level message parsing and connection management
RFC7231 - HTTP/1.1: Semantics and Content - methods, status codes and headers
RFC7232 - HTTP/1.1: Conditional Requests - e.g., If-Modified-Since
RFC7233 - HTTP/1.1: Range Requests - getting partial content
RFC7234 - HTTP/1.1: Caching - browser and intermediary caches
RFC7235 - HTTP/1.1: Authentication - a framework for HTTP authentication
我们应该在RFC7231中查找,而非RFC2616。 具体的介绍我就不列出了,大家可以自己在RFC7231查找详细介绍。
11.7
我电脑上倒还没有MPG格式的视频,拿一个WEBM的视频做示例——都是要将响应报头<code>Content-type:</code> 更改为对应的格式(这里是<code>video/webm</code> ):
效果如下:

11.8
根据书上8.5节的内容,我们先写一个<code>SIGCHLD</code>的信号处理函数<code>handler</code> :
并<code>serve_dynamic</code>中使用<code>signal</code>将其安装 :
经多次测试运行cgi程序未发现僵尸进程。
11.9
别忘记释放内存。
11.10
关于HTML表单的知识参考:HTML 表单
index.html:
由于我们不需要默认值,所以value为空。
由于我们此时的GET请求是“name=value”的形式,所以应该将adder.c中取参数的部分修改为:
运行效果:
11.11
根据RFC 7231, section 4.3.2: HEAD中的描述,HEAD方法大致描述如下:
HEAD方法与GET相同,但是HEAD并不返回消息体。在一个HEAD请求的消息响应中,HTTP投中包含的元信息应该和一个GET请求的响应消息相同。这种方法可以用来获取请求中隐含的元信息,而无需传输实体本身。这个方法经常用来测试超链接的有效性,可用性和最近修改。 一个HEAD请求响应可以被缓存,也就是说,响应中的信息可能用来更新之前缓存的实体。如果当前实体缓存实体阈值不同(可通过Content_Length、Content-MD5、ETag或Last-Modified的变化来表明),那么这个缓存被视为过期了。
所以我们只需要将处理GET方法的两个函数<code>serve_static</code>和<code>serve_dynamic</code>中增加一个如果是HEAD方法则不返回消息体的判断。而方法是在<code>doit</code>中判断的,所以我们设置一个标志<code>unsigned methods = 0;</code> 并在判断方法的语句中加上HEAD方法的判断,如果为GET,其值为0,如果为HEAD,其值为1:
最后将函数<code>serve_static</code>和<code>serve_dynamic</code>的参数增加一个<code>unsigned methods</code> :
在其中返回消息体之前加上这样一条语句:
Telnet测试效果:
11.12
如果我们用POST方法传递参数的话,URI中将不含参数,而是在消息体中传递参数,例如:
index.html(将method改为POST):
抓包request如下:
可以看到<code>FirstNumber=123&SecondNumber=321</code>放在了消息体内传递。
为了将<code>FirstNumber=123&SecondNumber=321</code>存入<code>cgiargs</code> ,我们需要在<code>read_requesthdrs</code>中判断是否是POST然后将请求中最后的消息体存入<code>cgiargs</code>。
首先,在<code>doit</code>中增加一个POST的method判断:
然后为<code>read_requesthdrs</code>增加两个参数:
在其中更据是否是POST方法来读取消息体:
这里要特别注意,消息体不是以\r\n作为结尾的,抓包如下:
可以看到最后不是以换行符作为结尾,所以我们这里不能像之前那样使用MAXLINE作为第三个参数调用Rio这样的函数(读取函数read无法判断是否已经到达了结尾,因为缓冲区很长,表现为一直等待输入,服务器无响应),而是应该根据rp->rio_cnt读取所缓冲器剩下的所有字符:
同时我们要更改一下<code>parse_uri</code>函数,因为如果是POST方法的话,<code>read_requesthdrs</code>就已经更新了<code>cgiargs</code>了,所有需要在<code>parse_uri</code>对方法做一个判断(也要增加一个method参数),如果是GET方法的话才对<code>cgiargs</code>进行更新:
最终效果:
关于传递方法详细的介绍可以参考:Sending form data
11.13
这里我们直接忽略EPIPE信号(在主函数中安装):
然后更换以前使用的<code>Rio_writen</code> ,使之能够处理EPIPE:
最后要注意cgi程序运行的时候也可能遇到EPIPE信号,我们要交给它自己处理: