简体   繁体   English

带有Node.js的REST API未能同时向自身发出请求

[英]REST API with Node.js failing at simultaneous requests to itself

I made a REST API service using node.js and it works perfectly, until I open a couple of tabs in my browser and make a couple of requests (almost) at the same time. 我使用node.js提供了REST API服务,并且该服务运行良好,直到在浏览器中打开几个标签并同时(几乎)发出几个请求。 The last tab that sends the request gets the response and the other one hangs. 发送请求的最后一个选项卡获取响应,另一个挂起。

Basically I have a module called "service" which does all the work: 基本上,我有一个名为“服务”的模块,可以完成所有工作:

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);
    }

}

And from the service module I call: 服务模块调用:

service.ready(result);

... whenever the result is ready and that's my server in a nutshell. ...只要结果准备就绪,那简直就是我的服务器。 So what can I do fix the issue? 那我该怎么解决这个问题呢?

EDIT: 编辑:

Here is what my service.js module looks like (after the suggested changes): 这是我的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();
}

In this situation the modules are getting overridden just like the "service" module in the "server.js" file, correct? 在这种情况下,这些模块就像“ server.js”文件中的“ service”模块一样被覆盖,对吗? I'm guessing that I need to implement callbacks everywhere instead of the .ready() functions, correct? 我猜想我需要在各处实现回调,而不是.ready()函数,对吗?

There are three problems here! 这里有三个问题!

  1. You're setting the ready property on service , while there's only one service object. 您将在service上设置ready属性,而只有一个service对象。 If two requests happen very near to each other, you might end up overwriting the ready property with the second request before it's been fired for the first request. 如果两个请求非常接近,您可能最终会在为第一个请求触发之前,用第二个请求覆盖ready属性。 This is because there's only one instance of the service object. 这是因为service对象只有一个实例。 That's almost definitely the way it should be, so don't worry about that, but rather we have to find a new way to signal to your consuming code that the service action has completed. 几乎肯定是应该采用的方式,所以不必担心,但是我们必须找到一种新的方式来向您的使用方代码发出服务动作已完成的信号。
  2. Your res variable inside service.read is probably not the same res that you think it is. 你的res里面的变量service.read可能是不一样的res ,你认为它是。 The second time it gets called, it'll be different. 第二次调用它会有所不同。 This is because you're redefining the ready property, so it's getting a different scope every time. 这是因为您要重新定义ready属性,因此每次都会获得不同的作用域。
  3. You're sending the headers twice. 您要发送两次标题。 That's an easy fix - just don't send any headers until you know what they should be. 这是一个简单的解决方法-只是不要发送任何标头,直到您知道它们应该是什么。 That'll become obvious just below. 这将在下面变得显而易见。

To fix the first problem, I suggest that you use callbacks! 要解决第一个问题,建议您使用回调! You should be familiar with them already from the node core, they're used for just about everything. 您应该已经从节点核心熟悉它们,它们几乎用于所有内容。 This also happens to fix the second problem, by virtue of scope in JavaScript. 借助于JavaScript的作用域 ,这也恰好解决了第二个问题。

Note: the reason I suggest using callbacks instead of a factory or some other method is that callbacks are pervasive in node-land, so if you use them, there's a good chance you'll have a much easier time integrating with other libraries and such. 注意:我建议使用回调代替工厂或其他方法的原因是,回调在节点领域普遍存在,因此,如果使用它们,则很有可能会更轻松地与其他库集成,例如。

Here's how I'd do your server code: 这是我处理服务器代码的方法:

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);
  });
};

And here's how I'd implement the service thing: 这就是我实现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);
  });
};

The problem is they all share the same service object. 问题在于它们都共享相同的服务对象。 So what happens is that when you receive a request you overwrite service.ready to a function that responds to that request. 因此,发生的事情是,当您收到请求时,您将覆盖service.ready,准备好响应该请求的函数。 So if you get 2 requests really close together, service.ready will be set to respond to the last request you got and so, only that one will get a response. 因此,如果您真的有2个请求并排在一起,则将service.ready设置为响应上一个收到的请求,因此,只有一个将得到响应。

The best way is to have the service module export a function that returns an instance of service like this: 最好的方法是让服务模块导出一个返回如下服务实例的函数:

 function serviceFactory() {

  }  

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

And then you can have 然后你可以

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);
 }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM