从注册登陆实例学习Cookie\Session\LocalStorage\SessionStorage\Cache-Control\Expires\ETag\Last-Modified
简介
从做一个注册、登陆、主页、服务端的过程,学习相关的前端知识。
注册 sign_up.html
登陆 sign_in.html
主页 index.html
服务端 server.js
效果
Sign_up页面
注册表单
- 邮箱
- 密码
- 确认密码
- 注册按钮
Sign_in页面
登陆表单
- 邮箱
- 密码
Index主页
显示密码 你的密码是:xxx
代码
注册流程
前端发请求
- 遍历 form 表单里
email、password、password_confirmation
的 3 个值 - 把 3 个值赋值给一个 hash
{ email:'123@jj.com', password:'123', password_confirmation:'123' }
- 通过 ajax 请求把 hash 作为请求体字符串发给服务器
$.post('/sign_up', hash)
后端发响应
- 拿到请求,进行 URI 解码、分割 hash 字符串、合成新 hash。
- 校验 email 格式,校验密码一致性,读取数据库检查 email 是否已经存在。
- 都通过,就把 email 和 password 新增写入数据库。给前端响应注册成功的 JSON 字符串。
- 若不通过,响应相应错误的 JSON 字符串。
前端收响应
- 拿到成功的200响应,在页面显示类似“恭喜你注册成功”之类的提示。
- 若拿到失败的400响应,则根据响应的 JSON 在页面展示“邮箱格式错误、邮箱已被使用、密码格式错误”之类的提示。
其他细节
- 前端也可以在发请求前,对 hash 请求体做验证。例如
- 用正则验证email、password的规则
- password === password_confirmation 验证密码一致性
- 遍历 email 字符串去空格
- 但是,无法验证 email 是否已被使用,因为数据在服务器。
- 前端的验证不是必须的,但是后端是肯定有验证的。
首次登陆流程
前端发请求
和注册一样,把 email、password 合成一个 hash
通过 ajax 把 hash 字符串发给服务器。$.post('/sign_in', hash)
后端发响应
- 拿到请求,进行 URI 解码、分割 hash 字符串、合成新 hash 。
- 遍历数据库的 email 和 password 做对比。
- 若不通过,响应相应错误的 JSON 字符串。
- 若通过,生成一个随机数的 SessionID,用一个名为 Session 的 hash 存下
{SessionID:email}
这样的对照表。 - 后端再通过 Cookie 给前端发送 SessionID。
前端收响应
前端若是收到服务器发来的401响应和相关的 JSON,则提示用户相应的错误,如密码错误之类。
前端若是收到带 SessionID 的 Cookie 相应头,以及响应码 200,说明登陆成功。
带Cookie登陆流程
前端带cookie访问主页
得到 cookie 后,浏览器会请求同域名下的页面(如首页 index.html) 会自动带着 Cookie 。
后端接收带cookie的请求
后端收到 cookie,得到 SessionID,拿 SessionID 在 Session 哈希表里找到对应的 email,就能知道用户是谁。
于是根据用户,读取 index.html 文件为字符串,并替换部分内容(本实例是替换密码显示)。
把替换过部分内容的 index.html 字符串发给前端。
前端收到替换过部分内容的 index.html 字符串
前端收到这个“针对用户定制”的 index.html 字符串,就能显示用户的个人信息了,如昵称、地址等。
其他细节
Cookie 访问同一个域名时(同域名,没同源那么严格),都是会自动带上的。
其实 Session 不是必要的,Cookie 也能直接存用户的 email,Session + SessionID 只是为了给 email 一个中转,中转出一个随机数 SessionID,让客户端无法通过篡改 Cookie 去登陆别人的账户。(email很容易猜,SessionID是随机的很难猜)
cookie
cookie是什么
cookie是服务器发给客户端的一个字符串。
cookie的流程
前端登陆成功后,后端发送cookie给前端
response.setHeader('Set-Cookie', `sign_in_eamil=${email}`)
以后前端发什么请求都会带上cookie
服务器收到cookie再分割后,拿它和数据库的账户对比
再针对该用户的信息,替换针对性的网页内容。
cookie的特性
-
服务器通过 set-cookie 头给客户端一串字符串
-
客户端每次访问相同域名的网页时,必须带上这段字符串
-
客户端要在一段时间内保存这个cookie
-
cookie是可以被修改作假的
-
cookie默认有效期20分钟左右,默认在关闭页面后失效,但服务器可以设置 cookie 有效期
session
session是什么
session是服务器存储的一个哈希表。
为什么有session
因为cookie直接发送账户email,会被用户修改cookie,登陆其他人的email账户。
所以我们需要用session+随机数sessionID来存储email,用户只有session,是没办法知道对应的email,更没办法修改session来登陆其他mail了。
session的作用
服务器把 sessionID(随机数)通过 cookie 发送给客户端
客户端访问服务器时,服务器读取 sessionID
服务器有一块内存(哈希表)保存了所有 session
通过 sessionID 可以得到对应用户的隐私信息,如账号、email
session的缺点
session会占用一定内存。
本来cookie直接把用户的账号发给客户端,服务器就只需要在数据库存储账号密码就行了。
使用session后,要用session来存储用户账号,服务器就多存了一份数据,即session。
session的特性
- 服务器将 sessionID(随机数)通过 cookie 发送给客户端
- 客户端访问服务器时,服务器读取 sessionID
- 服务器有一块内存(哈希表)保存了所有 session
- 通过 sessionID 我们可以得到对应用户的隐私信息(账户、email)
- 这块内存(哈希表)就是服务器上的所有 session
localStorage
localStorage是什么
localStorage是浏览器存储的一个哈希表。
localStorage是html5提供的API。
localStorage只能存储字符串。
localStorage的基本API
localStorage.setItem('a','1')
localStorage.getItem('a')
localStorage.clear()
localStorage.setItem('jsonString',JSON.stringify({name:'hehe'}))
localStorage的特性
- localStorage 跟 HTTP 无关
- HTTP 不会带上 localStorage 的值
- 只有相同域名的页面才能互相读取 localStorage
- 每个域名 localStorage 最大存储量为 5MB 左右(每个浏览器不一样)
- 常用场景:记录有没有提示过用户,例如域名搬迁的首次提示。(没有用的信息,不能记录密码)
- localStorage 永久有效,除非用户清理缓存
sessionStorage
sessionStorage的特性
- sessionStorage 跟 HTTP 无关
- HTTP 不会带上 sessionStorage 的值
- 只有相同域名的页面才能互相读取 sessionStorage
- 每个域名 sessionStorage 最大存储量为 5MB 左右(每个浏览器不一样)
- sessionStorage 在用户关闭页面(会话结束)后就失效
cookie、session、localStorage、sessionStorage的关系
- session 一般是通过 cookie 实现的,即服务器通过 cookie 把 sessionID 发给客户端。
- localStorage 是跟 HTTP 无关的,访问服务器不会带上 localStorage。sessionStorage同理。
- sessionStorage 和 localStorage 差不多,但是 sessionStorage 在关闭页面就失效。
session的2种实现方法
session用cookie的实现方法
服务器把sessionID发cookie请求头。
response.setHeader('Set-Cookie', `sessionId=${sessionId}`)
前端不用发sessionID,因为cookie自带,服务器能收到。
$.post('/sign_in', hash)
.then((response) => {
window.location.href = '/'
}, (request) => {
alert('邮箱与密码不匹配')
})
session不用cookie的实现方法
服务器直接发sessionID作为响应体
response.write(`sessionId=${sessionId}`)
前端把sessionID存在localStorage,并通过查询参数把sessionID作为请求体发送给服务器。
$.post('/sign_in', hash)
.then((response) => {
let object = JSON.parse(response)
localStorage.setItem('sessionId', object.sessionId)
window.location.href = `/?sessionId=${object.sessionId}`
}, (request) => {
alert('邮箱与密码不匹配')
})
服务器通过查询参数获取sessionID
let mySession = sessions[query.sessionId]
Cache-Control 缓存控制
同一URL请求,浏览器已经缓存过,就不再发该请求,直接本地加载缓存。
服务器代码
if (path === '/main.js') {
let string = fs.readFileSync('/main.js', 'utf8')
response.setHeader('Content-type', 'application/javascript;charset=utf8')
// Cache-Control 缓存控制
response.setHeader('Cache-Control', 'max-age=30')
response.statusCode = 200
response.write(string)
response.end()
}
一般来说,HTML不设置缓存,CSS和JS的缓存时间设置10年。
服务器要更新CSS和JS的话,修改URL,服务端和前端的URL查询参数加上v=2
这样:
http://www.baidu.com/main.js?v=2
Expires
跟 Cache-Control 差不多,时间设置是用格林尼治时间点。
服务器代码
// 缓存控制,格林尼治时间2025年前别再请求这个文件
response.setHeader('Expires', 'Wed, 21 Oct 2025 07:28:00 GMT')
Expires 设置的是本地时间点,如果用户本地时间被修改到2026年这样,Expires 就没用了。
ETag
服务器通过ETag响应头发文件的md5给客户端,客户端下次发请求会带上文件的md5,如果md5一致,则不再下载文件,直接从客户端本地读取文件。
if (path === '/main.js') {
let string = fs.readFileSync('/main.js', 'utf8')
response.setHeader('Content-type', 'application/javascript;charset=utf8')
let fileMd5 = md5(string)
// ETag
response.setHeader('ETag', 'fileMd5')
if (request.headers['if-none-match'] === fileMd5) {
response.statusCode = 304
} else {
response.write(string)
}
response.end()
}
ETag和Cache-Control的区别在于
ETag要发请求然后判读要不要下载,只响应304,不发响应体。
Cache-Control是直接不请求。
Last-Modified / If-Modified-Since
在浏览器第一次请求某一个 URL 时,服务器端的返回状态会是200,内容是你请求的资源,同时有一个 Last-Modified 的属性标记此文件在服务期端最后被修改的时间,格式类似这样:
Last-Modified: Mon, 30 Nov 2015 23:21:37 GMT
浏览器第二次请求此 URL 时,根据 HTTP 协议的规定,浏览器会向服务器传送 If-Modified-Since 报头,询问该时间之后文件是否有被修改过:
If-Modified-Since: Mon, 30 Nov 2015 23:21:37 GMT
如果服务器端的资源没有变化,则自动返回 HTTP 304 (Not Changed)状态码,内容为空,这样就节省了传输数据量。当服务器端代码发生改变或者重启服务器时,则重新发出资源,返回和第一次请求时类似。从而保证不向客户端重复发出资源,也保证当服务器有变化时,客户端能够得到最新的资源。