天天看点

深入理解计算机系统_3e 第十一章家庭作业 CS:APP3e chapter 11 homework

注: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中是将请求行和请求报头输出到标准输出,在命令行启动的时候用“&gt;”重定向到一个文件即可。

但是要注意一点:由于我们输出到文件而非一个交互设备,所以流缓冲默认是满缓冲的,如果我们在一个静态请求后按下“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> ):

效果如下:

深入理解计算机系统_3e 第十一章家庭作业 CS:APP3e chapter 11 homework

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中取参数的部分修改为:

运行效果:

深入理解计算机系统_3e 第十一章家庭作业 CS:APP3e chapter 11 homework

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&amp;SecondNumber=321</code>放在了消息体内传递。

为了将<code>FirstNumber=123&amp;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作为结尾的,抓包如下:

深入理解计算机系统_3e 第十一章家庭作业 CS:APP3e chapter 11 homework

可以看到最后不是以换行符作为结尾,所以我们这里不能像之前那样使用MAXLINE作为第三个参数调用Rio这样的函数(读取函数read无法判断是否已经到达了结尾,因为缓冲区很长,表现为一直等待输入,服务器无响应),而是应该根据rp-&gt;rio_cnt读取所缓冲器剩下的所有字符:

同时我们要更改一下<code>parse_uri</code>函数,因为如果是POST方法的话,<code>read_requesthdrs</code>就已经更新了<code>cgiargs</code>了,所有需要在<code>parse_uri</code>对方法做一个判断(也要增加一个method参数),如果是GET方法的话才对<code>cgiargs</code>进行更新:

最终效果:

深入理解计算机系统_3e 第十一章家庭作业 CS:APP3e chapter 11 homework

关于传递方法详细的介绍可以参考:Sending form data

11.13

这里我们直接忽略EPIPE信号(在主函数中安装):

然后更换以前使用的<code>Rio_writen</code> ,使之能够处理EPIPE:

最后要注意cgi程序运行的时候也可能遇到EPIPE信号,我们要交给它自己处理: