# 4.1 前端路由和服务端路由
# 4.1.1 前端路由和History API
# History API
提供操作控制浏览器会话历史,维护会话栈(Session stack)的能力
# history.go()
切换我们的会话栈,但并不改变我们的会话栈
# back & forward
back相当于go(-1),forward相当于go(1)
# pushState(state,title,url)
新增一个状态(State)到会话栈(session Stack)
- state:状态数据(自定义),可以通过history.state获取
- title:预留字段,多数浏览器不使用
- url:新状态的url
假如我们现在打开baidu.com网站,我们在控制台里输入以下内容
history.pushState(null,null,'/test')
// 发现页面并没有再次请求资源,我们就是利用这个能力去做单页面应用的
history.pushState({name:"demo1"},null,'/test')
// 第一个参数可以传递参数,可以用history.state拿到,可以用于区分页面
// history.pushState({name:"demo1"},null,'/test?name=demo1')跟query传递参数是一样的
2
3
4
5
6
有些垃圾网站当你打开后,你会发现按返回,一直返回不了,它们就是在你的会话栈中pushState很多次,它们push的url也不会导致页面变化,所以给人错觉进入这样的网站像中病毒一样,返回不了。
# pushState(state,title,url)
替换会话栈(session Stack)中当前的状态
- state:状态数据(自定义),可以通过history.state获取
- title:预留字段,多数浏览器不使用
- url:新状态的url
history.pushState(null,null,'/test1')
history.pushState(null,'/test2')
// 可以替换test1
history.replaceState(null,'/test3')
2
3
4
5
# 4.1.2 实战服务端路由
# 观察node.js实现服务端路由
源码地址:/Senior-FrontEnd/examples/computerNetwork/4.1
做的功能是当访问http://localhost:8080/details
返回的是details.html
,当访问http://localhost:8080/list
返回的是list.html
const app = require('express')()
const path = require('path')
const fs = require('fs')
// __dirname 当前文件相对目录
const pageDir = path.resolve(__dirname,'page')
const htmlFile = fs.readFileSync(pageDir)
function displayHtmlFile(name) {
return (req,res)=>{
const filePath= path.resolve(pageDir,name+".html")
res.sendFile(filePath)
}
}
htmlFile.forEach(file=>{
const [name,ext] = file.split('.')
app.get('/',name,displayHtmlFile(name))
})
app.listen(3000)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 观察用Cluster启动后多个实例进行负载均衡
const cluster = require('cluster');
// 想知道机器上有多少cpu核心
const numCPUs = require('os').cpus().length
const express = require('express')
//cluster.isMaster 判断主进程还是从进程
if(cluster.isMaster) {
console.log(`Master ${process.pid} is running`);
// Fork workers
for(let i=0;i<numCPUs;i++){
cluster.fork() // 创建worker,又启动了几次次当前文件,不过进不了主进程了,上面又isMaster判断了
}
cluster.on('exit',(worker,code,signal)=>{
console.log(`worker${worker.process.pid} died`)
})
}else {
// Worker can share any TCP connection
// in this case it is an HTTP server
// 把所有的进程都监听8888
const app = new express()
app.listen(8888)
console.log(`Worker${process.pid} started`)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 4.1.3 实战一个但页面应用
- 服务端怎么做?
const app = require('express')()
const path = require('path')
const htmlFile = path.resolve(__dirname, "page/spa.html")
// 请求products 或者product/123 都访问spa.html
app.get(/\/product(s|\/\d+)/,(req,res)=>{
res.sendFile(htmlFile)
})
app.listen(3000)
2
3
4
5
6
7
8
9
10
- 前端怎么做?
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
a {
color : skyblue;
cursor: pointer
}
</style>
<title>Document</title>
</head>
<body>
<h2>单页面应用示例</h2>
<div id="content"></div>
<ul>
<li><a onclick="route('/products')">列表</a></li>
<li><a onclick="route('/product/123')">详情</a></li>
</ul>
<script>
// 更新列表的函数
function pageList (){
const html = `
<ul>
<li>Apple</li>
<li>TicTok</li>
<li>Alibaba</li>
</ul>
`
document.getElementById('content').innerHTML = html
}
// 更新详情的函数
function pageDetail() {
document.getElementById('content').innerHTML = "DETAIL"
}
// 实现页面切换并加入记录
function route (page) {
history.pushState(null,null,page)
matchRoute(pages,window.location.href)
}
const pages = [
{
match: /\/products/,
route: pageList
},
{
match : /\/product\/\d+/,
route: pageDetail
}
]
// 监听浏览器前进回退按钮,实现页面渲染
window.onpopstate= function() {
matchRoute(pages,window.location.href)
}
// 匹配当前页面
function matchRoute(pages,href) {
const page = pages.find(page=>page.match.test(href))
page.route()
}
matchRoute(pages,window.location.href)
</script>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# 4.2 Session&Cookie&Storage和单点登录
# 4.2.1 Session和Storage
# 登录场景(理解Session和Cookie)
- Session 代表一次会话
- SessionID 是这一次会话的唯一标识
- Cookie 是浏览器用于存储少量数据的存储手段
# 实战Session/Cookie-1
- 观察浏览器发出请求,服务端返回cookie
- 观察Set-Cookie在跨域情况下会发生什么
const express = require('express')
const app1 = express()
app1.set('etag',false)
app1.get('/',(req,res)=>{
res.setHeader('Set-Cookie','abc=123')
res.send('ok')
})
app1.listen(3000)
2
3
4
5
6
7
8
9
nodemon ./cookie.js
为了后面演示方便我们用whistle
做代理服务;
whistle
怎么用过?可以参考:跳转地址
基础工具链第5个类型
从上图可以看出,我们配置了3个域名,分别代理到本地服务
我们在chrome浏览器上访问http://www.dev.com/
,就可以访问本地启动的3000端口的服务了,可以在Cookies里看见我们刚刚设置的cookie:apc=123;
cookie也是受同源策略限制的,只有同域名下才能访问我们设置的cookie,如果没有设置失效时间,就会一直在。
# 实战Session/Cookie-2 重要
我们在dev.com
下请求api.dev.com
看看会发生什么?
const express = require('express')
const app1 = express()
+ const app2 = express()
app1.set('etag',false)
app1.get('/',(req,res)=>{
res.setHeader('Set-Cookie','abc=123')
res.send('ok')
})
+ app2.get('/',(req,res)=>{
+ res.setHeader('Set-Cookie','apc=123')
+ res.setHeader(
+ "Access-Control-Allow-Origin",
+ "http://www.dev.com"
+)
+ res.send('ok')
+ })
app1.listen(3000)
+ app2.listen(3001)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
api.dev.com
是dev.com
的二级域名,受同源策略限制,我们通过设置Access-Control-Allow-Origin
允许在api.dev.com
跨域请求,虽然请求通了,但我们发现请求头里没有携带cookie
,这是因为只有同源的情况下才会自动携带cookie。那怎么在不同源的情况下携带cookie呢?
可以在请求参数里加上{credentials:'include'}
,不过你又会发现下面报错了,它希望服务端加上Access-Control-Allow-Credentials
const express = require('express')
const app1 = express()
const app2 = express()
app1.set('etag',false)
app1.get('/',(req,res)=>{
res.setHeader('Set-Cookie','abc=123')
res.send('ok')
})
app2.get('/',(req,res)=>{
res.setHeader('Set-Cookie','apc=123')
res.setHeader(
"Access-Control-Allow-Origin",
"http://www.dev.com"
)
+ res.setHeader(
+ "Access-Control-Allow-Credentials",
+ "true"
+ )
res.send('ok')
})
app1.listen(3000)
app2.listen(3001)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
总结
cookie也受同源策略限制,同源才会自动携带cookie,不同源需要添加Credentials
# 实战Session/Cookie-3 重要
我们在dev.com
下请求api.com
看看会发生什么?
因为2个域名属于不同的主域名,如果想让请求api.com
时候携带dev.com
下的cookie需要满足以下2个条件
- 必须是https协议
- SameSite=None;Secure
const express = require('express')
const app1 = express()
const app2 = express()
app1.set('etag',false)
app1.get('/',(req,res)=>{
+ res.setHeader('Set-Cookie','abc=123;SameSite=None;Secure')
res.send('ok')
})
app2.get('/',(req,res)=>{
res.setHeader('Set-Cookie','apc=123')
res.setHeader(
"Access-Control-Allow-Origin",
"http://www.dev.com"
)
res.setHeader(
"Access-Control-Allow-Credentials",
"true"
)
res.send('ok')
})
app1.listen(3000)
app2.listen(3001)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 其他浏览器存储
ccokie | Local storage | Session Storage | |
---|---|---|---|
容量 | 4kb | 10mb | 5mb |
作用域 | 同源 | 同源 | 当前网页 |
过期时间 | 手动 | 永久 | 当前网页关闭 |
位置 | 浏览器/服务端 | 浏览器 | 浏览器 |