预备知识
请预习 Cookie、Session、Cache-Control 等 HTTP 知识
- https://zh.wikipedia.org/wiki/Cookie
- https://zhuanlan.zhihu.com/p/22396872?refer=study-fe
- Session 维基百科:https://zh.wikipedia.org/wiki/%E4%BC%9A%E8%AF%9D_(%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%A7%91%E5%AD%A6)
- Cache-Control
要点:
Cookie 的特点
- 服务器通过 Set-Cookie 响应头设置 Cookie
- 浏览器得到 Cookie 之后,每次请求都要带上 Cookie
- 服务器读取 Cookie 就知道登录用户的信息(email)
问题
- 我在 Chrome 登录了得到 Cookie,用 Safari 访问,Safari 会带上 Cookie 吗
no - Cookie 存在哪
Windows 存在 C 盘的一个文件里 - Cookie会被用户篡改吗?
可以,下节课会讲 Session 来解决这个问题,防止用户篡改 - Cookie 有效期吗?
默认有效期20分钟左右,不同浏览器策略不同
后端可以强制设置有效期,具体语法看 MDN - Cookie 遵守同源策略吗?
也有,不过跟 AJAX 的同源策略稍微有些不同。
当请求 qq.com 下的资源时,浏览器会默认带上 qq.com 对应的 Cookie,不会带上 baidu.com 对应的 Cookie
当请求 v.qq.com 下的资源时,浏览器不仅会带上 v.qq.com 的Cookie,还会带上 qq.com 的 Cookie
另外 Cookie 还可以根据路径做限制,请自行了解,这个功能用得比较少。 - 今天讲的Cookie、Session、Cache-Control都是响应头里面的内容,请求头没有相关内容
- Cache-Control用于控制缓存的,Cookie的意思是曲奇饼
-
请求和响应
-
JS发送请求
注册
-
我们给原先创造的server添加一个路由,路径是sign_up,并创建相应的文件,以便访问
-
我们做一个登录界面
- 我们希望当点击提交的时候,会连带这些输入信息提交到服务器,服务器看看这些是不是正确的。
- 我们打开这个https://www.bootcdn.cn来找jQuery的链接
- 收集用户输入的东西,并存入哈希中
let hash = {} $('#signUpForm').on('submit', (e)=>{ e.preventDefault() let need = ['email', 'password', 'password_confirmation'] need.forEach((name)=>{ let value = $('#signUpForm').find(`[name=${name}]`).val() hash[name] = value }) console.log(hash) })
- 接下来,我们就可以使用jquery的ajax发送ajax,问,下面这种ajax返回的会是什么?是404,还是一串html?
let hash = {} $('#signUpForm').on('submit', (e)=>{ // 拿到用户提交的信息并传入hash中 e.preventDefault() let need = ['email', 'password', 'password_confirmation'] need.forEach((name)=>{ let value = $('#signUpForm').find(`[name=${name}]`).val() hash[name] = value }) // 发送ajax $.post('/sign_up', hash) .then(()=>{console.log('success')}, ()=>{console.log('error')}) })
-
返回的是一串字符串,因为我们在路由那边写了,只要是这个路径就是200,其他什么都没管,没有限定请求方式等。
-
我们可以区分一下这个路径及方式
-
我们点击提交的时候,还是200,返回的response是空的。接下来我们需要读取用户提交的数据
- email=1&password=2&password_confirmation=3,这个形式的代码,node不好读取,因为上传是一段一段上传的,我们需要借助工具。这个跟TCP有关,第四部分不一块上传
- google搜索node http get post data,
-
点击之后,后台会打印字符串,前端发送请求
-
解析这个拿到的字符串
-
我们对拿到的字符串解析后进行验证
-
当我们前后端分离后,我们需要定前后端协议,我们返回的信息可以使用JSON,JS就可以读取JSON语法数据
-
这样打印出来的是字符串还是对象呢?
-
答案是符合JSON对象语法的字符串,我们传入JSON.parse中去,就是一个对象了
- 我们每次都需要进行JSON.parse一下,比较麻烦,其实如果在server那边声明是JSON,jQuery就会自动识别的;response.setHeader('Content-Type', 'application/json;charset=utf-8'),然后使用request.responseJSON就可以拿到的,这是基于协议的,因此可以自动识别
-
这样我们就可以针对错误,发起不同提示
-
前端验证,在POST之前展示错误并return
-
后端验证是一定需要的,前端验证不一定要,因为有的直接使用curl发送请求了,并不通过JS
[
登录以及Cookie
如果服务器发现密码和邮箱等正确,一般会存下来,但是密码一般不存。
我们需要有文件存储数据,创建一个文件,专门用于存储数据
-
之前拿到的数据需要使用decodeURIComponent()进行转译
我们可以通过后台将这个新的账户存入到数据文件中
数据存储必须使用字符串,不可以使用对象,所以需要使用JSON.stringify
我们存入的时候,需要遍历,看看有没有存进去,如果存进去,就不要再存了
理论上,我们不能存用户的密码,应该存加密后的东西,只要后面能对上就代表输对了
我们再添加一个sing_in路由
假设登陆成功,我们跳到首页。
问题1:登录成功与否都可以到首页,这样有问题
问题2:能不能登录成功有名字显示呢
这就是引入今天的主题,需要一个Cookie,即告诉服务器,或者浏览器,我是谁;在登录成功的一瞬间,我们需要Cookie
可以查询http set cookie
-
设置好之后,点击preserver log阻止页面刷新,可以看到response有特殊的响应头,下面所有的请求,只要是相同的源,都会携带这个Cookie
有了这个标识信息,网页就可以认得是谁访问了
-
总结:
- 服务器通过 Set-Cookie 响应头设置 Cookie
- 浏览器得到 Cookie 之后,每次请求都要带上 Cookie
- 服务器读取 Cookie 就知道登录用户的信息(email)
-
问题
- 我在 Chrome 登录了得到的 Cookie,用 Safari 访问,Safari会带上Cookie吗?
答案: 不会 - Cookie存在哪?
Window 存在 C 盘的一个文件里 - 票能作假吗?
可以 -
Cookie有效期吗?
默认有效期20分钟左右
后端可以强制设置有效期
HttpOnly是可以防止用户使用JS修改的,但是还是可以使用手动
- 我在 Chrome 登录了得到的 Cookie,用 Safari 访问,Safari会带上Cookie吗?
拿到Cookie使用线程现成的框架就好
-
退出登录,点击X就好了
-
总结:
-
server代码
var http = require('http') var fs = require('fs') var url = require('url') var port = process.argv[2] if(!port){ console.log('请指定端口奥好不好?\nnode server.js 8888 这样不会吗?') process.exit(1) } var server = http.createServer(function(request, response){ var parsedUrl = url.parse(request.url, true) var pathWithQuery = request.url var queryString = '' if(pathWithQuery.indexOf('?') >= 0){queryString=pathWithQuery.substring(pathWithQuery.indexOf('?'))} var path = parsedUrl.pathname var query = parsedUrl.query var method = request.method /*********从这里开始看,上面不要看*********/ console.log('方方说:含查询字符串的路径为\n' + pathWithQuery) if(path === '/'){ let string = fs.readFileSync('./index.html', 'utf-8') //同步读取网页文件 let cookies = request.headers.cookie.split('; ') 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 = hash.sign_in_email let users = fs.readFileSync('./db/users', 'utf8') users = JSON.parse(users) let foundUser for(let i=0; i< users.length; i++){ if(users[i].email === email){ foundUser = users[i] break } } if(foundUser){ string = string.replace('__password__', foundUser.password) }else{ string = string.replace('__password__', '不知道') } response.statusCode = 200 response.setHeader('Content-Type', 'text/html;charset=utf-8') response.write(string) // 将网页文件写入到response中返回 response.end() }else if(path==='/sign_up' && method === 'GET'){ let string = fs.readFileSync('./sign_up.html', 'utf-8') response.statusCode = 200 response.setHeader('Content-Type', 'text/html;charset=utf-8') response.write(string) response.end() }else if(path==='/sign_up' && method === 'POST'){ readBody(request).then((body)=>{ let strings = body.split('&') // ['email=1', 'password=2', 'password_confirmation=3'] let hash = {} strings.forEach((string)=>{ let parts = string.split('=') // ['email', '1'] let key = parts[0] let value = parts[1] // 进行转译,否则会拿不到@,使用%40代替 hash[key] = decodeURIComponent(value) // hash['email'] = '1' }) // let email = hash['email'] // let password = hash['password'] // let password_confirmation = hash['password_confirmation'] let {email, password, password_confirmation} = hash if(email.indexOf('@') === -1){ response.statusCode = 400 response.setHeader('Content-Type', 'application/json;charset=utf-8') // 我们返回JSON数据,这样就可以实现选择性展示出错误信息,最外端的''不属于JSON,只表示是一个字符串 // JS可以理解JSON语法 response.write(`{ "errors": { "email": "invalid" } }`) }else if(password !== password_confirmation){ response.statusCode = 400 response.write('password not match') }else{ // 这个users拿到之后是一个字符串 var users = fs.readFileSync('./db/users', 'utf8') try{ users = JSON.parse(users) // [] }catch{ users = [] } let inUse = false for(let i=0; i<users.length; i++){ let user = users[i] if(user.email === email){ inUse = true break; } } if(inUse){ response.statusCode = 400 response.write('email in use') }else{ users.push({email: email, password: password}) // 对象不能直接存,我们需要使用字符串化 var usersString = JSON.stringify(users) fs.writeFileSync('./db/users', usersString) response.statusCode = 200 } } response.end() }) }else if(path === '/sign_in' && method === 'GET'){ let string = fs.readFileSync('./sign_in.html', 'utf-8') response.statusCode = 200 response.setHeader('Content-Type', 'text/html;charset=utf-8') response.write(string) response.end() }else if(path === '/sign_in' && method === 'POST'){ readBody(request).then((body)=>{ let strings = body.split('&') // ['email=1', 'password=2', 'password_confirmation=3'] let hash = {} strings.forEach((string)=>{ let parts = string.split('=') // ['email', '1'] let key = parts[0] let value = parts[1] // 进行转译,否则会拿不到@,使用%40代替 hash[key] = decodeURIComponent(value) // hash['email'] = '1' }) // let email = hash['email'] // let password = hash['password'] // let password_confirmation = hash['password_confirmation'] let {email, password} = hash var users = fs.readFileSync('./db/users', 'utf8') try{ users = JSON.parse(users) // [] }catch{ users = [] } let found for(let i=0;i<users.length;i++){ if(users[i].email === email && users[i].password === password){ found = true break } } console.log(found) if(found){ // 设置cookie response.setHeader('Set-Cookie', `sign_in_email=${email}; HttpOnly`) response.statusCode = 200 }else{ response.statusCode = 401 } response.end() }) }else if(path==='/xxxx'){ response.statusCode = 200 response.setHeader('Content-Type', 'text/json;charset=utf-8') // 加一个后台的响应头,告诉浏览器,这个域名可以访问我的后台 response.setHeader('Access-Control-Allow-Origin', 'http://frank.com:8001') // 这边其实返回的是一个字符串,这个字符串刚好符合JSON语法,此处绝对不是一个对象 response.write(` { "note": { "to": "小谷", "from": "芳芳", "heading": "打招呼", "content": "hi" } } `) response.end() }else if(path='/main.js'){ let string = fs.readFileSync('./main.js') //同步读取网页文件 response.statusCode = 200 response.setHeader('Content-Type', 'text/javascript;charset=utf-8') response.write(string) // 将网页文件写入到response中返回 response.end() }else{ response.statusCode = 404 response.setHeader('Content-Type', 'text/html;charset=utf-8') response.write('呜呜呜') response.end() } /*********代码结束,下面不要看*********/ }) function readBody(request){ return new Promise((resolve, reject)=>{ let body = []; // 请求体 // request监听data事件,每次data事件会给一小块数据 request.on('data', (chunk) => { // 将一小块数据放入body中 body.push(chunk); }).on('end', () => { // end就是上传结束,将body里面的数据全部连成字符串 body = Buffer.concat(body).toString(); resolve(body) }) }) } server.listen(port) console.log('监听 ' + port + ' 成功\n请在空中转体720度然后用电饭煲打开 http://localhost:' + port)