知识改变世界
生活可以更简单
注册 登录
***文章***

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

作者:chen 类型:转载 时间:2017-08-03

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

  1. {
    
      "id": "aaa",
    
      "score": 9.8,
    
      "token": "aaa-6F9619FF-8B86-D011-B42D-00C04FC964FF"
    
    };
    

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

  1. curl -X DELETE http://localhost:3000/leaderboards/aaa

或(使用REST-RPC语义)

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

查看TOP 10命令如下:

  1. curl http://localhost:3000/leaderboards

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

  1. 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

  1. var server = http.createServer(function(req, res) {
    
      var result;
    
      switch (req.method) {
    
        case 'POST':
    
          break;
    
        case 'GET':
    
          break;
    
        case 'DELETE':
    
          break;
    
      }
    
    });
    
    

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

  1.       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可以区分是需要添加或删除记录。

  1. 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字符串。

  1.        var records= [];
    
           var topTen = db('leaderboards')
    
             .chain()
    
             .sortBy('score')
    
             .take(10)
    
             .map(function(record) {
    
              records.push(record);
    
             })
    
             .value();
    
           res.end(JSON.stringify(records));
    
    

3. 删除记录

  1.           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服务。

  1. 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服务器实现框架,虽然我们用不上页面和页面渲染,不过作为样例,还是保留了缺省生成的页面,并对其进行简单解释。

  1. exports.index = function(req, res){
    
     res.render('index', { title: 'Express' });
    
    };
    

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

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

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

  1. 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数据了。

  1. 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')得到。

  1. 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里了,代码中可以直接使用。

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

希望本文所述对大家程序设计有所帮助。

***其他版块***

本站为编程爱好者提供计算机电子书免费资源下载,定期更新!

***其他***

更多内容,敬请期待!谢谢!!!