本文在个人博客上的地址为URL,欢迎品尝。

前段时间做了DTREE项目中的前后端数据存储功能,在原有的ngController上进行HTTP请求,后端接受到请求后再存储到mongoDB上。现将学习所得记录成这篇文章。大致内容为REST的相关概念的介绍,以及结合项目实践的一些实战经验,最后一个RESTful的Web Service就成功开发出来了(大雾)。


  1. 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)等数据格式

    这些数据格式能够使得服务可以运行在不同平台和设备上,并采用不同的语言编写。

  2. 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。

  3. 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相关

  1. 浅谈REST
  2. 基于REST的 Web 服务基础
  3. HTTP幂等性概念和应用

MEAN

  1. simple-device-management-app
  2. tv-traker
  3. update操作无法增加"__v"
  4. 貌似需翻墙的$resource文档

MEAN Stack:创建RESTful web service的更多相关文章

  1. 使用Java创建RESTful Web Service

    REST是REpresentational State Transfer的缩写(一般中文翻译为表述性状态转移).2000年Roy Fielding博士在他的博士论文“Architectural Sty ...

  2. 使用Java创建RESTful Web Service(转)

    REST是REpresentational State Transfer的缩写(一般中文翻译为表述性状态转移).2000年Roy Fielding博士在他的博士论文“Architectural Sty ...

  3. spring3创建RESTFul Web Service

    spring 3支持创建RESTFul Web Service,使用起来非常简单.不外乎一个@ResponseBody的问题. 例如:后台controller: 做一个JSP页面,使用ajax获取数据 ...

  4. 使用JAX-RS创建RESTful Web Service

    guice resteasy http://www.cnblogs.com/ydxblog/p/7891224.html http://blog.csdn.net/withiter/article/d ...

  5. 使用 Spring 3 来创建 RESTful Web Services

    来源于:https://www.ibm.com/developerworks/cn/web/wa-spring3webserv/ 在 Java™ 中,您可以使用以下几种方法来创建 RESTful We ...

  6. 使用 Spring 3 来创建 RESTful Web Services(转)

    使用 Spring 3 来创建 RESTful Web Services 在 Java™ 中,您可以使用以下几种方法来创建 RESTful Web Service:使用 JSR 311(311)及其参 ...

  7. Spring 3 来创建 RESTful Web Services

    Spring 3 创建 RESTful Web Services 在 Java™ 中,您可以使用以下几种方法来创建 RESTful Web Service:使用 JSR 311(311)及其参考实现 ...

  8. 怎样封装RESTful Web Service

    所谓Web Service是一个平台独立的,低耦合的.自包括的.可编程的Web应用程序.有了Web Service异构系统之间就能够通过XML或JSON来交换数据,这样就能够用于开发分布式的互操作的应 ...

  9. 如何封装RESTful Web Service

    所谓Web Service是一个平台独立的,低耦合的,自包含的.可编程的Web应用程序,有了Web Service异构系统之间就可以通过XML或JSON来交换数据,这样就可以用于开发分布式的互操作的应 ...

随机推荐

  1. android为应用程序添加退出动画

    原本想搞一个退出程序时,把前一个应用程序的VIEW或者截图抓过来为我用,以实现更复杂的动画效果,尝试了很多方法,但都有或多或少的缺陷,可惜最后失败了.不过也算有所得.写文以标记. 其实抓图在4.0以后 ...

  2. ubuntu下文件压缩/解压缩命令总结

    .gz 解压1:gunzip FileName.gz 解压2:gzip -d FileName.gz 压缩:gzip FileName .tar.gz 解压:tar zxvf FileName.tar ...

  3. Oracle 数据集成的实际解决方案

    就针对市场与企业的发展的需求,Oracle公司提供了一个相对统一的关于企业级的实时数据解决方案,即Oracle数据集成的解决方案.以下的文章主要是对其解决方案的具体描述,望你会有所收获. Oracle ...

  4. jquery ajax请求 清除缓存

    使用jquery里load方法或者ajax调用页面的时候会存在cache的问题,清除cache的方法: 调用jQuery.ajaxSetup ({cache:false}) 方法即可.

  5. 基于XMPP的即时通信系统的建立(一)— XMPP基础概念

    相关背景 IM(Instant Messaging)正在被广泛使用,特别是公司与它们的客户互动连接方案以及互联网与Web2.0相关的应用.为了解决即时通信的标准问题,IETF(互联网工程任务组 The ...

  6. Asp.Net操作FTP方法

    将用户上传的附件(文件.图片等)通过FTP方式传送到另外一台服务器上,从而缓解服务器压力 1.相关的文章如下: Discuz!NT中远程附件的功能实现[FTP协议] http://www.cnblog ...

  7. Asp.Net MVC Views页面不包含“GetEnumerator”的公共定义

    “/”应用程序中的服务器错误. 编译错误 说明: 在编译向该请求提供服务所需资源的过程中出现错误.请检查下列特定错误详细信息并适当地修改源代码. 编译器错误消息: CS1579: “Web.Model ...

  8. UVA 658 It's not a Bug, it's a Feature! (最短路,经典)

    题意:有n个bug,有m个补丁,每个补丁有一定的要求(比如某个bug必须存在,某个必须不存在,某些无所谓等等),打完出来后bug还可能变多了呢.但是打补丁是需要时间的,每个补丁耗时不同,那么问题来了: ...

  9. Java [Leetcode 136]Single Number

    题目描述: Given an array of integers, every element appears twice except for one. Find that single one. ...

  10. 【WEB小工具】BaseServlet—一个Servlet处理多个请求

    package cn.itcast.test.web.servlet; import java.io.IOException; import java.io.PrintWriter; import j ...