一块小饼干(Cookie)的故事-上篇
cookie 如果非要用汉语理解的话应该是 一段小型文本文件,由网景的创始人之一的卢 蒙特利在93年发明。
上篇是熟悉一下注册的大致流程,下篇熟悉登录流程以及真正的Cookie
实现基本的注册功能
我们打开网站,浏览网站,最常见的两个操作就是注册以及登录,所以有必要探索一下这两个功能如何实现的。
本地模拟,当输入localhost:8080/sign_up
的时候,浏览器发起get
请求,服务器给你响应sign_up.html
//服务器端代码
if (path === '/sign_up' && method === 'GET') {
let string = fs.readFileSync('./sign_up.html', 'utf8')
response.statusCode = 200
response.setHeader('Content-Type', 'text/html;charset=utf-8')
response.write(string)
response.end()
}
CSS布局的几个小坑
在写sign_up.html
的时候,注意几点css知识:
- 如果想让你的登录页面的body占满整个屏幕,随着窗口的大小变化而变化的话,可以写
body, html{height: 100%}
//或者
body{min-height: 100%}
html{height: 100%}
//不能这么写
body, html{min-height: 100%}
当然了,实际上这么写就可以了
body{min-height: 100vh}
label
标签是display: inline
,不能设置宽度,行内元素则会根据行内内容自适应宽度,所以行内元素设置width是没有效果的。改成inline-block就可以了
获得用户的数据
既然是注册的需求,那么我们首要关注的点就是--用户的注册信息我们如何获得呢
选择合理的数据结构存储数据是很重要的。
- 每个
input
的name
可以使用数组存储 input
的value
应该使用hash
,也就是对象来存储。- 上述的套路会一直用下去,
hash+[]
的组合。
//使用jq来写
let hash = {}
let $form = $('#signUpForm')
$form.on('submit', (e) => {
e.preventDefault() //不用form表单的默认提交,而是使用我们的的ajax提交
let need = ['email', 'password', 'password_confirmation']
need.forEach((name) => {
let value = $form.find(`[name=${name}]`).val()
hash[name] = value
})
最终hash
里面存储的就是
{
'email': '...',
'password': '...',
'password_confirmation': '...'
}
到目前为止我们把用户的数据封装到了一个对象里面了。
不过在把hash用ajax发出去之前要先进行一些必要的非空验证
非空验证
主要是检测邮箱是否为空、密码是否为空、两次输入的密码是否一致。
//发起请求之前验证是否为空
if (hash['email'] === '') {
$form.find('[name="email"]').siblings('.errors').text('请您输入邮箱')
return false //精髓啊,不然没用了
}
if (hash['password'] === '') {
$form.find('[name="password"]').siblings('.errors').text('请您输入密码')
return false //精髓啊,不然没用了
}
if (hash['password_confirmation'] === '') {
$form.find('[name="password_confirmation"]').siblings('.errors').text('请您再次输入确认密码')
return false //精髓啊,不然没用了
}
if (hash['password'] !== hash['password_confirmation']) {
$form.find('[name="password_confirmation"]').siblings('.errors').text('两次输入密码不匹配')
return false //精髓啊,不然没用了
}
- 如果忘记写return的话,即使你为空了还是会直接越过这一步检测,去发起ajax请求的,所以一定不要忘了写上return false.
- 如果仅仅这么写的话会有一个bug。当出现错误提示后,你把信息填对了,错误信息依然显示,这显然是不合理的。应该填入信息后,错误信息就消失的。
$form.find('.errors').each((index, span) => {
$(span).text('')
})
使用上述的jq代码来解决这个bug即可。
非空验证完了之后,意味着浏览器收集用户数据的工作完成了,可以把hash发到服务器端了,接下来就是ajax请求了。
使用ajax提交数据
$.post('/sign_up', hash)
.then((response) => {
//成功了就打印这个
console.log(response)
},
() => {
//错误了打印这个
})
服务器端解析formData
因为formData是一段一段上传的(具体原因略复杂,可以取极限法,如果formdata很多,不可能一下子上传过来),自己不会写,就去搜索代码片段解析formdata
google: node get post data
把获得的代码封装成了一个函数
function readBody(request) {
return new Promise((resolve, reject) => {
let body = []
request.on('data', (chunk) => {
body.push(chunk)
}).on('end', () => {
body = Buffer.concat(body).toString();
resolve(body)
})
}
)
}
如何使用上述代码片段呢
...
if (path === '/sign_up' && method === 'POST') {
readBody(request).then((body) => {
let strings = body.split('&') //['email=1', 'password=2', 'password_confirmmation=3']
let hash = {}
strings.forEach(string => {
//想得到类似这种的 string == 'email=1'
let parts = string.split('=') //再用=分割,得到['email', '1']
let key = parts[0]
let value = parts[1]
hash[key] = decodeURIComponent(value)//hash['email'] = '1'
})
let {email, password, password_confirmation} = hash //ES6的解构赋值
}
...
当服务器端接收到了所有的formdata数据后,其实是一串形如email=1&password=2&password_confirmation=3
的字符串,所以我们考虑使用&
字符分割成数组。
- 得到一个形如
['email=1', 'password=2', 'confirmation=3']
的数组之后,我们为了得到string = 'email=1'
这种形式的,开始遍历数组,把数组的每个元素按照=
分割,得到[email, 1]
- 用第二小节提供的
hash+[]
方法,处理成hash
服务器端简单的校验
既然服务器端已经获得了formdata
了,那么应该进行一下简单的校验,比如邮箱的格式,没有问题了就把数据存到数据库里面。(目前校验水平很入门,没有涉及到完备的注册校验功能)
校验前的准备工作
上一节我们把formdata完美的封装到了hash里面,为了校验我们要把hash再拆开一个一个的看
或许这么做是最直接的
let email = hash['emai']
let password = hash['password']
let password_confirmation = hash['password_confirmation']
不过ES6提供了一种解构赋值的语法糖,很甜很贴心……
let {email, password, password_confirmation} = hash
由@编码引发的bug
好了,我们这一步就先看看邮箱格式是否正确。
我是菜鸟级校验邮箱,看到了邮箱的独特标志---@
,最起码有这个标志才叫邮箱吧,也就是说没有这个标志,我就可以认为邮箱格式不对啊,翻译成代码就是
if (email.indexOf('@') === -1) {
response.statusCode = 400
response.write('email is bad') //单引号只是为了标记这是一个字符串
}
很好,目前来说,事情的发展都很正常,直到一个bug的到来。
一个合法的邮箱,却进入了非法邮箱处理的代码片段里面……
毫无疑问,邮箱是合法的,代码也是合理的,那么出问题的必然是我,某个地方的理解有问题。
- 找bug,把可能出错的代码片段分成几个区间,打log.
console.log(email.indexOf('@'))
console.log(email)
没错,email
这个字符串的@
索引真的是-1,可是我的邮箱写的明明有@
啊。
为啥呢,接着又打印出了email
的内容,终于真相大白了,email
字符串里面真的没有@
,
却发现了一串你没想到的%40
,(⊙v⊙)嗯,没错了,这就是我认为的那个@
的另一个形态。
- 我在浏览器看到的只是浏览器想让我看到的东西而已,既然已经被浏览器处理了,那到了服务器端自然无法处理。
- 那这个
%40
哪来的呢
Google走起,在w3schools的HTML URL Encoding Reference找到了解释(不是国内的w3school……)
URL encoding converts characters into a format that can be transmitted over the Internet.
URL编码把字符转化成了一种可以在互联网上传播的格式,也就是说,我在网页上看到的字符是被URL编码处理的结果。
- 那接下来就去搞定什么是URL编码
搞定这个之前,文档先要让你明白啥是URL
Web browsers request pages from web servers by using a URL.
The URL is the address of a web page, like: https://www.w3schools.com.
Web浏览器通过使用URL从Web服务器请求页面。 该网址是网页的地址,例如:https://www.w3schools.com。
复习一下URL的组成6部分:
https://www.baidu.com/s?wd=he... 通过这个你就可以访问到一个 "唯一的" 网址
名字 | 作用 |
---|---|
https: | 协议 |
www.baidu.com | 域名 |
/s | 路径 |
wd=hello&rsv_spt=1 | 查询参数 |
#5 | 锚点 |
端口 | 默认80 |
复习完了URL
,继续搞URL编码
URLs can only be sent over the Internet using the ASCII character-set.
Since URLs often contain characters outside the ASCII set, the URL has to be converted into a valid ASCII format.
URL encoding replaces unsafe ASCII characters with a "%" followed by two hexadecimal digits.
URLs cannot contain spaces. URL encoding normally replaces a space with a plus (+) sign or with %20.
- URL只能用ASCII编码在互联网之间发送。
- 既然URL通常包括ASCII字符编码集之外的字符(很明显嘛,ASCII码表太少),所以URL必须转化成有效的ASCII格式。
- 这是重点,URL编码使用
%
后面紧跟着两个16进制数字的编码格式来代替不安全的ASCII码表 - URL不能包括空格。所以URL编码通常使用+号或者
20%
来代替空格。
继续往下翻,找到了%40
。
所以要把value
的值解码回去
hash[key] = decodeURIComponent(value)
decodeURIComponent()
方法用于解码由 encodeURIComponent
方法或者其它类似方法编码的部分统一资源标识符(URI)。毕竟URL
属于URI
。
错误信息的提示方法
如果有了错,需要提示用户错了,后端写的代码,用户不一定看的懂,需要前端润色一下使用户看懂,或者前端和后端沟通一下,maybe后端脾气不好,前端也是暴脾气,所以应该选择一个前后端都要用的东西做桥梁,很明显JSON
是完美的候选人。
if (email.indexOf('@') === -1) {
response.statusCode = 400
response.setHeader('Content-Type', 'application/json;charset=utf-8') //直接告诉浏览器我是json
response.write(`
{
"errors": {
"email": "invalid"
}
}
`)
}
这就合理多了,后台只管写个json给前台看,其他不管了,前台翻译一下给用户看喽~
那么前台如何获得这个json
呢
$.post('/sign_up', hash)
.then((response) => {
//成功了就打印这个
console.log(response)
},
(request, b, c) => {
console.log(request)
console.log(b)
console.log(c)
})
忘记了错误函数里面的参数是啥了,那就都打印出来看看。
可以看到,如果没用JSON的话,request对象里面有一个后端写的responseText属性可以利用。
设置了Content-Type:application/json;charset=utf-8
之后,可以利用多出来的responseJSON
属性,获得json的内容啊。
最终失败函数里面写
(request) => {
let {errors} = request.responseJSON
if (errors.email && errors.email === 'invalid') {
$form.find('[name="email"]').siblings('.errors').text('您输入的邮箱错啦')
}
}
校验邮箱是否已经存在了
var users = fs.readFileSync('./db/users', 'utf8')
try {
users = JSON.parse(users) //[] JSON也支持数组
} catch (exception) {
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.setHeader('Content-Type', 'application/json;charset=utf-8')
response.write(`
{
"errors": {
"email": "inUse"
}
}
`)
}
本文并没有使用真正意义上的数据库,只是使用了简单的db文件做数据库,其实就是存的数组,也就是users其实就是数组[]
。
- 之所以使用了
try{}catch(){}
,是因为一旦除了错,可以将其初始化为空数组,后续代码可以继续执行,可能并不严谨,不过本文是侧重了解注册的思路的。
同样的,如果邮箱已经存在了,就提示用户
if (errors.email && errors.email === 'inUse') {
$form.find('[name="email"]').siblings('.errors').text('这个邮箱已被注册啦')
}
后端校验必须很严格,因为可以通过curl
越过前端的校验。
把信息写入数据库
没有错误之后,就可以把信息写到数据库里面啦
users.push({email: email, password: password})//是个对象啊
var usersString = JSON.stringify(users)
fs.writeFileSync('./db/users', usersString)
response.statusCode = 200
users实现是个对象,而对象是内存里面的东西,数据库里面应该存储的是字符串,所以用了JSON.stringify(users)
好啦,上篇注册篇结束啦,下篇讲一讲如何登录以及Cookie
登场
相关代码见sign_up.html
一块小饼干(Cookie)的故事-上篇的更多相关文章
- 一块小饼干(Cookie)的故事-下篇
上篇介绍了注册的基本流程,下篇简单的讲讲登录的流程以及Cookie的出现 实现登录的小功能 当你在浏览器的输入框里输入localhost:8080/sign_in的时候,会发起GET请求,去访问sig ...
- JSP中的“小饼干”Cookie,用来存储数组的方式(下方已String类型的数组为例:)
1.Cookie常用方法中,存储数据的方式: Cookie cookie = new Cookie("key","Value"); response.addCo ...
- 洛谷U5653 宋荣子的小饼干
题目描述 楼下机房的LYL有n个妹子,分别编号为a1,a2……an,每个妹子都拥有一定数量的小饼干.有一天,saruka没有吃晚饭,饿的不要不要的,这时,他忽然想起了LYL的妹子们有小饼干可以吃.于是 ...
- 微信小程序带cookie的request请求代码封装(小程序使用session)
微信小程序带cookie的request请求可,以使服务端知道是同一个客户端请求. session_id会不变,从而很好的使用服务端的session. 写一个工具函数,直接导入使用即可,接口同 wx. ...
- Java练习 SDUT-2733_小鑫の日常系列故事(二)——石头剪子布
小鑫の日常系列故事(二)--石头剪子布 Time Limit: 1000 ms Memory Limit: 65536 KiB Problem Description 小鑫在上幼儿园的时候,喜欢跟小伙 ...
- Java练习 SDUT-2737_小鑫の日常系列故事(六)——奇遇记
小鑫の日常系列故事(六)--奇遇记 Time Limit: 1000 ms Memory Limit: 65536 KiB Problem Description 今天,小鑫在山上玩的时候,意外被推下 ...
- 关于Cookie的一些小饼干
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOEx ...
- [转]在ASP.NET开发中容易忽略的2个小问题 Cookie乱码存取异常 和 iframe弹框的login跳转
本文转自:http://www.cnblogs.com/outtamyhead/p/3642729.html 本文地址:http://www.cnblogs.com/outtamyhead/p/364 ...
- cookie,session,fileter,liscen
会话技术: 会话:一次会话中发生多次请求和响应 一次会话:从浏览器的打开到关闭 功能:在会话的过程中 ,可以共享数据 cookie:客户端的会话技术session:服务端的会话技术 Cookie:小饼 ...
随机推荐
- oracle 中的decode函数
decode(条件,值1,返回值1,值2,返回值2,...值n,返回值n,缺省值) 该函数的含义如下:IF 条件=值1 THEN RETURN(翻译值1)ELSIF 条件=值2 THEN RETURN ...
- jmeter 24个常用函数
Jmeter_24个常用函数 JMeter提供了很多函数,如果能够熟练使用,可以为脚本带来很多方便. JMeter函数是一种特殊值,可用于除测试计划外的任何组件. 函数调用的格式如下所示:${__ ...
- 通过媒体查询来实现 WPF 响应式设计
WPF 客户端经常需要运行在各种不同大小屏幕下,为了显示友好,所以开发的时候都需要考虑响应式设计. 布局往往通过指定比例,而不直接指定准确的大小来实现响应式布局(如 Width="3*&qu ...
- 安装wkhtmltopdf
思路 在网上查了下前后端都可以将html生成pdf,考虑到实现效果以及效率,最后决定将转化工作在服务端使用PHP完成.本着最好不要额外安装软件的原则,搜索过后分别尝试了 TCPDF MPDF FPDF ...
- egg-jwt的使用
安装 npm install egg-jwt --save 配置 // config/config.default.js config.jwt = { secret: 'zidingyi', // 自 ...
- 软件工程homework-004
软件工程软件工程homework-004 博客信息 沈阳航空航天大学计算机学院2020软件工程作业 作业要求 https://edu.cnblogs.com/campus/sau/Computer17 ...
- java垃圾处理机制
java文件通过编译器(javac命令)生成class文件(字节码文件),其通过java命令启动虚拟机将字节码文件转换成平台能够理解的方式运行. 类存在于源文件里面,方法存在于类中,语句存在与方法中. ...
- table元素使用bug
一.问题的产生 javaWeb课上老师让我们用表单做一个简单的自我介绍,但是在对表单里的单元格进行合并时出现了变形的情况,这里做个记录. 二.实验 让我们先做一个简单的4*4表格 <!DOCTY ...
- python3输出“水仙花数”
for num in range(100,1000): #如果num在[100,1000)范围,就依次循环取出num的值,第一次取100,下一次取101....最后一次取999 bai = num / ...
- pygame.update()与pygame.flip()的区别
flip函数将重新绘制整个屏幕对应的窗口. update函数仅仅重新绘制窗口中有变化的区域. 如果仅仅是几个物体在移动,那么他只重绘其中移动的部分,没有变化的部分,并不进行重绘.update比flip ...