angularjs跨域請求,html5封裝進android與ios
前言
第一次正兒八經的寫部落格實在有點不知道怎麼開頭好,所學的東西也不夠系統,我相信我寫的東西瞄準了一個點去寫,無論從哪裡開始,都會讓人覺得有點突然,但是,我也沒辦法從所談主題的起源開始談,所以不糾結這個次序關係了,有關主題的前後我就稍微介紹一些,主要圍繞我所遇到的問題和如何去解決來談吧。
我使用的框架是springboot+angularjs ,內容主要包括:
- 為什麼需要跨域
- js跨域請求
- 使用localStorage替代本地Cookies
- 使用token替代跨域傳送Cookies
下面主要針對以上幾點來分別說說
為什麼需要跨域
我們這個俄專案是使用springboot+angularjs開發的,web端順利開發結束,接下來是手機端,手機端直接使用html在電腦端執行也開發的比較順利,因為是直接使用的PC端瀏覽器,所以和web端開發是沒有區別的,當然,手機端的瀏覽器也同樣不會有什麼問題。但是我們公司專案能打包成android和ios的app,android和ios都支援webview,按理直接把url告訴webview然後去請求伺服器訪問也沒什麼問題,畢竟通過webview的形式,理論上就相當於使用了android和ios提供的一個瀏覽器而已,只是它隱藏了url。
考慮到手機上的切換效果,app開發的同事會使用多個webview來切換渲染資料,像詳情之類的頁面,需要開啟新的webview,這樣問題就來了,體驗就非常的差了,非常的卡!非常的慢!為什麼?
我簡單的做了個比較.
Web端開啟新頁面過程
App端開啟新頁面過程
過程比較:
過程 | web | app |
---|---|---|
第一次載入 | 慢 | 慢 |
切換頁面 | 快 | 慢 |
由此可見,使用android或ios巢狀webview來達到一般原生的切換效果和訪問伺服器的速度,是很難的!
此時我們想到三個方案:
app端只開啟一個webview。
- 優點:可以減少切換頁面時重新開啟(特定的導航頁面在切換時沒有被關閉掉,但不重新整理)webview的時間。
- 缺點:b)頁面效果可能較差,並且ios打包後有可能影響appstore稽核。
伺服器端後臺檔案和前端檔案分開部署。
- 優點:稍微加快靜態頁面訪問速度。
- 缺點:提升速度不明顯。
html檔案本地化,打包進app檔案中。
- 優點:不需要從伺服器端請求html再返回到webview中載入,節省了載入html的時間,速度將有較大提升。
- 缺點:需要對當前專案做很多修改(檔案訪問路徑方面,後續webview對接也給我們帶來了許多困難)
雖然是想到了三個可行方案,但第一種方案直接被pass,第二種方案速度提升非常不明顯,那隻能考慮採用第三種了,將原先html檔案與java檔案共同打包成一個war包,然後現在需要將html檔案直接打包進app檔案,必然就需要js進行跨域請求了。
JS跨域請求
s跨域請求需要在js和伺服器端都有申明跨域,具體如下:
- 原生ajax 請求可以這樣寫:
$.ajax({
type: 'POST',
url: "/user",
data: {
'phone': 'xxxxxxxx',
'password':'xxxxxxx'
},
withCredentials: true, // 跨域
dataType: 'json'
}).success(function(){
});
- angularjs
$http.get(url, {
withCredentials : true //跨域
});
- java過濾器程式碼
@Component
public class CorsFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
response.setHeader("Access-Control-Allow-Origin", request.getHeader("origin")); //允許跨域
response.setHeader("Access-Control-Allow-Credentials", "true");
if ("OPTIONS".equals(request.getMethod())) {
response.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT,OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization");
}
filterChain.doFilter(request, response);
}
}
response.setHeader("Access-Control-Allow-Origin", request.getHeader("origin"));
這行程式碼是有點意思的,我這裡沒有將值直接設為“*”號,將值設為星號無法跨域傳送cookies,需要傳送cookies的可以設定成
request.getHeader("origin");
這樣跨域傳送請求就沒有問題了。
使用localStorage替代本地Cookies
跨域後有個問題真的讓我頭疼了很久,雖然成功跨域了,資料也都已經成功返回並渲染頁面了。但依然有幾個問題。
- 登入問題
比如,登入成功後session在後臺儲存了使用者會話資訊,再次傳送請求獲取資料時,再次讀取session驗證使用者身份時,始終無法取到登入時儲存的會話資訊,因為沒有獲取到cookies,而伺服器與瀏覽器之間的會話sessionID是儲存在cookies中的,為什麼沒有獲取到cookies,是因為伺服器端允許跨域是這樣設定的:
response.setHeader("Access-Control-Allow-Origin", "*"); //允許跨域
origin設定成“*”,是不支援檔案形式的訪問所傳送cookies的,需要怎麼改呢?改成與本地檔案一樣的路徑形式就成,比如,在我的機器上如果html路徑是 file:///D:test.html,我在java端允許跨域就必須設定成
response.setHeader("Access-Control-Allow-Origin", "file://"); //允許跨域
才能獲取到cookies,考慮到html打包進ios或android app中的路徑可能不同,則將路徑設為:
response.setHeader("Access-Control-Allow-Origin", request.getHeader("origin"));
這點可讓我找了很久,“*”居然不行,我到現在也沒想通。
- 本地cookies儲存
登入的問題解決了,傳送cookies也沒有問題了,但是本地檔案的cookies儲存又出了問題,無法儲存!也許有辦法只是我沒找到。
考慮到不同瀏覽器之間的差別和ios對cookies的可能支援不太好的問題,決定打算使用用localStorage在本地檔案儲存一些資訊,因為localStorage是html5自帶的,不存在不同瀏覽器之間的差別。
localStorage的使用也比較簡單,我在這個專案中也只使用了幾個方法。
首先申明一個全域性的storage,因為本身用他的目的就是替代cookies儲存使用者狀態,當然是全域性的啦
var storage = window.localStorage;
然後儲存值
storage.setItem('userid','深藍淺藍的天')
取值
storage.getItem('userid');
退出清空storage
storage.clear();
so easy,我很快就搜尋全部程式碼將cookies的使用全部替換掉了(注意搜尋路徑哦),但是問題到了這裡還是沒有結束,在電腦上測試完了本地檔案的訪問都沒有了問題,但是用android或ios打包html檔案又出現問題了。
post方法可正常跨域,因為它的headers中的origin是有指的,而get方法origin卻是是null,而伺服器端對於null是不接收跨域傳送cookies,所以cookies還是要忍痛替換掉。++
使用token替代跨域傳送Cookies
cookies既然有那麼多的問題,而且可能在某些地方支援不太好,之後若是需要對app進行功能擴充套件,第三方授權之類的,也還是要走token,所以索性直接換掉cookies,當然,我在js中是有區分web/ios/android的,所以手機瀏覽器端仍然延續使用cookies,ios和android打包檔案形式則用token。
雖然在伺服器程式碼中session幾乎無處不在,但要替換成token也不會特別麻煩,將原先儲存在session中的資料,使用token作為一個鍵儲存在redis中,然後在使用者請求伺服器時,攔截請求並取出token,再從redis中取出資料手動賦值到session中就可以了,只是我把原先伺服器接收請求並取出cookies中sessionID,從而取出對應session的動作替換了下而已。
簡單圖示就是:
- 伺服器處理cookies
第一次請求伺服器
第二次請求伺服器
大致過程如上圖,用markdown不會畫圖,下次畫個好點的~~
- token替換cookie
第一次請求伺服器
第二次請求伺服器
過程幾乎是一樣的,只是我在攔截中做了一步處理,以便我不需要大幅度改變原有程式碼,如下:
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HttpSession session = request.getSession();
if (session.getAttribute("userId") == null) {
String token = request.getHeader("Authorization");
if (token != null && !token.equals("null")) {
String jsonToken = (String) redisTemplate.opsForValue().get(token);
UserToken userToken = JSON.parseObject(jsonToken, UserToken.class);
if (userToken != null) {
session.setAttribute("userId", userToken.getUserId());
}
}
}
return true;
}
過程大致如此。
第一次寫這麼長的博文,歡迎指正。
歡迎關注我的個人公眾號:逍遙的心。主推程式設計師寫的生活類文章,有興趣的朋友可以共同探討下: