介紹:$http service
在Angular中用於簡化與后台的交互過程,其本質上使用XMLHttpRequest或JSONP進行與后台的數據交互。在與后台的交互過程中,可能會對每條請求發送到Server之前進行預處理(如加入token),或者是在Server返回數據到達客戶端還未被處理之前進行預處理(如將非JSON格式數據進行轉換);當然還有可能對在請求和響應過程過發生的問題進行捕獲處理。所有這些需求在開發中都非常常見,所以Angular為我們提供了$http攔截器,用來實現上述需求。
Angular的$http攔截器是通過$httpProvider.interceptors
數組定義的一組攔截器,每個攔截器都是實現了某些特定方法的Factory:
實現:
http攔截器一般通過定義factory的方式實現:
myApp.factory('MyInterceptor', function($q) {return { // 可選,攔截成功的請求 request: function(config) {// 進行預處理// ...return config || $q.when(config); }, // 可選,攔截失敗的請求 requestError: function(rejection) {// 對失敗的請求進行處理// ...if (canRecover(rejection)) { return responseOrNewPromise } return $q.reject(rejection); }, // 可選,攔截成功的響應 response: function(response) {// 進行預處理// ....return response || $q.when(reponse); }, // 可選,攔截失敗的響應 responseError: function(rejection) {// 對失敗的響應進行處理// ...if (canRecover(rejection)) { return responseOrNewPromise } return $q.reject(rejection); } }; });
隨后,我們需要將實現的攔截器加入到$httpProvider.interceptors
數組中,此操作一般在config方法中進行:
myApp.config(function($httpProvider) { $httpProvider.interceptors.push(MyInterceptor); });
當然,我們也可以通過匿名factroy的方式實現:
$httpProvider.interceptors.push(function($q) {return { request: function(config) {// bala }, response: function(response) {// bala }, // bala }; });
可以看到,每個攔截器都可以實現4個可選的處理函數,分別對應請求(成功/失敗)和響應(成功/失敗)的攔截:
request
:此函數在$http向Server發送請求之前被調用,在此函數中可以對成功的http請求進行處理,其包含一個http config對象作為參數,這里對config對象具有完全的處理權限,甚至可以重新構造,然后直接返回此對象或返回包含此對象的promise即可。如果返回有誤,會造成$http請求失敗。如開發中經常需要在請求頭中加入token以便驗證身份,我們可以作如下處理:
request: function(config) {
config.headers = config.headers || {};
if ($window.sessionStorage.token) {
config.headers['X-Access-Token'] = $window.sessionStorage.token;
}
return config || $q.when(config);
}
requestError
:此方法會在前一個攔截器拋出異常或進行了reject操作時被調用,在這里可以進行恢復請求的操作,或者進行一些對於請求時發起動作的處理(如取消loading等);response
:此函數在$http從Server接收到響應時被調用,在此函數中可以對成功的http響應進行處理,這里具有對響應的完全處理權限,甚至可以重新構造,然后直接返回響應或返回包含響應的promise即可。如果返回有誤,會造成$http接收響應失敗;responseError
:此方法會在前一個攔截器拋出異常或進行了reject操作時被調用,在這里可以進行恢復響應的操作,進行一些針對錯誤的處理
使用用例
利用request攔截器模擬實現Angular的XSRF(即CSRF)防御
CSRF,即“跨站請求偽造”,不過不知道為什么Angular將其稱為XSRF。當處理與后台交互時,Angular的$http會嘗試從客戶端cookie中讀取一個token,其默認的key為XSRF-TOKEN
,並構造一個名為X-XSRF-TOKEN
的http頭部,與http請求一起發送到后台。Server端就可以根據此token識別出請求來源於同域,當然跨域的請求$http不會加入X-XSRF-TOKEN
頭部。那我們可以利用request攔截器通過如下方式在同域請求頭部中加入此頭部以達到模擬Angular的XSRF(即CSRF)防御機制的實現效果:
/** * 正式開發中Angular會主動進行XSRF防御(只要cookie中存在key為`XSRF-TOKEN`的token), * 一般不需要手動進行,除非cookie中不存在key為`XSRF-TOKEN`的token,這里只是模擬實現 */
request: function(config) {
if(config.url.indexOf('SAME_DOMAIN_API_URL') > -1) {
config.headers['X-XSRF-TOKEN'] = $cookies.get('XSRF-TOKEN');
}
return config;
}
如果初始http請求頭部類似於:
"headers": {
"Accept": "application/json, text/plain, */*"
}
那么經過上述的攔截器后,其http請求頭部就變成了:
"headers": {
"Accept": "application/json, text/plain, */*",
"X-XSRF-TOKEN": X-XSRF-TOKEN-VALUE
}
利用response攔截器模擬實現Angular JSON易損性(JSON vulnerability)防御
Angular在$http請求安全性方面不僅為我們設計了XSRF(CSRF)防御,而且針對請求JSON數據的Url可能通過類似於<script>
標簽加載的方式被惡意網站獲取到我們的JSON數據的情況,設計了Angular JSON易損性(JSON vulnerability)防御,即Server端返回的JSON數據頭部可以添加")]}',\n"
字符串,得到包含此前綴的響應數據后,Angular會將此前綴刪去,將響應還原成正式的JSON數據。此時我們就可以通過response攔截器模擬此過程:
response: function(response) {
var data = examineJSONResponse(response); // 假設存在這樣一個方法
if(!data) {
response = validateJSONResponse(response); // 假設存在這樣一個方法
}
return response || $q.when(reponse);
}
利用request攔截器和response攔截器計算http請求耗時
這個需求可能在開發中並不常用,這里只是作為同時使用request攔截器和response攔截器的例子,我們可以在request攔截器和response攔截器中分別計時,然后求得其差值即可:
myApp.factory('timestampMarker', [function() {
return {
request: function(config) {
config.requestTimestamp = new Date().getTime();
return config;
},
response: function(response) {
response.config.responseTimestamp = new Date().getTime();
return response;
}
};
}]);
myApp.config(['$httpProvider', function($httpProvider) {
$httpProvider.interceptors.push('timestampMarker');
}]);
這樣我們在每次請求后台時,就能夠計算出相應請求的耗時了,如:
$http.get('https://api.github.com/users/liuwenzhuang/repos').then(function(response) {
var time = response.config.responseTimestamp - response.config.requestTimestamp;
console.log('The request took ' + (time / 1000) + ' seconds.');
});
http攔截器一般通過定義factory的方式實現:
myApp.factory('MyInterceptor', function($q) {
return {
// 可選,攔截成功的請求
request: function(config) {
// 進行預處理
// ...
return config || $q.when(config);
},
// 可選,攔截失敗的請求
requestError: function(rejection) {
// 對失敗的請求進行處理
// ...
if (canRecover(rejection)) {
return responseOrNewPromise
}
return $q.reject(rejection);
},
// 可選,攔截成功的響應
response: function(response) {
// 進行預處理
// ....
return response || $q.when(reponse);
},
// 可選,攔截失敗的響應
responseError: function(rejection) {
// 對失敗的響應進行處理
// ...
if (canRecover(rejection)) {
return responseOrNewPromise
}
return $q.reject(rejection);
}
};
});
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
隨后,我們需要將實現的攔截器加入到$httpProvider.interceptors
數組中,此操作一般在config方法中進行:
myApp.config(function($httpProvider) {
$httpProvider.interceptors.push(MyInterceptor);
});
- 1
- 2
- 3
- 1
- 2
- 3
當然,我們也可以通過匿名factroy的方式實現:
$httpProvider.interceptors.push(function($q) {
return {
request: function(config) {
// bala
},
response: function(response) {
// bala
},
// bala
};
});
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
可以看到,每個攔截器都可以實現4個可選的處理函數,分別對應請求(成功/失敗)和響應(成功/失敗)的攔截:
request
:此函數在$http向Server發送請求之前被調用,在此函數中可以對成功的http請求進行處理,其包含一個http config對象作為參數,這里對config對象具有完全的處理權限,甚至可以重新構造,然后直接返回此對象或返回包含此對象的promise即可。如果返回有誤,會造成$http請求失敗。如開發中經常需要在請求頭中加入token以便驗證身份,我們可以作如下處理:
request: function(config) {
config.headers = config.headers || {};
if ($window.sessionStorage.token) {
config.headers['X-Access-Token'] = $window.sessionStorage.token;
}
return config || $q.when(config);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 1
- 2
- 3
- 4
- 5
- 6
- 7
requestError
:此方法會在前一個攔截器拋出異常或進行了reject操作時被調用,在這里可以進行恢復請求的操作,或者進行一些對於請求時發起動作的處理(如取消loading等);response
:此函數在$http從Server接收到響應時被調用,在此函數中可以對成功的http響應進行處理,這里具有對響應的完全處理權限,甚至可以重新構造,然后直接返回響應或返回包含響應的promise即可。如果返回有誤,會造成$http接收響應失敗;responseError
:此方法會在前一個攔截器拋出異常或進行了reject操作時被調用,在這里可以進行恢復響應的操作,進行一些針對錯誤的處理。
使用用例
為演示Angular $http攔截器的使用方法,下面通過幾個常用的用例來說明:
利用request攔截器模擬實現Angular的XSRF(即CSRF)防御
CSRF,即“跨站請求偽造”,不過不知道為什么Angular將其稱為XSRF。當處理與后台交互時,Angular的$http會嘗試從客戶端cookie中讀取一個token,其默認的key為XSRF-TOKEN
,並構造一個名為X-XSRF-TOKEN
的http頭部,與http請求一起發送到后台。Server端就可以根據此token識別出請求來源於同域,當然跨域的請求$http不會加入X-XSRF-TOKEN
頭部。那我們可以利用request攔截器通過如下方式在同域請求頭部中加入此頭部以達到模擬Angular的XSRF(即CSRF)防御機制的實現效果:
/** * 正式開發中Angular會主動進行XSRF防御(只要cookie中存在key為`XSRF-TOKEN`的token), * 一般不需要手動進行,除非cookie中不存在key為`XSRF-TOKEN`的token,這里只是模擬實現 */
request: function(config) {
if(config.url.indexOf('SAME_DOMAIN_API_URL') > -1) {
config.headers['X-XSRF-TOKEN'] = $cookies.get('XSRF-TOKEN');
}
return config;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
如果初始http請求頭部類似於:
"headers": {
"Accept": "application/json, text/plain, */*"
}
- 1
- 2
- 3
- 1
- 2
- 3
那么經過上述的攔截器后,其http請求頭部就變成了:
"headers": {
"Accept": "application/json, text/plain, */*",
"X-XSRF-TOKEN": X-XSRF-TOKEN-VALUE
}
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
利用response攔截器模擬實現Angular JSON易損性(JSON vulnerability)防御
Angular在$http請求安全性方面不僅為我們設計了XSRF(CSRF)防御,而且針對請求JSON數據的Url可能通過類似於<script>
標簽加載的方式被惡意網站獲取到我們的JSON數據的情況,設計了Angular JSON易損性(JSON vulnerability)防御,即Server端返回的JSON數據頭部可以添加")]}',\n"
字符串,得到包含此前綴的響應數據后,Angular會將此前綴刪去,將響應還原成正式的JSON數據。此時我們就可以通過response攔截器模擬此過程:
response: function(response) {
var data = examineJSONResponse(response); // 假設存在這樣一個方法
if(!data) {
response = validateJSONResponse(response); // 假設存在這樣一個方法
}
return response || $q.when(reponse);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 1
- 2
- 3
- 4
- 5
- 6
- 7
利用request攔截器和response攔截器計算http請求耗時
這個需求可能在開發中並不常用,這里只是作為同時使用request攔截器和response攔截器的例子,我們可以在request攔截器和response攔截器中分別計時,然后求得其差值即可:
myApp.factory('timestampMarker', [function() {
return {
request: function(config) {
config.requestTimestamp = new Date().getTime();
return config;
},
response: function(response) {
response.config.responseTimestamp = new Date().getTime();
return response;
}
};
}]);
myApp.config(['$httpProvider', function($httpProvider) {
$httpProvider.interceptors.push('timestampMarker');
}]);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
這樣我們在每次請求后台時,就能夠計算出相應請求的耗時了,如:
$http.get('https://api.github.com/users/liuwenzhuang/repos').then(function(response) {
var time = response.config.responseTimestamp - response.config.requestTimestamp;
console.log('The request took ' + (time / 1000) + ' seconds.');
});