TOC
Open TOC
Proxy Lab
preparation
tar
RFC 1945
https://datatracker.ietf.org/doc/html/rfc1945
part
- 实现一个基础的 Web proxy
- 处理并发问题
- 向 Web proxy 加入缓存机制
proxy
server ⇔ proxy ⇔ browser (client)
tiny
make 得到可执行文件后
使用 port-for-user.pl 得到端口号
然后运行 ./tiny 29718
并在浏览器中进入 http://localhost:29718/
可以得到如下的信息
注意这里的 29718 是服务器的端口号,54094 是浏览器的临时端口号
第一行是服务器 accept 成功后打印的信息
下面的内容则是来自浏览器的 HTTP 请求,包括请求行和请求报头
我们也可以使用 telnet 访问服务器
这里的 localhost 为域名,数字地址为 127.0.0.1,即为 host
键入
服务器会打印如下信息
并返回 telnet 如下信息
由于我们没有设置请求报头 Connection,服务器终止了连接
上述的浏览器会处理这些信息,从而显示出了网页
- autograder: driver.sh
- tiny: for ref.
- telnet
- curl
you can request a page from Tiny via your proxy using the following curl command:
一方面可以当做 telnet 使用
另一方面可以充当服务器
服务器只会打印 HTTP 请求,不会发送客户端任何信息
Network Settings -> Proxy
当测试 cache 时,需要关掉 browser 自带的 cache
note
- 使用 rio
- 错误处理
- refer to page 678 of zh-cn textbook
Part Ⅰ Basic
requirement
Web proxy 需要响应客户端的请求,并能够正确处理 GET 请求,即 Web proxy 需要向服务器发送 HTTP/1.0 GET 请求,并接受来自服务器的响应,最终将结果发送给客户端
需要参考 RFC 1945,以应对客户端的不同请求
当 Web proxy 向服务器发送 HTTP 请求时,需要包含如下的请求报头:
- Host
- 服务器的 hostname
- 若客户端的请求中含有 Host 报头,则保持一致
- User-Agent
- Connection 和 Proxy-Connection
- 客户端的其他请求报头
除了 proxy 的端口号,客户端的 HTTP 请求中也可能包含端口号,如在浏览器中键入
Web proxy should connect to the host www.cmu.edu on port 8080 instead of the default HTTP port, which is port 80
也就是说 Web proxy 应当向 www.cmu.edu 的 8080 端口发送如下的 HTTP 请求
driver
启动 tiny 和 proxy
依次对列表中的文件进行 download_proxy 和 download_noproxy
最后对比文件是否一致
process
首先确认客户端的 HTTP 请求,从而进行解析
手动启动 proxy 和 tiny 后
注意需要在 tiny 文件夹内启动服务器,否则会 404
键入
发现客户端的 HTTP 请求有固定的形式
从中解析出端口号和文件名后,生成 Web proxy 向服务器发送的 HTTP 请求:
Web proxy 通过端口号连接服务器,并发送上述 HTTP 请求
观察到测试文件的最大大小约为 39KB,设置 MAXFILESIZE 为 81920B,使用无缓冲的 RIO 函数一次性读写
Web proxy 在得到服务器的 HTTP 响应后,不加修改直接返回给客户端
于是 Basic 就通过了……
note
由于在测试中客户端请求的服务器始终为 tiny 服务器,HTTP 请求格式也已知
所以并未实现 writeup 中的全部要求
理论上客户端可以向 proxy 请求任意服务器,HTTP 请求格式也无法预先得知
不过从测试的角度来说,这就足够了
Part Ⅱ Concurrency
有如下几种做法:
考虑到第三阶段有缓存这个共享变量,我们使用基于线程的方法
requirement
proxy 需要能够应对客户端的并发请求
driver
启动 tiny 和 proxy 后,启动另一个服务器 nop-server,该服务器会阻塞客户端的请求,不作任何响应
然后依次对列表中的文件进行 download_proxy 和 download_noproxy
最后对比文件是否一致
process
简单的照着课本写一遍就可以通过
目前由于 doit 是线程安全的函数,所以不需要进行同步
Part Ⅲ Caching
requirement
向 Web proxy 加入缓存机制
proxy 的缓存总大小为 1MB,只缓存 ≤ 100KB 的文件
注:这里的大小不包含元数据,如响应行和响应报头
驱逐策略为 LRU,同时,需要考虑客户端对缓存的并发读写需求,即 Readers-Writers Problem
driver
启动 tiny 和 proxy 后
依次对列表中的文件进行 download_proxy
然后关闭 tiny 服务器
再对某个列表中特定文件进行 download_proxy
最后对比文件是否一致
process
设计如下的 Cache
Cache 通过文件名索引,全相联映射
contents 中存放了 HTTP 响应的全部信息,包括一些元数据
使用 time_tag,配合线程安全的 time 函数实现 LRU 驱逐策略
由于读 file_cache 时会更新 time_tag,所以不考虑 Readers-Writers Problem,读写 file_cache 时均使用信号量同步
由于 proxy 在读服务器的响应时,需要动态的判断响应内容的大小是否足以缓存
所以修改 proxy 每次读 MAXBUF 个字节,立刻返回给客户端,并逐步拷贝到 cache_buf 中,当不够缓存时,则放弃拷贝
然后测试就可以通过了
Part Ⅳ Web browsers
由于测试实在太弱,我们考虑让 proxy 处理实际环境
使用 Firefox
设置代理服务器
禁用缓存
同时,我们需要修改解析 uri 和发送 HTTP 请求的地方,使其通用化
使用 POSIX 的正则表达式库解析 uri
目前只支持如下两种格式:
由于 POSIX 的正则表达式库总是 greedy 的,所以还要进行一些修正
配置好后,我们访问 http://csapp.cs.cmu.edu/3e/labs.html
根据 proxy 的调试信息,可以拦截到如下的 HTTP 请求
再次访问该网站时,只有如下的 HTTP 请求
从中可以发现一些现象:
- 未实现 CONNECT 方法
- 会错误的将动态内容解析为静态内容
- 可以观察到一些外部服务网站,如 feedburner 和 google-analytics
- 还有浏览器的跟踪信息,如 mozilla firefox
- 第二次访问成功利用了缓存机制
- 客户端对 proxy 的并发请求,如
其他大部分网站均无法访问……
Summary
这个 lab 涉及到了相当多的内容:
- RIO
- 客户端 - 服务器模型,网络编程
- Web HTTP
- 基于线程的并发编程,线程同步
- 缓存
- ……
虽然可以用不到 500 行的代码实现一个简易的 Web Proxy,但这背后依赖了很多已有的库:
- 在 socket 上使用 RIO 进行读写
- socket 的辅助函数,如 Open_listenfd 和 Open_clientfd
- POSIX 提供的线程、信号量和正则表达式
另外,Web Proxy 在错误处理方面也不够完善,代码中只出现了三处 clienterror:
- 501 - 未实现的 HTTP 方法
- 500 - 解析 URI 或读服务器时出错
这里随便使用了 500 这个 status code
还可以再完善的地方:
- 支持更多的 URI 格式和 HTTP 方法
- 缓存优化,如考虑 Readers-Writers Problem,LRU 的其他实现
- 线程优化,如考虑预线程化,引入线程池和生产者 - 消费者模型
- 对 HTTP 请求和响应的精细化处理,目前除了 host,请求报头是固定的,而 HTTP 响应则完整的缓存了下来