深入理解WEB请求过程

随着web2.0时代的到来,互联网的网络架构已经从传统的C/S架构转变成更加方便快捷的B/S架构,B/S架构大大简化了用户使用网络应用的难度,带来了以下两方面的好处:

1、客户端使用统一的浏览器。由于浏览器具有统一性,不需要特殊的配合和网络连接,有效的屏蔽了不同服务提供商给用户使用服务的差异性。

2、服务端基于统一的HTTP。和传统的C/S架构使用自定义的应用层协议不同,B/S架构使用的都是统一的HTTP。使用统一的HTTP也为服务提供商简化了开发模式,使得服务器开发者可以采用相对规范的开发模式,节省开发成本。由于使用统一的HTTP,所以基于HTTP的服务器就有很多,如Apache、IIS、Nginx、Tomcat、Jboss等,这些服务器可以直接拿来使用,不需要服务开发者单独来开发。不仅如此,连开发服务的通用框架都不需要单独开发,服务开发者只需要关注提供服务的应用逻辑,其他一切平台和框架都可以直接拿来使用,所以B/S架构同样简化了服务提供者的开发。

本篇文章我们将深入学习web请求过程的工作原理,涉及浏览器的基本行为和HTTP的解析过程、DNS如何解析到对应的IP地址、CDN又是如何工作和涉及的,以及浏览器如何渲染出返回结果等

B/S网络架构

B/S网络架构都基于统一的应用层协议HTTP来交互数据,与大多数传统C/S互联网应用程序采用的长连接的交互模式不同,http采用无状态的短连接的通信方式。通常情况下,一次请求就完成了一次数据交互,通常也对应一个业务逻辑,然后这次通信连接就断开了。采用这种方式是为了能够同时服务更多的用户,因为当前互联网应用每天都会处理上亿用户的请求,不可能每个用户访问一次后就一直保持这个连接。

基于http本身的特点,目前B/S网络架构大多采用如下所示的架构设计,既要满足海量用户的访问请求,又要保持用户请求的快速响应,所以现在的网络架构也越来越复杂。

深入理解WEB请求过程

当一个用户在浏览器里输入www.taobao.com这个URL的时候,将会发生很多操作。

首先,它会请求DNS把这个域名解析成对应的IP地址,然后根据这个IP地址在互联网上找到对应的服务器,向这个服务器发起一个get请求,由这个服务器决定返回默认的数据资源给访问用户。在服务器端实际上还有很复杂的业务逻辑。服务器可能有很多台,到底指定哪台服务器来处理请求,这需要一个负载均衡设备来平均分配所有用户的请求;还有请求的数据是存储在分布式缓存里还是一个静态文件中,或者是在数据库里;当数据返回浏览器时,浏览器解析数据发现还有一些静态资源(如CSS、JS或者图片)时又会发起另外的http请求,而这些请求很可能会在CDN上,那么CDN服务器又会处理这个用户的请求,大体上一个用户请求会涉及这么多的操作。每一个细节都会影响这个请求最终是否会成功。

不管网络架构如何变化,始终有一些固定不变的原则需要遵守。

1、互联网上的所有资源都要用一个URL(统一资源定位符)来表示。

2、必须基于http与服务端交互。不管你要访问的是国内的还是国外的数据,是文本数据还是流媒体,都必须按照套路出牌,也就是都得采用统一打招呼的方式,这样人家才会明白你要的是什么。

3、数据展示必须在浏览器中进行。当你获取到数据资源后,必须在浏览器上才能恢复她的容貌。

只要满足上面的几点,一个互联网应用基本上就能正确的运转起来了。

如何发起一个请求

思考两个问题:

1、能不能自己组装一个符合http的数据包?
2、除了浏览器还有哪些方式也能简单的发起一个http请求?

如何发起一个http请求?

如何发起一个http请求和如何建立一个socket连接区别不大,只不过outputstream.write写的二进制字节数据格式要符合http。

浏览器在建立socket连接之前必须根据地址栏输入的URL的域名使用DNS解析出IP地址,再根据这个IP地址和默认的80端口与远程服务器建立socket连接,然后浏览器根据这个URL组装成一个get类型的http请求头,通过outputsream.write发送到目标服务器,服务器等待inputstream.read返回数据,最后断开这个连接。

发起一个http请求的过程就是建立一个socket通信的过程。

当然,不同浏览器在如何使用这个已经建立好的连接以及根据什么规则来管理连接上,有个种不同的实现方法。

上面我们已经知道了发起一个http连接本质上就是建立一个socket连接,我们可以模拟浏览器来发起http请求

1、通过程序实现的方式;如httpclient就是一个开源的通过程序实现的处理http请求的工具包。基本的调用示例如下:

深入理解WEB请求过程

2、通过Linux命令的形式;如Linux的curl命令,通过curl+URL就可以简单的发起一个http请求。

(1)get请求
curl “http://www.baidu.com” 如果这里的URL指向的是一个文件或者一幅图都可以直接下载到本地
curl -i “http://www.baidu.com” 显示全部信息
curl -l “http://www.baidu.com” 只显示头部信息
curl -v “http://www.baidu.com” 显示get请求全过程解析
wget “http://www.baidu.com“也可以
(2)post请求
curl -d “param1=value1¶m2=value2” “http://www.baidu.com

HTTP解析

B/S网络架构的核心是HTTP,要理解http最重要的就是要熟悉http中的http header,http header控制着互联网上成千上万的用户的数据传输,最关键的是,控制着用户浏览器的渲染行为和服务器的执行逻辑。如,当服务器没用用户请求的数据时就会返回一个404状态码,告诉浏览器没有要请求的数据,浏览器展示一个页面不存在的错误信息。

常见的http请求头,响应头,状态码分别如下:

深入理解WEB请求过程

可以通过Firefox的firebug、httpfox或者其他浏览器自带的调试工具(F12键打开)来查看。

浏览器的缓存机制

在我们浏览一个页面发现有异常的时候,通常考虑的就是是不是浏览器做了缓存,一般的做法就是按Ctrl+F5组合件刷新页面,这样浏览器会直接向目标URL发送请求,而不会使用浏览器缓存的数据。其次即使请求发送到服务器端,也有可能访问到的是缓存数据。所以为了保证用户能都看到最新的数据,必须通过http来控制。

当我么使用Ctrl+F5组合键来刷新一个页面时,在http请求头中会增加一些请求头,它告诉服务端我们要获取最新的数据而不是缓存。

http请求头返回缓冲数据:

深入理解WEB请求过程

使用Ctrl+F5组合键刷新页面时http请求头返回的数据:

深入理解WEB请求过程

后面的请求从服务端返回数据,最重要的是在其请求头上增加了两个请求项:Pragma:no-cache和Cache-Control:no-cache,有什么作用呢?

Cache-Control/Pragma

这个http head字段用于指定所有缓存机制在整个请求/响应链中必须服从的指令,如果知道该页面是否为缓存,不仅可以控制浏览器,还可以控制和http相关的缓存或者代理服务器。http head字段还有一些别的可选值:

深入理解WEB请求过程

Cache-Control请求字段被各个浏览器支持的比较好,优先级也比较高,它和其他一些请求字段(如Expires)同时出现时,会覆盖其它字段。

Pragma字段的作用和Cache-Control有点类似,它也是在http头中包含了一个特殊的指令,使相关的服务器遵守该指令。最常用的就是Pragma:no-cache,和Cache=Control:no-cache的作用是一样的。

Expires(过期时间)

HTTP头信息Expires(过期时间) 属性是HTTP控制缓存的基本手段,这个属性告诉缓存器:相关副本在多长时间内是新鲜的。过了这个时间,缓存器就会向源服务器发送请求,检查文档是否被修 改。几乎所有的缓存服务器都支持Expires(过期时间)属性;

大部分Web服务器支持你用几种方式设置Expires属性;一般 的:可以设计一个绝对时间间隔:基于客户最后查看副本的时间(最后访问时间)或者根据服务器上文档最后被修改的时间;

Expires 头信息:对于设置静态图片文件(例如导航栏和图片按钮)可缓存特别有用;因为这些图片修改很少,你可以给它们设置一个特别长的过期时间,这会使你的网站对 用户变得相应非常快;他们对于控制有规律改变的网页也很有用,例如:你每天早上6点更新新闻页,你可以设置副本的过期时间也是这个时间,这样缓存 服务器就知道什么时候去取一个更新版本,而不必让用户去按浏览器的“刷新”按钮。

过期时间头信息属性值只能是HTTP格式的日期时间,其他的都会被解析成当前时间“之前”,副本会过期,记住:HTTP的日期时间必须是格林威治时 间(GMT),而不是本地时间。举例:

Expires: Fri, 30 Oct 1998 14:19:41

所以使用过期时间属性一定要确认你的Web服务器时间设置正确,一个途径是通过网络时间同步协议(Network Time Protocol NTP),和你的系统管理员那里你可以了解更多细节。

虽然过期时间属性非常有用,但是它还是有些局限,首先:是牵扯到了日期,这样Web服务器的时间和缓存服务器的时间必须是同步的,如果有些不同步, 要么是应该缓存的内容提前过期了,要么是过期结果没及时更新。

还有一个过期时间设置的问题也不容忽视:如果你设置的过期时间是一个固定的时间,如果你返回内容的时候又没有连带更新下次过期的时间,那么之后所有 访问请求都会被发送给源Web服务器,反而增加了负载和响应时间

Last-Modified/If-Modified-Since

有些数据随时都在变化。 CNN.com 的主页经常几分钟就更新。另一方面,Google.com 的主页几个星期才更新一次 (当他们上传特殊的假日 logo,或为一个新服务作广告时)。 Web 服务是不变的:通常服务器知道你所请求的数据的最后修改时间,并且 HTTP 为服务器提供了一种将最近修改数据连同你请求的数据一同发送的方法。

如果你第二次 (或第三次,或第四次) 请求相同的数据,你可以告诉服务器你上一次获得的最后修改日期:在你的请求中发送一个 If-Modified-Since 头信息,它包含了上一次从服务器连同数据所获得的日期。如果数据从那时起没有改变,服务器将返回一个特殊的 HTTP 状态代码 304,这意味着 “从上一次请求后这个数据没有改变”。这一点有何进步呢?当服务器发送状态编码 304 时,不再重新发送数据。您仅仅获得了这个状态代码。所以当数据没有更新时,你不需要一次又一次地下载相同的数据;服务器假定你有本地的缓存数据。

所有现代的浏览器都支持最近修改 (last-modified) 的数据检查。如果你曾经访问过某页,一天后重新访问相同的页时发现它没有变化,并奇怪第二次访问时页面加载得如此之快——这就是原因所在。你的浏览器首次 访问时会在本地缓存页面内容,当你第二次访问,浏览器自动发送首次访问时从服务器获得的最近修改日期。服务器简单地返回 304: Not Modified (没有修改),因此浏览器就会知道从本地缓存加载页面。在这一点上,Web 服务也如此智能。

ETag/If-None-Match

ETag 是实现与最近修改数据检查同样的功能的另一种方法:没有变化时不重新下载数据。其工作方式是:服务器发送你所请求的数据的同时,发送某种数据的 hash (在 ETag 头信息中给出)。hash 的确定完全取决于服务器。当第二次请求相同的数据时,你需要在 If-None-Match: 头信息中包含 ETag hash,如果数据没有改变,服务器将返回 304 状态代码。与最近修改数据检查相同,服务器仅仅 发送 304 状态代码;第二次将不为你发送相同的数据。在第二次请求时,通过包含 ETag hash,你告诉服务器:如果 hash 仍旧匹配就没有必要重新发送相同的数据,因为你还有上一次访问过的数据。

DNS域名解析

我们知道互联网都是通过URL来发布和请求资源的,而URL中的域名需要解析成IP地址才能与远程主机建立连接,如何将域名解析成IP地址就属于DNS解析的工作范畴。

如图,是一个DNS域名解析的主要请求过程示例图;

深入理解WEB请求过程

当一个用户在浏览器中输入www.abc.com时,DNS解析将会有10个步骤,过程如下

1、浏览器会检查缓存中有没有这个域名对应的解析过的IP地址。如果缓存中有,这个解析过程就将结束。浏览器缓存域名也是有限制的,不仅浏览器缓存大小有限制,而且缓存的时间也有限制,通常情况下为几分钟到几小时不等,域名被缓存的时间限制可以通过TTL属性来设置。这个缓存时间太长和太短都不好,如果缓存时间太长,一旦域名被解析到IP有变化,会导致被客户端缓存的域名无法解析到变化后的IP地址。以致于该域名不能正常解析,这段时间内有可能会有一部分用户无法访问网站。如果设置时间太短,会导致用户每次访问网站都要重新解析一次域名。

2、如果用户浏览器缓存中没有数据,浏览器会查找操作系统缓存中是否有这个域名对应的DNS解析结果。其实操作系统也有一个域名解析的过程,在Windows中可以通过C:\Windows\System32\drivers\etc\hosts文件来设置,在Linux中可以通过/etc/hosts文件来设置,用户可以将任何域名解析到任何能够访问的IP地址。例如,我们在测试时可以将一个域名解析到一台测试服务器上,这样不用修改任何代码就能测试到单独服务器上的代码的业务逻辑是否正确。正是因为有这种本地DNS解析的规程,所以有黑客就可能通过修改用户的域名来把特定的域名解析到他指定的IP地址上,导致这些域名被劫持。

3、前两个过程无法解析时,就要用到我们网络配置中的”DNS服务器地址”了。操作系统会把这个域名发送给这个LDNS,也就是本地区的域名服务器。这个DNS通常都提供给用户本地互联网接入的一个DNS解析服务,例如用户是在学校接入互联网,那么用户的DNS服务器肯定在学校;如果用户是在小区接入互联网,那么用户的DNS就是再提供接入互联网的应用提供商,即电信或联通,也就是通常说的SPA,那么这个DNS通常也会在用户所在城市的某个角落,不会很远。Windows环境下通过命令行输入ipconfig,Linux环境下通过cat /etc/resolv.conf就可以查询配置的DNS服务器了。这个专门的域名解析服务器性能都会很好,它们一般都会缓存域名解析结果,当然缓存时间是受到域名的失效时间控制的。大约80%的域名解析到这里就结束了,所以LDNS主要承担了域名的解析工作。

4、如果LDNS仍然没有命中,就直接到Root Server域名服务器请求解析

5、根域名服务器返回给本地域名服务器一个所查询的主域名服务器(gTLD Server)地址。gTLD是国际*域名服务器,如.com、.cn、.org等,全球只有13台左右

6、本地域名服务器LDNS再向上一步返回的gTLD服务器发送请求

7、接受请求的gTLD服务器查找并返回此域名对应的Name Server域名服务器的地址,这个Name Server通常就是用户注册的域名服务器,例如用户在某个域名服务提供商申请的域名,那么这个域名解析任务就由这个域名提供商的服务器来完成

8、Name Server域名服务器会查询存储的域名和IP的映射关系表,在正常情况下都根据域名得到目标IP地址,连同一个TTL值返回给DNS Server域名服务器

9、返回该域名对应的IP和TTL值,LDNS会缓存这个域名和IP的对应关系,缓存时间由TTL值控制

10、把解析的结果返回给用户,用户根据TTL值缓存在本地系统缓存中,域名解析过程结束。

在实际的DNS解析过程中,可能还不止这10步,如Name Server可能有很多级,或者有一个GTM来负载均衡控制,这都有可能会影响域名解析过程。

在Linux和Windows下可以使用nslookup命令来查询域名的解析结果;

在Linux系统中还可以使用dig来查询DNS的解析过程。

清除缓存的域名

我们知道DNS域名解析后会缓存解析结果,其中主要在两个地方缓存:

1、Local DNS Server

2、用户的本地机器

这两个缓存都是TTL值和本机缓存大小控制的,但是最大缓存时间是TTL值,基本上Local DNS Server的缓存时间就是TTL控制的,很难人工介入,但是我们的本机缓存可以通过如下方式清除

1、Windows环境下可以再命令行执行ipconfig /flushdns命令来刷新缓存

2、Linux环境下可以通过/etc/init.d/nscd restart来清除缓存。

重启依然是解决很多问题的第一选择。

在Java应用中JVM也会缓存DNS的解析结果,这个缓存是在InetAddress类中完成的,而且这个缓存时间还比较特殊,它有两种缓存策略

1、正确解析结果缓存

2、失败解析结果缓存

这两个缓存时间有两个配置项控制,配置项在%JAVA_HOME%\lib\security\java.security文件中配置的,这两个配置项分别是networkaddress.cache.ttl和networkaddress.cache.negative.ttl,它们的默认值分别是-1(永不失效)和10(缓存10秒),直接修改这两个值就可以了,也可以通过在Java启动参数中增加-Dsun.net.inetaddr.ttl=xxx来修改默认值,也可以通过InetAddress类动态修改。

几种域名解析方式

域名解析记录主要分为A记录、MX记录、CNAME记录、NS记录和TXT记录:

1、A记录

A代表Address,用来指定域名对应的IP地址,如将item.taobao.com指定到115.238.23.xxx,将switch.taobao.com指定到121.14.24.xxx。A记录可以将多个域名解析到一个IP地址,但是不能将一个域名解析到多个IP地址

2、MX记录

Mail Exchange,就是可以将某个域名下的邮件服务器指向自己的Mail Server,如taobao.com域名的A记录IP地址是115.238.25.xxx,如果将MX记录设置为115.238.25.xxx,即[email protected]的邮件路由,DNS会将邮件发送到115.238.25.xxx所在的服务器,而正常通过Web请求的话仍然解析到A记录的IP地址

3、CNAME记录

Canonical Name,即别名解析。所谓别名解析就是可以为一个域名设置一个或者多个别名,如将aaa.com解析到bbb.net、将ccc.com也解析到bbb.net,其中bbb.net分别是aaa.com和ccc.com的别名

4、NS记录

为某个域名指定DNS解析服务器,也就是这个域名由指定的IP地址的DNS服务器取解析

5、TXT记录

为某个主机名或域名设置说明,如可以为ddd.net设置TXT记录为”这是XXX的博客”这样的说明