[翻译] 在你输入URL后到底发生了什么?

从原博客迁移至此
原文:What really happens when you navigate to a URL

1、输入URL到浏览器
一切从这里开始:

2、浏览器通过域名查看IP地址

导航中的第一步是为要访问的域名找出IP地址,DNS查询按照如下顺序进行:

  1. 浏览器缓存 – 浏览器会缓存DNS记录一段时间。有趣的是操作系统不会告诉浏览器每个DNS记录的存活时间,所以浏览器以一个固定时间来缓存这些记录(因浏览器不同,2-30分钟)。
  2. 操作系统缓存 – 如果浏览器缓存没有包含想要的记录,浏览器会进行一次系统调用(在Windows中是gethostbyname)。操作系统有自己的缓存。
  3. 路由器缓存 – 请求会继续传递到路由器,路由器有自己的缓存。
  4. 网络服务供应商(ISP) DNS 缓存 – 下一个要检查的地方是ISP的DNS服务器缓存。
  5. 递归搜索 – 你的ISP的DNS服务器开始进行递归搜索,从根名称服务器开始,经过.com顶级名称服务器,一直到Facebook的名称服务器。正常情况下,DNS服务器会缓存.com名称服务器的域名,因此查询根名称服务器的过程是不必要的。

下面是一张图表,DNS递归搜索看起来就像这个样子:

关于DNS,有一点令人担心的是,wikipedia.org或者facebook.com的域名总是会映射到同一个的IP地址上。幸运的是有一些方法可以改善这种情况:

  • 循环域名服务(Round-robin DNS)是一个解决方案,在这种方案中,DNS查询会返回多个IP地址而不仅仅是一个。例如,facebook.com实际上映射到四个IP地址上。
  • 负载均衡器(load-balancer)是一种硬件设备,他可以监听一些特定的IP地址,并把请求转发到其他服务器上。大型站点都会使用昂贵的高性能负载均衡器。
  • 地理域名服务(Geographic DNS)可以通过把一个域名根据客户端的地理位置不同映射到不同的IP地址上来改善可扩展性,这对于提供静态内容的站点来说很棒,因为不同的服务器不需要更新共享状态。
  • 任播(anycast)是一种路由技术,在这种技术中,单一IP地址会映射到多个物理服务器上。不幸的是,任播和TCP网络不太适应,所以很少在TCP网络中使用。

大部分的DNS服务器本身使用任播技术来实现高稳定、低延迟的DNS查询。

3、浏览器向Web服务器发送HTTP请求。 你可以相当确信,Facebook的页面不是从浏览器缓存中读取的,因为动态页面会非常快地或者立即(将过期时间设为过去)过期。
所以,浏览器将会向Facebook服务器发送请求:

GET http://facebook.com/ HTTP/1.1 Accept: application/x-ms-application, image/jpeg, application/xaml+xml, [...]
User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; [...]
Accept-Encoding: gzip, deflate
Connection: Keep-Alive
Host: facebook.com
Cookie: datr=1265876274-[...]; locale=enUS; lsd=WW[...]; cuser=2101[...]

GET请求将URL包装成“http://facebook.com ”用来提取。浏览器识别自身(User-Agent 头部),并陈述将要接收的响应文件类型(Accept 和 Accept-Encoding 头)。Connection 头让服务器为后续的请求保持TCP连接开放。
请求还包括了浏览器为域名保存的cookies,也许你已经知道了。cookies是一系列的键值对,用来在不同的页面请求中跟踪Web站点的状态。所以cookie保存了登录用户的名字,一段有服务器分配给用户的秘密数字,一些用户设置,等等。cookie将会保存在客户端的一个文本文件中,然后随着每个请求发送到服务器。

有许多的工具可以让你查看原始的HTTP请求和对应的响应。我最喜欢使用的工具是fiddler,但是还有许多其他的工具(比如,FireBug)。这些工具对于优化站点有很大的帮助。
除了GET请求,另一个你可能比较熟悉的请求类型是POST,这个请求通常用来发送表单。GET请求通过URL来发送他的参数(比如,http://robozzle.com/puzzle.aspx?id=85 )。POST请求在请求主体中发送参数,就在请求头部下面。
URL“http://facebook.com/ ”中的尾斜杠很重要。在加了尾斜杠的情况下,浏览器可以安全地添加斜杠。对于http://example.com/folderOrFile 这种形式的URL,浏览器就不能自动地添加斜杠,因为浏览器不清楚folderOrFile到底是一个文件还是一个文件夹。在这样的情况下,浏览器将会访问没有斜杠的URL,服务器将会响应一个重定向,导致了一些无谓的往返。

4、facebook服务器响应一个永久重定向。

这是facebook服务器发送给浏览器的响应:

HTTP/1.1 301 Moved Permanently Cache-Control: private, no-store, no-cache, must-revalidate, post-check=0,
pre-check=0
Expires: Sat, 01 Jan 2000 00:00:00 GMT
Location: http://www.facebook.com/
P3P: CP=”DSP LAW”
Pragma: no-cache
Set-Cookie: madewriteconn=deleted; expires=Thu, 12-Feb-2009 05:09:50 GMT;
path=/; domain=.facebook.com; httponly
Content-Type: text/html; charset=utf-8
X-Cnection: close
Date: Fri, 12 Feb 2010 05:09:51 GMT
Content-Length: 0

服务器发送一个永久移动响应,告诉浏览器去访问http://www.facebook.com/ 而不是http://facebook.com/。

关于为什么坚持使用重定向,而不是立即返回用户想要看的网页,有一些有趣的原因。
其中一个原因是要考虑到搜索引擎排名。想想看,如果有两个URL指向同一个页面,比如说,http://www.igoro.com/http://igoro.com/ ,搜索引擎也许会认为他们是两个不同的网站,这样每个都有着较少的外部链接,因此排名就会比较低。搜索引擎能够理解永久重定向(301),因此会将两个来源的外部链接数合并到一个排名当中。
另外,拥有相同内容的多个URL对缓存不友好。当一份内容有了多个名字,他可能会在缓存中出现多次。

5、浏览器跟随重定向 浏览器现在知道“http://www.facebook.com/ ”是该访问的URL,所以他发送了另一个GET请求

GET http://www.facebook.com/ HTTP/1.1 Accept: application/x-ms-application, image/jpeg, application/xaml+xml, [...]
Accept-Language: en-US
User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; [...]
Accept-Encoding: gzip, deflate
Connection: Keep-Alive
Cookie: lsd=XW[...]; c_user=21[...]; x-referer=[...]
Host: www.facebook.com

头部的意思和上一个相同。

6、服务器处理请求

服务器接收到GET请求,处理请求,然后返回一个响应。
这看起来似乎是一个很简单的任务,但事实上有很多有趣的事情在这个过程中发生 – 甚至是在像我的博客这样的简单站点上,更别说是在像Facebook这样的大规模可扩展网站上了。

  • Web服务器软件

    Web服务器软件(例如,IIS或者Apache)接收到HTTP请求,然后决定使用哪种请求处理器来处理请求。请求处理器是一种可以读取请求,并产生作为响应的HTML代码的程序(例如,ASP.NET、PHP、Ruby等等……)。
    在最简单的情况中,请求处理器可以被储存在一个文件层次结构中,这个层次结构对应了URL的结构。例如,http://example.com/folder1/page1.aspx 的URL将会映射到文件/httpdocs/folder1/page1.aspx。Web服务器软件也可以配置成手动映射URL到请求处理器上,如此一来,page1.aspx的公共URL可以是http://example.com/folder1/page1 了。

  • 请求处理器

    请求处理器读取请求,他的参数和cookie。他会读取并有可能更新一些存储在服务器上的数据。然后,请求处理器将会产生一个HTML请求。
    每个动态网站都要面对的一个有趣的困难是如何存储数据。小型的站点通常有一个简单的SQL数据库来存储数据,但是那些存储大量数据和(或者)拥有大量访客的站点必须要找到一种将数据库分割到多台机器上的办法。解决方法包括分片(sharding,将一个数据库拆分为多个基于主关键字的数据库),复制(replication)和使用多个具有弱化一致性语义的简化数据库。
    保持数据更新廉价的一种技术,是将一些工序批处理化。例如,Facebook必须及时地更新新闻推送,但是在”People you may know“背后的数据只需要在每晚更新(我猜的,我不知道实际中他们是怎么来执行这个功能的)。批处理作业会导致一些不重要数据过时,但是会使数据更新变得快捷和方便得多。

7、服务器返回一个HTML响应。

HTTP/1.1 200 OK Cache-Control: private, no-store, no-cache, must-revalidate, post-check=0,
pre-check=0
Expires: Sat, 01 Jan 2000 00:00:00 GMT
P3P: CP=”DSP LAW”
Pragma: no-cache
Content-Encoding: gzip
Content-Type: text/html; charset=utf-8
X-Cnection: close
Transfer-Encoding: chunked
Date: Fri, 12 Feb 2010 09:05:55 GMT 2b3
Tn@[...]

整个响应大小为36kB,the bulk of them in the byte blob at the end that I trimmed(不会翻)。
Content-Encoding头部告诉浏览器,这个响应主体通过gzip算法进行了压缩。解压了二进制大文件之后,你将会看见你期望的HTML网页。

<!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Strict//EN”
“http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd”>
<html xmlns=”http://www.w3.org/1999/xhtml” xml:lang=”en”
lang=”en” id=”facebook” class=” no_js”>
<head>
<meta http-equiv=”Content-type” content=”text/html; charset=utf-8″ />
<meta http-equiv=”Content-language” content=”en” />

除了压缩,头部还会指定是否和如何缓存页面,要设置的cookies(在这个响应中没有),保密信息,等等。

注意把Content-Type设置成text/html的头部。这个头部引导浏览器按照HTML来渲染响应内容,而不是当做普通文件来下载。浏览器会使用头部来决定怎样解释响应,但是也会考虑到其他因素,比如URL的扩展。

8、浏览器开始渲染HTML

甚至在浏览器接收到整个HTML文件之前,浏览器就开始渲染网站:

9、浏览器请求嵌入在HTML中的对象 在浏览器渲染HTML的过程中,浏览器会注意到需要获取其他URL。浏览器会发送一个GET请求,去获取其中的每一个文件。
下面是一些在我访问facebook.com时会获取的URL。

其中每一个URL都会通过类似HTML页面发送的过程,所以浏览器会在DNS里查询域名,向URL发送请求,跟随重定向,等等。
但是,静态文件(与动态页面不同)允许浏览器缓存他们。一些文件可能会有缓存提供,而根本不需要和服务器联系。浏览器知道需要缓存一个特定的文件多长时间,因为返回文件的响应中包含了过期(Expires)头部。另外,每个响应也包含了ETag头部,他就像是一个版本号 – 如果一个文件的Etag浏览器中已经有了,他就会立即停止文件的传输。

你能猜到fbcdn.net在URL中代表什么含义吗?Facebook内容分发网络(Facebook content delivery network)是一个安全的尝试回答。Facebook使用内容分发网络(CDN)来分发静态内容 – 图像,样式表和JavaScript文件。所以这些文件会复制到许多机器当中。
静态内容经常占据了一个站点的大部分带宽,并且可以轻易通过CDN来复制。很多站点会选择使用第三方CDN提供商,而不是选择自己运营。例如,Facebook的静态文件由Akamai提供,最大的CDN提供商。
作为示范,当我们尝试去ping static.ak.fbcdn.net,你将会得到来自一个akamai.net服务器返回的响应。另外,有趣的是,如果你去多次ping这个URL,也许会得到来自不同服务器的响应,在这些现象背后是负载均衡器在起作用。

10、浏览器发送额外的异步请求(AJAX) 本着Web2.0的精神,客户端甚至会在页面已经渲染完毕后,仍然继续和服务器进行通信。
举个例子,Facebook chat会随着你已经登陆的好友的登入和退出,持续更新他们的列表。为了更新已经登录的好友列表,运行在你的浏览器里的JavaScript会向服务器发送一个异步请求。这个异步请求是由程序生成的GET或者POST请求,这些请求会发送专门的URL。在Facebook的例子里,客户端向http://www.facebook.com/ajax/chat/buddy_list.php 发送一个POST请求来获取你的在线好友列表。
这种模式有时候被称作“AJAX”,表示“异步的JavaScript和XML(Asynchronous JavaScript And XML)”,虽然服务器并没有必要一定以XML的形式返回响应。例如,Facebook返回了一些JavaScript的片段用作异步请求的响应。

fiddler工具可以让你查看由你的浏览器发出的异步请求。事实上,你不仅可以被动地查看这些请求,而且可以调整和重新发送这些请求。能够轻易耍弄AJAX的事实给那些带计分板的在线游戏的开发者,带来了很多苦恼。(很明显,请不要以这种方式作弊)
open-quoteFacebook chat提供了一个有趣的AJAX问题:从服务器向客户端推送数据。因为HTTP是一个请求-响应式的协议,聊天服务器不能像客户端推送新的消息。相反,客户端必须每几秒就询问一次服务器,来确认是否有新的消息。
长轮询(Long polling)是一种用来减少这些情况下的服务器负载的有趣技术。如果服务器在轮询时没有任何新的消息,他就简单地不返回响应。然后,如果在超时周期内接收到针对这个客户端的新消息,服务器就找出那个未解决的请求,通过响应返回这则消息。

总结
希望这篇文章能够帮助你更好地理解Web中不同部分是如何在一起工作的。