• web客户端和服务器之间使用HTTP协议通信,HTTP协议内容相当广泛,涵盖应用层协议需考虑的诸多方面

实例总图

  • A机运行squid代理服务器,B机运行wget客户端,B机通过A机的代理服务器作为中转,获取Internet上www.baidu.com的首页文档index.html,如图4.1 fig_4_1
  • HTTP协议是应用层协议,它默认使用的传输层协议是TCP
  • 通过环境变量http_proxy设置HTTP代理服务器
1
2
3
# 在B机上设置代理服务器为A机的3128端口(该端口是squid的默认端口)
$ export http_proxy="192.168.1.108:3128"
# 也可在~/.bashrc中设置,永久生效
  • 3128是squid的默认端口,可通过lsof命令查看服务器程序监听的端口号
  • A机上squid代理服务器接收到B机上wget客户端的HTTP请求后,简单修改此请求并发送给最终的目标web服务器

部署代理服务器

HTTP代理服务器的工作原理

  • HTTP通信链上,客户端和目标服务器之间通常存在中转的代理服务器,提供对目标资源的中转访问
  • 一个HTTP请求可能被多个代理服务器转发,后面的服务器称为前面服务器的上游服务器
  • 代理服务器可分为正向代理、反向代理、透明代理:
    • 正向代理:设置在客户端。客户端的每次请求都直接发送到正向代理服务器,并由正向代理来请求目标资源
    • 反向代理:设置在服务器端。用反向代理接收Internet上的连接请求,然后将请求转发给内网中的服务器,将内网服务器的结果返回给客户端
      • 反向代理对外表现为一个真实的服务器,大型网站通常分区域设置多个反向代理,在不同地方ping同一个域名可能得到不同IP地址,这些IP地址是代理服务器的地址
    • 透明代理:只能设置在网关。用户访问Internet的数据报必经过网关,若在网关上设置代理则对用户透明。透明代理可看作正向代理的特例
  • 正向代理和客户端处在同一逻辑网络,反向代理和真正的web服务器处在同一逻辑网络,如图4.2 fig_4_2
  • 代理服务器通常还可提供缓存目标资源的功能,这样用户下次访问同一资源时速度很快
  • squidvarnish都是提供缓存的代理服务器软件,squid支持所有代理方式,varnish只能用作反向代理

部署squid代理服务器

  • 在A机安装squid并编辑/etc/squid/squid.conf,加入两行(应加在合适的位置):
1
2
acl localnet src 192.168.1.0/24
http_access allow localnet
  • 含义:
    • 允许网络192.168.1.0上的所有机器通过该代理服务器来访问web服务器
    • 192.168.1.0/24是CIDR(Classless Inter-Domain Routing,无类域间路由)风格的IP地址表示
      • /前的部分指定网络的IP地址
      • /后的部分指定子网掩码中1的位数,对IPv4而言,24代表子网掩码255.255.255.0
      • 总的含义是192.168.1.0/255.255.255.0
  • 在A机重启squid服务器:
1
$ sudo service squid restart
  • service是一个脚本程序(/usr/sbin/service),它为/etc/init.d/目录下的众多服务器程序(如httpd、vsftpd、sshd、mysqld等)的startstoprestart等提供了统一管理

使用tcpdump抓取传输数据包

  • 在B机执行wget命令前,先清空A机的ARP缓存,观察TCP/IP通信中ARP何时起作用
1
2
3
4
5
# A机执行
$ sudo ip neigh flush dev eth0
$ sudo tcpdump -s 2000 -i eth0 -nt '(src 192.168.1.108) or (dst 192.168.1.108) or (arp)'
# B机执行
$ wget --header="Connection: close" http://www.baidu.com/index.html
  • 输出:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# IP 192.168.1.109.57912 > 192.168.1.108.3128: Flags [S], seq 1798396369, win 64240, options [mss 1460,sackOK,TS val 1306956812 ecr 0,nop,wscale 7], length 0
# ARP, Request who-has 192.168.1.109 tell 192.168.1.108, length 28
# ARP, Reply 192.168.1.109 is-at 24:ee:9a:14:58:3e, length 46
# IP 192.168.1.108.3128 > 192.168.1.109.57912: Flags [S.], seq 1406030770, ack 1798396370, win 65160, options [mss 1460,sackOK,TS val 3263575042 ecr 1306956812,nop,wscale 7], length 0
# IP 192.168.1.109.57912 > 192.168.1.108.3128: Flags [.], ack 1, win 502, options [nop,nop,TS val 1306956890 ecr 3263575042], length 0
# IP 192.168.1.109.57912 > 192.168.1.108.3128: Flags [P.], seq 1:196, ack 1, win 502, options [nop,nop,TS val 1306956891 ecr 3263575042], length 195
# IP 192.168.1.108.3128 > 192.168.1.109.57912: Flags [.], ack 196, win 508, options [nop,nop,TS val 3263575120 ecr 1306956891], length 0
# ARP, Request who-has 192.168.1.1 tell 192.168.1.108, length 28
# ARP, Reply 192.168.1.1 is-at a4:56:02:d0:5a:66, length 46
# IP 192.168.1.108.44099 > 219.239.26.42.53: 29049+ PTR? 109.1.168.192.in-addr.arpa. (44)
# IP 192.168.1.108.44099 > 219.239.26.42.53: 31127+ A? www.baidu.com. (31)
# IP 192.168.1.108.44099 > 219.239.26.42.53: 20861+ AAAA? www.baidu.com. (31)
# IP 219.239.26.42.53 > 192.168.1.108.44099: 31127 3/5/5 CNAME www.a.shifen.com., A 182.61.200.6, A 182.61.200.7 (260)
# IP 219.239.26.42.53 > 192.168.1.108.44099: 20861 1/1/0 CNAME www.a.shifen.com. (115)
# IP 192.168.1.108.58076 > 182.61.200.6.80: Flags [S], seq 4264541302, win 64240, options [mss 1460,sackOK,TS val 83429147 ecr 0,nop,wscale 7], length 0
# IP 182.61.200.6.80 > 192.168.1.108.58076: Flags [S.], seq 111490540, ack 4264541303, win 8192, options [mss 1360,sackOK,nop,nop,nop,nop,nop,nop,nop,nop,nop,nop,nop,wscale 5], length 0
# IP 192.168.1.108.58076 > 182.61.200.6.80: Flags [.], ack 1, win 502, length 0
# IP 192.168.1.108 > 182.61.200.6: ICMP echo request, id 64771, seq 1280, length 38
# IP 192.168.1.108.58076 > 182.61.200.6.80: Flags [P.], seq 1:303, ack 1, win 502, length 302: HTTP: GET /index.html HTTP/1.1
# IP 182.61.200.6 > 192.168.1.108: ICMP echo reply, id 64771, seq 1280, length 38
# IP 182.61.200.6.80 > 192.168.1.108.58076: Flags [.], ack 303, win 948, length 0
# IP 182.61.200.6.80 > 192.168.1.108.58076: Flags [.], seq 1:1361, ack 303, win 948, length 1360: HTTP: HTTP/1.1 200 OK
# IP 192.168.1.108.58076 > 182.61.200.6.80: Flags [.], ack 1361, win 501, length 0
# IP 192.168.1.108.3128 > 192.168.1.109.57912: Flags [P.], seq 1:254, ack 196, win 508, options [nop,nop,TS val 3263575331 ecr 1306956891], length 253
# IP 192.168.1.108.3128 > 192.168.1.109.57912: Flags [P.], seq 254:1498, ack 196, win 508, options [nop,nop,TS val 3263575331 ecr 1306956891], length 1244
# IP 182.61.200.6.80 > 192.168.1.108.58076: Flags [P.], seq 1361:2498, ack 303, win 948, length 1137: HTTP
# IP 192.168.1.108.58076 > 182.61.200.6.80: Flags [.], ack 2498, win 501, length 0
# IP 192.168.1.108.3128 > 192.168.1.109.57912: Flags [P.], seq 1498:2635, ack 196, win 508, options [nop,nop,TS val 3263575331 ecr 1306956891], length 1137
# IP 192.168.1.108.3128 > 192.168.1.109.57912: Flags [F.], seq 2635, ack 196, win 508, options [nop,nop,TS val 3263575331 ecr 1306956891], length 0
# IP 192.168.1.109.57912 > 192.168.1.108.3128: Flags [.], ack 254, win 501, options [nop,nop,TS val 1306957103 ecr 3263575331], length 0
# IP 192.168.1.109.57912 > 192.168.1.108.3128: Flags [.], ack 1498, win 496, options [nop,nop,TS val 1306957103 ecr 3263575331], length 0
# IP 192.168.1.109.57912 > 192.168.1.108.3128: Flags [.], ack 2635, win 489, options [nop,nop,TS val 1306957103 ecr 3263575331], length 0
# IP 192.168.1.109.57912 > 192.168.1.108.3128: Flags [F.], seq 196, ack 2636, win 501, options [nop,nop,TS val 1306957104 ecr 3263575331], length 0
# IP 192.168.1.108.3128 > 192.168.1.109.57912: Flags [.], ack 197, win 508, options [nop,nop,TS val 3263575333 ecr 1306957104], length 0
# IP 182.61.200.6.80 > 192.168.1.108.58076: Flags [P.], seq 1361:2498, ack 303, win 948, length 1137: HTTP
# IP 192.168.1.108.58076 > 182.61.200.6.80: Flags [.], ack 2498, win 501, options [nop,nop,sack 1 {1361:2498}], length 0
  • 输出:
    • 第1行是B机向A机上的代理服务器(3128端口)发出请求
    • 第2-3行是ARP,A机得到B机的IP地址后,在内网中询问B机的MAC地址,B机应答
    • 第4-7行是B机和A机上的代理服务器(3128端口)之间通信。(三次握手发生在1、4、5行)
    • 第8-9行是ARP,A机准备访问公网,在内网中询问网关的MAC地址,网关应答
    • 第10-14行是A机向DNS服务器(53端口)查询www.baidu.com的IP地址,DNS服务器应答
    • 第15-23行是A机与百度的服务器(80端口)通信。(三次握手发生在15、16、17行)
    • 第24-25行是A机向B机发送数据,并令B机尽快取走缓冲区(P)
    • 第26-27行是百度的服务器向A机发送数据,A机应答
    • 第28-34行是A机向B机发送数据,随后请求关闭连接,B机对数据进行确认后也关闭连接,A机确认关闭连接。至此A机与B机之间的连接关闭。(四次挥手发生在29、32、33、34行)
    • 第35-36行是百度的服务器向A机发送数据,A机应答。此时A机与百度服务器的连接还未关闭

访问DNS服务器

  • 查询DNS服务器的完整过程如图4.3 fig_4_3
  • DNS查询的过程:
    • 应用程序读取/etc/resolv.conf文件获得DNS服务器的IP地址
    • UDP模块将DNS查询报文封装为UDP数据报,调用IP服务
    • IP模块将UDP数据报封装为IP数据报,将源端IP和DNS服务器的IP放入IP数据报头部
    • IP模块查询路由表决定如何发送该IP数据报,此处是通过路由器来转发数据报
    • 由于ARP缓存被清空,故需发送ARP广播来查询路由器的MAC地址
    • 以太网驱动将IP数据报封装为以太网帧,根据MAC地址发送给路由器
  • 虽然IP数据报是先发送到路由器再转发给目标主机,但头部的目标IP地址始终是最终的目标主机IP。
  • IP头部的源端IP地址和目的端IP地址在转发中不变(一种例外是源路由选择),但以太网帧头部的源端MAC地址和目的端MAC地址在转发中不断变化

本地名称查询

  • 通过域名访问Internet上的主机需用DNS来获取该主机的IP地址
  • 通过主机名来访问局域网上的主机可用本地静态文件获得主机的IP地址
  • 局域网内的目的主机名及其IP地址存储在/etc/hosts文件,需要查询主机名对应的IP时首先检查该文件。若该文件内未找到目标机器名的IP则求助于DNS服务
  • 可通过修改/etc/host.conf来自定义解析主机名的方法和顺序(一般是先查/etc/hosts再看DNS)
1
2
3
# 典型的/etc/host.conf
order hosts,bind
multi on
  • 内容:
    • 第一行表示优先使用/etc/hosts解析主机名,失败后再使用DNS
    • 第二行表示若/etc/hosts的一个主机名对应多个IP,则解析的结果包含多个IP

HTTP通信

  • 4.3节的抓包中,A机与百度服务器之间的TCP通信如图4.4: fig_4_4
  • tcpdump抓包时开启-X可查看报文的内容

HTTP请求

  • 4.3节的抓包中,A机向百度服务器发送的HTTP请求的部分内容:
1
2
3
4
GET http://www.baidu.com/index.html HTTP/1.0
User-Agent: Wget/1.12 (linux-gnu)
Host: www.baidu.com
Connection: close
  • 第一行是请求行:
    • GET请求方法,表示客户端以只读方式申请资源
    • http://www.baidu.com/index.html是目标资源的URL
      • http是scheme,表示获取目标资源所需的应用层协议。scheme还有ftp、rtsp、file等
      • www.baidu.com指定资源所在目标主机
      • index.html指定资源名称是站点根目录的该文件
    • HTTP/1.0表示客户端发出请求时使用的HTTP版本是1.0
  • 第2-4行都是HTTP请求的头部字段
    • 一个HTTP请求可包含多个头部字段,一个头部一行
    • 每个头部字段包含字段名称、冒号:、空格 字段值
    • 头部字段可按任意顺序排列
    • 2-4行头部字段:
      • User-Agent: Wget/1.12 (linux-gnu)表示客户端的程序是wget
      • Host: www.baidu.com表示主机名,HTTP协议规定请求中必须包含的字段是主机名
      • Connection: close告诉服务器,处理完这个HTTP请求后关闭连接
  • 在所有头部字段后,HTTP请求必须包含一个空行表示头部字段的结束
  • HTTP请求行和每个头部字段都必须以<CR><LF>(回车和换行)结束,空行必须只包含,不能有其他字符(包括空白符)
  • 在空行后,HTTP请求可包含可选的消息体,若消息体非空则HTTP请求的头部字段中必须包含Content-Length用于描述该消息体的长度
  • 常见的HTTP请求方法有9种,见表4.1 tab_4_1
  • 上表中:
    • GET、HEAD、OPTIONS、TRACE只从服务器获取资源而不修改服务器,是安全的方法
    • POST、PUT、DELETE、PATCH会影响服务器上的资源,是不安全的方法
    • GET、HEAD、PUT、DELETE、TRACE、OPTIONS是等幂的,即多次连续重复请求和只发送一次请求具有相同效果
    • POST是非等幂的,多次重复一个请求可能进一步影响服务器的资源
    • linux的命令GET、HEAD、POST与该表中同名方法含义基本相同,可用于快速测试web服务器
  • 短连接和长连接:
    • 短连接:HTTP旧标准中,一个TCP连接只能服务于一个HTTP,服务器处理完一个HTTP请求后主动关闭TCP连接
    • 长连接:多个HTTP请求可共用一个TCP连接
    • 长连接可极大减少网络上为建立TCP连接导致的负荷,并为每次请求缩短处理时间
    • HTTP请求/应答中的Connection头部字段是专门用于告知对方一个请求完成后如何处理连接,如close表示立即关闭,keep-alive表示保持一段时间

HTTP应答

  • 4.3节的抓包中,A机收到百度服务器HTTP应答的部分内容:
1
2
3
4
5
6
HTTP/1.0 200 OK
Server: BWS/1.0
Content-Length: 8024
Content-Type: text/html;charset =gbk
Set-Cookie: BAIDUID=A5B6C72D68CF639CE8896FD79A03FBD8:FG=1; expires=Wed,04-Jul-42 00:10:47 GMT; path=/; domain=.baidu.com
Via: 1.0 localhost (squid/3.0 STABLE18)
  • 第一行是状态行:
    • HTTP/1.0是服务器使用的HTTP协议的版本号,通常服务器需使用和客户端相同版本的HTTP协议
    • 200 OK是状态码和状态信息,常见的状态码和状态信息见表4.2 tab_4_2
  • 第2-7行是HTTP应答的头部字段:
    • Server: BWS/1.0表示目标web服务器程序是BWS
    • Content-Length: 8024表示目标文档的长度为8024字节,该值和wget得到的文档长度一致
    • Content-Type: text/html;charset =gbk表示目标文档的MIME类型:
      • text是主文档类型,html是子文档类型,text/html表示目标文档是text类型中的html文档
      • charset是text文档类型的一个参数,指定文档的字符编码
    • Set-Cookie行表示服务器传送一个Cookie给客户端:
      • BAIDUID指定Cookie的名字
      • expires指定Cookie的生存时间
      • path和domain指定该Cookie生效的域名和路径
    • Via: 1.0 localhost (squid/3.0 STABLE18)表示HTTP应答在返回过程中经历过的所有代理服务器和名称,类似IP协议的记录路由
  • 在所有头部字段后,HTTP应答必须包含一个空行表示头部字段的结束
  • HTTP状态行和每个头部字段都必须以<CR><LF>(回车和换行)结束,空行必须只包含,不能有其他字符(包括空白符)
  • 空行后是被请求的文档index.html的内容,其长度是8024字节
  • Cookie
    • HTTP协议是无状态的协议,即每个HTTP请求间无上下文关系。若服务器处理后续请求时需用到前面的请求,需要客户端重传
    • 交互式web应用的请求通常要承上启下,使用Cookie来保持HTTP的连接状态
    • Cookie是服务器发给客户端的特殊信息(通过HTTP应答的头部字段Set-Cookie),客户端每次向服务器发送请求时都需带上这些信息(通过HTTP请求的头部字段Cookie),这样服务器就可区分不同的客户端

实例总结