日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

AngualrJS之服务器端通信

發(fā)布時間:2023/12/19 javascript 40 豆豆
生活随笔 收集整理的這篇文章主要介紹了 AngualrJS之服务器端通信 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

譯自《AngularJS》

?

與服務(wù)器通信

?

目前,我們已經(jīng)接觸過下面要談的主題的主要內(nèi)容,這些內(nèi)容包括你的Angular應(yīng)用如何規(guī)劃設(shè)計、不同的angularjs部件如何裝配在一起并正常工作以及AngularJS中的模板代碼運行機制的一小部分內(nèi)容。把它們結(jié)合在一起,你就可以搭建一些簡潔優(yōu)雅的Web應(yīng)用,但他們的運作主要還是限制在客戶端.在前面第二章,我們接觸了一點用$http服務(wù)做與服務(wù)器端通信的內(nèi)容,但是在這一章,我們將會深入探討如何在現(xiàn)實世界的真實應(yīng)用中使用它($http).

在這一章,我們將討論一下AngularJS如何幫你與服務(wù)器端通信,這其中包括在最低抽象等級的層面或者用它提供的優(yōu)雅的封裝器。而且我們將會深入探討AngularJS如何用內(nèi)建緩存機制來幫你加速你的應(yīng)用.如果你想用SocketIO開發(fā)一個實時的Angular應(yīng)用,那么第八章有一個例子,演示了如何把·SocketIO·封裝成一個指令然后如何使用這個指令,在這一章,我們就不涉及這方面內(nèi)容了.

目錄

  • 通過$http進行通行
    • 進一步配置你的請求
    • 設(shè)定HTTP頭信息(Headers)
    • 緩存響應(yīng)數(shù)據(jù)
    • 對請求(Request)和響應(yīng)(Response)的數(shù)據(jù)所做的轉(zhuǎn)換
  • 單元測試
  • 使用RESTful資源
    • resource資源的聲明
    • 定制方法
    • 不要使用回調(diào)函數(shù)機制!(除非你真的需要它們)
    • 簡化的服務(wù)器端操作
    • 對ngResource做單元測試
  • $q和預(yù)期值(Promise)
  • 響應(yīng)攔截處理
  • 安全方面的考慮
    • JSON的安全脆弱性
    • 跨站請求偽造(XSRF)

通過$http進行通行

從Ajax應(yīng)用(使用XMLHttpRequests)發(fā)動一個請求到服務(wù)器的傳統(tǒng)方式包括:得到一個XMLHttpRequest對象的引用、發(fā)起請求、讀取響應(yīng)、檢驗錯誤代碼然后最后處理服務(wù)器響應(yīng)。它就是下面這樣:

var xmlhttp = new XMLHttpRequest(); xmlhttp.onreadystatechange = function() {if (xmlhttp.readystate == 4 && xmlhttp.status == 200) {var response = xmlhttp.responseText;} else if (xmlhttp.status == 400) { // or really anything in the 4 series// Handle error gracefully} }; // Setup connection xmlhttp.open(“GET”, “http://myserver/api”, true); // Make the request xmlhttp.send();

對于這樣一個簡單、常用且經(jīng)常重復(fù)的任務(wù),上面這個代碼量比較大.如果你想重復(fù)性地做這件事,你最終可能會做一個封裝或者使用現(xiàn)成的庫.

AngularJS XHR(XMLHttpRequest) API遵循Promise接口.因為XHRs是異步方法調(diào)用,服務(wù)器響應(yīng)將會在未來一個不定的時間返回(當然希望是越快越好).Promise接口保證了這樣的響應(yīng)將會如何處理,它允許Promise接口的消費者以一種可預(yù)計的方式使用這些響應(yīng).

假設(shè)我們想從我們的服務(wù)器取回用戶的信息.如果接口在/api/user地址可用,并且接受id作為url參數(shù),那么我們的XHR請求就可以像下面這樣使用Angular的核心$http服務(wù):

$http.get('api/user', {params: {id: '5'} }).success(function(data, status, headers, config) { // Do something successful. }).error(function(data, status, headers, config) { // Handle the error });

如果你來自jQuery世界,你可能會注意到:AngularJS和jquery處理異步需求的方式很相似.

我們上面例子中使用的$htttp.get方法僅僅是AngularJS核心服務(wù)$http提供的眾多簡便方法之一.類似的,如果你想使用AngularJS向相同URL帶一些POST請求數(shù)據(jù)發(fā)起一個POST請求,你可以像下面這樣做:

var postData = {text: 'long blob of text'}; // The next line gets appended to the URL as params // so it would become a post request to /api/user?id=5 var config = {params: {id: '5'}}; $http.post('api/user', postData, config ).success(function(data, status, headers, config) { // Do something successful }).error(function(data, status, headers, config) { // Handle the error });

AngularJS為大多數(shù)常用請求類型都提供了類似的簡便方法,他們包括:

  • GET
  • HEAD
  • POST
  • DELETE
  • PUT
  • JSONP

進一步配置你的請求

有時,工具箱提供的標準請求配置還不夠,它可能是因為你想做下面這些事情:

  • 你可能想為請求添加權(quán)限驗證的頭信息
  • 改變請求數(shù)據(jù)的緩存方式
  • 在請求被發(fā)送或者響應(yīng)返回時,對數(shù)據(jù)以一些方式做一定的轉(zhuǎn)換處理

在上面這樣的情況之下,你可以進一步配置請求,通過可選的傳遞進請求的配置對象.在之前的例子中,我們使用配置對象來標明可選的URL參數(shù),即便我們哪兒演示的GET和POST方法是簡便方法。內(nèi)部的原生方法可能看上面像相面這樣:

$http(config)

下面演示的是一個調(diào)用這個方法的偽代碼模板:

$http({method: string,url: string,params: object,data: string or object,headers: object,transformRequest: function transform(data, headersGetter) or an array of functions,transformResponse: function transform(data, headersGetter) or an array of functions,cache: boolean or Cache object,timeout: number,withCredentials: boolean });

GET、POST和其它的簡便方法已經(jīng)設(shè)置了請求的method類型,所以不需要再設(shè)置這個,config配置對象是傳給·$http.get·、·$http.post·方法的最后一個參數(shù),所以當你使用任何簡便方法的時候,你任何能用這個config配置對象.

你也可以通過傳入含有下面這些鍵的屬性集config對象來改變已有的request對象

  • method : 一個表示http請求類型的字符串,比如GET,或者POST
  • url : 一個URL字符串代表要請求資源的絕對或相對URL
  • params : 一個對象(準確的說是鍵值映射)包含字符串到字符串內(nèi)容,它代表了將會轉(zhuǎn)換為URL參數(shù)的鍵值對,比如下面這樣: [{key1: 'value1', key2: 'value2'}] 它將會被轉(zhuǎn)換為: ?key1=value&key2=value2 這串字符將會加在URL后面,如果在value的位置你用一個對象取代字符串或數(shù)字,那這個對象將會轉(zhuǎn)換為JSON字符串.
  • data :一個字符串或一個對象,它將會被作為請求消息數(shù)據(jù)被發(fā)送.
  • timeout : 這是請求被認定為過期之前所要等待的毫秒數(shù).

還有部分另外的選項可以被配置,在下面的章節(jié)中,我們將會深度探索這些選項.

設(shè)定HTTP頭信息(Headers)

AngularJS有一個默認的頭信息,這個頭信息將會對所有的發(fā)送請求使用,它包含以下信息: 1.Accept: application/json, text/plain, / 2.X-Requested-With:XMLHttpRequest

如果你想設(shè)置任何特定的頭信息,這兒有兩種方法來做這件事:

第一種方法,如果你相對所有的發(fā)送請求都使用這些特定頭信息,那你需要把特定有信息設(shè)置為Angular默認頭信息的一部分.可以在$httpProvider.defaults.headers配置對象里面設(shè)置這個,這個步驟通常會在你的app設(shè)置config部分來做.所以如果你想移除"Requested-With"頭信息且對所有的GET請求啟用"DO NOT TRACK"設(shè)置,你可以簡單地通過以下代碼來做:

angular.module('MyApp',[]).config(function($httpProvider) {// Remove the default AngularJS X-Request-With headerdelete $httpProvider.default.headers.common['X-Requested-With'];// Set DO NOT TRACK for all Get requests$httpProvider.default.headers.get['DNT'] = '1'; });

如果你只想對某個特定的請求設(shè)置頭信息,而不是設(shè)置默認頭信息.那么你可以通過給$http服務(wù)傳遞包含指定頭信息的config對象來做.相同的定制頭信息可以作為第二個參數(shù)傳遞給GET請求,第一個參數(shù)是URL字符串:

$http.get('api/user', { // Set the Authorization header. In an actual app, you would get the auth // token from a service headers: {'Authorization': 'Basic Qzsda231231'}, params: {id: 5} }).success(function() { // Handle success });

如何在應(yīng)用中處理權(quán)限驗證頭信息的成熟示例將會在第八章的Cheetsheets示例部分給出.

緩存響應(yīng)數(shù)據(jù)

AngularJS為HTTP GET請求提供了一個開箱即用的簡單緩存系統(tǒng).缺省情況下,它對所有的請求都是禁用的,但是如果你想對你的請求啟用緩存系統(tǒng),你可以使用以下代碼:

$http.get('http://server/myapi', {cache: true }).success(function() { // Handle success });

這段代碼啟用了緩存系統(tǒng),然后AngularJS將會緩存來自Server的響應(yīng)數(shù)據(jù).但對相同的URL的請求第二次發(fā)出時,AngularJS將會從緩存里面取出前一次的響應(yīng)數(shù)據(jù)作為響應(yīng)返回.這個緩存系統(tǒng)也很智能,即使你同時對相同URL發(fā)出多個請求,只有一個請求會發(fā)向Server,這個請求的響應(yīng)數(shù)據(jù)將會反饋給所有(同時發(fā)起的)請求。

然而這種做法從可用性的角度看可能是有所沖突的,當一個用戶首先看到舊的結(jié)果,然后新的結(jié)果突然冒出來,比如一個用戶可能即將單擊一個數(shù)據(jù)項,而實際上這個數(shù)據(jù)項后臺已經(jīng)發(fā)生了變化.

注意所有響應(yīng)(即使是從緩存里取出的)本質(zhì)上仍舊是異步響應(yīng).換句話說,期望你的利用緩存響應(yīng)時的異步代碼運行仍舊和他向后臺服務(wù)器發(fā)出請求時的代碼運行機制是一樣的.

對請求(Request)和響應(yīng)(Response)的數(shù)據(jù)所做的轉(zhuǎn)換

AngularJS對所有$http服務(wù)發(fā)起的請求和響應(yīng)做一些基本的轉(zhuǎn)換,它們包括:

  • 請求(Request)轉(zhuǎn)換: 如果請求的Cofig配置對象的data屬性包含一個對象,將會把這個對象序列化為JSON格式.
  • 響應(yīng)(Response)轉(zhuǎn)換: 如果探測到一個XSRF頭,把它剝離掉.如果響應(yīng)數(shù)據(jù)被探測為JSON格式,用JSON解析器把它反序列化為JSON對象.

如果你需要部分系統(tǒng)默認提供的轉(zhuǎn)換,或者想使用你自己的轉(zhuǎn)換,你可以把你的轉(zhuǎn)換函數(shù)作為Config配置對象的一部分傳遞進去(后面有細述).這些轉(zhuǎn)換函數(shù)得到HTTP請求和HTTP響應(yīng)的數(shù)據(jù)主體以及它們的頭信息.然后把序列化的修改后版本返回出來.在Config對象里面配置這些函數(shù)需要使用·transformRequest·鍵和·transformResponse·鍵,這些都可以通過使用`$httpProvider·服務(wù)在模塊的config函數(shù)里面配置它.

我們什么時候使用這些哪?讓我假設(shè)我們有一個服務(wù)器,它更習(xí)慣于jQuery運行的方式.它可能希望我們的POST數(shù)據(jù)以key1=val1&key2=val2字符串的形式傳遞,而不是以{key1:val1,key2:val2}這樣的JSON格式.這個時候,我們可能相對每個請求做這樣的轉(zhuǎn)換,或者單個地增加transformRequest轉(zhuǎn)換函數(shù),為了達成這個示例這樣的目標,我們將要設(shè)置一個通用transformRequet轉(zhuǎn)換函數(shù),以便對所有的發(fā)出請求,這個函數(shù)都可以把JSON格式轉(zhuǎn)換為鍵值對字符串,下面代碼演示了如何做這個工作:

var module = angular.module('myApp'); module.config(function ($httpProvider) {$httpProvider.defaults.transformRequest = function(data) {// We are using jQuery’s param method to convert our// JSON data into the string formreturn $.param(data);}; });

單元測試

目前為止,我們已經(jīng)了解如何使用$http服務(wù)以及如何以可能的方式做你需要的配置.但是如何寫一些單元測試來保證這些夠真實有效的運行哪?

正如我們曾經(jīng)三番五次的提到的那樣,AngularJS一直以測試為先的原則而設(shè)計.所以Angualr有一個模擬服務(wù)器后端,在單元測試中,它可以幫你就可以測試你發(fā)出的請求是否正確,甚至可以精確控制模擬響應(yīng)如何得到處理,什么時候得到處理.

讓我們探索一下下面這樣的單元測試場景:一個控制向服務(wù)器發(fā)起請求,從服務(wù)器得到數(shù)據(jù),把它賦給作用域內(nèi)的模型,然后以具體的模板格式顯示出來.

我們的NameListCtrl控制器是一個非常簡單的控制器.它的存在只有一個目的:訪問namesAPI接口,然后把得到數(shù)據(jù)存儲在作用域scope模型內(nèi).

function NamesListCtrl($scope, $http) {$http.get('http://server/names', {params: {filter: ‘none’}}).success(function(data) {$scope.names = data;}); }

怎樣對這個控制器做單元測試?在我們的單元測試中,我們必須保證下面這些條件:

  • NamesListCtrl能夠找到所有的依賴項(并且正確的得到注入的這些依賴)》
  • 當控制器加載時盡可能快地立刻發(fā)情請求從服務(wù)器得到names數(shù)據(jù).
  • 控制器能夠正確地把響應(yīng)數(shù)據(jù)存儲到作用域scope的names變量屬性中.

在我們的單元測試中構(gòu)造一個控制器時,我們給它注入一個scope作用域和一個偽造的HTTP服務(wù),在構(gòu)建測試控制器的方式和生產(chǎn)中構(gòu)建控制器的方式其實是一樣的.這是推薦方法,盡管它看上去上有點復(fù)雜。讓我看一下具體代碼:

describe('NamesListCtrl', function(){var scope, ctrl, mockBackend;// AngularJS is responsible for injecting these in testsbeforeEach(inject(function(_$httpBackend_, $rootScope, $controller) {// This is a fake backend, so that you can control the requests// and responses from the servermockBackend = _$httpBackend_;// We set an expectation before creating our controller,// because this call will get triggered when the controller is createdmockBackend.expectGET('http://server/names?filter=none').respond(['Brad', 'Shyam']);scope = $rootScope.$new();// Create a controller the same way AngularJS would in productionctrl = $controller(PhoneListCtrl, {$scope: scope});}));it('should fetch names from server on load', function() {// Initially, the request has not returned a responseexpect(scope.names).toBeUndefined();// Tell the fake backend to return responses to all current requests// that are in flight.mockBackend.flush();// Now names should be set on the scopeexpect(scope.names).toEqual(['Brad', 'Shyam’]);}); });

使用RESTful資源

·$http·服務(wù)提供一個比較底層的實現(xiàn)來幫你發(fā)起XHR請求,但是同時也給提供了很強的可控性和彈性.在大多數(shù)情況下,我們處理的是對象集或者是封裝有一定屬性和方法的對象模型,比如帶有個人資料的自然人對象或者信用卡對象.

在上面這樣的情況下,如果我們自己構(gòu)建一個js對象來表示這種較復(fù)雜對象模型,那做法就有點不夠nice.如果我們僅僅想編輯某個對象的屬性、保存或者更新一個對象,那我們?nèi)绾巫屵@些狀態(tài)在服務(wù)器端持久化.

$resource正好給你提供這種能力.AngularJS resources可以幫助我們以描述的方式來定義對象模型,可以定義一下這些特征:

  • resource的服務(wù)器端URL
  • 這種請求常用參數(shù)的類型
  • (你可以免費自動得到get、save、query、remove和delete方法),除了那些方法,你可以定義其它的方法,這些方法封裝了對象模型的特定功能和業(yè)務(wù)邏輯(比如信用卡模型的charge()付費方法)
  • 響應(yīng)的期望類型(數(shù)組或者一個獨立對象)
  • 頭信息

什么時候你可以用Angular Resources組件?

只有你的服務(wù)器端設(shè)施是以RESTful方式提供服務(wù)的時候,你才應(yīng)該用Angular resources組件.比如信用卡那個案例,我們將用它作為本章這一部分的例子,他將包括以下內(nèi)容:

  • 給地址/user/123/card發(fā)送一個GET請求,將會得到用戶123的信用卡列表.
  • 給地址/user/123/card/15發(fā)送一個GET請求,將會得到用戶123本人的ID為15的信用卡信息
  • 給地址/user/123/card發(fā)送一個在POST請求數(shù)據(jù)部分包含信用卡信息的POST請求,將會為用戶123新創(chuàng)建一個信用卡
  • 給地址/user/123/card/15發(fā)送一個在POST請求數(shù)據(jù)部分包含信用卡信息的POST請求,將會更新用戶123的ID為5的信用卡的信息.
  • 給地址/user/123/card/15一個方法為DELETE類型的請求,將會刪除掉用戶123的ID為5的信用卡的數(shù)據(jù).

  • 除了按照你的要求給你提供一個查詢服務(wù)器端信息的對象,$resource還可以讓你使用返回的數(shù)據(jù)對象就像他們是持久化數(shù)據(jù)模型一樣,可以修改他們,還可以把你的修改持久化存儲下來.

    ngResource是一個單獨的、可選的模塊.要想使用它,你看需要做以下事情:

    • 在你的HTML文件里面引用angular-resource.js的實際地址
    • 在你的模塊依賴里面聲明對它的依賴(例如,angular.module('myModule',['ngResource'])).
    • 在需要的地方,注入$resource這個依賴項.

    在我們看怎樣用ngResource方法創(chuàng)建一個resource資源之前,我們先看一下怎樣用基本的$http服務(wù)做類似的事情.比如我們的信用卡resource,我們想能夠讀取、查詢、保存信用卡信息,另外還要能為信用卡還款.

    這兒是上述需求一個可能的實現(xiàn):

    myAppModule.factory('CreditCard', ['$http', function($http) {var baseUrl = '/user/123/card';return {get: function(cardId) {return $http.get(baseUrl + '/' + cardId);},save: function(card) {var url = card.id ? baseUrl + '/' + card.id : baseUrl;return $http.post(url, card);},query: function() {return $http.get(baseUrl);},charge: function(card) {return $http.post(baseUrl + '/' + card.id, card, {params: {charge: true}});}}; }]);

    取代以上方式,你也可以輕松創(chuàng)建一個在你的應(yīng)用中始終如一的Angular資源服務(wù),就像下面代碼這樣:

    myAppModule.factory('CreditCard', ['$resource', function($resource) {return $resource('/user/:userId/card/:cardId',{userId: 123, cardId: '@id'},{charge: {method:'POST', params:{charge:true}, isArray:false}); }]);

    做到現(xiàn)在,你就可以任何時候從Angular注入器里面請求一個CreditCard依賴,你就會得到一個Angular資源,默認情況下,這個資源會提供一些初始的可用方法.表格5-1列出了這些初始方法以及他們的運行行為,這樣你就可以知道在服務(wù)器怎樣配置來配合這些方法了.

    表格5-1 一個信用卡reource Function Method URL Expected Return CreditCard.get({id: 11}) GET /user/123/card/11 Single JSON CreditCard.save({}, ccard) POST /user/123/card with post data “ccard” Single JSON CreditCard.save({id: 11}, ccard) POST /user/123/card/11 with post data “ccard” Single JSON CreditCard.query() GET /user/123/card JSON Array CreditCard.remove({id: 11}) DELETE /user/123/card/11 Single JSON CreditCard.delete({id: 11}) DELETE /user/123/card/11 Single JSON

    讓我們看一個信用卡resource使用的代碼樣例,這樣可以讓你理解起來覺得更淺顯易懂.

    // Let us assume that the CreditCard service is injected here // We can retrieve a collection from the server which makes the request // GET: /user/123/card var cards = CreditCard.query(); // We can get a single card, and work with it from the callback as well CreditCard.get({cardId: 456}, function(card) {// each item is an instance of CreditCardexpect(card instanceof CreditCard).toEqual(true);card.name = "J. Smith";// non-GET methods are mapped onto the instancescard.$save();// our custom method is mapped as well.card.$charge({amount:9.99});// Makes a POST: /user/123/card/456?amount=9.99&charge=true// with data {id:456, number:'1234', name:'J. Smith'} });

    前面這個樣例代碼里面發(fā)生了很多事情,所以我們將會一個一個地認真講解其中的重要部分:

    resource資源的聲明

    聲明你自己的$resource非常簡單,只要調(diào)用注入的$resource函數(shù),并給他傳入正確的參數(shù)即可。(你現(xiàn)在應(yīng)該已經(jīng)知道如何注入依賴,對吧?)

    $resource函數(shù)只有一個必須參數(shù),就是提供后臺資源數(shù)據(jù)的URL地址,另外還有兩個可選參數(shù):默認request參數(shù)信息和其它的想在資源上要配置的動作.

    請注意:第一個URL地址參數(shù)中的的變量數(shù)據(jù)是參數(shù)化可配置的(:冒號是參數(shù)變量的語法符號,比如:userId以為這個參數(shù)將會被實際的userId參數(shù)變量取代(譯者注:寫過參數(shù)化SQL語句的人應(yīng)該很熟悉),而:cardId將會被cardId參數(shù)變量的值所取代),如果沒有給函數(shù)傳遞這些參數(shù)變量,那那個位置將會被空字符取代.

    函數(shù)的第二個參數(shù)則負責提供所有請求的默認參數(shù)變量信息.在這個案例中,我們給userId參數(shù)傳遞一個常量:123,cardId參數(shù)則更有意思,我們給cardId參數(shù)傳遞了一個"@id"字符串.這意味著如果我們使用一個從服務(wù)器返回的對象而且我們可以調(diào)用這個對象的任何方法(比如$save),那么這個對象的id屬性將會被取出來賦給cardId字段.

    函數(shù)的第三個參數(shù)是一些我們想要暴露的其它方法,這些方法是對定制資源做操作的方法.在下面的章節(jié),我們將會深度討論這個話題

    定制方法

    $resource函數(shù)的第三個參數(shù)是可選的,主要用來傳遞要在resource資源上暴露的其它自定義方法。

    在這個案例中,我們自定義了一個方法charge.這個自定義方法可以通過傳遞一個對象而被配置上.這個對象里有個鍵值,表明了此方法的暴露名稱.這個配置需要頂一個request請求的方法類型(GET,POST等等),以及該請求中需要的參數(shù)也要被傳遞(比如charge=true),并且聲明返回對象是數(shù)組還是單個普通對象。這一切到搞定之后,你就可以在有這個業(yè)務(wù)實際需要求的時候,自由地調(diào)用CreditCard.charge()方法.

    不要使用回調(diào)函數(shù)機制!(除非你真的需要它們)

    第三個需要注意的事情是資源調(diào)用的返回類型.讓我們再次關(guān)注一下CreditCard.query()這個函數(shù).你將會看到不是在回調(diào)函數(shù)中給cards賦值,而是直接把它賦給card變量.在異步服務(wù)器請求的情況下唉,這樣的代碼是如何運作的哪?

    你擔心代碼是否正常工作是對的,但是代碼實際上是可以正常工作的.這里實際發(fā)生的是AngularJS賦值了一個引用(是普通對象的還是數(shù)組的取決于你期望的返回類型),這個引用將會在未來服務(wù)器請求響應(yīng)返回時被填充.在這期間,這個引用是個空應(yīng)用.

    因為在AngularJS應(yīng)用中的大多數(shù)通用過程都是從服務(wù)器端取數(shù)據(jù),把它賦給一個變量,然后在模版上顯示它,而上面這樣的簡化機制非常優(yōu)雅.在你的控制器代碼中,你所有需要去做的就是發(fā)出服務(wù)器端請求,把返回值賦給正確的作用域(scope)變量.然后剩下的合適渲染這些數(shù)據(jù)就由模板系統(tǒng)去操心了.

    如果你想對返回值做一些業(yè)務(wù)邏輯處理,拿著匯總方法就不能奏效了.在這種情況下,你就得依賴回調(diào)函數(shù)機制了,比如在Credit.get()調(diào)用中使用的那種機制.

    簡化的服務(wù)器端操作

    無論你使用資源簡化函數(shù)機制還是回調(diào)函數(shù),關(guān)于返回對象都有幾點問題需要我們注意.

    返回的對象不是一個普通JS對象,實際上,他是“resource”類型的對象.這就意味著對象里除了包含服務(wù)器返回的數(shù)據(jù)以外,還有一些附加的行為函數(shù)(在這個案例中如$save()和$charge函數(shù)).這樣我們就可以很方便的執(zhí)行服務(wù)器端操作,比如取數(shù)據(jù)、修改數(shù)據(jù)并把修改在服務(wù)器端持久化保存下來(其實也就是一般CURD應(yīng)用里面的通用操作).

    對ngResource做單元測試

    ngResource依賴項是一個封裝,它以Angular核心服務(wù)$http為基礎(chǔ).因此,你可能已經(jīng)知道如何對它做單元測試了.它和我們看到的對$http做單元測試的樣例比起來基本沒什么真正的變化.你只需要知道最終的服務(wù)器端請求應(yīng)該由resource發(fā)起,告訴模擬$http服務(wù)關(guān)于請求的信息.其他的基本都一樣.下面我們來看看如何本節(jié)測試前面的代碼:

    describe('Credit Card Resource', function(){var scope, ctrl, mockBackend;beforeEach(inject(function(_$httpBackend_, $rootScope, $controller) {mockBackend = _$httpBackend_;scope = $rootScope.$new();// Assume that CreditCard resource is used by the controllerctrl = $controller(CreditCardCtrl, {$scope: scope});}));it('should fetched list of credit cards', function() {// Set expectation for CreditCard.query() callmockBackend.expectGET('/user/123/card').respond([{id: '234', number: '11112222'}]);ctrl.fetchAllCards();// Initially, the request has not returned a responseexpect(scope.cards).toBeUndefined();// Tell the fake backend to return responses to all current requests// that are in flight.mockBackend.flush();// Now cards should be set on the scopeexpect(scope.cards).toEqualData([{id: '234', number: '11112222'}]);}); });

    這個測試看上去和簡單的$http單元測試非常相似,除了一些細微區(qū)別.注意在我們的expect語句里面,取代了簡單的"equals"方法,哦我們用的是特殊的"toEqualData"方法.這種eapect語句會智能地省略ngResource添加到對象上的附加方法.

    $q和預(yù)期值(Promise)

    目前為止,我們已經(jīng)看到了AngulrJS是如何實現(xiàn)它的異步延遲API機制.

    預(yù)期值建議(Promise proposal)是AngularJS構(gòu)建異步延遲API的底層基礎(chǔ).作為底層機制,預(yù)期值建議(Promise proposal)為異步請求做了下面這些事:

    • 異步請求返回的是一個預(yù)期(promise)而不是一個具體數(shù)據(jù)值.
    • 預(yù)期值有一個then函數(shù),這個函數(shù)有兩個參數(shù),一個參數(shù)函數(shù)響應(yīng)"resolved“或者"sucess"事件,另外一個參數(shù)函數(shù)響應(yīng)"rejected”或者"failure"事件.這些函數(shù)以一個結(jié)果參數(shù)調(diào)用,或者以一個拒絕原因參數(shù)調(diào)用.
    • 確保當結(jié)果返回的時候,兩個參數(shù)函數(shù)中有一個將會被調(diào)用

    大多數(shù)的延遲機制和Q(詳見$q API文檔)是以上面這種方法實現(xiàn)的,AngularJS為什么這樣實現(xiàn)具體是因為以下原因:

    • $q對于整個AngularJS是可見的,因此它被集成到作用域數(shù)據(jù)模型里面。這樣返回數(shù)據(jù)就能快速傳遞,UI上的閃爍更新也就更少.
    • AngularJS模板也能識別$q預(yù)期值,因為預(yù)期值可以被當作結(jié)果值一樣對待,而不是把它僅僅當作結(jié)果的預(yù)期.這種預(yù)期值會在響應(yīng)返回時被通知提醒.
    • 更小的覆蓋范圍:AngularJS僅僅實現(xiàn)那些基本的、對于公共異步任務(wù)的需求來說最重要的延遲函數(shù)機制.

    你也許會問這樣的問題:為什么我們會做如此瘋狂激進的實現(xiàn)機制?讓我們先看一個在在異步函數(shù)使用方面的標準問題:

    fetchUser(function(user) {fetchUserPermissions(user, function(permissions) {fetchUserListData(user, permissions, function(list) {// Do something with the list of data that you want to display});}); });

    上面這個代碼就是人們使用JavaScirpt時經(jīng)常抱怨的令人恐懼的深層嵌套縮進椎體的噩夢.返回值異步本質(zhì)與實際問題的同步需求之間產(chǎn)生矛盾:導(dǎo)致多級函數(shù)包含關(guān)系,在這種情況下要想準確跟蹤里面某句代碼的執(zhí)行上下文環(huán)境就很難.

    另外,這種情況對錯誤處理也有很大影響.錯誤處理的最好方法是什么?在每次都做錯誤處理?那代碼結(jié)構(gòu)就會非常亂.

    為了解決上面這些問題,預(yù)期值建議(Promise proposal)機制提供了一個then函數(shù)的概念,這個函數(shù)會在響應(yīng)成功返回的時候調(diào)用相關(guān)的函數(shù)去執(zhí)行,另一方面,當產(chǎn)生錯誤的時候也會干相同的事,這樣整個代碼就有嵌套結(jié)構(gòu)變?yōu)殒準浇Y(jié)構(gòu).所以之前那個例子用預(yù)期值A(chǔ)PI機制(至少在AngularJS中已經(jīng)被實現(xiàn)的)改造一下,代碼結(jié)構(gòu)會平整許多:

    var deferred = $q.defer(); var fetchUser = function() {// After async calls, call deferred.resolve with the response valuedeferred.resolve(user);// In case of error, calldeferred.reject(‘Reason for failure’); } // Similarly, fetchUserPermissions and fetchUserListData are handleddeferred.promise.then(fetchUser).then(fetchUserPermissions).then(fetchUserListData).then(function(list) {// Do something with the list of data}, function(errorReason) {// Handle error in any of the steps here in a single stop });

    那個完全的橫椎體代碼一下子被優(yōu)雅地平整了,而且提供了鏈式的作用域,以及一個單點的錯誤處理.你在你自己的應(yīng)用中處理異步請求回調(diào)時也可以用相同的代碼,只要調(diào)用Angular的$q服務(wù).這種機制可以幫我做一些很酷的事情:比如響應(yīng)攔截.

    響應(yīng)攔截處理

    我們的講解已經(jīng)覆蓋了怎樣調(diào)用服務(wù)器端服務(wù)、怎樣處理響應(yīng)、怎樣把響應(yīng)優(yōu)雅地抽象化封裝、怎樣處理異步回調(diào).但是在真實世界的Web應(yīng)用中,你最終還不得不對每個服務(wù)器端請求調(diào)用做一些通用的處理操作,比如錯誤處理、權(quán)限認證、以及其它考慮到安全問題的處理操作,比如對響應(yīng)數(shù)據(jù)做裁剪處理(譯注:有的Ajax響應(yīng)為了安全需要,會添加一定約定好的噪聲數(shù)據(jù)).

    有著現(xiàn)在已經(jīng)對$q API的深入理解,我們目前就可以利用響應(yīng)攔截器機制來做上面所有提出過的功能.響應(yīng)攔截器(正如其名)可以在響應(yīng)數(shù)據(jù)被應(yīng)用使用之前攔截他它,并且對它做數(shù)據(jù)轉(zhuǎn)換處理,比如錯誤處理以及其它任何處理,包括廚房洗碗槽.(估計是指數(shù)據(jù)清洗)

    讓我們看一個代碼例子,這個例子中的代碼攔截響應(yīng),并對響應(yīng)數(shù)據(jù)做了輕微的數(shù)據(jù)轉(zhuǎn)換.

    // register the interceptor as a service myModule.factory('myInterceptor', function($q, notifyService, errorLog) {return function(promise) {return promise.then(function(response) {// Do nothingreturn response;}, function(response) {// My notify service updates the UI with the error messagenotifyService(response);// Also log it in the console for debug purposeserrorLog(response);return $q.reject(response);});} });// Ensure that the interceptor we created is part of the interceptor chain $httpProvider.responseInterceptors.push('myInterceptor');

    安全方面的考慮

    目前我們開發(fā)Web應(yīng)用的時候,安全是一個非常重要的關(guān)注點,在我們的考慮維度直中,它必須作為首位被考慮.AngularJS給我們提供了一些幫助,同時也帶來了兩個安全攻擊的角度,下面這一節(jié)我們將會講解這些內(nèi)容.

    JSON的安全脆弱性

    當我們對服務(wù)器發(fā)送一個請求JSON數(shù)組數(shù)據(jù)的GET請求時(特別是當這些數(shù)據(jù)是敏感數(shù)據(jù)且需要登錄驗證或讀取授權(quán)時),就會有一個不易察覺的JSON安全漏洞被暴露出來.

    當我們使用一個惡意站點時,站點可能會用<script>標簽發(fā)起同樣的請求而得到相同的信息.因為你仍舊是登錄狀態(tài),惡意站點利用了你的驗證信息而請求了JSON數(shù)據(jù),并且得到了它.

    你或許驚奇是如何做到的,因為信息仍舊在你客戶端,服務(wù)器也得不到這個數(shù)組信息的引用.并且通常作為請求腳本返回響應(yīng)JSO對象會導(dǎo)致一個執(zhí)行錯誤,雖然數(shù)組是個列外.

    但是漏洞真正的切入點是:在JavaScript里,你是可以對內(nèi)建對象做重定義的.在這個漏洞里面,數(shù)組的構(gòu)造函數(shù)可以被重定義,通過這種重定義,惡意站點腳本就可以得到對響應(yīng)數(shù)據(jù)的引用,然后就可以把響應(yīng)數(shù)據(jù)發(fā)回它自己的服務(wù)器嘍.

    有兩種方法可以防止這個漏洞:一是通常要確保敏感數(shù)據(jù)信息只作為POST請求的響應(yīng)被返回,二是返回一個不合法的JSON表達式,然后客戶端用約定好的邏輯把不合法數(shù)據(jù)轉(zhuǎn)換為可用的真實數(shù)據(jù).

    AngulaJS中你可以兩種方法都用來阻止這個漏洞.在你的應(yīng)用中,你可以而且應(yīng)該選擇敏感JSON信息只通過POST請求來獲取.

    進一步,你可以在服務(wù)器端給JSON響應(yīng)數(shù)據(jù)配置一個前綴字符串:

    ")]}`,\n"

    那么一個正常JSON響應(yīng)比如:

    ['one','two']

    通過前綴字符串設(shè)置,這個JSON響應(yīng)就會變?yōu)?/p> ")]}'", ['one', 'two']

    AngularJS將會自動的把前綴字符串過濾掉,然后僅僅處理真實JSON數(shù)據(jù).

    跨站請求偽造(XSRF)

    跨站請求偽造攻擊主要有以下特征:

    • 它們影響的站點通常依賴于授權(quán)或者用戶認證.
    • 它們往往利用漏洞站點保存登錄或者授權(quán)信息這個事實.
    • 它們發(fā)起以假亂真的HTTP或者XMLHTTPRequest請求來制造副作用,這種副作用通常是有害的.

    考慮依稀下面這個跨站請求偽造攻擊的案例:

    • 用戶A登錄進他的銀行帳號(http://www.examplebank.com/)
    • 用戶B意識到這點,然后誘導(dǎo)用戶A訪問用戶B的個人主頁
    • 主頁上有一個特殊手工生成的圖片連接地址,這個圖片的的指向地址將會導(dǎo)致一次跨站請求偽造攻擊,比如如下代碼:<img src="http://www.examplebank.com/xfer?from=UserA&amount=10000&to=UserB" />

    如果用戶A的銀行站點把授權(quán)信息保存在cookie里,且Cookie還沒過期.當用戶A打開用戶B的站點時,就會導(dǎo)致非授權(quán)的用戶A給用戶B轉(zhuǎn)賬行為.

    那么AngularJS是怎么幫助你防止這種事情發(fā)生?它提供一種雙步機制來防止跨站請求偽造攻擊.

    在客戶端,當發(fā)起XHR異步請求時,$http服務(wù)會從一個叫XSRF-TOKEN的cookie中讀取令牌值,然后把它設(shè)置成X-XSRF-TOKEN頭信息的值,因為只有你自己域的請求才能讀取和設(shè)置這個令牌,你可以保證XHR請求只來自你自己的域.

    同時,服務(wù)器端代碼也需要一點輕微的修改,以便于你收到你的第一個HTTP GET請求時就設(shè)置一個可讀取的對話Cookie,這個對話Cookie鍵叫XSRF-TOKEN。后續(xù)客戶端發(fā)往服務(wù)器的請求就可以通過對比請求頭信息的令牌值和之前第一個請求設(shè)置的Cookie令牌值來達到驗證的目的.當然,令牌必須是一個用戶一個唯一的令牌值.這個令牌值必須在服務(wù)器端驗證(以防止惡意腳本捏造假令牌).

    總結(jié)

    以上是生活随笔為你收集整理的AngualrJS之服务器端通信的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。