有关Session、LocalStorage、Cache-Control等等等等

解释一下 Session

Session 代表服务器与浏览器之间的一次会话过程,这是一种服务端的机制,它用这样的机制来识别具体的用户,用于跟踪用户的状态

一般来说,Session 是基于 Cookie 来实现的,那么它们的区别在哪儿呢

  • Cookie
    1. 服务器通过 Set-Cookie 头给客户端一串字符串
    2. 客户端每次访问 相同域名 的网页时,必须带上这串字符串
    3. 客户端 要在一段时间内保存这个 Cookie
    4. 默认情况下 Cookie 在用户关闭页面后就失效,但是后端可以任意修改 Cookie 的过期时间(通过修改 Set-Cookie 头的 Expires 或者 Max-age 属性实现)
    5. Cookie 的大小一般在 4kb 以内
  • Session
    1. 将一串随机数组成的 session-id 通过 Cookie 发送给客户端
    2. 客户端在访问服务器时,服务器就会通过客户端发送回来的 Cookie 中读取到这个 session-id
    3. 服务器有块内存也就是哈希表保存了所有的 session
    4. 然后通过这个 session-id 服务器就知道了用户的一些隐私信息
    5. 这块内存就是服务器上所有的 session

需要注意的是,Cookie 能被用户篡改,所以 session 也是用来解决这个问题的

与 localStorage 的关系

localStorage 自 firefox 3.5 引进,是一个与 sessionStorage 相似的浏览器接口,主要用于 js 脚本在浏览器保存数据用,它的特点如下

  • localStorage
    1. localStorage 与 HTTP 无关,所以 HTTP 不会带上 localStorage 的值
    2. 只有相同域名的页面才能互相读取 localStorage,但它没有同源那么严格(也就是说协议和端口不一致也能互相读取)
    3. 每个域名 localStorage 最大存储量为 5Mb 左右,但每个浏览器对应的存储上限不一样(Chrome 是 2.5MB,Firefox 和 Opera 是 5MB,IE 是 10MB)
    4. 它的常用场景是记录有无提示用户
    5. localStorage 永久有效,除非用户清理缓存

与 sessionStorage 的关系

除了上面 localStorage 的第 5 点,对应的它是 会在用户关闭页面即会话结束时数据就被清空,所以它并不是永久有效的,其他方面与 localStorage 一致

Cache-Control

即 HTTP 缓存控制,一般用于 web 相关性能优化,这个 cache-control 首部可以给资源或文档附加一个”过期日期”,在后端代码中指定 cache-control 的 max-age 属性来指定文档更新的 相对时间,这样的话可以一定程度上优化 web 网站加载资源的速度,从而提高用户体验
代码链接
而如何进行缓存的更新呢? 其实主要的方式还是在源文件的引用路径上加上版本号,有点类似于查询参数的写法

<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" href="/css/default.css?v=2">
<meta charset="UTF-8">
<title></title>
</head>
<body>
<h1>你的密码是:__password__</h1>
<script src="/js/main.js?v=3" charset="utf-8"></script>
</body>
</html>

需要对 css 或者 js 进行升级的时候就修改问号后面的版本号即可,或者版本号也可以用一串随机数来代替,这样既能做到缓存又能随时升级的作用

Expire

Expire 则用来指定 实际的日期 而不是秒数,因为服务器的时间都不是同步的,那么用这个绝对的时间来表示过期的话就很麻烦,于是出现了 Expire 的升级版,即 Cache-Control,即用多长时间过期来替代, Expire 的格式如下

Expires: Wed, 21 Oct 2015 07:28:00 GMT

格林威治时间格式

ETag

ETag 这个 HTTP 响应头是资源的特定版本的标识符,每次若在 url 对应的资源更新时,则生成一个新的 ETag 值,若资源没有改变,则 ETag 值不变,这样对于没有改变的资源服务器就没有必要发送完整响应(注意这里是完整响应),所以这样就可以让缓存更高效,带宽更节省,而使用 MD5 生成的一串字符来生成对应资源的 ETag 值可以避免值的碰撞

与 Cache-Control 的区别是,若 MD5 一样,则用 Cache-Control 是直接不请求,而 ETag 是有请求,但是响应体是空的,这里可以具体看这个代码链接
其关键代码如下

diff --git a/server.js b/server.js
index 0c39ea9..81a1e37 100644
--- a/server.js
+++ b/server.js
@@ -6,6 +6,8 @@ var port = process.argv[2]
// 定义一个空 session
var sessions = {}

+var md5 = require('md5')
+
if(!port){
console.log('请指定端口号好不啦?\nnode server.js 8888 这样不会吗?')
process.exit(1)
@@ -23,10 +25,16 @@ var server = http.createServer(function(request, response){
console.log('含查询字符串的路径\n' + pathWithQuery)
if(path === '/js/main.js'){
let string = fs.readFileSync('./js/main.js', 'utf8')
- response.statusCode = 200
+ let fileMd5 = md5(string)
response.setHeader('Content-Type', 'application/javascript;charset=utf-8')
- response.setHeader('Cache-Control', 'max-age=3000000')
- response.write(string)
+ response.setHeader('ETag', fileMd5)
+ if(request.headers['if-none-match'] === fileMd5){
+ // 资源未改变,就返回 304 ,没有响应体
+ response.statusCode = 304
+ }else{
+ // 资源改变了,有响应体
+ response.write(string)
+ }
response.end()
}
else if(path === '/css/default.css'){

当第一次访问首页时,正常加载 js 文件,第一次响应头中出现 ETag,然后第二次发送请求时,这个时候第二次的请求头中会出现 If-None-Match 这个头,这个头的值就是 ETag 的值,服务器接收到这个头的值以后,就会与其当前版本的资源文件的 Etag 值进行比较,若资源没改变就返回 304,资源改变了就返回新资源

Last-Modified

这个响应头是服务器用来认定资源被修改的时间以及日期用的,由于每个服务器上的时间均不同步,所以用这个来验证资源是否修改还是不精确的, Last-Modified 的格式如下

Last-Modified: Wed, 21 Oct 2015 07:28:00 GMT

缓存与 304 的区别

  • 缓存是没有请求的
  • 304 是有请求,也有响应的,但是响应没有第四部分,即响应体没有

用 LocalStorage + 查询参数实现 Session

可以通过查询参数以及 localStorage 的方式实现,代码链接
关键代码如下

diff --git a/server.js b/server.js
index ed88b7e..349e35d 100644
--- a/server.js
+++ b/server.js
@@ -25,19 +25,12 @@ var server = http.createServer(function(request, response){
if(path === '/'){
// 若请求是根路径
let string = fs.readFileSync('./index.html', 'utf8')
- // 解析请求中的 Cookie, 并存放到一个 hash 中
- let cookies = request.headers.cookie.split('; ') // ['email=1@', 'a=1', 'b=2']
- let hash = {}
- for(let i =0;i<cookies.length; i++){
- let parts = cookies[i].split('=')
- let key = parts[0]
- let value = parts[1]
- hash[key] = value
- }

//从文件模拟的数据库中查找用户是否存在
let email = ''
- let sessionId = hash['session-id']
+ // 通过查询参数拿到 sessionId
+ let sessionId = query.sessionId
+ console.log(sessionId)
if(sessions[sessionId]){
email = sessions[sessionId].sign_in_email
}
@@ -159,7 +152,10 @@ var server = http.createServer(function(request, response){
// 先生成一个 session-id
let sessionId = Math.random() * 100000
sessions[sessionId] = {sign_in_email: email}
- response.setHeader('Set-Cookie', `session-id=${sessionId}`)
+ // sessionId 直接通过 JSON 传
+ response.write(`{
+ "sessionId": ${sessionId}
+ }`)
response.statusCode = 200
}else{
response.statusCode = 401
diff --git a/sign_in.html b/sign_in.html
index 61217c4..e75d616 100644
--- a/sign_in.html
+++ b/sign_in.html
@@ -72,9 +72,11 @@
.text('填密码呀同学')
return
}
- $.post('/sign_in', hash)
+ $.post('/sign_in', hash)
.then((response)=>{
- window.location.href = '/'
+ let sessionId = JSON.parse(response).sessionId
+ localStorage.setItem('sessionId', sessionId)
+ window.location.href = `/?sessionId=${sessionId}`
}, (request)=>{
alert('邮箱与密码不匹配')
})