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=hello&rsv_spt=1#5 通過這個你就可以訪問到一個 "唯一的" 網址
名字 | 作用 |
---|---|
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