带有Node.js的REST API在同时向自身发出请求时失败

时间:2013-06-26 19:23:25

标签: javascript json node.js rest

我使用node.js创建了一个REST API服务并且它运行良好,直到我在浏览器中打开几个选项卡并同时发出几个请求(几乎)。发送请求的最后一个选项卡获取响应,另一个选项卡挂起。

基本上我有一个名为“service”的模块可以完成所有工作:

var service = require('./service');
var server = http.createServer(function(req, res) {
    ...
    var serviceResult = service.runService(parsedURL.query.username, parsedURL.query.api_key);

    res.writeHead(200, {'content-type': 'application/json', 'connection' : 'keep-alive' });

    service.ready = function(serviceResult) {
        var serviceResultJSON = JSON.stringify(serviceResult);
        res.writeHead(200, {'content-type': 'application/json', 'connection' : 'close' });
        res.end(serviceResultJSON);
    }

}

我从 服务 模块致电:

service.ready(result);

......只要结果准备就绪,那就是我的服务器。那么我该怎样解决这个问题呢?

编辑:

这是我的service.js模块的样子(在建议的更改之后):

// PUBLIC

exports.runService = function(username, apiKey, callback) {
    _.username              = username;
    _.apiKey                = apiKey;
    init();

    userManager.setLastServiceGlobal(function() {
            // This call triggers the whole cycle. Below is snapshotManager.createdSnapshot(), which gets executed from within snapshotManager and the cycle moves on until apiManager.ready() gets called from within api-manager.js
        snapshotManager.createSnapshot(false);
    });

    // This is the last .ready() function that gets executed when all other modules have finished their job.

    apiManager.ready = function() {
        console.log('API Manager ready.');
        userManager.updateLastService();
        callback(null, serviceResult);
    }
}

// PRIVATE

var userManager             = require('./user-manager'),
    productManager          = require('./product-manager'),
    commentsManager         = require('./comments-manager'),
    apiManager          = require('./api-manager'),
    milestonesManager       = require('./milestones-manager'),
    statisticsManager       = require('./statistics-manager'),
    snapshotManager         = require('./snapshot-manager'),
    serviceResult;

...

snapshotManager.createdSnapshot = function() {
    userManager.refreshUserData();
}
snapshotManager.createdNewSnapshot = function() {
    milestonesManager.generateMilestones();
}
userManager.refreshedUserData = function() {
    userManager.isTimeForPortfolioParse();
}

...

userManager.ready = function() {
    console.log('User Manager ready.');
    userManagerReady = true;
    isDataFetchingOver();
}
productManager.ready = function() {
    console.log('Product Manager ready.');
    productManagerReady = true;
    isDataFetchingOver();
}
commentsManager.ready = function() {
    console.log('Comments Manager ready.');
    commentsManagerReady = true;
    isDataFetchingOver();
}

在这种情况下,模块被覆盖,就像“server.js”文件中的“service”模块一样,对吗? 我猜我需要在所有地方实现回调而不是.ready()函数,对吗?

2 个答案:

答案 0 :(得分:3)

这里有三个问题!

  1. 您在ready上设置了service属性,而只有一个service对象。如果两个请求彼此非常接近,那么在第一个请求被触发之前,您可能最终会使用第二个请求覆盖ready属性。这是因为service对象只有一个实例。这几乎肯定是应该的方式,所以不要担心,而是我们必须找到一种新的方式来向您的消费代码发出服务行动已经完成的信号。
  2. res内的service.read变量可能与您认为的res不同。它第二次被调用,它会有所不同。这是因为您正在重新定义ready属性,因此每次都会获得不同的范围。
  3. 您要两次发送标题。这是一个简单的修复 - 只要不知道它们应该是什么,就不要发送任何标题。这将在下面变得明显。
  4. 要解决第一个问题,我建议您使用回调!您应该已经从节点核心熟悉它们,它们几乎用于所有事情。这也恰好通过JavaScript中的scope解决了第二个问题。

    注意:我建议使用回调代替工厂或其他方法的原因是回调在node-land中普遍存在,所以如果你使用它们,你很有可能会与其他库等集成更容易。

    以下是我如何处理您的服务器代码:

    var service = require('./service');
    var server = http.createServer(function(req, res) {
      // note that we're not immediately sending headers anymore - this
      // fixes the third problem with what you had before
    
      service.runService(parsedURL.query.username, parsedURL.query.api_key, function(err, serviceResult) {
        // check for errors! tell the client!
        if (err) {
          res.writeHead(500);
          res.end();
          return;
        }
    
        res.writeHead(200, {
          'content-type': 'application/json',
          'connection' : 'keep-alive',
        });
    
        var serviceResultJSON = JSON.stringify(serviceResult);
    
        res.end(serviceResultJSON);
      });
    };
    

    以下是我实施service事情的方法:

    var service = module.exports = {};
    
    service.runService = function runService(username, key, cb) {
      // assume `database` exists from somewhere else...
      database.getUser(username, function(err, user) {
        // make sure we send any errors up the line
        if (err) {
          cb(err);
          return;
        }
    
        // here's an error we've decided on
        if (user.key !== key) {
          cb(Error("key is incorrect!"));
          return;
        }
    
        // this is a very common usage pattern - cb(error, result, ...)
        // the reason we're calling this will `null` for the error is a bit
        // of a double negative - we're saying "there was no error".
        cb(null, user);
      });
    };
    

答案 1 :(得分:0)

问题是它们都共享相同的服务对象。因此,当您收到请求时,您会将service.ready覆盖到响应该请求的函数。因此,如果你真的把2个请求放在一起,service.ready将被设置为响应你上次的请求,所以,只有那个会得到一个响应。

最好的方法是让服务模块导出一个返回服务实例的函数,如下所示:

 function serviceFactory() {

  }  

 serviceFactory.prototype.getService() {
    return new Service();
 }
 module.exports = serviceFactory;

然后你可以拥有

var serviceFactory = require(./service);
var server = http.createServer(function(req, res) {
 ...
 var service = serviceFactory.getService();
 var serviceResult = service.runService(parsedURL.query.username, parsedURL.query.api_key);

 res.writeHead(200, {'content-type': 'application/json', 'connection' : 'keep-alive' }); 

 service.ready = function(serviceResult) {
     var serviceResultJSON = JSON.stringify(serviceResult);
     res.writeHead(200, {'content-type': 'application/json', 'connection' : 'close' });
     res.end(serviceResultJSON);
 }
}