MEAN Stack:创建RESTful web service
本文在个人博客上的地址为URL,欢迎品尝。
前段时间做了DTREE项目中的前后端数据存储功能,在原有的ngController上进行HTTP请求,后端接受到请求后再存储到mongoDB上。现将学习所得记录成这篇文章。大致内容为REST的相关概念的介绍,以及结合项目实践的一些实战经验,最后一个RESTful的Web Service就成功开发出来了(大雾)。
REST
REST(Representational State Transfer)是一种软件设计架构风格,它定义了一堆概念,这些抽象的概念和相关规则能有效地降低业务复杂度。而日常上网使用HTTP就是REST风格最好的践行,RESTful Web服务就是将HTTP当做应用层的协议来遵守使用,而不像其他(比如SOA)将HTTP当做传输层的工具,然后在HTTP之上建立自己的一套应用层协议。当我们在浏览器地址栏键入的URL/URI就是REST提供的资源(resource)定义,而资源在REST中是一个的概念(concepts),当浏览器发出请求时,它想要得到的是一个概念的特定表示(representation),我们常见的网页就是资源的具体表示。REST这一些定义的概念、原则为了就是最小化服务与使用服务的应用程序之间的耦合。我们设计RESTful Web服务的原则也只是遵守了REST的部分原则。图片来自"浅谈REST",我们可以在REST Triangle图中看出,URL就是REST所谓的名词(资源的位置),一些HTTP方法就是动词,JSON、XML数据格式充当资源的具体表示。
下面简述4个RESTful Web Service基本原则,摘自IBM的"基于REST的 Web 服务基础"
显式地使用 HTTP 方法
设计RESTful时,使用4种比较多态的方法(POST、GET、PUT、DELETE)实现数据存储的CRUD操作。HTTP协议已经对这些动词(POST、GET、PUT 和 DELETE)进行了定义。
- HTTP GET方法用于获取资源,无论调用的次数都不会改变资源的状态,可能会得到不同的结果,但它本身不产生副作用(即对存储在source服务器的数据不产生的修改)。
- HTTP DELETE方法用于删除资源虽然有副作用,但多次调用时产生的副作用应该是相同的,即URI所对应资源的删除。
- HTTP POST方法用于接受创建的资源,如果资源已经在服务器上创建,服务器响应应该是表示Created的201状态以及资源的URI,避免POST请求会在服务器创建多份相同的资源。
- HTTP PUT方法用于创建或更新资源,多次操作的副作用与一个PUT操作是相同的。即若服务器没有PUT的资源则创建一个,否则更新已有资源即可。
这些动作的结果是必须有预期,不应该出现语义问题,很典型的例子就是在应用中只使用GET充当一切前后端交互的方法,导致的结果就是一些Web爬虫的行为无意间导致服务器端的资源更改。
无状态
网络传输的无状态使得每个请求是独立完整的,这样能够将请求从一个服务器路由到另一个服务器而无状态上的协调。当然请求是有状态的,将大部分状态维护职责转移给客户端应用程序,能够节省带宽和最小化服务器端应用程序状态改进了性能。
公开目录结构式的URI
URI是具有在节点上连接在一起的下级和上级分支的树,这种树形结构能够直观地表达交互类型和资源名称,也可将URI看做文档说明的接口。
传输格式使用XML、JavaScript Object Notation(JSON)等数据格式
这些数据格式能够使得服务可以运行在不同平台和设备上,并采用不同的语言编写。
ngResource
一直以来在前端进行向后端数据交互都是使用$http服务,这次师哥教育到要学习使用ngResource,提倡"keeping $http out of the controllers and leave that job to services"。使用ngResource就不用去关心更底层的$http服务,它帮我们封装好用一种更简单的方式来发送XHR请求。ngResource提供$resource服务(service)来与RESTful后端交互。首先我们需要将angular-resource.js引入,然后建立工厂方法,在里面返回$resource('URI',,params, methods)获得的值,这样一个自定义的服务就产生了。使得用短短的几行代码就可以创建一个RESTful客户端,简化controllers。
Code
首先我们设计Node接受前端发来的请求时的路由分配,暂时只提供了下列针对单个dtree对象的CRUD操作。
///store API
app.post('/dtree', dtreeCtrl.createDTree);//create
app.get('/dtree/:dtree_id', dtreeCtrl.readDTree);//read
app.put('/dtree/:dtree_id', dtreeCtrl.updateDTree);//update
app.delete('/dtree/:dtree_id', dtreeCtrl.deleteDTree); // delete
我们在dtreeCtrl暴露出处理请求API,对于POST操作处理如下。
对于GET操作,我们使用dtree_id当做_id进行搜索,调用mongoose API .lean() 将结果转换为plain javascript objects。
//execute route GET /dtree/:dtree_id
exports.readDTree = function (req, res) {
var checkId = new ObjectId(req.params.dtree_id);
//存储到mongoDB前的预处理
//..
Dtree.findById(checkId).lean().exec(function (err, dtree) {
if(err){
console.log('Error: readDTree: DB failed to findById due to ', err);
res.send({'success':false, 'err': err});
}else{
console.log('Info: readDTree: DB findById successfully dtree = ', dtree);
//发送到客户端的预处理
//...
res.send({'success':true, dtree: dtree});
}
});
};
对于PUT操作,以前我写过v为versionKey,可以充当数据库文档的版本控制flag,但mongoose的.update()方法有些许bug,执行操作后并没有修改v的值,暂时的解决方法是通过$inc: {key: value}来手动设置。
//execute route PUT /dtree/:dtree_id
exports.updateDTree = function (req, res) {
//存储到mongoDB前的预处理
//..
Dtree.update({"_id": id}, {
modifiedKey: modifiedData,
$inc: {__v: 1}
}, function(err){
if(err){
console.log('Error: updateDTree: DB failed to update due to ', err);
res.send({'success':false, 'err':err});
}else{
console.log('Info: updateDTree: DB updated successully dtree');
res.send({'success':true});
}
});
};
DELETE操作相对就比较简单了。
//execute route DELETE /dtree/:dtree_id
exports.deleteDTree = function(req, res){
Dtree.findByIdAndRemove(req.params.dtree_id, function(err, dropDtree){
if(err){
console.log('Error: deleteDTree: DB failed to delete due to ', err);
res.send({'success': false, 'err': err});
}else{
console.log('Info: deleteDTree: DB deleted successfully dtree = ', dropDtree);
res.send({'success': true});
}
});
}
至此,后端已经建立以一套RESTful的API提供给客户端HTTP请求调用。我们首先需要使用前面提到的$resource服务定义决策树CRUD操作的服务dtreeCrudService,这里URI使用的是cors的写法,其实没有必要,单独使用/dtree/:dtree_id也可,这就是访问本机的资源。CORS(Cross Origin Resource Sharing)是与SOP(Same Origin Policy,指的是在客户端上只能访问服务器自身域的文档或脚本,不能获取或修改另一个域的文档的属性)相对,它支持跨域请求。NodeJS通过cors依赖包的调用开启CORS。如果浏览器端检测到相应的设置,就可以允许XHR进行跨域的访问。
$resource('http://localhost\\:4000/dtree/:dtree_id', {},{
'get': {
method:'GET'
},
update: {
method: 'PUT' //a PUT request
},
'delete': {
method:'DELETE'
}
});
这里定义了HTTP操作的方法,在controller中正确"注入"服务即可调用服务的方法。
app.controller('createDTreeCtrl', [
'$scope',
'dtreeCrudService'
function (
$scope,
dtreeCrudService
) {
//...具体实现
]);
这里通过定义CRUD的方法来实现业务逻辑的划分。
//将决策树数据传到后端存入数据库
$scope.createDtreeData = function(){
//前端处理后数据
var data = someOperate(data);
console.log('Info: createDtreeData: data = ', data);
dtreeCrudService.save(data, function(res){
if(res.success){
console.log('Info: createDtreeData: Back-end successfully saved dtree = ', data);
}else{
console.log('Error: createDtreeData: Back-end failed to save dtree due to ', res.err);
}
}, function(error){
console.log("Error: createDtreeData: Fail to create due to ", error);
});
}
//读取数据
$scope.readDtreeData = function (){
dtreeCrudService.get({dtree_id: '54a4c0947752adcc1764f0d4'}, function(res) {
if(res.success){
console.log("Info: readDtreeData: Back-end successfully read dtree = ", res.dtree);
}else{
console.log('Error: readDtreeData: Back-end failed to read dtree due to ', res.err);
}
}, function(error){
console.log("Error: readDtreeData: Fail to read due to ", error);
});
}
//更新数据
$scope.updateDtreeData = function () {
//数据规整成json对象。
var data = someChange(data);
console.log('Info: updateDtreeData: data = ', data);
dtreeCrudService.update({dtree_id: data._id},data, function(res){
if(res.success){
console.log("Info: updateDtreeData: Back-end successfully update dtree = ", data);
}else{
console.log('Error: updateDtreeData: Back-end failed to read dtree due to ', res.err);
}
}, function(error){
console.log("Error: updateDtreeData: Fail to update due to ", error);
});
}
//删除某个ID的决策树
$scope.deleteDtreeData = function () {
dtreeCrudService.delete({dtree_id: $scope.operate_dtreeId}, function(res){
if(res.success){
console.log("Info: deleteDtreeData: Back-end successfully delete dtree");
}else{
console.log('Error: updateDtreeData: Back-end failed to read dtree due to ', res.err);
}
}, function(error){
console.log("Error: deleteDtreeData: Fail to delete due to ", error);
});
}
这4个方法对应的请求如图,使用ng-click指令即可调用这些方法。
ngResource的CRUD方法的使用就是这么的方便,以后修改也特别方便。
$resource(url, [paramDefaults], [actions], options);
再提及一下这个$resource方法中的pramas参数的作用,当这个参数不为空时就会在运行HTTP请求方法被覆盖掉。举个例子对于 $resource("/dtree/:id", {id: @dtreeid}, methods) 这一段资源服务,我们POST的数据为{"dtreeid": 2333, "dtreeData": YoYo }时,这个URL就会成为 /dtree/2333。而当没有这个@符号时,URL就是直接为的 /dtree/dtree_id。
还有一点就是我们前后端传输的数据采用的是JSON格式,而JSON是不支持循环结构,即 a.b = c; c.d = b; 这种数据结构,但在前端使用D3画图过程中,有所需要保持这种结构才能实时更新界面上的图片。若强行使用$resource传递,Javascript会先将数据转换为JSON对象,然后就报TypeError的错误。
这时我们不用慌张,直接遍历决策树数据将循环结构干掉就可以了。
总结
这次功能模块的实现分为前后端,后端的Node提供RESTful的资源获取API,AngularJS在前端使用$resource进行HTT请求的封装来获取资源。这一套HTTP + CRUD Method + URL只是REST部分概念的实现,REST的真正价值在于低耦合的设计理念。
References:
REST相关
MEAN
MEAN Stack:创建RESTful web service的更多相关文章
- 使用Java创建RESTful Web Service
REST是REpresentational State Transfer的缩写(一般中文翻译为表述性状态转移).2000年Roy Fielding博士在他的博士论文“Architectural Sty ...
- 使用Java创建RESTful Web Service(转)
REST是REpresentational State Transfer的缩写(一般中文翻译为表述性状态转移).2000年Roy Fielding博士在他的博士论文“Architectural Sty ...
- spring3创建RESTFul Web Service
spring 3支持创建RESTFul Web Service,使用起来非常简单.不外乎一个@ResponseBody的问题. 例如:后台controller: 做一个JSP页面,使用ajax获取数据 ...
- 使用JAX-RS创建RESTful Web Service
guice resteasy http://www.cnblogs.com/ydxblog/p/7891224.html http://blog.csdn.net/withiter/article/d ...
- 使用 Spring 3 来创建 RESTful Web Services
来源于:https://www.ibm.com/developerworks/cn/web/wa-spring3webserv/ 在 Java™ 中,您可以使用以下几种方法来创建 RESTful We ...
- 使用 Spring 3 来创建 RESTful Web Services(转)
使用 Spring 3 来创建 RESTful Web Services 在 Java™ 中,您可以使用以下几种方法来创建 RESTful Web Service:使用 JSR 311(311)及其参 ...
- Spring 3 来创建 RESTful Web Services
Spring 3 创建 RESTful Web Services 在 Java™ 中,您可以使用以下几种方法来创建 RESTful Web Service:使用 JSR 311(311)及其参考实现 ...
- 怎样封装RESTful Web Service
所谓Web Service是一个平台独立的,低耦合的.自包括的.可编程的Web应用程序.有了Web Service异构系统之间就能够通过XML或JSON来交换数据,这样就能够用于开发分布式的互操作的应 ...
- 如何封装RESTful Web Service
所谓Web Service是一个平台独立的,低耦合的,自包含的.可编程的Web应用程序,有了Web Service异构系统之间就可以通过XML或JSON来交换数据,这样就可以用于开发分布式的互操作的应 ...
随机推荐
- 确认某端口占用情况并结束相应进程(Windows)
(1)确认某端口是否被占用 (2)通过查找对应的PID号,定位是哪一个进程在使用该端口 (3)通过PID号结束该进程 # 查找端口2000是否被占用C:\Users\tdcqma>netstat ...
- SqlServer几个注意点
1.修改系统参数时,必须是单用户情况下才能更改成功!在Properties->Options中修改. 2.数据库字段值默认是不区分大小写的,修改方法如下: 2.1.右键数据库,选择Propert ...
- How to learn linux device driver
To learn device driver development, like any other new knowledge, the bestapproach for me is to lear ...
- oracle lsnrctl status|start|stop
[oracle@redhat4 ~]$ lsnrctl status LSNRCTL for Linux: Version 11.2.0.1.0 - Production on 06-OCT-2015 ...
- 深入理解Java虚拟机
1. Java内存模型,Java内存管理,Java堆和栈,垃圾回收 http://nibnait.com/6f8dd084-about-Java-Virtual-Machine/ 2. JVM性能调优 ...
- tornado中使用torndb,连接数过高的问题
问题背景 最近新的产品开发中,使用了到了Tornado和mysql数据库.但在基本框架完成之后,我在开发时候发现了一个很奇怪的现象,我在测试时,发现数据库返回不了结果,于是我在mysql中输入show ...
- freemarker中判断对象是否为空
<#if xxx?exists> 或则 <#if xxx??>两个问号??最简单方便
- Codeforces 383A - Milking cows
原题地址:http://codeforces.com/problemset/problem/383/A 题目大意:有 n 头奶牛,全部看着左边或者右边,现在开始给奶牛挤奶,给一头奶牛挤奶时,所有能看到 ...
- Web网站的性能测试工具
随着Web 2.0技术的迅速发展,许多公司都开发了一些基于Web的网站服务,通常在设计开发Web应用系统的时候很难模拟出大量用户同时访问系统的实际情况,因此,当Web网站遇到访问高峰时,容易发生服务器 ...
- 转:MVC3系列:~Html.BeginForm与Ajax.BeginForm
Html.BeginForm与Ajax.BeginForm都是MVC架构中的表单元素,它们从字面上可以看到区别,即Html.BeginForm是普通的表单提交,而Ajax.BeginForm是支持异步 ...