动态服务器
1.静态服务器 V.S. 动态服务器
- 也叫做:静态网页 V.S. 动态网页
1.1 判断依据
- 是否请求了数据库
没有请求数据库,就是静态服务器
请求了数据库,就是动态服务器 - 数据库
1.数据库不属于前端范围,但程序员应该懂一点数据库
2.直接用json文件当作数据库
1.2 /db/users.json(简化数据库)
- 基于静态服务器的文件创建一个db目录
- db:data base 数据库
创建一个user.json - 读取数据库,test.js文件中第一行
const fs = require('fs')
require()不是标准JavaScript API的一部分。但是在Node.js中,它是一个内置函数,具有特殊目的:加载模块。 - 结构:一个数组
[{"id":1,"name":"lzy","password":"xxx"},
{"id":2,"name":"frank","password":"yyy"}]
- 读取 users 数据
1.先fs.readFileSync('./db/users.json').toString()
2.然后JSON.parse一下(反序列化),得到对象 - 存储 users 数据
1.先JSON.stringify一下(序列化),得到数组
2.然后fs.writeFileSync('./db/users.json',data)
1.3 读/写数据库
const fs = require('fs')
//读数据库
const usersString = fs.readFileSync('./db/users.json').toString()
const usersArray = JSON.parse(usersString)//变成对象
//写数据库
const user3 = {id:3,name:'tom',password:'zzz'}
usersArray.push(user3)
const string = JSON.stringify(usersArray)//变数组
fs.writeFileSync('./db/users.json',string)//写文件
2.目标1
- 实现用户注册功能
1.用户提交用户名和密码
2.users.json 里就新增一行数据
2.1 思路
- 前端写一个 form(表单),让用户填写 name 和 password
- 前端监听 submit 事件
回车或者鼠标点击 - 前端发送 post 请求,数据位于请求体
发送请求到服务器 - 后端接收 post 请求
- 后端获取请求体中的 name 和 password
- 后端存储数据
2.2 发送请求
- 数据库要清空,users.json中只留下一个空数组(不能什么都没有)
- 先新建一个新页面register.html(记得所有页面都要在手机上运行),做个表单,表单里面是3个div,2个div里面是用户名和密码的label,把input写在label里面,另一个是登陆的botton(注意:button的属性要是submit)
- 要先引入一个jQuery(BootCDN),点击注册之后,要监听表单的submit事件,获取事件
- 第一步是阻止默认事件,然后再写自己的代码
- 获取到用户填写的用户名和密码:在$form表单里面找(find)name=name的input,获取它的值,password同理。
- 用jQuery发送一个AJAX请求,POST请求,新建一个 /registe 的url,data是得到的用户名和密码(记得变成数组)
上传的是一个JSON字符串,要告诉浏览器Content-Type
//users.json
[]
//register.html 注册页面
//上传一个 json,服务器你最好获取到
<form id="registerForm">
<div>
<label>用户名<input type="text" name="name"></label>
</div>
<div>
<label>密码<input type="password" name="password"></label>
</div>
<div>
<button type="submit">注册</button>
</div>
</form>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"</script>
<script>
const $form = $('#registerForm')
$form.on('submit',(e)=>{
e.preventDefault()
const name = $form.find('input[name=name]').val()
const password = $form.find('input[name=password]').val()
console.log(name,password)
})
//jQuery的ajax发送POST请求
$.ajax({
method:'POST',
url:'/register',
contentType:'text/json;charset=UTF-8',
data:JSON.stringify({name,password})
})
</script>
2.3 接收请求
- 发送了一个请求去 /register,但它不对应任何一个文件,因此要在server.js中添加一个路由
- 判断路径是不是 /register,method是不是post
- get请求可以通过query拿到,post请求通过request
- 首先声明一个数组,然后往数组里面塞数据(因为数据有可能是分段上传的)
- 监听request(请求)的data(上传)事件,获取到上传的数据,然后把数据push到array里
- 监听一个end事件,结束的时候把数组打出来
- 打出来的是UTF-8编码的数据,Buffer提供了一个把不同的字符合成一个字符串的功能
- 这样就可以获取到字符串,然后把字符串变成一个对象,服务器就能获取到对象的name和password
- 声明一个新的user,读一下数据库看看新的user的ID应该是第几个,如果存在数据就最大的ID+1,如果没有数据就取ID=1
- 然后就可以存入文件
//server.js 获取到上传的数据
if (path === "/register" && method === "POST") {
response.setHeader("Content-Type", "text/html; charset=utf-8");
const userArray = JSON.parse(fs.readFileSync("./db/users.json"));
const array = [];
request.on("data", chunk => {
array.push(chunk);
});
request.on("end", () => {
const string = Buffer.concat(array).toString();
const obj = JSON.parse(string);
const lastUser = userArray[userArray.length - 1];
const newUser = {
// id 为最后一个用户的 id + 1
id: lastUser ? lastUser.id + 1 : 1,
name: obj.name,
password: obj.password
};
userArray.push(newUser);
fs.writeFileSync("./db/users.json", JSON.stringify(userArray));
response.end()
});
}
2.4 注册之后
- 注册成功之后,ajax 的 then 函数,成功则提示注册(alert)成功,跳转(location.href)到登陆页(新的HTML文件)
//register.html
$.ajax({
method:'POST',
url:'/register',
contentType:'text/json;charset=UTF-8',
data:JSON.stringify({name,password})
}).then(()=>{
alert('注册成功!')
location.href = '/sign_in.html'
},()=>{})
// sign_in.html
登陆成功页面!
3.目标2
3.1 实现用户登陆功能
- 首页 home.html,已登陆用户可看到自己的用户名
- 登陆页 sign_in.html,供提交用户名和密码
- 输入的用户名和密码如果是匹配的,就自动跳转首页
3.2 sign_in.html 思路
- 前端写一个 form,让用户填写 name 和password
- 前端监听submit事件
- 前端发送post请求,数据位于请求体
//sign_in.html 登录页
//和 注册页面 差不太多:都是表单,输入用户名和密码,发送post请求,成功转跳首页
<form id="signInForm">
<div>
<label>用户名 <input type="text" name="name"></label>
</div>
<div>
<label>密码 <input type="password" name="password"></label>
</div>
<div>
<button type="submit">登录</button>
</div>
</form>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
<script>
const $form = $('#signInForm')
$form.on('submit', (e)=>{
e.preventDefault()
const name = $form.find('input[name=name]').val()
const password = $form.find('input[name=password]').val()
$.ajax({
method: 'POST',
url: '/sign_in',
contentType: 'text/json; charset=UTF-8',
data: JSON.stringify({name, password})
}).then(()=>{
alert('登录成功')
location.href = '/home.html' //转跳首页
}, ()=>{})
})
</script>
- 后端接收post请求
- 后端获取请求体中的 name和password
- 后端读取数据,看是否有匹配的 name 和 password
- 如果匹配,后端应标记用户已经登陆
//server.js 匹配传上来的name和password
if (path === "/sign_in" && method === "POST") {
const userArray = JSON.parse(fs.readFileSync("./db/users.json"));
const array = [];
request.on("data", chunk => {
array.push(chunk);
});
request.on("end", () => {
const string = Buffer.concat(array).toString();
const obj = JSON.parse(string); // name password
const user = userArray.find(
user => user.name === obj.name && user.password === obj.password
);
if (user === undefined) {
response.statusCode = 400;
response.setHeader("Content-Type", "text/json; charset=utf-8");
response.end(`{"errorCode":4001}`)//公司自定义的错误码
} else {
response.statusCode = 200;
response.end()
}
});
}
3.Cookie
- home.html
问题:如何区别从 登陆页 跳转的用户和直接输入网址进入该页面的用户
解决:使用 Cookie!
使用 Cookie 就能知道用户是否登陆了页面
3.1 定义
- Cookie是服务器下发给浏览器的一段字符串
- 浏览器必须保存这个Cookie(除非用户删除)
- 之后发起相同二级域名请求(任何请求)时,浏览器必须附上Cookie
二级域名:CSS、JS等
3.2 举例
- 假如你是公园检票员,你怎么知道谁能进谁不能进?
- 有票就能进,没票不能进
- Cookie就是门票,用户登陆后用户名和密码匹配后,就发一张门票
由后端发门票 - 有Cookie就是登录后跳转的用户,没有Cookie就是没登陆过的用户
3.3 Set-Cookie响应头
- 从服务器设置给浏览器
前端最好不要设置Cookie response.setHeader('Set-Cookie','logined=1;HttpOnly')
1.Cookie可以设置过期时间
2.HttpOnly:不准前端访问Cookie
——访问:document.cookie
——可以访问就可以修改:document.cookie=‘logined=0’
——加上HttpOnly之后,就没办法读到- 浏览器会保存这张票,在Application中的Cookies中
1.name:logined
2.value:1
//server.js 匹配传上来的name和password
if (path === "/sign_in" && method === "POST") {
//读取用户信息文件;获取 POST 的数据;进行用户信息匹配
if (user === undefined) {
//未匹配用户信息
} else {
response.statusCode = 200;
response.setHeader("Set-Cookie",'logined=1;HttpOnly')
response.end()
}
});
}
3.4 home页面
- 页面登陆过会有Cookie,在请求 home页面的时候Cookie会自动被请求带到home中
- Home页面读取Cookie
1.如果登陆过,Cookie就是:logined=1
2.如果没有登陆过,Cookie就是:undefined
- 判断如果Cookie等于
'logined=1'
(Cookie就是字符串)
如果是,就替换掉home页面中的占位符为已登陆,否则就是未登录
//server.js
if (path === "/home.html") {
const cookie = request.headers["cookie"]; //打出来是:logined=1
if(cookie === "logined=1"){
const homeHtml = fs.readFileSync("./public/home.html").toString()
const string = homeHtml.replace('{{loginStatus}}','已登录')
response.write(string)
}else{
const homeHtml = fs.readFileSync("./public/home.html").toString()
const string = homeHtml.replace('{{loginStatus}}','未登录')
response.write(string)
}
}
3.5 home.html 显示登陆的是谁
- home.html 怎么知道登陆的是谁?
把Cookie中的 logined=1 改成 user_id
user_id=${user.id}
- 这样的话,读到Cookie,home就知道登陆的是谁了
1.获取到Cookie
2.通过层层筛选切割,获取到id数字 - home.html渲染前获取user信息
如果有 userId,说明已经登陆,则将{{user.name}}替换成user.name
如果无 user,则显示登陆按钮
//server.js
if (path === "/home.html") {
const cookie = request.headers["cookie"]; //打出来是:logined=1
let userId
try{
userId = cookie.split(';').filter(s=>s.indexOf('user_id=')>=0)[0].split('=')[1]
}catch(error){} //出现错误就什么都不做
if(userId){
const userArray = JSON.parse(fs.readFileSync("./db/users.json"))
const user = userArray.find(user=>user.id.toString()===userId)
const homeHtml = fs.readFileSync("./public/home.html").toString()
let string
if(user){
string = homeHtml.replace('{{loginStatus}}','已登录')
.replace('{{user.name}}',user.name)
}else{} //没有就什么都不做
response.write(string)
}else{
const homeHtml = fs.readFileSync("./public/home.html").toString()
const string = homeHtml.replace('{{loginStatus}}','未登录')
.replace('{{user.name}}','')
response.write(string)
}
}
3.6 有一个大bug
- 用户可以篡改 user_id
1.当用户登录之后可以在application中看到Cookie,用户就可以修改value
2.如果知道了其他人的Cookie value 就可以改成别人的,这样就能跳转到别人的页面 - 需要防止篡改 user_id
- 方法一:加密
1.将user_id加密发送给前段,后端读取 user_id 时解密,此法可行,但有安全漏洞
2.漏洞:加密后内容可无限期使用(如果在机场登陆WiFi被人窃听了加密的user_id,那么那个人就可以使用这个加密的user_id登陆页面)
3.解决方法:JWT - 方法二:把信息隐藏在服务器
也就是session
4.session 会话
- 把信息隐藏在服务器里
1.把用户信息放在服务器 x 里,再给信息一个随机id
2.把随机id发给服务器
3.后端下次想读取到id是,通过 x[id] 获取用户信息 - 特点
1.此法用户为什么无法篡改id?
——答:因为id很长,而且随机,根本没法猜到别的用户的ID
2.x 是什么?
——答:是文件,不能用内存,因为断电就清空
3.这个 x 又被叫做 session(会话)
4.为什么叫做会话?
——当你登陆成功,服务器就开始了服务,这就很像服务器和浏览器之间的会话
1.session.json
//session.json 表驱动编程
{
//"随机数":"user_id"
"12837982247893682":{"user_id":2}
}
- 如果我给你浏览器
12837982247893682
,那代表我给的 user_id 是 2 - 如何给 session
1.session.json清空
2.使用随机数传递Cookie
//session.json
{
}
//server.js
if (path === "/sign_in" && method === "POST") {
//读取用户信息文件;获取 POST 的数据;进行用户信息匹配
if (user === undefined) {
//未匹配用户信息
} else {
response.statusCode = 200;
const random = Math.random()
const session = JSON.parse(fs.readFileSync('./session.json').toString())
session[random] = {user_id:user.id}
fs.writeFileSync('./session.json',JSON.stringfy(session))
response.setHeader("Set-Cookie",`session_id=${random};HttpOnly`)
response.end()
}
});
}
- 请求 sign_in 文件后,在 Response Header 里面的
- home页面就不获取userId,就获取 session id
//server.js
if (path === "/home.html") {
const cookie = request.headers["cookie"]; //打出来是:logined=1
let sessionId
try{
sessionId= cookie.split(';').filter(s=>s.indexOf('session_id=')>=0)[0].split('=')[1]
}catch(error){} //出现错误就什么都不做
if(sessionId && session[sessionId]){
const session = JSON.parse(fs.readFileSync("./session.json").toString())
const userId = session[sessionId].user_id
const userArray = JSON.parse(fs.readFileSync("./db/users.json").toString())
const user = userArray.find(user=>user.id===userId)
const homeHtml = fs.readFileSync("./public/home.html").toString()
let string = ''
if(user){
string = homeHtml.replace('{{loginStatus}}','已登录')
.replace('{{user.name}}',user.name)
}else{} //没有就什么都不做
response.write(string)
}else{
const homeHtml = fs.readFileSync("./public/home.html").toString()
const string = homeHtml.replace('{{loginStatus}}','未登录')
.replace('{{user.name}}','')
response.write(string)
}
}
- session的时效性
1.session可以定时注销
2.每次登陆可以创建一个新的ID
3.如果怀疑自己泄露就重新登陆即可
4.使用session,信息是在服务器这边的 session.json 文件里面,我们可以随时修改,而加密信息是在用户那边,加密是不能删除的