本文主要学习一下一些高级的HTTP知识,例如
Session
LocalStorage Cache-Control Expires ETag
javascript其实主要就是涉及到了持久化存储与缓存的技术css
在此以前已经学习了Cookie
的相关知识,可是Cookie
有个缺点是能够人为修改,有必定的安全隐患。html
因此,针对这个缺点,诞生了Session
html5
通常来讲Session
是基于Cookie实现的,它利用一个sessionId
把用户的敏感数据隐藏起来,除非暴力穷举才有可能得到敏感数据。java
sessionId
咱们使用Cookie
的时候,通常是服务器给用户一个响应头,设置Cookie
node
response.setHeader('Set-Cookie', 'sign_in_email=...;HTTPOnly')
复制代码
既然Session仍是基于Cookie
实现的,那么仍是应该在Set-Cookie
上搞事情。web
//预先在服务器端预留对象准备存储各类session
let sessions = {
}
...
let sessionId = Math.random() * 100000
sessions[sessionId] = {sign_in_email: email}
response.setHeader('Set-Cookie', `sessionId=${sessionId};HTTPOnly`)
复制代码
使用随机数来作sessionId
,最终只是把这串随机数暴露给外界,而真正的信息却保存在了服务器端的sessions
对象里面。它就像一个密码簿同样,有效的信息与sessionId
一一对应,这是服务器的事,保证了安全性。算法
当下次用户访问该网站的其余页面的时候,就会带着登陆时服务器给的这个sessionId
,服务器得到这个sessionId
后,而后一转化就知道是正确的用户了。npm
let sessions = {
sessionId: {
sign_in_email: ...
}
}
复制代码
在HTML里面js文件
里面的变量或对象,每当网页刷新的时候,就会死掉,又从新生成,虽然仍是那个a
,可是刷新后已是另外一块内存了。既然它也没变,咱们为何不把它一直保留着呢,即便刷新了a
仍是那个a
,也就是持久化存储的意义。之前使用Cookie
作这个功能,不过Cookie
每次发请求会把Cookie里面的全部东西都带着去服务器,加剧内存的负担,并且请求响应时间长,因此html5
给了一个新的API localStorage
segmentfault
关于Cookie如何工做的,我发现这篇文章写得特别好
LocalStorage
它本质上仍是个hash
,不过是存在于浏览器端的,不一样于session
存在与服务器端的hash
。通常存储的都是没有用的或者不敏感的信息。
localStorage
是window的全局属性,经常使用的有三个方法
//1. 添加键、值
localStorage.setItem('a', '...')
//2. 得到键、值
localStorage。getItem('a')
//3.清空localStorage
localStorage.clear()
复制代码
注意,它存的值全是字符串,即便你写的像对象也没有卵用。
若是想存储字符串须要用到JSON.stringify( )
很简单的一个例子:网站进行更新了,用户登陆进来了,想提示用户一下---我有新东西啦,这个提示并不该该在每次刷新的时候反复告诉用户,只是在第一次用户进来的时候告诉他便可。
let already = localStorage.getItem('已经提示过了')
if (!already) {
alert('咱们的网站新进了一些货物,您看一下有没有您须要的啊O(∩_∩)O~')
localStorage.setItem('已经提示过了', true)
} else {
}
复制代码
当第一次访问的时候,already
为null,因此进入if
代码片断,提示用户一次,接着把already
设为true
,不会进入if
,也就再也不提示了。
Cookie
的session
学习了localStorage
,就能够搞一些黑科技了,前面说了,session
通常是基于Cookie
的,那么有没有例外呢。
有的。利用查询参数和localStorage
但是实现session
Id`。
Expires
会话存储主要特色与localStorage
基本相同,最大的不一样是SessionStorage
在用户关闭页面(会话结束)后就失效。
假如说咱们要访问的的文件比较大,咱们请求完以后,下载须要花很长时间,当咱们刷新页面的时候,虽然文件没有任何更新,可是咱们又从服务器端下载了一遍大文件,致使每次响应时间依然很长。
经过上图的实验能够看到localhost
的请求响应很快,10ms;而default.css
、main.js
文件较大,响应时间是localhost
的25倍,而jq
文件使用了cdn
加速,是从内存的缓存中得到的,几乎瞬间。若是每次都这样的话,用户体验确定不好。
那么咱们能不能在第一次响应完毕以后,若是资源没有更新,就不去服务器端下载,而是去某个地方得到呢?
答案是确定的,能够实现,经过缓存,正如上图的jq
实现的方法同样。
这部分能够做为web性能优化的一个方法。
经过max-age
设置缓存的有效时间(持续时间)
if (path === '/css/default.css'){
let string = fs.readFileSync('./css/default.css', 'utf8')
response.setHeader('Content-Type', 'text/css;charset=utf-8')
response.setHeader('Cache-Control', 'max-age=1000000')
response.write(string)
response.end()
}
复制代码
在响应头里面加上Cache-Control
,表示在100000秒内不要再去向服务器要这个资源了,就从个人内存缓存里面得到。
虽然使用了缓存技术,不过有一点疑惑的就是有时候从硬盘的缓存里面得到,这个速度提高并不大,可是仍然避免了向服务器再次发起请求得到资源的过程;有时候从内存的缓存里面得到,这个就特别快了。大概是由于内存的缓存特别快吧。
一般咱们把Cache-Control
的有效时间设的很长。
以常常逛得知乎为例。
若是一个文件长期不变,把它设为从缓存里面得到,知乎设置了32596169秒的有效时间,超过了1年=31536000秒的时间。
咱们刷一些论坛性质的或者新闻性质的网站,注重时效性,通常会把爆炸性的、高质量的内容放到首页去,若是咱们看了一会,想刷新看看新的更新的内容,而你设了缓存,看到的仍是10分钟以前的首页,那就太尴尬了☺……
因此首页尽可能不用缓存技术,只对那些长期不变的文件、图片等使用缓存技术。
仍是以知乎为例。
对于知乎的Cache-Control
的写法我是比较懵逼的。
public
Indicates that the response may be cached by any cache.
private
Indicates that the response is intended for a single user and must not be stored by a shared cache. A private cache may store the response.
no-cache
Forces caches to submit the request to the origin server for validation before releasing a cached copy.
no-store
The cache should not store anything about the client request or server response.
must-revalidate
The cache must verify the status of the stale resources before using it and expired ones should not be used.
MDN推荐关闭缓存的写法是Cache-Control: no-cache, no-store, must-revalidate
。
那么若是有的资源确实被更新了,如何去更新缓存呢。
经过服务器端代码server.js
咱们能够发现
if (path === '/js/main.js') {
...
response.setHeader('Cache-Control', 'max-age=1000000')
...
} else if (path === '/css/default.css'){
...
response.setHeader('Cache-Control', 'max-age=1000000')
...
}
复制代码
只要当URL
符合要求的时候,会使用缓存技术,不去发起请求从新下载资源。
因此当文件确实被更新了以后,咱们能够改变URL
,那么就会去从新下载新的文件了。
既然咱们的网页入口是html
,能够在这里面动手脚
...
<script src="./js/main.js?V2"></script>
...
复制代码
当你更新代码以后,理论上只须要在URL上添加查询参数?V2
便可。
咱们仍是去知乎看看他们的例子。
能够看到知乎也是把URL
改了,只不过比我那种高级,它在文件名字动了手脚,大概是用了什么框架或者处理工具吧,不过更新缓存的思路上是同样的。文件变了,知乎就把文件缓存的URL
填点东西;没变的话,就缓存一年,在你的硬盘某处睡一年^_^。
使用缓存就用response.setHeader('Cache-Control', 'max-age=100000')
,当你想更新的时候就改变文件的URL
。
固然,缓存存多了,你的硬盘估计就爆了,浏览器会去权衡这些的,应该优先清楚哪些缓存,是浏览器的事。
俗话说得好啊,吃井不忘挖井人啊,要学会忆苦思甜啊,咱们如今用的可爽的Cache-Control
也不是凭空冒出来的,是有历史缘由的,之前呢,是用Expires
实现缓存的技术。
Expires
的英文是到期的意思,很明显是与缓存有关的技术,不过从其英文意思也能看出它是到某个时间点截止的意思,不是Cache-Control
的有效时间。
从语法和示例能够看出它是基于格林威治时间的。
咱们还要处理一下时间
var d = new Date() //Sat Feb 10 2018 11:18:54 GMT+0800 (CST)
d.toGMTString() //"Sat, 10 Feb 2018 03:18:54 GMT"
复制代码
能看出来,这个响应头的最大的弊端在于,时间戳是与你的本地时间关联的
若是本地电脑的时间系统错乱了,并且这种毛病还真的时常发生,那你的缓存就毫无做用了。maybe这就是HTTP要升级这个响应头的缘由吧O(∩_∩)O~
当Cache-Control
和Expires
共同存在的时候
若是还有一个 设置了 "max-age" 或者 "s-max-age" 指令的
Cache-Control
响应头,那么Expires
头就会被忽略。
关于缓存的技术,还有最后一个兄弟ETag
,在搞定它以前,先来学习一下它的小跟班MD5
MD5
是一个摘要算法。常常用于比较两个文件是否彻底同样,若是有一点不同,偏差会放大。例如咱们常常重装系统的话,有良心的系统提供者会给你一个对应的MD5
值,当你下载完毕后,查看你下载的系统的MD5值是否与官方提供给你的同样,确保是否会由于网络缘由致使你下载的东西不完整。
在Linux
系统里面使用md5sum
指令进行MD5校验
第一个红框里面就是1.txt
文件(内容设定为123456)的MD5值,第二个红框里面就是1-copy
文件(内容被我改成了123460)的MD5值。
在nodejs
里面如何使用呢,Google后发现有npm
的MD5。
npm install md5
...
//在server.js引入
var md5 = require('md5');
复制代码
准备工做作完,能够搞ETag
了。
The ETag HTTP response header is an identifier for a specific version of a resource.It allows caches to be more efficient, and saves bandwidth, as a web server does not need to send a full response if the content has not changed. On the other side, if the content has changed, etags are useful to help prevent simultaneous updates of a resource from overwriting each other ("mid-air collisions").
If the resource at a given URL changes, a new
Etag
value must be generated. Etags are therefore similar to fingerprints and might also be used for tracking purposes by some servers. A comparison of them allows to quickly determine whether two representations of a resource are the same, but they might also be set to persist indefinitely by a tracking server.
能够看出ETag
应该是一串值,此时上一节的MD5
就派上用场了,咱们使用MD5来比较先后两次请求文件的内容。
当某个URL来访问服务器的资源的时候,若是服务器设置了响应头ETag:一串md5值
,那么
如今没有什么其余变化,若是第二次刷新的话,你会发现
请求头多了一个If-None-Match:一串MD5值
。
比较上述两图,个人main.js
没有改变过,发现ETag:一串md5值
和If-None-Match:一串MD5值
的同样,稍微一思考的话,就能明白,第二次刷新的时候若是个人main.js
变了的话,那么
第二次向服务器发起请求,下载的main.js
的ETag
的MD5值必然不一样了。
根据这个现象,而后结合MDN文档
ETag头的另外一个典型用例是缓存未更改的资源。 若是用户再次访问给定的URL(设有ETag字段),显示资源过时了且不可用,客户端就发送值为ETag的
If-None -Match
header字段:If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4" 复制代码
服务器将客户端的ETag(做为If-None-Match字段的值一块儿发送)与其当前版本的资源的ETag进行比较,若是两个值匹配(即资源未更改),服务器将返回不带任何内容的
304
未修改状态,告诉客户端缓存版本可用(新鲜)。
能够推理出以下的代码了:
if (path === '/js/main.js') {
let string = fs.readFileSync('./js/main.js', 'utf8')
response.setHeader('Content-Type', 'application/javascript;charset=utf-8')
let fileMd5 = md5(string)
response.setHeader('ETag', fileMd5)
if (request.headers['if-none-match'] === fileMd5) {
response.statusCode = 304
} else {
response.write(string)
}
response.end()
}
复制代码
HTTP 304 说明无需再次传输请求的内容,也就是说可使用缓存的内容。这一般是在一些安全的方法(safe),例如
GET
或HEAD
或在请求中附带了头部信息:If-None-Match
或If-Modified-Since
。
304和缓存的区别:
cookie
是浏览器提供的功能。cookie
实际上是存储在浏览器中的纯文本,浏览器的安装目录下会专门有一个 cookie 文件夹来存放各个域下设置的cookie
。Set-Cookie
以后,用户的每次访问服务器,请求里面都会带着Cookie
到服务器上,与HTTP有关,而LocalStorage
不用发到服务器端,它是存储在浏览器里面的,与HTTP无关,是浏览器的属性,window.localStorage
。Cookie
通常比较小,大约4k左右,而LocalStorage
大约能用5MCookie
默认会在用户关闭页面后失效,不事后端能够设置保存时间,而LocalStorage
永久有效,除非用户手动清理。LocalStorage
永久有效,除非用户手动清理localStorage.clear()
。不会自动过时设置过时时间:Set-Cookie: <cookie-name>=<cookie-value>; Expires=<date>
data`是格林威治时间,响应头里里面应该这么写代码
response.setHeader('Expires', 'Fri, 09 Feb 2018 11:29:48 GMT')
复制代码
也就是说Cookie在格林威治时间的2018年2月9号的11点29分48秒失效。
设置cookie过时时间小于当前时间,那么就会删除该cookie。
function deleteCookie(name) {
document.cookie = name + '=; expires=Thu, 01 Jan 1970 00:00:01 GMT;'
}
复制代码
Cache-Control: max-age=1000
的缓存 是直接不发请求的,1000秒内相同URL的用户请求资源的时候,不会再去发请求访问服务器了,直接从本地内存的缓存里面获取ETag
的缓存是无论怎么样都要发起请求,第二次访问的是时候会多一个请求头If-None-Match : md5值
,若是两次请求之间的MD5值相同就不会去下载新的文件,响应体是第一次下载的;若是MD5值变了,就要去下载新的文件。