当前位置: 移动技术网 > IT编程>开发语言>JavaScript > 使用Node.js实现RESTful API的示例

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

2017年12月12日  | 移动技术网IT编程  | 我要评论

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表示,如下:

{

  "id": "aaa",

  "score": 9.8,

  "token": "aaa-6f9619ff-8b86-d011-b42d-00c04fc964ff"

};

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

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

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

复制代码 代码如下:

curl -d "{\"cmd\":1,\"record\":{\"id\":\"test11\",\"score\":29.8,\"token\":\"aaa\"}}"

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

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

curl -x delete http://localhost:3000/leaderboards/aaa

或(使用rest-rpc语义)

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

查看top 10命令如下:

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的文件,并写入以下代码:

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回调函数,根据请求类型返回不同的内容。

代码如下:

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请求,是要求我们添加或更新记录,请求的数据可以通过回调得到。

代码如下:

      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.score+ '('+ command.record.token+ ')');

         if (commandnand === cmd.update_score){

           addrecord(command.record,result);

         }

         else if (commandnand === cmd.del_use){

           db('leaderboards').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添加或更新记录。

代码如下:

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('leaderboards')

       .chain()

       .find({id:record.id})

       .assign({score:record.score})

       .value();

      result.comment= 'ok, new score is '+ record.score;

    }

  }

  else{

    db('leaderboards').push(record);

  }

}

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

在这个简单的样例中,使用了lowdb()。

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

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

2. 返回top 10

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

代码如下:

       var records= [];

       var topten = db('leaderboards')

         .chain()

         .sortby('score')

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

代码如下:

          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('leaderboards').remove({id:delobjid});

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

                   break;

              }

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

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

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

express框架

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

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

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文件处理,其内容如下:

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对应的操作函数。 

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请求路由给对应函数。

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数据,可以使用

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请求数据,包括处理文件上传,详细可以参见

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

exports.fnlist = function(req, res){

  var result= {code:errcode.ok,comment: 'ok'};

  try {

    var records= [];

    var topten = db('leaderboards')

      .chain()

      .sortby('score')

      .take(10)

      .map(function(record) {

       records.push(record);

       })

      .value();

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

  }catch (err) {

    result.comment= 'can\'t get leaderboards, 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')得到。

exports.fndelete = function(req, res){

  var result= {code:errcode.ok,comment: 'ok'};

  try {

    var resid= req.param('id');

    db('leaderboards').remove(resid);

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

 

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

  }

  catch (err) {

    result.comment= 'can\'t delete at '+ req.param('id')+ ', error: '+ err.message;

    result.code= errcode.delerror;

    console.log(result.comment);

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

  }

}; 

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

function processcmd(req, res){

  var result= {code:errcode.ok,comment: 'ok'}; 

  try{

    var command = req.body;

    console.log(req.bodynand+ ';'+ req.body.record.id+ ':'+ req.body.record.score+ '('+ req.body.record.token+ ')');

    if (commandnand === cmd.update_score){

      addrecord(command.record,result);

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

    }

    else if (commandnand === cmd.del_use){

      db('leaderboards').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的好处是有一些细节可以扔给框架处理,代码结构上也更容易写得清晰一些。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持移动技术网。

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网