前言
《深入理解计算机系统》官网:http://csapp.cs.cmu.edu/3e/labs.html
该篇文章是
实验八Proxy Lab的Writeup(proxylab.pdf)机翻
原文:http://csapp.cs.cmu.edu/3e/proxylab.pdf
在官网点击下方即可下载实验八的文件
1 介绍
Web代理是在Web浏览器和终端服务器之间充当中间人的程序。浏览器不直接与终端服务器联系以获得Web页面,而是与代理联系,后者将请求转发到终端服务器。当终端服务器回复代理时,代理将回复发送给浏览器。
代理在很多方面都很有用。有时在防火墙中使用代理,因此防火墙后的浏览器只能通过代理与防火墙外的服务器联系。代理还可以充当匿名者:通过剥离请求中的所有标识信息,代理可以使浏览器对Web服务器是匿名的。代理甚至可以用来缓存web对象,通过存储来自服务器的对象的本地副本,然后通过从缓存中读取它们来响应未来的请求,而不是再次与远程服务器通信。
在本实验中,您将编写一个简单的HTTP代理来缓存web对象。对于本实验的第一部分,您将设置代理来接受传入的连接、读取和解析请求、将请求转发到web服务器、读取服务器的响应,并将这些响应转发到相应的客户机。第一部分将学习基本的HTTP操作,以及如何使用套接字编写通过网络连接进行通信的程序。在第二部分中,您将升级代理以处理多个并发连接。本文将向您介绍如何处理并发,这是一个重要的系统概念。在第三部分和最后一部分,您将使用一个简单的主内存缓存最近访问的web内容添加缓存到您的代理。
2 组织工作
这是一个个人项目。
3 材料说明
特定地点:在这里插入一段解释教师将如何向学生分发proxylab-handout.tar文件。
将讲义文件复制到您计划工作的Linux机器上的受保护目录中,然后发出以下命令:
这将生成一个名为proxylab-handout的分发目录。README文件描述了各种文件。
4 第一部分:实现顺序web代理
第一步是实现处理HTTP/1.0 GET请求的基本顺序代理。其他请求类型(如POST)是严格可选的。
启动后,您的代理应该在一个端口上侦听传入的连接,该端口的编号将在命令行中指定。一旦建立了连接,代理就应该从客户端读取请求的全部内容并解析请求。它应该确定客户端是否发送了有效的HTTP请求;如果是,它可以建立自己的连接到适当的web服务器,然后请求客户端指定的对象。最后,代理应该读取服务器的响应,并将其转发给客户机。
4.1 HTTP / 1.0 GET请求
当终端用户在web浏览器的地址栏中输入一个URL(例如http://www.cmu.edu/hub/index.html)时,浏览器会向代理发送一个HTTP请求,请求的开头可能是这样的:
在这种情况下,代理应该将请求解析为至少以下字段:hostname,www.cmu.edu;还有path或查询以及它后面的所有东西,/hub/index.html。这样,代理就可以确定它应该打开到www.cmu.edu的连接,并发送自己的HTTP请求,请求的开头行如下所示
请注意,HTTP请求中的所有行都以回车符’ \r ‘结尾,后面跟着换行符’ \n '。同样重要的是,每个HTTP请求都以空行结束:“\r\n”。
你应该注意在上面的例子中,web浏览器的请求行以HTTP/1.1结束,而代理的请求行以HTTP/1.0结束。现代的web浏览器会生成HTTP/1.1请求,但你的代理应该处理它们,并将它们作为HTTP/1.0请求转发。
重要的是要考虑HTTP请求,即使只是HTTP/1.0 GET请求的子集,也可能非常复杂。教科书描述了HTTP事务的某些细节,但你应该参考RFC 1945以获得完整的HTTP/1.0规范。理想情况下,根据RFC 1945的相关章节,您的HTTP请求解析器应该是完全健壮的,除了一个细节:虽然规范允许多行请求字段,但不需要您的代理来正确处理它们。当然,您的代理永远不应该由于错误的请求而过早中止。
4.2 请求头
本实验室的重要请求头是Host、User-Agent、Connection和Proxy-Connection头:
- 总是发送一个Host报头。虽然HTTP/1.0规范在技术上不允许这种行为,但有必要从某些Web服务器(特别是那些使用虚拟主机的服务器)获得合理的响应。
Host报头描述了终端服务器的主机名。例如,访问http://www. cmu.edu/hub/index.html,您的代理将发送以下报头:
web浏览器有可能将自己的Host头附加到HTTP请求中。如果是这种情况,您的代理应该使用与浏览器相同的Host头。 - 你可以选择总是发送以下User-Agent头:
标题是在两行单独提供的,因为它不适合作为单行写入,但您的代理应该作为单行发送标题。
User-Agent头标识客户端(根据操作系统和浏览器等参数),web服务器通常使用标识信息来操作它们提供的内容。发送这个特定的用户代理:string可以在内容和多样性方面改进您在简单的telnet风格测试中得到的材料。 - 总是发送以下Connection头:
- 总是发送以下Proxy-Connection头:
Connection和Proxy-Connection头用于指定在第一次请求/响应交换完成后连接是否保持活动状态。让您的代理为每个请求打开一个新连接是完全可以接受的(并建议这样做)。将close指定为这些标头的值会提醒web服务器,您的代理打算在第一次请求/响应交换后关闭连接。
为了方便起见,所描述的User-Agent头的值在proxy.c中作为字符串常量提供给您。
最后,如果浏览器发送任何附加的请求头作为HTTP请求的一部分,您的代理应该不加更改地转发它们。
4.3 端口号
本实验室有两类重要的端口号:HTTP请求端口和代理的侦听端口。
HTTP请求端口是HTTP请求URL中的可选字段。也就是说,URL的形式可能是http://www.cmu.edu:8080/hub/index.html,在这种情况下,您的代理应该连接到端口8080上的主机www.cmu.edu,而不是默认的HTTP端口80。无论URL中是否包含端口号,代理都必须正常工作。
侦听端口是代理侦听传入连接的端口。您的代理应该接受一个命令行参数,指定代理的监听端口号。例如,使用以下命令,您的代理应该监听端口15213上的连接:
您可以选择任何非特权侦听端口(大于1024小于65,536),只要它不被其他进程使用。由于每个代理必须使用一个惟一的侦听端口,而且许多人将同时在每台机器上工作,因此提供了脚本port-for-user.pl来帮助您选择自己的端口号。使用它来根据用户ID生成端口号:
port-for-user.pl返回的端口p总是偶数。因此,如果您需要一个额外的端口号,例如对于Tiny服务器,您可以安全地使用端口p和p+1。
5 第二部分:处理多个并发请求
一旦您有了一个工作的顺序代理,您应该修改它以同时处理多个请求。实现并发服务器的最简单方法是生成一个新线程来处理每个新连接请求。其他的设计也是可能的,比如课本12.5.5节中描述的预线程服务器。
- 请注意,您的线程应该在分离模式下运行,以避免内存泄漏。
- CS:APP3e教科书中描述的open clientfd和open listenfd函数是基于现代的独立于协议的getaddrinfo函数,因此是线程安全的。
6 第三部分:缓存web对象
对于实验的最后一部分,您将向代理添加一个缓存,该缓存将最近使用的Web对象存储在内存中。HTTP实际上定义了一个相当复杂的模型,通过这个模型,web服务器可以给出指令,说明它们服务的对象应该如何缓存,而客户端可以指定缓存应该如何代表它们使用。然而,您的代理将采用一种简化的方法。
当你的代理从服务器接收到一个web对象时,它应该在向客户端发送对象时将其缓存在内存中。如果另一个客户端从相同的服务器请求相同的对象,您的代理不需要重新连接到服务器;它可以简单地重新发送缓存的对象。
显然,如果您的代理要缓存每一个被请求的对象,它将需要无限数量的内存。此外,由于某些web对象比其他对象大,可能会出现这样的情况:一个巨大的对象将消耗整个缓存,从而完全阻止其他对象被缓存。为了避免这些问题,代理应该同时具有最大缓存大小和最大缓存对象大小。
6.1 最大缓存大小
整个代理的缓存应该有以下最大大小:
在计算缓存大小时,代理必须只计算用于存储实际web对象的字节数;应忽略任何无关字节,包括元数据。
6.2 最大对象大小
你的代理应该只缓存不超过以下最大大小的web对象:
为了方便起见,这两个大小限制都在proxy.c中以宏的形式提供。
实现正确缓存的最简单方法是为每个活动连接和从服务器接收到的累计数据分配一个缓冲区。如果缓冲区的大小超过了最大对象大小,则可以丢弃该缓冲区。如果在超过对象的最大大小之前,整个web服务器的响应被读取,那么对象就可以被缓存。使用这个方案,你的代理将使用的web对象的最大数据量如下,其中T是活动连接的最大数量:
6.3 驱逐政策
代理的缓存应该采用类似于最近最少使用(least-recently-used, LRU)的驱逐策略。它并不一定是严格意义上的LRU,但应该是相当接近的。注意,读取和写入对象都算作使用对象。
6.4 同步
对缓存的访问必须是线程安全的,确保缓存访问不存在竞争条件可能是实验室这一部分更有趣的方面。事实上,有一个特殊的要求,即多个线程必须能够同时从缓存读取数据。当然,一次只能允许一个线程写缓存,但是对于读线程不能有这个限制。
因此,用一个大的独占锁保护对缓存的访问不是一个可接受的解决方案。您可能希望探索一些选项,例如对缓存进行分区、使用Pthreads readers writers锁,或者使用信号量来实现自己的readers writers解决方案。在任何一种情况下,您都不必执行严格的LRU驱逐政策,这将为您提供支持多个读者的一些灵活性。
7 评价
这项作业的评分标准为总分70分:
- 基本正确性:40分基本代理操作(自动评分)
- 并发性:15点用于处理并发请求(自动评分)
- 缓存:工作缓存15分(自动评分)
7.1 评分
你们的讲义材料包括一个名为driver.sh的自动评分器,你们的老师将用它来为基本正确性、并发性和缓存打分。proxylab-handout目录:
您必须在Linux机器上运行驱动程序。
7.2 健壮性
与往常一样,您必须交付一个对错误、甚至畸形或恶意输入都很健壮的程序。服务器通常是长时间运行的流程,web代理也不例外。仔细考虑长时间运行的流程应该如何对不同类型的错误作出反应。对于许多类型的错误,代理立即退出肯定是不合适的。
健壮性还意味着其他需求,包括对错误情况(如分段错误)的无懈可击以及缺乏内存泄漏和文件描述符泄漏。
8 测试与调试
除了简单的自动分级器,你不会有任何样本输入或测试程序来测试你的实现。你必须想出你自己的测试,甚至你自己的测试工具来帮助你调试你的代码,并决定什么时候你有一个正确的实现。在现实世界中,这是一项很有价值的技能,因为在现实世界中,精确的操作条件很少为人所知,通常也没有参考解决方案。
幸运的是,有许多工具可以用来调试和测试代理。一定要测试所有的代码路径,并测试一组具有代表性的输入,包括基本用例、典型用例和边缘用例。
8.1 微型网络服务器(Tiny web server)
您的讲义目录的源代码CS:APP Tiny web服务器。虽然不如thttpd强大,CS:APP Tiny web服务器将很容易为你修改你认为合适的。对于代理代码来说,这也是一个合理的起点。驱动程序代码使用服务器来获取页面。
8.2 telnet(远程登录)
如课本(11.5.3)所述,您可以使用telnet打开到代理的连接,并向其发送HTTP请求。
8.3 curl
您可以使用curl生成到任何服务器的HTTP请求,包括您自己的代理。它是一个非常有用的调试工具。例如,如果你的代理和Tiny都在本地机器上运行,Tiny监听的端口是15213,而代理监听的端口是15214,那么你可以通过你的代理使用以下curl命令从Tiny请求一个页面:
8.4 netcat
Netcat,也称为nc,是一个通用的网络工具。您可以像使用telnet一样使用netcat来打开到服务器的连接。因此,假设你的代理使用12345端口运行在catshark上,你可以像下面这样手动测试你的代理:
除了能够连接到Web服务器之外,netcat本身也可以作为服务器运行。使用如下命令,可以将netcat作为监听12345端口的服务器:
设置netcat服务器后,可以通过代理向其上的虚假对象生成请求,并且可以检查代理发送给netcat的确切请求。
8.5 Web浏览器
最后,您应该使用最新版本的Mozilla Firefox来测试代理。Firefox会自动更新您的浏览器到最新版本。
要配置Firefox使用代理,请访问
看到您的代理通过真正的Web浏览器工作将是非常令人兴奋的。虽然你的代理的功能将是有限的,你会注意到,你可以浏览绝大多数网站通过你的代理。
一个重要的警告是,在使用Web浏览器测试缓存时必须非常小心。所有现代Web浏览器都有自己的缓存,在尝试测试代理的缓存之前应该禁用这些缓存。
9 提交指令
所提供的Makefile包含构建最终提交文件的功能。从你的工作目录发出以下命令:
输出是 …/proxylab-handin.tar文件,然后您可以将其提交。
特点地点:在这里插入一段话,告诉每个学生如何提交他们的proxylab-hand.tar解决方案文件。
- 本书的第10-12章包含了系统级I/O、网络编程、HTTP协议和并发编程的有用信息。
- RFC1945(http://www.ietf.org/rfc/rfc1945.txt)is HTTP/1.0协议的完整规范。
10 提示
- 正如课本第10.11节所讨论的,为套接字输入和输出使用标准I/O函数是一个问题。相反,我们建议您使用健壮的I/O(RIO)包,该包在讲义目录中的csapp.c文件中提供。
- csapp.c中提供的错误处理函数并不适合您的代理,因为一旦服务器开始接受连接,它就不应该终止。你需要修改它们或者自己写。
- 您可以随意修改讲义目录中的文件。例如,为了实现良好的模块化,你可以在cache.c和cache.h文件中将缓存函数实现为库。当然,添加新文件需要更新所提供的Makefile。
- 正如CS:APP3e文本第964页的Aside中所讨论的,您的代理必须忽略SIGPIPE信号,并且应该优雅地处理返回EPIPE错误的写操作。
- 有时,调用read从提前关闭的套接字接收字节将导致read返回-1,并将errno设置为ECONNRESET。你的代理也不应该因为这个错误而终止。
- 请记住,并不是网络上的所有内容都是ASCII文本。网络上的很多内容都是二进制数据,比如图像和视频。确保在选择和使用网络I/O功能时考虑二进制数据。
- 将所有请求转发为HTTP/1.0,即使原始请求是HTTP/1.1。
祝你好运