HTTP协议相关
Http协议
(一)从输入URL到页面加载发生了什么
1. 在浏览器中输入URL
2. 浏览器通过域名去找对应的IP
- 浏览器缓存-浏览器时不时会缓存DNS记录,OS并没有明确指明浏览器每条记录的生命周期是多长,所以浏览器定期的缓存DNS记录(大概2-30分钟不等)。下图为chrome浏览器缓存记录(chrome://net-internals#dns)
- 系统缓存-如果缓存中没有,就去调用gethostbyname库函数(操作系统不同函数也不同)进行查询。函数在试图进行DNS解析之前首先检查域名是否在本地Hosts文件里,Hosts文件的位置,不同的操作系统也有所不同。
- 路由缓存-如果gethostbyname没有这个域名的缓存记录,也没有在hosts里找到,它将会向DNS服务器发送一条DNS查询请求。DNS服务器是由网络通信栈提供的,通常是本地路由器或者ISP的缓存DNS服务器。
- ISP DNS缓存-查询本地网络提供商的DNS服务器。
- 递归查找
3.浏览器向服务器发送一个HTTP请求
由于srs.testpub.cn页面是动态的,所以浏览器不会从缓存里读页面的内容,浏览器会向服务器发送HTTP请求,请求可能如下所示:
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=en_US; lsd=WW[...]; c_user=2101[...]
GET请求的URL是“http://facebook.com”,浏览器通过(User-Agent header)来表明自己是谁,以及自己接受什么样类型的响应(Accept and Accept-Encoding headers)。Connection header告诉服务器不要关闭TCP连接以便为后来请求所重用。
请求中还会带上cookie,cookie就是一些键值对,可以用来保持不同页面请求间的状态。因此cookie可以储存登录的用户的名称,服务器为用户分配的唯一密码以及一些用户设置信息等。cookie是存在客户端的,每次客户端向服务器发送请求都会带上cookie。
4.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: made_write_conn=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/”。而之所以重定向的原因,就是为了增加域名的权重,在这里即增加了www.facebook.com域名的权重。
5.浏览器跟随重定向
浏览器向“http://www.facebook.com/”发送请求:
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. 浏览器开始处理请求
浏览器开始处理请求并返回响应。
- Web服务器软件
web服务器软件,如IIS(
Internet Information Services-互联网信息服务)或Apache接收到HTTP请求,决定哪个处理器来处理该请求。一个请求处理器是一个程序(可由ASP.NET、PHP、Ruby或其他语言编写)用来读请求并生成响应的HTML。最简单的例子,一个请求处理器可被储存在一个文件结构中,该文件结构映射URL结构,比如URL:http://example.com/folder1/page1.aspx对应文件/httpdocs/folder1/page1.aspx。web服务器软件可配置,因此URL可由被手动映射到请求处理器,因此公page1.aspx的公有URL可由是http://example.com/folder1/page1。
- 请求处理器
请求处理器读取请求,请求参数,cookie。读取过程中有可能更新某些数据到服务器端。然后,请求处理器会生成一个HTML格式的响应。
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
Content-Encoding header告诉浏览器响应的body是被gzip给压缩过的,将body解压之后,原本的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" />
...
除了解压方式之外,headers还会告诉浏览器如何缓存页面等,总之headers很重要。
8.浏览器开始渲染HTML
9.浏览器发送请求去获取HTML中的一些内嵌对象
浏览器会开始获取页面上的一些引用资源,比如:
Images
http://static.ak.fbcdn.net/rsrc.php/z12E0/hash/8q2anwu7.gif
http://static.ak.fbcdn.net/rsrc.php/zBS5C/hash/7hwy7at6.gif
…
CSS style sheets
http://static.ak.fbcdn.net/rsrc.php/z448Z/hash/2plh8s4n.css
http://static.ak.fbcdn.net/rsrc.php/zANE1/hash/cvtutcee.css
…
JavaScript files
http://static.ak.fbcdn.net/rsrc.php/zEMOA/hash/c8yzb6ub.js
http://static.ak.fbcdn.net/rsrc.php/z6R9L/hash/cq2lgbs8.js
…
每一个请求都是跟上面的GET请求差不多,都要经过dns解析等一系统过程。
不过静态文件是可以被浏览器缓存的,所以有一些静态文件浏览器可能直接从缓存里读取,而不需要去服务器端读。可以通过chrome://cache/来查看浏览器缓存。
10.浏览器发送异步的AJAX请求
AJAX = 异步Javascript和XML。
AJAX是一种用于创建快速动态网页的技术。通过在后台与服务器进行少量的数据交换,AJAX可以使网页实现异步更新。这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新。
(二)HTTP协议概述
HTTP是一种能够获取如HTML这样网络资源的协议。它是Web上数据交换的基础,是一种client-server协议,也就是说请求通常是由像浏览器这样的接收方发起的。一个完整的web文档是由不同的子文档重新组合而成的,像是文本、布局描述、图片、视频、脚本等等。
客户端和服务器端通过交换各种的信息(与数据流正好相反)来进行交互。通常由像浏览器这样的客户端发出的消息叫requests,被服务器端回应的消息就叫做responses。
HTTP被设计于20世纪90年代,是一种可扩展性的协议,他是应用层的协议,虽然理论上可以通过任何可靠的传输协议来发送,但是还是通过TCP或者TLS-加密的TCP连接来发送。因为由很好的扩展性,时至今日不仅被用来传输超文本文档,还用来传输图片、视频或向服务器发送如HTML表单这样的信息。HTTP还可以根据网页的需求,来获取部分web文档的内容来更新网页。
HTTP协议的参与者
HTTP是一个client-server协议:请求通过一个实体被发出,实体也就是用户代理。大多数情况下,这个用户代理都是指浏览器,当然它也可能是任何东西,比如一个爬取网页来生成河维护搜素引擎索引的机器。
每一个发送到服务器的请求,都会被服务器处理并且返回一个消息,也就是response。在client与server之间,还有许许多多的被称为proxies的实体,他们的作用与表现各不相同,比如有些是网关,还有些是caches等。
实际上,在一个浏览器和处理请求的服务器之间,还有计算机、路由器、调制解调器等等许多实体。由于Web的层次设计,那些在网络层和传输层都不可见了。HTTP是在最上层应用层中的,虽然下面的层次对分析网络问题非常重要,但是对HTTP的描述老说,这些大多数都是不相干的。
1.客户端:user-agent
严格意义来说,user-agent就是任何能够表现出用户一般行为的工具。但实际上,这个角色通常都是由浏览器来扮演的。
简单来说user-agent就是来者何人,留下姓名的意思。
对于发起请求来说,浏览器总是作为发起一个请求的实体,而永远不是服务器(虽然一些机制已经能够模拟服务器发起请求的消息了)。
要渲染出一个网页,浏览器首先要发送第一个请求来获取这个页面的HTML文档,再解析它并根据文档中的资源信息发送其他的请求来获取脚本信息,或者CSS来进行页面布局渲染,还有一些其他的页面资源(如图片和视频等)。然后,它把这些资源结合到一起,展现出来一个完整的文档,也就是网页。打开一个网页后,浏览器还可以根据脚本内容来获取更多的资源来更新网页。
一个网页就是一个超文本文档,也就是说由一部分显示的文本可能是链接,启动它(通常是鼠标的点击)就可以获取一个新的网页。网页使得用户可以控制它的user-agent来导航Web。浏览器来负责翻译HTTP请求的命令,并翻译HTTP的返回消息让用户能明白返回消息的内容。
2.Web服务端
在上述通信过程的另一端,就是一个Web Server来服务并提供客户端请求的文档。Server至少虚拟意义上:它可以是许多共同分担负载(负载平衡)的一组服务器组成的计算机群,也可以是一种复杂的软件,通过向其他计算机发起请求来获取部分或全部资源的软件。
Server不再只是一个单独的机器,它可以是在同一个机器上装载的许多服务之一。在HTTP/1.1和Host头部中,它们甚至可以共享同一个IP地址。
3.Proxies
在浏览器和服务器之间,有许多计算机和其他设备转发了HTTP的消息。因为Web栈层次结果的原因,它们大多数都出现在传输层、网络层和物理层上,对于HTTP的应用层来说就是透明的(虽然它们可能会对应用的性能有重要影响)。而还有一部分表现在应用层上的,就叫做proxies(中文翻译为代理)了。Proxies既可以表现得透明,又可以不透明(看请求是否通过它们),主要表现在这几个功能上:
- 缓存(可以是公开的或私有的,像浏览器的缓存)
- 过滤(像反病毒扫描,家长监护)
- 负载均衡,让多个服务器服务不同的请求
- 对不同资源的权限控制
- 登录,允许存储历史信息
HTTP的基本性质
HTTP是简单的
即便在HTTP/2中把HTTP消息封装到了frames中,HTTP大体上还是被设计成可读的而且简单的。HTTP的消息能够让人读懂且明白它的意思,还允许见得测试,放低了门槛,更有利于新来者了解。
HTTP是可扩展的
在HTTP/1中就出现了,HTTP Headers让协议扩展变得非常容易。只要服务端和客户端在新的headers上语义达成一致,新的功能就可以轻松地被加进来。
HTTP是无状态的,有会话的
HTTP是无状态的:在同一个连接中,两个成功执行的请求之间是没有关系的。
而HTTP的核心是无状态的,cookies的使用可以创建有状态的会话。
HTTP和连接
一个连接是由传输层来控制的,基本不属于HTTP的范围内。然而HTTP并不需要下面传输层的协议是面向连接的,它只需要它是可靠的,就是说不能丢失消息(至少没有错误)。在因特网两个最常用的传输层协议中,TCP是可靠的,而UDP不是。因此,HTTP依赖于TCP进行消息传递,虽然TCP是面向连接的,但这并不是必须的。
HTTP/1.0曾经为每一个请求/回应交换都打开一个TCP连接,导致了2个缺点:打开一个连接需要多次的消息往返因此很慢。但是当多个消息周期性发送时,这就变得更加高效:暖连接比冷连接更高效。
为了减轻这些负担,HTTP/1.1引入了流水线的概念和持久连接的概念:下层的TCP连接可以通过Connection头部来被部分控制。通过一个连接多个消息的方式来让这个连接始终保持为暖连接。
为了更好的适合HTTP,设计一种更好的传输层协议就一直在进行中,Google就研发了一张以UDP为基础,能提供更可靠更有效传输层协议的QUIC。
HTTP能控制什么
- 缓存
文档怎么缓存能够通过HTTP来控制。服务器端告诉代理和客户端什么需要被缓存,缓存多久,而客户端能够命令中间缓存代理来忽略存储的文档。
-
开放同源限制
HTTP可以通过修改头部来开放浏览器强制对Web网址做的分割限制。 -
认证
基本的认证功能(Basic Authenticate)可以直接通过HTTP提供,使用Authenticate相似的头部就可以,或者用HTTP cookie来设定指定的会话。 -
代理
服务器和客户端通常都处于内部网上,HTTP请求就要通过代理穿过网络障碍,但不是所有的代理都是HTTP代理的。 -
会话
Cookies用一个服务端的状态连接起每一个请求。这就创建了会话,虽然基本的HTTP协议是无状态协议。这很有用,不仅是因为能用的购物车这样的电商业务上,更是因为,是的任何网站都能够配置页面展现的东西。
HTTP流
当客户端想要和服务端进行信息交互时(服务端是指作为最终的服务器,或者是作为中间代理),过程表现为下面几步:
-
打开一个TCP连接(或者重用之前的一个):TCP连接用来发送一条或多条请求,当然也用来接受回应消息。客户端可能重用一个已经存在的连接,或者也可能重开几个新的与服务端的TCP连接。
-
发送一个HTTP报文:HTTP报文(在HTTP/2之前)是语义可能的。 在HTTP/2中,这些简单的消息被封装在了帧中。这使得报文不可能被直接读出来,但是规则仍旧是相同的。
GET / HTTP/1.1
Host: developer.mozilla.org
Accept-Language: fr
- 读取服务端返回的报文:
HTTP/1.1 200 OK
Date: Sat, 09 Oct 2010 14:28:02 GMT
Server: Apache
Last-Modified: Tue, 01 Dec 2009 20:18:22 GMT
ETag: "51142bc1-7449-479b075b2891b"
Accept-Ranges: bytes
Content-Length: 29769
Content-Type: text/html
<!DOCTYPE html... (here comes the 29769 bytes of the requested web page)
- 关闭连接或者为以后的请求重用连接。
HTTP报文
HTTP/1.1和更早的HTTP报文都是语义可读的。在HTTP/2中,这些报文被嵌入到了一个新的二进制结构中 - 帧。帧可以允许实现很有优化,如复用和报文头部的压缩。即使只有原始HTTP报文的一部分以这种HTTP/2版本的方式发送出来,每个报文的语义依旧不变,客户端会重组原始的HTTP/1.1请求。因此用HTTP/1.1格式来考虑HTTP/2报文依旧有效。
有两种HTTP报文的类型,请求与响应,每种都有其特定的格式。
请求
HTTP请求的一个例子:
GET / HTTP/1.1
Host: www.baidu.com
Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
请求由厦门的元素组成:
- 一个HTTP的method,经常是由一个动词像GET, POST或者一个名词像OPTIONS,HEAD来定义客户端的动作行为的。通常客户端的操作都是获取资源(用GET方法)或者发送一个HTML form表单的值(用POST方法),虽然在一些情况下也会有其他的操作。
- 要获取的资源的路径,通常是上下文中就很明显的元素的URL,它没有protocol(http://),domain(developer.mozilla.org),或是TCP的port(HTTP是80端口)
- HTTP协议的版本号
- 为服务端表达其他信息的可选择性的headers
- 对于一些像POST这样的方法,报文的body就包含了发送的资源,这个body与回应报文的body类似
响应
HTTP响应的一个例子:
HTTP/1.1 200 OK
Bdpagetype: 2
Bdqid: 0xa10573290003626a
Cache-Control: private
Connection: Keep-Alive
Content-Encoding: gzip
Content-Type: text/html;charset=utf-8
Date: Wed, 18 Apr 2018 08:23:03 GMT
Expires: Wed, 18 Apr 2018 08:23:03 GMT
Server: BWS/1.1
响应报文包含下面的元素:
- HTTP的版本号
- 一个状态码(status code),来告知对应的请求发送成功或失败,以及失败的原因
- 一个状态信息,这个信息是非权威的状态码描述信息,也就是说可以由服务器咨询设定的
- HTTP headers,与请求的很像
- 可选的,但是比在请求报文中更加常见地包含获取资源的body
总结
HTTP是很简单可扩展的一直协议。结合了轻松添加头部信息能力的Client-server结构使得HTTP可以和Web的功能扩充一同发展。
即使HTTP/2为了提高性能把HTTP报文嵌到HTTP报文帧中这一举措增加了复杂度,但是从Web应用的角度看,报文的基本结束是没有变化的,从HTTP/1.0发布起就是相同的。会话流依旧很简单,用一个简单的HTTP message monitor就可以查看它和debug。
(三)实战Chrome开发者工具
步骤
- 打开chrome浏览器,输入网址:itest.info
- 按F12打开chrome开发者工具,并选择Network标签
- 刷新页面
- 找到itest.info请求
结果(查看headers标签)
1. General
Request URL: http://www.itest.info/
Request Method: GET
Status Code: 200 OK
Remote Address: 119.29.203.242:80
Referrer Policy: no-referrer-when-downgrade
2. Response Headers
Cache-Control: max-age=0, private, must-revalidate
Connection: keep-alive
Content-Encoding: gzip
Content-Type: text/html; charset=utf-8
Date: Thu, 19 Apr 2018 01:19:54 GMT
ETag: W/"f64211eeefa54e7f13f091bb162d68f4"
Server: nginx/1.10.3 (Ubuntu)
Set-Cookie: _itest5_session=T0NTeUQ0OGFxaTl5aWR5MG4zTDlzUjJ3U3pNUWNvK096YkROcnVhL2w5clMrdGk5UGNUaTliaFNuM1F1R2hxWTBCOUpTd1BLRXNSNjkvbXQ5YUppZVJaZWxiZ3dMcnJVQTN0UWJqUFlFNTV1Zng3UnJ0ZnJYV0Z1NjBWSW9DV1gvNXA0a3hHR3J5YmV3U3FTdyt0WkdBPT0tLVFxQ2pxNzIvaXRRNU5wWWMrdHdLS2c9PQ%3D%3D--1967ac769cb1b7ee4f1e8203481f591f8bffea25; path=/; HttpOnly
Transfer-Encoding: chunked
Vary: Accept-Encoding
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
X-Request-Id: ccd6569c-3004-4ff3-bce2-88b3581d8941
X-Runtime: 0.008415
X-XSS-Protection: 1; mode=block
3. Request Headers
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7
Cache-Control: max-age=0
Connection: keep-alive
Cookie: _itest5_session=Zzc1WEkzSENiSHp1bSs0blZnMkNlS0l3VndVUXJEbTdiNGlXcmJuRjBsVVNDT080Rkg1V1Y4NFhqUmNVSU5vb2JVZC9VZU5CYnV1K3hBQlMrK21LUFBjdFJiVEo5SnNQQXo5Z0h0YzFIUWNndDI1L2ZhTkRrR0Zvc3REQnRZdTlkSUc1RXRmQjJMSFAxcGlVS1NNZDBnPT0tLS84akN1Mjl2U1dNQWkxRmJPaENjQmc9PQ%3D%3D--add7f6226ae3747a638a65c45a8fd7eecfc0fbce; Hm_lvt_906c6961a45234ebc29e93442b414707=1524100786; Hm_lpvt_906c6961a45234ebc29e93442b414707=1524100786
Host: www.itest.info
If-None-Match: W/"36e8ed1080e1b05a1f5df880fc857d08"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36
X-FirePHP-Version: 0.0.6
(四)实战yslow前端性能测试
安装Yslow工具
- 下载yslow插件(感谢pf04同学提供yslow插件)
- 打开chrome://extensions/,直接将插件拖拽过去即可
对ur.tencent.com前端页面进行测试
-
chrome打开网页ur.tencent.com
-
点击yslow的扩展程序图标
- 点击Run Test
- 查看测试结果
(五)HTTP缓存
重用已经获取的资源能够有效的提升网站与应用的性能。Web缓存能够减少延迟与网络阻塞,进而减少显示某个资源所用的时间。借助HTTP缓存,Web站点变得更具有响应性。
总而言之合理使用缓存可以加快站点的响应速度,让用户有页面秒开的快感。
各种各样的缓存
思考一个问题:从硬盘拷贝文件到指定文件夹比较快还是网络上传下载同样一份文件比较快?——拷贝比较快。
缓存就是尽量让我们从本地读取内容(这些内容往往是远程服务器上相应内容的拷贝),从而替代从服务器上读取内容的技术。
缓存是指存储指定资源的一份拷贝,并在下次请求该资源时提供该拷贝的技术。当Web缓存发现请求的资源已经被存储,它会拦截请求,返回该资源的拷贝,而不是去源服务器重新下载。这样带来的好处:缓解服务器端压力,提示性能(获取资源的耗时更短)。对于网站来说,缓存是达到高性能的重要组成部分。缓存需要合理配置,因为并不是所有资源都是永久不变的;重要的是对一个资源的缓存应截止到其下一次发生改变(即不能缓存过期资源)。
1. 私有服务器缓存
私有缓存只能用于单独用户。可能已经见过浏览器设置中的“缓存”选项。浏览器缓存拥有用户通过HTTP下载的所有文档。这些缓存为浏览过的文档提供向后/向前导航,保存网页,查看源码等功能,可以避免再次向服务器发起多余的请求。它同样可以提供缓存内容的离线浏览。
2. 共享代理缓存
共享缓存可以被多个用户使用。例如,ISP或你所在的公司可能会架设一个web代理来作为本地网络基础的一部分提供给用户。这样热门的资源就会被重复使用,减少网络拥堵与延迟。
缓存操作的目标
虽然HTTP缓存不是必须的,但重用缓存的资源通常是必要的。然而常见的HTTP缓存只能存储GET响应,对于其他类型的响应则无能为力。缓存的关键主要包括request method和目标URI(一般只有GET请求才会被缓存)。普遍的缓存案例:
- 一个检索请求的成功响应:状态码为200,一个包含例如HTML文档、图片或者文件的响应
- 不变的重定向:响应状态码为301
- 错误响应:响应状态码为206,只返回局部的信息
- 除了GET请求外,如果匹配到昨晚一个已被定义的cache键名的响应
针对一些特定的请求,也可以通过关键字区分多个存储的不同响应以组成缓存的内容。
缓存控制
Cache-control Headers
HTTP/1.1定义的Cache-Control头用来区分对缓存机制的支持情况,请求头和响应头都支持这个属性。通过它提供不同的值来定义缓存策略。
完全不支持缓存
缓存中不得存储任何关于客户端请求和服务端响应的内容。每次由客户端发起的请求都会下载完整的响应内容。
不缓存内容
在释放缓存内容前向服务端源地址发送请求以验证缓存释放有效。
私有缓存和公共缓存
-
public: 响应可以被任何请求来源缓存。针对需要进行http身份验证的页面或者一些不能被顺利缓存的响应码,通过定义public以支持缓存。
-
private:响应的内容只能被唯一的用户缓存,不可以被共享缓存存储。隐私模式下的浏览器会通过这种方式存储缓存。
缓存过期
判断缓存释放过期的一个最常用的标志为max-age。相对Expires而言,max-age是距离请求发起的时间的秒数。针对应用中那些不会改变的文件,通常可以收到设置一定时长以保证缓存有效,例如图片、css、js等静态资源。
缓存验证
must-revalidate:在使用一些老的资源钱强制验证状态判断是否过期。
Pragma Headers
Pragma是HTTP/1.0标准中定义的一个header属性,请求中包含Pragma的效果跟在头信息中定义Cache-Control:no-cache相同,但是HTTP的响应头不支持这个属性,所以它不能拿来完全替代HTTP/1.1中定义的Cache-control头。通常定义Pragma以向后兼容基于HTTP/1.0客户端。
缓存有效性
一旦一个资源文件被存入缓存,理论上来说这个文件就永远处于缓存中。但是缓存的存储空间通常有限,这也意味着定期会移除一部分缓存文件。这个过程被称为缓存抛弃。另一方面,针对那些在服务端发生改变的资源,应该做响应的缓存内容更新。因为HTTP协议在C-S架构中是无状态的,服务端在资源发生变化时无法通知到客户端,通过定义过期时间以同步两端的缓存资源。在过期时间前,资源缓存是有效的,反正则缓存失效。通过不停抛弃过期的缓存资源以保证资源的实时性。注意,旧的缓存不会被抛弃或者忽略;当发起一个针对旧缓存资源的请求时,会在请求头里带上If-None-Match用来判断缓存是否有效。如果有效,服务端返回304(Not Modified)和空的body以节省一部分带宽。
对于含有特定头信息的请求,会去计算缓存寿命。比如Cache-control:max-age=N的请求头,相应的缓存的寿命就是N。通常情况下,对于不含这个属性的请求则会查看是否包含Expires属性,通过比较Expires的值和头里面Date属性的值来判断是否缓存还有效。如果max-age和expires属性都没有,找找头里面的Last-Modified信息。如果有,缓存的寿命就等于头里面Data的值减去Last-Modified的值除以10。
缓存时长计算公式如下:
缓存时长 = 响应时间 + 缓存寿命 - 当前时间
响应时间指浏览器接收到服务端的响应的时间。
加速资源
更多地利用缓存资源,可以提高网站的性能和相应速度。为了优化缓存,过期时间设置得尽量长是一种很好的测量。对应定期或者频繁更新的资源,这么做是比较稳妥的,但是对于那些长期不更新的资源会有点儿问题。这些固定的资源在一定时间内受益于这种长期保持的缓存策略,但一旦要更新就会很困难。特指网页上引入的一些js/css文件,当它们变动时需要尽快更新线上资源。
web开发者发明了一种Steve Sounders称作加速的技术。不频繁更新的文件会使用特定的命名方式:在URL后面(通常是文件名后面)会加上版本号。加上版本号后的资源就被视作一个完全新的独立的资源,同时拥有一年甚至更长的缓存过期时长。但是这么做也存在一个弊端,所有引用这个资源的地方都需要更新链接。web开发者们通常会才有自动化构建工具在实际工作中完成这些琐碎的工作。当低频更新的资源(js/css)变动了,只用在高频变动的资源文件(html)里做入口的改动。
这种方法还有一个好处:同时更新两个缓存资源不会造成部分缓存先更新而引起新旧文件内容不一致。对于互相有依赖关系的css和js文件,避免这种不一致性是非常重要的。
加在加速文件后面的版本号不一定是一个正式的版本号字符串,如1.1.3这样或者其他固定自增的版本数。它可以是任何防止缓存碰撞的标记例如hash或者时间戳。
缓存验证
用户点击刷新按钮时会开始缓存验证。如果缓存的响应头信息里含有“Cache-control:must-revalidate”的定义,在浏览的过程中也会触发缓存验证。另外,在浏览器偏好设置里设置Advanced-> Cache为强制验证缓存也能达到相同的效果。
当缓存的文档过期后,需要进行缓存验证或者重新获取资源。只有在服务器返回强校验器或者弱校验器时才会进行验证。
ETag
作为缓存的一种强校验器,ETag响应头是一个对用户代理(User Agent,下面简称UA)不可知的值。对于像浏览器这样的HTTP UA,不知道ETag代表什么,值是多少。如果资源请求的响应头里含有ETag,客户端可以在后续的所有请求的头中带上If-None-Match头来验证缓存。
Last-Modified响应头可以作为一种弱校验器。说它弱是因为它是一次性的。如果响应头里含有这个信息,客户端可以在后续的一次请求中带上If-Modified-Since来验证缓存。
当向服务端发起缓存校验的请求时,服务端会返回200 ok表示返回正常的结果或者304 Not Modified(不返回body)表示浏览器可以使用本地缓存文件。304的响应头也可以同时更新缓存文档的过期时间。
1. 带Vary头的响应
Vary HTTP响应头决定了对于后续的请求头,如何判断是请求一个新的资源还是使用缓存的文件。
当缓存服务器收到一个请求,只有当前的请求和原始(缓存)的请求头跟缓存的响应头里的Vary都匹配,才能使用缓存的响应。
使用vary头有利于内容服务的动态多样性。例如,使用Vary:User-Agent头,缓存服务器需要通过UA判断是否使用缓存的页面。如果需要区分移动端和桌面端的展示内容,利用这种方式就能避免在不同的终端展示错误的布局。另外,它可以帮助google或者其他搜索引擎更好地发现页面的移动版本,并且告诉搜索引擎没有引入Cloaking。
因为移动版和桌面的客户端的请求头中的User-Agent不同,缓存服务器不会错误地把移动端的内容输出到桌面端用户。
2. max-age
max-age=120s,表示在120s之内,这个请求的内容都是可以从缓存里读取的。
3. ETags
上面的请求120s之后就会过期,这时候如果浏览器再去发送一个请求去获取资源的话,那还是会蛮浪费的。毕竟资源的内容已经被浏览器缓存下来了。
通过ETags可以解决这个问题。
服务器生成1个ETags,客户端不需要知道这个ETags是如何生成的,在下一个请求发送时,客户端向服务器带上这个ETags,如果没有变化,那么证明内容没变,本次下载可以被忽略。
(六)HTTP请求
HTTP消息是服务器和客户端之间交换数据的方式。有两种类型的消息:
- 请求 - 由客户端发送用来触发一个服务器上的动作;
- 响应 - 来着服务器的应答
HTTP请求
** 起始行 **
HTTP请求是由客户端发出的消息,用来使服务器执行动作。起始行(start-line)包含三个元素:
-
- 一个HTTP方法,一个动词(像GET,PUT或者POST)或者一个名词(像HEAD或者OPTIONS),描述要执行的动作。例如,GET表示要获取资源,POST表示向服务器推送数据(创建或修改资源,或者产生要返回的临时文件)。
-
- 请求目标(request target),通常是一个URL,或者是协议、端口和域名的绝对路径,通常以请求的环境为特征。请求的格式因不同的HTTP方法而异。它可以是:
a. 一个绝对路径,末位跟上一个"?"和查询字符串。这是最常见的形式,称为原始形式(origin form),被GET,POST, HEAD和OPTIONS方法所使用。
b. 一个完整的URL,被称为绝对形式(absolute form),主要在GET连接到代理时使用。
GET http://developer.mozilla.org/en-US/docs/Web/HTTP/Messages HTTP/1.1
c. 由郁闷和可选端口(以“:”为前缀)组成的URL的authority component,称为authority form。仅在使用CONNECT建立HTTP隧道时才使用。
CONNECT developer.mozilla.org:80 HTTP/1.1
d. 星号形式(asterisk form),一个简单的星号(/*/),配合OPTIONS方法使用,代表整个服务器。
-
- HTTP版本(HTTP version),定义了剩余报文的结构,作为对期望的响应版本的指示符。
** Headers **
来自请求的HTTP headers遵循和HTTP header相同的基本结构:不区分大小写的字符串,紧跟着的冒号(:)和一个结构取决于header的值。这个header(包括值)由一行组成,这一行可以相当长。
有需要请求头可用,它们可以分为几组:
- General headers, 例如
Via
,适用于整个报文。 - Request headers, 例如
User-Agent
,Accept-Type
,通过进一步的定义(例如Accept-Language
), 或者给定上下文(例如Referer),或者进行有条件的限制(例如If-None)来修改请求。 - Entity headers,例如
Content-Length
,适用于请求的body。显然,如果请求中没有任何body,则不会发送这样的头文件。
** Body **
请求的最后一部分是它的body。不是所有的请求都有一个body:例如获取资源的请求,GET,HEAD, DELETE和OPTIONS,通常他们不需要body。有些请求将数据发送到服务器以便更新数据:常见的情况是POST请求(包含HTML表单数据)。
Body大致可分为两类:
-
Singel-resource bodies,由一个单文件组成。该类型body由两个header定义:
Content-Type
和Content-Length
-
Multiple-resource bodies, 由多部分body组成,每一部分包含不同的信息位。通常是和HTML Forms连系在一起
HTTP响应
** 状态行 **
HTTP响应的起始行被称作状态行(status line),包含以下信息:
-
- 协议版本,通常为HTTP/1.1
-
- 状态码(status code),表明请求是成功或失败。常见的状态码是200,400或302。
-
- 状态文本(status text)。一个简短的,纯粹的信息,同状态码的文本描述,帮助人们理解该HTTP消息
一个典型的状态行看起来像这样的: HTTP/1.1 404 Not Found
** Headers **
响应的HTTP headers遵循和任何其他header相同的结构:不区分大小写的字符串,紧跟着冒号(:)和一个结构取决于header类型的值。整个header(包括其值)表现为单行形式。
有很多响应头可用,这些响应头可以分为几组:
-
General headers,例如Via,适用于整个报文;
-
Response header,例如
Vary
和Accept-Ranges
,提供其它不符合状态行的关于服务器的信息; -
Entity headers,例如
Content-Length
,适用于请求的body。显然,如果请求中没有任何body,则不会发送这样的头文件。
** Body **
响应的最好一部分是body。不是所有的响应都有body:具有状态码(如201或204)的响应,通常不会有body。
body大致可分为三类:
-
Single-resource bodies, 由已知长度的单个文件组成。该类型的body由两个header定义:Content-Type和Content-Length。
-
Single-resource bodies,由未知长度的单个文件组成,通过将Transfer-Encoding设置为chunked 来使用chunks编码。
-
Multiple-resource bodies,由多部分body组成,每部分包含不同的信息段。但这是比较少见的。
(七)HTTP cookie
HTTP Cookie(也叫Web cookie或者浏览器Cookie)是服务器发送到用户浏览器并保存在浏览器上的一块数据,它会在浏览器下一次发起请求时被携带并发送到服务器上。比较经典的,可以用来确定两次请求是否来自同一个浏览器,从而能够确认和保持用户的登录状态。Cookie的使用是的基于无状态的HTTP协议记录稳定的状态信息成为了可能。
Cookie 主要用作以下三个方面:
- 会话状态管理(如用户登录状态、购物车)
- 个性化设置(如用户自定义设置)
- 浏览器行为跟踪(如跟踪分析用户行为)
Cookie可用于客户端数据的存储,在没有其他存储办法时,使用这种方式是可行的,但随着现在浏览器开始支持各种各样的存储方式而逐渐被废弃。由于服务器指定Cookie以后浏览器的每次请求都会携带Cookie数据,这会带来额外的性能负担(尤其是在移动环境下)。新的浏览器API已经允许开发者直接在本地存储数据,如可以使用Web storage API(本地存储和会话存储)和IndexedDB。
创建cookie
当服务器收到HTTP请求时,可以在响应头里面增加一个Set-Cookie头部。浏览器收到响应之后会取出Cookie信息并保存,之后对该服务器每一次请求中都通过Cookie请求头部将Cookie信息发送给服务器。另外,Cookie的过期时间、域、路径、有效期、站点都可以根据需要来指定。
Set-Cookie响应头部和Cookie请求头部
服务器使用Set-Cookie响应头部向用户代理(一般指浏览器)发送Cookie信息。一个简单的Cookie可能像这样: Set-Cookie: <cookie名称> = <cookie值>
服务器告诉客户端要保存Cookie信息(服务端程序可以是PHP、Node.js、Python或者Ruby on Rails等语言所写),响应的数据里面应该包含Set-Cookie头,浏览器收到之后会降Cookie保存。
对该服务器发起的每一次显得呃请求,浏览器都会将之前保存的Cookie信息通过Cookie请求头发送给服务器。
会话期Cookie
会话期Cookie是最简单的Cookie:浏览器关闭之后它会被自动删除,也就是它仅在会话期间有效。会话期Cookie不需要指定过期时间(Expires)或者有效期(Max-Age)。需要注意的是,有效浏览器提供了会话恢复的功能,这种情况下即使关闭了浏览器会话期Cookie也会被保存,就好像浏览器从来没有关闭一样。
持久Cookie
和关闭浏览器便失效不同,持久Cookie可以指定一个特定的过期时间(Expires)或者有效期(Max-Age)
Set-Cookie: id=a3fWa;Expires=Web, 21 Oct 2015 07:28:00 GMT;
安全和HttpOnly类型Cookie
只有在使用SLL和HTTPS协议向服务器发起请求时,才能确保Cookie被安全地发送到服务器。HttpOnly标志并没有给你提过额外的加密或者安全性上的能力,当整个机器暴露在不安全的环境时,切记绝不能通过HTTP Cookie存储、传输机密或者敏感信息。
HTTP-only类型的Cookie不能使用Javascript通过Document.cookie属性来访问,从而能够在一定程度上阻止跨域脚本攻击(XSS)。当你不需要在JavaScript代码中访问你的Cookie时,可以将该Cookie设置成HttpOnly类型。特别地,当你的Cookie仅仅是用于定义会话的情况下,最好给它设置一下HttpOnly标志。
Cookie的作用域
Domain和Path指令定义了Cookie的作用域,即需要发送Cookie的URL集合。
Domain指令规定了需要发送Cookie的主机名。如果没有指定,默认为当前的文档地址上的主机名(但是不包含子域名)。如果指定了Domain,则一般包含子域名。
如果设置了Domain=mozilla.org,则Cookie包含在子域名中(如developer.mozilla.org)。
Path指令表明需要发送Cookie的URL路径。字符%x2F(即“/”)用作文件夹分隔符,子文件夹也会被匹配到。
同站Cookie
同站Cookie允许服务器指定在跨站请求时Cookie是否会被发送,从而可以阻止跨站请求伪造攻击(CSRF)。
安全
当整个机器暴露在不安全的环境时,切记绝不能通过HTTP Cookie存储、传输机密或者敏感信息。
会话挟持和XSS
在Web应用中,Cookie常常用例标记用户和会话授权。因此,如果窃取了Web应用的Cookie,可能导致授权用户的会话受到攻击(可以能会导致攻击者获得用户的身份)。常用的窃取Cookie的方法有利用社会工程学进行攻击和利用应用程序的漏洞进行XSS攻击。
HttpOnly类型的Cookie由于阻止了JavaScript对Cookie进行访问而能在一定程度上环境此类攻击。
跨站请求伪造(CSRF)
维基百科有一个比较好的CSRF的例子:有一张并不真实存在的图片(可能是在不安全聊天室或论坛),实际上是向你的银行服务器发送了提现请求。
<img src="http://bank.example.com/withdraw?account=bob&amount=1000000&for=mallory">
当你打开含有这张图片的HTML页面是,如果你已经等了了你的银行账户并且还有效(而且没有其他验证步骤),你的银行里的钱可能会被自动转走。这里有一些方法可以阻止该类事情的发生:
- 对用户输入进行过滤来阻止XSS;
- 任何敏感的操作都应该来确认;
- 用于敏感信息的Cookie只能拥有较短的生命周期;
追踪和隐私
1.第三方Cookie
每个Cookie都有与之关联的域(Domain),如果Cookie的域和页面的域是一样的,那么称为这个Cookie为第一方Cookie,如果Cookie的域和页面的域不一样,则称之为第三方Cookie。大多数浏览器默认情况下都允许第三方Cookie,但是可以通过附加组件来阻止第三方Cookie。
2.僵尸Cookie和删不掉的Cookie
Cookie的一个极端使用例子是僵尸Cookie(也称为删不掉的Cookie)。这类Cookie较难以删除,甚至删除之后会自动重现。