AngualrJS之服务器端通信
译自《AngularJS》
与服务器通信
目前,我们已经接触过下面要谈的主题的主要内容,这些内容包括你的Angular应用如何规划设计、不同的angularjs部件如何装配在一起并正常工作以及AngularJS中的模板代码运行机制的一小部分内容。把它们结合在一起,你就可以搭建一些简洁优雅的Web应用,但他们的运作主要还是限制在客户端.在前面第二章,我们接触了一点用$http
服务做与服务器端通信的内容,但是在这一章,我们将会深入探讨如何在现实世界的真实应用中使用它($http
).
在这一章,我们将讨论一下AngularJS如何帮你与服务器端通信,这其中包括在最低抽象等级的层面或者用它提供的优雅的封装器。而且我们将会深入探讨AngularJS如何用内建缓存机制来帮你加速你的应用.如果你想用SocketIO
开发一个实时的Angular应用,那么第八章有一个例子,演示了如何把·SocketIO·封装成一个指令然后如何使用这个指令,在这一章,我们就不涉及这方面内容了.
目录
- 通过$http进行通行
- 进一步配置你的请求
- 设定HTTP头信息(Headers)
- 缓存响应数据
- 对请求(Request)和响应(Response)的数据所做的转换
- 单元测试
- 使用RESTful资源
- resource资源的声明
- 定制方法
- 不要使用回调函数机制!(除非你真的需要它们)
- 简化的服务器端操作
- 对ngResource做单元测试
- $q和预期值(Promise)
- 响应拦截处理
- 安全方面的考虑
- JSON的安全脆弱性
- 跨站请求伪造(XSRF)
通过$http进行通行
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();
对于这样一个简单、常用且经常重复的任务,上面这个代码量比较大.如果你想重复性地做这件事,你最终可能会做一个封装或者使用现成的库.
假设我们想从我们的服务器取回用户的信息.如果接口在/api/user地址可用,并且接受id作为url参数,那么我们的XHR请求就可以像下面这样使用Angular的核心$http服务:
$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处理异步需求的方式很相似.
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为大多数常用请求类型都提供了类似的简便方法,他们包括:
进一步配置你的请求
有时,工具箱提供的标准请求配置还不够,它可能是因为你想做下面这些事情:
$http(config)
$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
});
你也可以通过传入含有下面这些键的属性集config对象来改变已有的request对象
- method : 一个表示http请求类型的字符串,比如GET,或者POST
- url : 一个URL字符串代表要请求资源的绝对或相对URL
- params : 一个对象(准确的说是键值映射)包含字符串到字符串内容,它代表了将会转换为URL参数的键值对,比如下面这样: [{key1: 'value1', key2: 'value2'}] 它将会被转换为: ?key1=value&key2=value2 这串字符将会加在URL后面,如果在value的位置你用一个对象取代字符串或数字,那这个对象将会转换为JSON字符串.
- data :一个字符串或一个对象,它将会被作为请求消息数据被发送.
- timeout : 这是请求被认定为过期之前所要等待的毫秒数.
还有部分另外的选项可以被配置,在下面的章节中,我们将会深度探索这些选项.
设定HTTP头信息(Headers)
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';
});
$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 });
如何在应用中处理权限验证头信息的成熟示例将会在第八章的Cheetsheets示例部分给出.
缓存响应数据
AngularJS为HTTP GET请求提供了一个开箱即用的简单缓存系统.缺省情况下,它对所有的请求都是禁用的,但是如果你想对你的请求启用缓存系统,你可以使用以下代码:
$http.get('http://server/myapi', {cache: true
}).success(function() { // Handle success });
然而这种做法从可用性的角度看可能是有所冲突的,当一个用户首先看到旧的结果,然后新的结果突然冒出来,比如一个用户可能即将单击一个数据项,而实际上这个数据项后台已经发生了变化.
注意所有响应(即使是从缓存里取出的)本质上仍旧是异步响应.换句话说,期望你的利用缓存响应时的异步代码运行仍旧和他向后台服务器发出请求时的代码运行机制是一样的.
对请求(Request)和响应(Response)的数据所做的转换
AngularJS对所有$http
服务发起的请求和响应做一些基本的转换,它们包括:
- 请求(Request)转换: 如果请求的Cofig配置对象的data属性包含一个对象,将会把这个对象序列化为JSON格式.
- 响应(Response)转换: 如果探测到一个XSRF头,把它剥离掉.如果响应数据被探测为JSON格式,用JSON解析器把它反序列化为JSON对象.
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);};
});
单元测试
目前为止,我们已经了解如何使用$http
服务以及如何以可能的方式做你需要的配置.但是如何写一些单元测试来保证这些够真实有效的运行哪?
让我们探索一下下面这样的单元测试场景:一个控制向服务器发起请求,从服务器得到数据,把它赋给作用域内的模型,然后以具体的模板格式显示出来.
我们的NameListCtrl
控制器是一个非常简单的控制器.它的存在只有一个目的:访问names
API接口,然后把得到数据存储在作用域scope模型内.
function NamesListCtrl($scope, $http) {$http.get('http://server/names', {params: {filter: ‘none’}}).success(function(data) {$scope.names = data;});
}
怎样对这个控制器做单元测试?在我们的单元测试中,我们必须保证下面这些条件:
NamesListCtrl
能够找到所有的依赖项(并且正确的得到注入的这些依赖)》- 当控制器加载时尽可能快地立刻发情请求从服务器得到names数据.
- 控制器能够正确地把响应数据存储到作用域scope的
names
变量属性中.
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资源
在上面这样的情况下,如果我们自己构建一个js对象来表示这种较复杂对象模型,那做法就有点不够nice.如果我们仅仅想编辑某个对象的属性、保存或者更新一个对象,那我们如何让这些状态在服务器端持久化.
$resource
正好给你提供这种能力.AngularJS resources可以帮助我们以描述的方式来定义对象模型,可以定义一下这些特征:
- resource的服务器端URL
- 这种请求常用参数的类型
- (你可以免费自动得到get、save、query、remove和delete方法),除了那些方法,你可以定义其它的方法,这些方法封装了对象模型的特定功能和业务逻辑(比如信用卡模型的charge()付费方法)
- 响应的期望类型(数组或者一个独立对象)
- 头信息
只有你的服务器端设施是以RESTful方式提供服务的时候,你才应该用Angular resources组件.比如信用卡那个案例,我们将用它作为本章这一部分的例子,他将包括以下内容:
- 给地址
/user/123/card
发送一个GET请求,将会得到用户123的信用卡列表. - 给地址
/user/123/card/15
发送一个GET请求,将会得到用户123本人的ID为15的信用卡信息 - 给地址
/user/123/card
发送一个在POST请求数据部分包含信用卡信息的POST请求,将会为用户123新创建一个信用卡 - 给地址
/user/123/card/15
发送一个在POST请求数据部分包含信用卡信息的POST请求,将会更新用户123的ID为5的信用卡的信息. - 给地址
/user/123/card/15
一个方法为DELETE类型的请求,将会删除掉用户123的ID为5的信用卡的数据.
除了按照你的要求给你提供一个查询服务器端信息的对象,$resource
还可以让你使用返回的数据对象就像他们是持久化数据模型一样,可以修改他们,还可以把你的修改持久化存储下来.
ngResource
是一个单独的、可选的模块.要想使用它,你看需要做以下事情:
- 在你的HTML文件里面引用angular-resource.js的实际地址
- 在你的模块依赖里面声明对它的依赖(例如,angular.module('myModule',['ngResource'])).
- 在需要的地方,注入$resource这个依赖项.
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}});}};
}]);
取代以上方式,你也可以轻松创建一个在你的应用中始终如一的Angular资源服务,就像下面代码这样:
myAppModule.factory('CreditCard', ['$resource', function($resource) {return $resource('/user/:userId/card/:cardId',{userId: 123, cardId: '@id'},{charge: {method:'POST', params:{charge:true}, isArray:false});
}]);
让我们看一个信用卡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'}
});
前面这个样例代码里面发生了很多事情,所以我们将会一个一个地认真讲解其中的重要部分:
resource资源的声明
声明你自己的$resource
非常简单,只要调用注入的$resource函数,并给他传入正确的参数即可。(你现在应该已经知道如何注入依赖,对吧?)
$resource函数只有一个必须参数,就是提供后台资源数据的URL地址,另外还有两个可选参数:默认request参数信息和其它的想在资源上要配置的动作.
函数的第三个参数是一些我们想要暴露的其它方法,这些方法是对定制资源做操作的方法.在下面的章节,我们将会深度讨论这个话题
定制方法
$resource函数的第三个参数是可选的,主要用来传递要在resource资源上暴露的其它自定义方法。
不要使用回调函数机制!(除非你真的需要它们)
如果你想对返回值做一些业务逻辑处理,拿着汇总方法就不能奏效了.在这种情况下,你就得依赖回调函数机制了,比如在Credit.get()调用中使用的那种机制.
简化的服务器端操作
无论你使用资源简化函数机制还是回调函数,关于返回对象都有几点问题需要我们注意.
对ngResource做单元测试
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'}]);});
});
$q
和预期值(Promise)
目前为止,我们已经看到了AngulrJS是如何实现它的异步延迟API机制.
预期值建议(Promise proposal)是AngularJS构建异步延迟API的底层基础.作为底层机制,预期值建议(Promise proposal)为异步请求做了下面这些事:
- 异步请求返回的是一个预期(promise)而不是一个具体数据值.
- 预期值有一个
then
函数,这个函数有两个参数,一个参数函数响应"resolved“或者"sucess"事件,另外一个参数函数响应"rejected”或者"failure"事件.这些函数以一个结果参数调用,或者以一个拒绝原因参数调用. - 确保当结果返回的时候,两个参数函数中有一个将会被调用
大多数的延迟机制和Q(详见$q API文档)是以上面这种方法实现的,AngularJS为什么这样实现具体是因为以下原因:
- $q对于整个AngularJS是可见的,因此它被集成到作用域数据模型里面。这样返回数据就能快速传递,UI上的闪烁更新也就更少.
- AngularJS模板也能识别$q预期值,因为预期值可以被当作结果值一样对待,而不是把它仅仅当作结果的预期.这种预期值会在响应返回时被通知提醒.
- 更小的覆盖范围:AngularJS仅仅实现那些基本的、对于公共异步任务的需求来说最重要的延迟函数机制.
你也许会问这样的问题:为什么我们会做如此疯狂激进的实现机制?让我们先看一个在在异步函数使用方面的标准问题:
fetchUser(function(user) {fetchUserPermissions(user, function(permissions) {fetchUserListData(user, permissions, function(list) {// Do something with the list of data that you want to display});});
});
另外,这种情况对错误处理也有很大影响.错误处理的最好方法是什么?在每次都做错误处理?那代码结构就会非常乱.
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
});
响应拦截处理
让我们看一个代码例子,这个例子中的代码拦截响应,并对响应数据做了轻微的数据转换.
// 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');
安全方面的考虑
JSON的安全脆弱性
当我们对服务器发送一个请求JSON数组数据的GET请求时(特别是当这些数据是敏感数据且需要登录验证或读取授权时),就会有一个不易察觉的JSON安全漏洞被暴露出来.
当我们使用一个恶意站点时,站点可能会用<script>标签发起同样的请求而得到相同的信息.因为你仍旧是登录状态,恶意站点利用了你的验证信息而请求了JSON数据,并且得到了它.
你或许惊奇是如何做到的,因为信息仍旧在你客户端,服务器也得不到这个数组信息的引用.并且通常作为请求脚本返回响应JSO对象会导致一个执行错误,虽然数组是个列外.
有两种方法可以防止这个漏洞:一是通常要确保敏感数据信息只作为POST请求的响应被返回,二是返回一个不合法的JSON表达式,然后客户端用约定好的逻辑把不合法数据转换为可用的真实数据.
AngulaJS中你可以两种方法都用来阻止这个漏洞.在你的应用中,你可以而且应该选择敏感JSON信息只通过POST请求来获取.
进一步,你可以在服务器端给JSON响应数据配置一个前缀字符串:
")]}`,\n"
['one','two']
")]}'",
['one', 'two']
AngularJS将会自动的把前缀字符串过滤掉,然后仅仅处理真实JSON数据.
跨站请求伪造(XSRF)
- 用户A登录进他的银行帐号(http://www.examplebank.com/)
- 用户B意识到这点,然后诱导用户A访问用户B的个人主页
- 主页上有一个特殊手工生成的图片连接地址,这个图片的的指向地址将会导致一次跨站请求伪造攻击,比如如下代码:
<img src="http://www.examplebank.com/xfer?from=UserA&amount=10000&to=UserB" />
如果用户A的银行站点把授权信息保存在cookie里,且Cookie还没过期.当用户A打开用户B的站点时,就会导致非授权的用户A给用户B转账行为.
AngualrJS之服务器端通信相关推荐
- 北风网ajax,[T8:JavaScript中利用Ajax实现客户端与服务器端通信北风网收费视频讲座.ppt...
[T8:JavaScript中利用Ajax实现客户端与服务器端通信北风网收费视频讲座 Ajax简介 XMLHttpRequest对象 综合案例 1.HTTP请求 现在,很多浏览器都可以直接从JavaS ...
- java socket通信 客户端_JavaのSocket编程之简单客户端与服务器端通信
Socket编程之简单客户端与服务器端通信 socket 通常用来实现客户端和服务端的连接,socket 是Tcp/Ip协议的一个十分流行的编程界面,一个socket 由一个Ip地址和一个端口号唯一确 ...
- 手机客户端和服务器端通信
2019独角兽企业重金招聘Python工程师标准>>> 手机客户端与服务器端的通信,不同于浏览器与服务器端的通信.浏览器和服务器端的通信依靠session去维持一个会话, 当这一切搬 ...
- python客户端与服务器端通信_python客户端与服务器端的通信
服务器端: import java.io.*;import java.net.*;public class xin {ServerSocket mSS;Socket mSocket;static in ...
- AJAX-客户端服务器端通信
AJAX 客户端和服务器端的交互,是如何进行通信的.所有发请求建立连接这些事情,都跟代码没干系,都是浏览器帮我们发请求的:遇到link img script也是浏览器主动发请求做这件事情. 还有一部分 ...
- Java实现简易TCP客户端、服务器端通信程序
本学期计算机网络课程要求完成一个TCP和一个UDP的通信程序,我完成了功能的简单实现,下面讲讲我的TCP程序的实现.(UDP的见另一篇博客) 目录 效果展示 一.项目结构 二.完整代码 1.TCPCl ...
- Java实现简易UDP客户端、服务器端通信程序
本学期计算机网络课程要求完成一个TCP和一个UDP的通信程序,我完成了功能的简单实现.我在上一篇博客已经讲了TCP的具体实现,接下来讲一讲UDP程序的实现. 目录 效果展示 一.项目结构 二.完整代码 ...
- Ajax学习笔记--- 【xmind 详细展示 浏览器与 服务器端通信,请求与响应报文】
php和express用来做后端,还是express强大和方便!!! 翻看笔记才知道自己当时下载Fiddler,Wampserver,phpstudy_pro是为了什么,实在不敢说自己学过php 文章 ...
- 服务器客户端通信原理,客户端到服务器端通信原理
我们首先要了解一个概念性的词汇:Socket socket的英文原义是"孔"或"插座".作为进程通信机制,取后一种意思.通常也称作"套接字" ...
最新文章
- Request对象的主要方法有哪些?
- Spread for Windows Forms快速入门(2)---设置Spread表单
- Reachable Numbers
- python 布尔值 bool( ) 与逻辑运算符
- java的乐趣_分享java带来的快乐
- 常用excel函数 vlookup,concatenate, 的使用
- TIOBE 2 月编程语言排行榜:Python 逼近 C,Groovy 重回 TOP20
- mysql导入超大sql文件时mysql服务重启
- [视频]MAC中如何单独放大文本字体
- 数据结构上机实践第八周项目7—对称矩阵的压缩存储及基本运算
- python 取模_中年大叔学编程-Python的基础语法和运算符
- 微信团购小程序怎么做?一般要多少钱?
- IE 11 无法安全地连接到此页面
- 《你可能不知道免像控的两个细节问题》
- 论文笔记27 -- (视频压缩)Learned Video Codec with Enriched Reconstruction for CLIC P-frame Coding
- SmartGit功能介绍
- servlet3.1规范翻译:第13章 安全
- Linux磁盘挂载和共享
- PHP实现 网页图片上传
- 数据挖掘BUC算法实现
热门文章
- Oracle中通过Job实现定时同步两个数据表之间的数据
- JAVA中对象的序列化的作用?
- mysql alter table if_MySQL中的alter table命令的基本使用方法及提速优化
- opencv计算图像亮度调节_OpenCV教程创建Trackbar图像对比度、亮度值调整
- Android入门(15)| 网络
- C++(STL):26 ---关联式容器set用法
- 剑指offer(刷题31-40)--c++,Python版本
- c++面向对象高级编程 学习九 pointer-like classes
- ArrayList和HashMap遍历比较
- 2013科目三道路驾驶技能通用评判标准