常艷
(晉城技師學院 山西省晉城市 048000)
隨著計算機網絡的發(fā)展與普及,網站設計與開發(fā)是現(xiàn)在非常熱門的一個行業(yè)。前后端分離開發(fā)方式已成為網站開發(fā)的主流開發(fā)方式。前后端代碼分離導致了Ajax 請求時的跨域訪問問題,本文就Ajax 訪問機制、Ajax 跨域訪問問題產生的原因以及解決方案進行了深入的分析與研究。
在傳統(tǒng)的web 開發(fā)技術中,加載或者刷新數據需要重新請求頁面,會造成網頁進行重新加載,從而刷新整個頁面。Ajax(Asynchronous JavaScript & XML)是一種新的web 開發(fā)技術,通過在后臺與服務器進行少量數據交換,AJAX 可以使網頁實現(xiàn)異步更新。這意味著可以在不重新加載整個網頁的情況下,對網頁的數據進行更新,使得用戶體驗更好。
Ajax 的請求步驟如下:
(1)創(chuàng)建XMLHttpRequest 對象,也就是創(chuàng)建一個異步調用對象;
(2)創(chuàng)建一個新的HTTP 請求,并指定該HTTP 請求的方式、URL 及驗證信息;
(3)設置響應HTTP 請求狀態(tài)變化的函數;
(4)發(fā)送HTTP 請求;
(5)獲取異步調用返回的數據;
(6)使用JavaScript 和DOM 實現(xiàn)局部刷新。
現(xiàn)在前后端分離開發(fā)技術已成為網站開發(fā)的主流發(fā)展方向。前后端分離開發(fā)即網站開發(fā)前臺代碼(HTML 和JavaScript)和后臺代碼(本文中采用PHP)獨立開發(fā),前后端代碼可放在同一個服務器上,也可分布于不同的服務器上。后端代碼負責提供數據接口(以下簡稱API)用于完成數據處理和交互,前臺使用Ajax 技術請求后臺API,來實現(xiàn)網站功能、以及數據交互。前后端分離開發(fā)技術可以使得前后端代碼同時開發(fā),提高開發(fā)效率,同時易于排錯,擴展性好。
前端頁面的域名和頁面中訪問的API 的域名不一致,導致頁面無法正常使用API。例如,開發(fā)初期前端頁面多在本機PC 上進行開發(fā)和測試,那么訪問前端頁面的URL 為http://localhost/ems/yjglxt/login.html,那么前端域名為localhost,后臺接口部署于服務器上,API 訪問URL 為http://39.106.33.124/ems/public/index.php/login,那么API 的域名為39.106.33.124,前端和后端域名不一致,導致Ajax 訪問時發(fā)生跨域訪問問題,不能正常訪問API 接口。
前后端代碼位于同一臺服務器或PC,域名一致,但是訪問端口不一致,導致Ajax 跨域訪問問題,無法正常訪問接口。例如訪問前端頁面的URL 為http:// 39.106.33.124/ems/yjglxt/login.html,那么前端域名是39.106.33.124,端口默認為80。訪問API 的URL 為http://39.106.33.124:8080/ems/public/index.php/login,那么后端域名為39.106.33.124,端口為8080,前后端域名一致,但是端口不一致,同樣會導致Ajax 跨域訪問問題,不能正常訪問API 接口。
如果前后端域名不一致或前后端訪問端口不一致,在頁面中使用Ajax 訪問后臺API 接口會報錯,例如前端訪問域名是127.0.0.1,后端API 訪問域名是localhost,則會報錯,錯誤信息如下所示:
Access to XMLHttpRequest at 'http://localhost/ems/public/index.php/login' from origin 'http://127.0.0.1' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
4.2.1 瀏覽器訪問機制
HTTP 是一種無狀態(tài)的通信協(xié)議,它允許將超文本標記語言(HTML)文檔從Web 服務器傳送到客戶端瀏覽器(以下簡稱客戶端)。HTTP 協(xié)議是無狀態(tài)的協(xié)議,一旦數據交換完畢,客戶端與服務器端的連接就會關閉,再次交換數據需要建立新的連接。這就意味著服務器無法從連接上跟蹤會話。
會話指用戶登錄網站后的一系列動作,比如瀏覽商品添加到購物車并購買。會話(session)是web 程序中常用的技術,用來跟蹤用戶的整個會話。常用的會話跟蹤技術是cookie 與session。cookie通過在客戶端記錄信息確定身份,session 通過在服務器端記錄信息確定用戶身份。
通常情況下,客戶端每次請求服務器時,都會形成一次會話,即創(chuàng)建一個session,服務器端會生成一個sessionId,用來唯一標識一次會話。服務器會把一些登錄信息保存在session 中,同時會把sessionId 傳給客戶端,客戶端會把sessionId 存儲在cookie 中??蛻舳讼麓卧僬埱蠓掌鲿r,會把cookie 信息傳送到服務器,這樣服務器會自動獲取到客戶端cookie 中的sessionId,根據sessionId 去查詢服務器端session 中是否保存有客戶端的登錄信息,如果查詢到登錄信息,說明客戶端已登錄;如果查詢不到登錄信息,說明客戶端未登錄。
4.2.2 Ajax 跨域訪問導致session 失效
cookie 不可跨域名訪問,客戶端在進行ajax 跨域訪問時不會把cookie 值帶過去,就會造成服務器獲取不到客戶端cookie 中的sessionId,因此服務器在每次跨域訪問時自動創(chuàng)建一個新的sessionId,這樣也等于是開始一次新的會話,導致服務器端無法記住客戶端的登錄狀態(tài)。
Ajax 跨域訪問后端API 時,會導致每次請求時,服務器都會在客戶端的cookie 里設置一個新的sessionId,會將原有的sessionId覆蓋,這樣會導致客戶端每一次請求服務器都相當于是開始一個新的會話,客戶端無法記住自己的登錄狀態(tài),也就是客戶端對于服務器來說始終處于未登錄狀態(tài)。
header(“Access-Control-Allow-Origin:*”),表示該API 接口允許所有網站進行跨域訪問,這種方法操作簡單,但是安全性不高。
這種方法可以使得Ajax 可以正常訪問后臺API 接口,但是無法解決sessionId 丟失或sessionId 改變的問題,服務器仍然沒有辦法記住客戶端的登錄狀態(tài)。
在后臺數據接口進行修改,加上下面的代碼:
5.3.1 使用數據庫存儲客戶端登錄數據
Ajax 跨域訪問后端API 接口,如果登錄成功,服務器端把本次會話的sessionId,以及相關的用戶登錄信息,保存到數據庫中。同時編寫代碼把sessionId 傳送給客戶端,客戶端使用JS 代碼把sessionId 保存到COOKIE 中。
此后客戶端每次進行Ajax 跨域訪問其他接口時,把sessionId放在header 中發(fā)送到服務器,服務器從客戶端的header 中獲取到sessionId 后,根據sessionId 去數據庫查詢對應的登錄信息,若查詢到登錄信息,則說明客戶端已登錄;若查詢不到登錄信息,則說明客戶端處于未登錄狀態(tài)。
5.3.2 使用Redis 存儲客戶端登錄數據
使用數據庫存儲客戶端登錄信息的一個弊端是,每次Ajax 跨域訪問后端API 時,都要讀/寫數據庫,在服務器并發(fā)訪問數量較多的情況下,會造成服務器性能下降,用戶等待時間變長,用戶體驗較差,甚至導致服務器崩潰,這種情況下使用數據庫存儲客戶端登錄數據變得不可取。
在服務器并發(fā)訪問量較大的情況下,可以使用Redis 來存儲客戶端登錄數據。Redis(Remote Dictionary Server),即遠程字典服務,是一個開源的使用ANSI C 語言編寫、支持網絡、可基于內存亦可持久化的日志型、Key-Value 數據庫,并提供多種語言的API。Redis 數據庫中所有數據都存儲在內存中,由于內存的讀寫速度遠快于硬盤和普通數據庫,因此把客戶端登錄數據存儲在Redis 中,會使得服務器具有很好的并發(fā)性,極大的提升服務器的訪問速度和性能。
網站前后端代碼分離的開發(fā)方法是現(xiàn)階段主流的網站開發(fā)技術,是程序設計與開發(fā)的發(fā)展方向,但是會導致Ajax 跨域訪問問題的發(fā)生,使得在網站開發(fā)中產生了許多的問題和困難。深入了解瀏覽器的訪問機制,服務器和客戶端的交互過程,session 會話的發(fā)生機制,以及深入學習Ajax 跨域訪問的原因和解決方案,會在以后的網站開發(fā)過程中,避免許多可避免錯誤的發(fā)生,起到事半功倍的作用。