微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

使用Node.js实现RESTful API的示例

RESTful基础概念

REST(Representational State Transfer)描述了一个架构样式的网络系统,它首次出现在 2000 年 Roy Fielding 的博士论文中。在REST服务中,应用程序状态和功能可以分为各种资源。资源向客户端公开,客户端可以对资源进行增删改操作。资源的例子有:应用程序对象、数据库记录、算法等等。

REST通过抽象资源,提供了一个非常容易理解和使用的API,它使用 URI (Universal Resource Identifier) 唯一表示资源。REST接口使用标准的 HTTP 方法,比如 GET、PUT、POST 和 DELET在客户端和服务器之间传输状态。

狭义的RESTful关注点在于资源,使用URL表示的资源及对资源的操作。Leonard Richardson 和 Sam Ruby 在他们的著作 RESTful Web Services 中引入了术语 REST-RPC 混合架构。REST-RPC 混合 Web 服务不使用信封包装方法、参数和数据,而是直接通过 HTTP 传输数据,这与 REST 样式的 Web 服务是类似的。但是它不使用标准的 HTTP 方法操作资源。

和传统的RPC、SOA相比,RESTful的更为简单直接,且构建于标准的HTTP之上,使得它非常快速地流行起来。

Node.js可以用很少代码简单地实现一个Web服务,并且它有一个非常活跃的社区,通过Node出色的包管理机制(NPM)可以非常容易获得各种扩展支持

对简单的应用场景Node.js实现REST是一个非常合适的选择(有非常多的理由选择这个或者那个技术栈,本文不会介入各种语言、架构的争论,我们着眼点仅仅是简单)。

应用样例场景

下面,就用一个App游戏排行榜后台服务来说明一下如何用Node.js快速地开发一个的RESTful服务。

当App游戏玩家过关时,会提交游戏过关时间(秒)数值到REST服务器上,服务器记录并对过关记录进行排序,用户可以查看游戏TOP 10排行榜

游戏应用提交的数据格式使用JSON表示,如下:

rush:js;"> {

"id": "aaa","score": 9.8,"token": "aaa-6F9619FF-8B86-D011-B42D-00C04FC964FF"

};

Id为用户输入的用户名,token用于区别不同的用户,避免id重名,score为过关所耗费的时间(秒)。

可以使用curl作为客户端测试RESTful服务。

提交游戏记录的命令如下:

代码如下:
score\":29.8,\"token\":\"aaa\"}}" http://localhost:3000/leaderboards

这个命令的语义不仅仅是狭义的REST增删改,我们为它添加一个cmd命令,实际上通过POST一个JSON命令,把这个服务实现为REST-RPC。

删除游戏记录的命令格式如下:

rush:bash;"> curl -X DELETE http://localhost:3000/leaderboards/aaa

或(使用REST-RPC语义)

rush:bash;"> curl -d "{\"cmd\":2,\"record\":{\"id\":\"test11\"}}" http://localhost:3000/leaderboards

查看TOP 10命令如下:

rush:bash;"> curl http://localhost:3000/leaderboards

标准REST定义中,POST和PUT有不同含义,GET可以区分单个资源或者资源列表。对这个应用我们做了简化,ADD和UPDATE都统一使用POST,对单个资源和列表也不再区分,直接返回TOP 10数据。

一些准备工作

安装Node.js

本文使用的版本是v5.5.0。

寻找一款方便的IDE

本文作者使用Sublime敲打代码,eclipse+nodeclipse生成框架代码和调试。

Node.js中基础的HTTP服务器

在Node中,实现一个HTTP服务器是很简单的事情。在项目根目录下创建一个叫app.js的文件,并写入以下代码

rush:js;"> var http = require("http");

http.createServer(function(request,response) {

response.writeHead(200,{"Content-Type": "text/plain"});

response.write("Hello World");

response.end();

}).listen(3000);

用Node.js执行你的脚本:node server.js

打开浏览器访问http://localhost: 3000/,你就会看到一个写着“Hello World”的网页。

即使完全不懂Node,也可以非常直观的看到这里通过require引入了一个http模块,然后使用createServer创建HTTP服务对象,当收到客户端发出的HTTP请求后,将调用我们提供的函数,并在回调函数里写入返回的页面

接下来,我们将把这个简单的应用扩展为一个RESTful服务。

简单直观的RESTful服务

现在需要超越“hello world”,我们将修改之前的http回调函数,根据请求类型返回不同的内容

代码如下:

rush:js;"> var server = http.createServer(function(req,res) {

var result;

switch (req.method) {

case 'POST':

  break;

case 'GET':

  break;

case 'DELETE':

  break;

}

});

通过req.method,可以得到请求的类型。

1.增加修改

其中POST请求,是要求我们添加或更新记录,请求的数据可以通过回调得到。

代码如下:

rush:js;"> var item = '';
  req.setEncoding('utf8');

  req.on('data',function(chunk) {

   item += chunk;

  });

  req.on('end',function() {    

   try {

     var command = JSON.parse(item);

     console.log(commandNaNd+ ';'+ command.record.id+ ':'+ command.record.<a href="https://www.jb51.cc/tag/score/" target="_blank" class="keywords">score</a>+ '('+ command.record.token+ ')');

     if (commandNaNd === CMD.UPDATE_<a href="https://www.jb51.cc/tag/score/" target="_blank" class="keywords">score</a>){

       addRecord(command.record,result);

     }

     else if (commandNaNd === CMD.DEL_USE){

       db('<a href="https://www.jb51.cc/tag/leader/" target="_blank" class="keywords">leader</a>boards').remove({id:command.record.id});

     }

     res.end(JSON.stringify(result));

   }

   catch (err) {

     result.comment= 'Can\'t accept post,Error: '+ err.message;

     result.code= ErrCode.DataError;

     console.log(result.comment);

     res.end(JSON.stringify(result));

   }

  });

当框架解析读入数据时,会调用req.on('data',function(chunk)提供的回调函数,我们把请求的数据记录在item中,一有数据,就调用item += chunk,直到数据读入完成,框架调用req.on('end',function()回调,在回调函数中,使用JSON.parse把请求的JSON数据还原为Javascript对象,这是一个命令对象,通过commandNaNd可以区分是需要添加删除记录。

addRecord添加或更新记录。

代码如下:

rush:js;"> function addRecord(record,result) {

var dbRecord = db('leaderboards').find({ id: record.id });

if (dbRecord){

if (dbRecord.token !== record.token){

  result.code= ErrCode.DataError;

  result.comment= 'User exist';

}

else{

  db('<a href="https://www.jb51.cc/tag/leader/" target="_blank" class="keywords">leader</a>boards')

   .chain()

   .find({id:record.id})

   .assign({<a href="https://www.jb51.cc/tag/score/" target="_blank" class="keywords">score</a>:record.<a href="https://www.jb51.cc/tag/score/" target="_blank" class="keywords">score</a>})

   .value();

  result.comment= 'OK,New <a href="https://www.jb51.cc/tag/score/" target="_blank" class="keywords">score</a> is '+ record.<a href="https://www.jb51.cc/tag/score/" target="_blank" class="keywords">score</a>;

}

}

else{

db('<a href="https://www.jb51.cc/tag/leader/" target="_blank" class="keywords">leader</a>boards').push(record);

}

}

命令执行结束后,通过res.end(JSON.stringify(result))写入返回数据。返回数据同样是一个JSON字符串。

在这个简单的样例中,使用了lowdb(https://github.com/typicode/lowdb#license?utm_source=ourjs.com)。

LowDB 是一个基于Node的纯Json文件数据库,它无需服务器,可以同步或异步持久化到文件中,也可以单纯作为内存数据库,非常快速简单。LowDB 提供Lo-Dash接口,可以使用类似.find({id:record.id})风格的方法进行查询。通过chain(),可以把多个操作连接在一起,完成数据库的查找更新操作。

这个简单的数据库实现,如果游戏仅保存得分高的用户记录,实际上已经可以满足我们的应用了。对更复杂的应用,Node也提供了各种数据库连接模块,比较常见的是mongodb或MysqL

2.返回TOP 10

通过查询数据库里的数据,首先使用.sortBy('score'),取前10个,返回到记录集中,然后使用JSON.stringify转为字符串,通过res返回。

代码如下:

rush:js;"> var records= [];
   var topTen = db('<a href="https://www.jb51.cc/tag/leader/" target="_blank" class="keywords">leader</a>boards')

     .chain()

     <a href="https://www.jb51.cc/tag/so/" target="_blank" class="keywords">.so</a>rtBy('<a href="https://www.jb51.cc/tag/score/" target="_blank" class="keywords">score</a>')

     .take(10)

     .map(function(record) {

      records.push(record);

     })

     .value();

   res.end(JSON.stringify(records));

3.删除记录

RESTful的删除资源ID一般带着URL里,类似“http://localhost:3000/leaderboards/aaa”,因此使用var path = parse(req.url).pathname解析出资源ID“aaa”。

代码如下:

rush:js;"> case 'DELETE':
          result= {code:ErrCode.OK,comment: 'OK'};

          try {

               var path = parse(req.url).pathname;

               var arrPath = path.split("/");

               var delObjID= arrPath[arrPath.length-1];



               db('<a href="https://www.jb51.cc/tag/leader/" target="_blank" class="keywords">leader</a>boards').remove({id:delObjID});

               res.end(JSON.stringify(result));

               break;

          }

至此,我们实现了一个带基本功能,可真正使用的RESTful服务。

实际应用场合的REST服务可能会更复杂一些,一个应用或者会提供多个资源URL的服务;或者还同时提供了基本的WEB服务功能;或者REST请求带有文件上传等等。

这样,我们的简单实现就不够看了。

Express框架

Express 是一个基于 Node.js 平台的 web 应用开发框架,它提供一系列强大的特性,帮助你创建各种 Web应用。

可以使用eclipse+nodeclipse生成认的express应用框架。一个express应用如下所示

rush:js;"> var express = require('express'),routes = require('./routes'),user = require('./routes/user'),http = require('http'),path = require('path');

var app = express();

// all environments

app.set('port',process.env.PORT || 3000);

app.set('views',__dirname + '/views');

app.set('view engine','ejs');

app.use(express.favicon());

app.use(express.logger('dev'));

app.use(express.bodyParser());

app.use(express.methodoverride());

app.use(app.router);

app.use(express.static(path.join(__dirname,'public')));

// development only

if ('development' == app.get('env')) {

app.use(express.errorHandler());

}

app.get('/',routes.index);

app.get('/users',user.list);

http.createServer(app).listen(app.get('port'),function(){

console.log('Express server listening on port ' + app.get('port'));

});

Express是一个Web服务器实现框架,虽然我们用不上页面页面渲染,不过作为样例,还是保留了缺省生成页面,并对其进行简单解释。

在这生成的框架代码里,选择view engine模板为ejs,这是一个类似JSP的HTML渲染模板引擎,app.get('/',routes.index)表示把HTTP的“/”请求路由给routes.index处理,routes.index对应于工程结构下的index.js文件处理,其内容如下:

rush:js;"> exports.index = function(req,res){

res.render('index',{ title: 'Express' });

};

这个函数调用了对应view目录下的index.ejs模板,并把{ title: 'Express' }传递给ejs模板,在ejs模板中,可以使用<%= title %>得到传入的json对象。

Express框架实现RESTful服务

首先我们实现一个自己的服务类,在routes子目录中,创建leaderboards.js文件,这个文件结构大致为定义REST对应的操作函数

rush:js;"> exports.fnList = function(req,res){

};

exports.fnGet = function(req,res){

};

exports.fnDelete = function(req,res){

};

exports.fnUpdate = function(req,res){

};

exports.fnAdd = function(req,res){

};

在app.js文件中,需要把HTTP请求路由给对应函数

rush:js;"> var leaderboards = require('./routes/leaderboards');

app.get('/leaderboards',leaderboards.fnList);

app.get('/leaderboards/:id',leaderboards.fnGet);

app.delete('/leaderboards/:id',leaderboards.fnDelete);

app.post('/leaderboards',leaderboards.fnAdd);

app.put('/leaderboards/:id',leaderboards.fnUpdate);

这样就把标准Web服务请求路由到leaderboards处理。因为请求中带有POST数据,可以使用

rush:js;"> var bodyParser = require('body-parser');

// parse varIoUs different custom JSON types as JSON

app.use(bodyParser.json({ limit: '1mb',type: 'application/*' }));

把请求的JSON结构解析后添加到req.body中。Limit是为避免非法数据占用服务器资源,正常情况下,如果解析JSON数据,type应该定义为'application/*+json',在本应用里,为避免某些客户端请求不指明类型,把所有输入都解析为JSON数据了。

'body-parser'是一个很有用的库,可以解析各种类型的HTTP请求数据,包括处理文件上传,详细可以参见https://www.npmjs.com/package/body-parser。

有了这个路由映射机制,我们不再需要考虑URL和数据的解析,仅仅指定路由,实现对应函数就可以了。

rush:js;"> exports.fnList = function(req,res){

var result= {code:ErrCode.OK,comment: 'OK'};

try {

var records= [];

var topTen = db('<a href="https://www.jb51.cc/tag/leader/" target="_blank" class="keywords">leader</a>boards')

  .chain()

  <a href="https://www.jb51.cc/tag/so/" target="_blank" class="keywords">.so</a>rtBy('<a href="https://www.jb51.cc/tag/score/" target="_blank" class="keywords">score</a>')

  .take(10)

  .map(function(record) {

   records.push(record);

   })

  .value();

res.end(JSON.stringify(records));

}catch (err) {

result.comment= 'Can\'t get <a href="https://www.jb51.cc/tag/leader/" target="_blank" class="keywords">leader</a>boards,Error: '+ err.message;

result.code= ErrCode.DataError;

console.log(result.comment);

res.end(JSON.stringify(result));

}

return;

};

对类似http://localhost:3000/leaderboards/aaa的URL,express已经解析到req.param里了,可以通过req.param('id')得到。

rush:js;"> exports.fnDelete = function(req,comment: 'OK'};

try {

var resID= req.p<a href="https://www.jb51.cc/tag/ara/" target="_blank" class="keywords">ara</a>m('id');

db('<a href="https://www.jb51.cc/tag/leader/" target="_blank" class="keywords">leader</a>boards').remove(resID);

res.end(JSON.stringify(result));



console.log('delete record:'+ req.p<a href="https://www.jb51.cc/tag/ara/" target="_blank" class="keywords">ara</a>m('id'));

}

catch (err) {

result.comment= 'Can\'t DELETE at '+ req.p<a href="https://www.jb51.cc/tag/ara/" target="_blank" class="keywords">ara</a>m('id')+ ',Error: '+ err.message;

result.code= ErrCode.DelError;

console.log(result.comment);

res.end(JSON.stringify(result)); 

}

};

使用了bodyParser.json()后,对POST请求中的JSON数据,已经解析好放到req.body里了,代码中可以直接使用。

rush:js;"> function processCmd(req,comment: 'OK'};

try{

var command = req.body;

console.log(req.bodyNaNd+ ';'+ req.body.record.id+ ':'+ req.body.record.<a href="https://www.jb51.cc/tag/score/" target="_blank" class="keywords">score</a>+ '('+ req.body.record.token+ ')');

if (commandNaNd === CMD.UPDATE_<a href="https://www.jb51.cc/tag/score/" target="_blank" class="keywords">score</a>){

  addRecord(command.record,result);

  console.log('add record:'+ command.record.id);

}

else if (commandNaNd === CMD.DEL_USE){

  db('<a href="https://www.jb51.cc/tag/leader/" target="_blank" class="keywords">leader</a>boards').remove({id:command.record.id});

  console.log('delete record:'+ command.record.id);

}

res.end(JSON.stringify(result));

}

catch (err) {

result.comment= 'Can\'t accept post,Error: '+ err.message;

result.code= ErrCode.DataError;

console.log(result.comment);

res.end(JSON.stringify(result));

}

return;

}

exports.fnUpdate = function(req,res){

processCmd(req,res);

};

exports.fnAdd = function(req,res){

processCmd(req,res);

};

使用express的好处是有一些细节可以扔给框架处理,代码结构上也更容易写得清晰一些。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持编程之家。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐