如何在Nodejs / Expressjs中保持多个请求的分离

时间:2022-10-13 13:23:52

I am developing a NodeJS / ExpressJS application on my computer. I have node running locally. I have a single page web app. When it needs information it makes ajax requests with jQuery.

我正在电脑上开发一个NodeJS / ExpressJS应用程序。我有一个本地运行的节点。我有一个网页web应用程序,当它需要信息时,它会用jQuery发出ajax请求。

The problem is when I have multiple requests for different sets of data. Node/express will start processing the first request and then the second request comes in before the first request has been fulfilled, it sends the response to the second request from the first request instead of sending it to the first request like it is suppose to. If I put a pause ( using an alert ) in my app so that is slows it down so the next request doesn't get sent until the first request was fulfilled, everything works fine.

问题是当我对不同的数据集有多个请求时。Node/express将开始处理第一个请求,然后第二个请求在第一个请求完成之前进入,它将响应发送到第一个请求的第二个请求,而不是像它所假定的那样发送到第一个请求。如果我在我的应用程序中暂停(使用一个警告),这样就会减慢它的速度,所以下一个请求在第一个请求完成之前不会发送,所以一切正常。

I don't understand why this is happening. I thought node / express was suppose to be able to handle this and keep the requests separate.

我不明白为什么会这样。我认为node / express应该能够处理这个问题,并将请求分开。

Also, I get a "Can't set headers after they are sent" error in node because it is apparently merging the requests....

同时,我得到一个“不能设置标题后发送“错误节点,因为它显然是合并请求....

Here is what happens

这是发生了什么

ajax request 1 -> server
ajax request 2 -> server
ajax request 3 -> server

server -> request1 ( no response )
server -> request2 ( request 1's data)
server -> request3 ( request 2's data)
server for request3 --> error: header's already sent

I am using Google Chrome 63 with jQuery 3.3.1 on a Windows 10 machine running Node 8.9.4 and Express 4.16.2

我在Windows 10机器运行节点8.9.4和Express 4.16.2上使用的是谷歌Chrome 63和jQuery 3.3.1。

My work-around is to chain the ajax requests so that each request doesn't get called until the prior request has received a response from the server. But I shouldn't have to do that...

我的工作是对ajax请求进行链接,以便在之前的请求收到服务器的响应之前不会调用每个请求。但我不应该那样做…

Here is the relevant server code:

以下是相关的服务器代码:

var mysql = require("mysql");
var express = require('express');
var app = express();

var DEBUG = true;

var request = null;
var response = null;

var currentDataRowParser = null;
var con = mysql.createConnection(config);

function ParseMySqlRowData(rowData)
{
    if (DEBUG) console.log("ParseMySqlRowData");

    return JSON.stringify(rowData);
}

var ParseMySqlRowsDatatoResponse = function (err, rows)
{
    if (DEBUG) console.log("ParseMySqlRowsDatatoResponse");

    var MySQLRows;

    try
    {
        if (!err)
        {
            MySQLRows = "[";
            for (var i = 0; i < rows.length; i++)
            {
                if (i > 0)
                    MySQLRows += ", ";

                MySQLRows += currentDataRowParser(rows[i]);
            }

            MySQLRows += "]";

            if (DEBUG) console.log("write rows");
            if (DEBUG) console.log(MySQLRows);
            response.send(MySQLRows);
            response.end();
        }
    }
    catch (ex)
    {
        if (DEBUG) console.log("ParseMySQLRowsDatatoResponse: ERROR");
        if (DEBUG) console.log(ex);
    }
};

var GetQueryData = function (query, dataRowParser, parseDataCallbackFunction)
{
    if (DEBUG) console.log("GetQueryData");

    currentDataRowParser = dataRowParser;

    if (parseDataCallbackFunction == null || parseDataCallbackFunction == undefined)
        parseDataCallbackFunction = ParseDataCallback;

    try
    {
        if (DEBUGQUERY)
        {
            console.log("before query");
            console.log(con.query(query, parseDataCallbackFunction));
            console.log("after query");
            console.log(query.sql);
            DEBUGQUERY = false;
        }
        else
        {
            con.query(query, parseDataCallbackFunction);
        }
    }
    catch (ex)
    {
        console.log(ex);
    }
};

app.post('/getdata', function(req, res)
{
    request = req;
    response = res;
    var query;
    switch (request.body.loaddata)
    {
    case "dataset1":
        query = "SELECT * FROM table1 WHERE key='" + request.body.key + "'";
        GetQueryData(query,ParseMySqlRowData,ParseMySqlRowsDatatoResponse);
        break;
    case "dataset2":
        query = "SELECT * FROM table2 WHERE key='" + request.body.key + "'";
        GetQueryData(query,ParseMySqlRowData,ParseMySqlRowsDatatoResponse);
        break;
    case "dataset3":
        query = "SELECT * FROM table3 WHERE key='" + request.body.key + "'";
        GetQueryData(query,ParseMySqlRowData,ParseMySqlRowsDatatoResponse);
        break;

    }
};

1 个解决方案

#1


3  

You cannot store req and res in global or module-level variables. When a second request comes in, it will immediately overwrite your globals and it will mix up the data for the various requests.

不能在全局变量或模块级变量中存储req和res。当第二个请求出现时,它将立即覆盖全局变量,并将各种请求的数据混合在一起。

Does't node separate each request instance?

节点不分离每个请求实例吗?

Yes, there is a separate request instance, but not a separate global or module-level namespace. So, when you assign the req into the global space, you are overwriting the previous one and your code will then use the wrong one.

是的,有一个单独的请求实例,但没有一个单独的全局或模块级名称空间。因此,当您将req分配到全局空间时,您正在覆盖前面的那个,然后您的代码将使用错误的那个。

It is very helpful to have the request and response as a global variable. Otherwise I would have to be passing them all over the place.

将请求和响应作为全局变量非常有用。否则我就得把它们传遍整个地方。

You HAVE to pass them to lower level functions that need them. That's how you keep each request separate from the others. ANY function that needs to operate on req or res should be passed those variables so it knows exactly which ones to operate on.

您必须将它们传递给需要它们的低级函数。这就是将每个请求与其他请求分开的方式。任何需要在req或res上操作的函数都应该通过这些变量,这样它就知道要进行哪些操作了。

node.js has a shared global and module-level namespace. So, all requests in flight at the same time use that same namespace. The ONLY data that should ever be stored there is data that you specifically want to be shared between requests (such as session state, for example). Individual request or response objects should never be stored in a shared variable.

节点。js有一个共享的全局和模块级名称空间。因此,所有在飞行中的请求同时使用相同的名称空间。惟一应该存储的数据是您特别希望在请求之间共享的数据(例如会话状态)。单独的请求或响应对象不应该存储在共享变量中。


A more common way to code your type of code is to call a function like your GetQueryData() and have it return the data (likely via a promise) and then you send the response in the original request handler. Then, you don't have to pass req or res down multiple levels at all. Your helper functions just fetch data. The request handlers fetch data, then send the response. It's often a better encapsulation of functionality.

编写代码类型的一种更常见的方法是调用GetQueryData()之类的函数,让它返回数据(可能通过一个承诺),然后在原始请求处理程序中发送响应。然后,你不需要将req或res传递到多个级别。助手函数只是获取数据。请求处理程序获取数据,然后发送响应。它通常是更好的功能封装。


Here's one way to restructure your code along the lines described above.

这里有一种方法可以按照上面描述的代码进行重构。

  1. GetQueryData() returns a promise that fulfills with the data
  2. GetQueryData()返回一个承诺,该承诺实现数据
  3. ParseMySqlRowsData() just returns a parsed result or null if a parsing error
  4. ParseMySqlRowsData()只是在解析错误时返回解析结果或null
  5. app.post() just gets the data (via a promise) and then sends the appropriate response.
  6. post()只获取数据(通过一个承诺),然后发送适当的响应。
  7. There's no global req or res and there's no need to pass them anywhere.
  8. 没有全局req或res,也不需要在任何地方传递它们。

Here's the code:

这是代码:

var mysql = require("mysql");
var express = require('express');
var app = express();

var DEBUG = true;

var currentDataRowParser = null;
var con = mysql.createConnection(config);

function ParseMySqlRowData(rowData) {
    if (DEBUG) console.log("ParseMySqlRowData");

    return JSON.stringify(rowData);
}

var ParseMySqlRowsData = function(err, rows) {
    if (DEBUG) console.log("ParseMySqlRowsDatatoResponse");

    var MySQLRows;

    try {
        if (!err) {
            MySQLRows = "[";
            for (var i = 0; i < rows.length; i++) {
                if (i > 0)
                    MySQLRows += ", ";

                MySQLRows += currentDataRowParser(rows[i]);
            }

            MySQLRows += "]";

            if (DEBUG) console.log("write rows");
            if (DEBUG) console.log(MySQLRows);
            return MySQLRows;
        }
    } catch (ex) {
        if (DEBUG) console.log("ParseMySQLRowsDatatoResponse: ERROR");
        if (DEBUG) console.log(ex);
        return null;
    }
};

var GetQueryData = function(query, dataRowParser, parseDataCallbackFunction) {
    return new Promise((resolve, reject) =>{
        if (DEBUG) console.log("GetQueryData");

        let currentDataRowParser = dataRowParser;

        if (parseDataCallbackFunction == null || parseDataCallbackFunction == undefined)
            parseDataCallbackFunction = ParseDataCallback;

        try {
            if (DEBUGQUERY) {
                console.log("before query");
                console.log(con.query(query, parseDataCallbackFunction));
                console.log("after query");
                console.log(query.sql);
                DEBUGQUERY = false;
            } else {
                con.query(query, function(err, rows) {
                    if (err) {
                        reject(err);
                    } else {
                        let result = parseDataCallbackFunction(rows);
                        if (result) {
                            resolve(result);
                        } else {
                            reject(new Error("ParseMySqlRowsData error"));
                        }
                    }
                });
            }
        } catch (ex) {
            console.log(ex);
            reject(new Error("GetQueryData error"));
        }
    });
};

app.post('/getdata', function(req, res) {
    var query;
    let p;
    switch (request.body.loaddata) {
        case "dataset1":
            query = "SELECT * FROM table1 WHERE key='" + request.body.key + "'";
            p = GetQueryData(query, ParseMySqlRowData, ParseMySqlRowsData);
            break;
        case "dataset2":
            query = "SELECT * FROM table2 WHERE key='" + request.body.key + "'";
            p = GetQueryData(query, ParseMySqlRowData, ParseMySqlRowsData);
            break;
        case "dataset3":
            query = "SELECT * FROM table3 WHERE key='" + request.body.key + "'";
            p = GetQueryData(query, ParseMySqlRowData, ParseMySqlRowsData);
            break;
        default:
            p = Promise.reject(new Error("Invalid request.body.loaddata"));
            break;
    }
    p.then(data => {
        res.send(data);
    }).catch(err => {
        console.log(err);
        res.sendStatus(500);
    });
};

P.S. I see you still have a module-level variable you should not have: currentDataRowParser. That needs to be replaced by passing the appropriate parser to ParseMySqlRowsData() as an argument, not using a module-level shared variable. I will leave that as an excercise for you to fix. Shared variables for operating on a specific request state are a bad idea in ANY server programming and specifically in node.js programming. Keep your request-specific state in your function arguments or on the req or res object itself. That's how you prevent overwritting the data from one request with the data from another while they are being processed.

我看到您仍然有一个模块级变量,您不应该有:currentDataRowParser。需要将适当的解析器作为参数传递给ParseMySqlRowsData(),而不是使用模块级别的共享变量,从而替换这一点。我将把它作为一种练习,供您修改。在任何服务器编程中,特别是在节点中,在特定请求状态下操作的共享变量都不是一个好主意。js编程。在函数参数或req或res对象本身中保持特定于请求的状态。这就是防止在处理一个请求时用来自另一个请求的数据重写数据的方法。

#1


3  

You cannot store req and res in global or module-level variables. When a second request comes in, it will immediately overwrite your globals and it will mix up the data for the various requests.

不能在全局变量或模块级变量中存储req和res。当第二个请求出现时,它将立即覆盖全局变量,并将各种请求的数据混合在一起。

Does't node separate each request instance?

节点不分离每个请求实例吗?

Yes, there is a separate request instance, but not a separate global or module-level namespace. So, when you assign the req into the global space, you are overwriting the previous one and your code will then use the wrong one.

是的,有一个单独的请求实例,但没有一个单独的全局或模块级名称空间。因此,当您将req分配到全局空间时,您正在覆盖前面的那个,然后您的代码将使用错误的那个。

It is very helpful to have the request and response as a global variable. Otherwise I would have to be passing them all over the place.

将请求和响应作为全局变量非常有用。否则我就得把它们传遍整个地方。

You HAVE to pass them to lower level functions that need them. That's how you keep each request separate from the others. ANY function that needs to operate on req or res should be passed those variables so it knows exactly which ones to operate on.

您必须将它们传递给需要它们的低级函数。这就是将每个请求与其他请求分开的方式。任何需要在req或res上操作的函数都应该通过这些变量,这样它就知道要进行哪些操作了。

node.js has a shared global and module-level namespace. So, all requests in flight at the same time use that same namespace. The ONLY data that should ever be stored there is data that you specifically want to be shared between requests (such as session state, for example). Individual request or response objects should never be stored in a shared variable.

节点。js有一个共享的全局和模块级名称空间。因此,所有在飞行中的请求同时使用相同的名称空间。惟一应该存储的数据是您特别希望在请求之间共享的数据(例如会话状态)。单独的请求或响应对象不应该存储在共享变量中。


A more common way to code your type of code is to call a function like your GetQueryData() and have it return the data (likely via a promise) and then you send the response in the original request handler. Then, you don't have to pass req or res down multiple levels at all. Your helper functions just fetch data. The request handlers fetch data, then send the response. It's often a better encapsulation of functionality.

编写代码类型的一种更常见的方法是调用GetQueryData()之类的函数,让它返回数据(可能通过一个承诺),然后在原始请求处理程序中发送响应。然后,你不需要将req或res传递到多个级别。助手函数只是获取数据。请求处理程序获取数据,然后发送响应。它通常是更好的功能封装。


Here's one way to restructure your code along the lines described above.

这里有一种方法可以按照上面描述的代码进行重构。

  1. GetQueryData() returns a promise that fulfills with the data
  2. GetQueryData()返回一个承诺,该承诺实现数据
  3. ParseMySqlRowsData() just returns a parsed result or null if a parsing error
  4. ParseMySqlRowsData()只是在解析错误时返回解析结果或null
  5. app.post() just gets the data (via a promise) and then sends the appropriate response.
  6. post()只获取数据(通过一个承诺),然后发送适当的响应。
  7. There's no global req or res and there's no need to pass them anywhere.
  8. 没有全局req或res,也不需要在任何地方传递它们。

Here's the code:

这是代码:

var mysql = require("mysql");
var express = require('express');
var app = express();

var DEBUG = true;

var currentDataRowParser = null;
var con = mysql.createConnection(config);

function ParseMySqlRowData(rowData) {
    if (DEBUG) console.log("ParseMySqlRowData");

    return JSON.stringify(rowData);
}

var ParseMySqlRowsData = function(err, rows) {
    if (DEBUG) console.log("ParseMySqlRowsDatatoResponse");

    var MySQLRows;

    try {
        if (!err) {
            MySQLRows = "[";
            for (var i = 0; i < rows.length; i++) {
                if (i > 0)
                    MySQLRows += ", ";

                MySQLRows += currentDataRowParser(rows[i]);
            }

            MySQLRows += "]";

            if (DEBUG) console.log("write rows");
            if (DEBUG) console.log(MySQLRows);
            return MySQLRows;
        }
    } catch (ex) {
        if (DEBUG) console.log("ParseMySQLRowsDatatoResponse: ERROR");
        if (DEBUG) console.log(ex);
        return null;
    }
};

var GetQueryData = function(query, dataRowParser, parseDataCallbackFunction) {
    return new Promise((resolve, reject) =>{
        if (DEBUG) console.log("GetQueryData");

        let currentDataRowParser = dataRowParser;

        if (parseDataCallbackFunction == null || parseDataCallbackFunction == undefined)
            parseDataCallbackFunction = ParseDataCallback;

        try {
            if (DEBUGQUERY) {
                console.log("before query");
                console.log(con.query(query, parseDataCallbackFunction));
                console.log("after query");
                console.log(query.sql);
                DEBUGQUERY = false;
            } else {
                con.query(query, function(err, rows) {
                    if (err) {
                        reject(err);
                    } else {
                        let result = parseDataCallbackFunction(rows);
                        if (result) {
                            resolve(result);
                        } else {
                            reject(new Error("ParseMySqlRowsData error"));
                        }
                    }
                });
            }
        } catch (ex) {
            console.log(ex);
            reject(new Error("GetQueryData error"));
        }
    });
};

app.post('/getdata', function(req, res) {
    var query;
    let p;
    switch (request.body.loaddata) {
        case "dataset1":
            query = "SELECT * FROM table1 WHERE key='" + request.body.key + "'";
            p = GetQueryData(query, ParseMySqlRowData, ParseMySqlRowsData);
            break;
        case "dataset2":
            query = "SELECT * FROM table2 WHERE key='" + request.body.key + "'";
            p = GetQueryData(query, ParseMySqlRowData, ParseMySqlRowsData);
            break;
        case "dataset3":
            query = "SELECT * FROM table3 WHERE key='" + request.body.key + "'";
            p = GetQueryData(query, ParseMySqlRowData, ParseMySqlRowsData);
            break;
        default:
            p = Promise.reject(new Error("Invalid request.body.loaddata"));
            break;
    }
    p.then(data => {
        res.send(data);
    }).catch(err => {
        console.log(err);
        res.sendStatus(500);
    });
};

P.S. I see you still have a module-level variable you should not have: currentDataRowParser. That needs to be replaced by passing the appropriate parser to ParseMySqlRowsData() as an argument, not using a module-level shared variable. I will leave that as an excercise for you to fix. Shared variables for operating on a specific request state are a bad idea in ANY server programming and specifically in node.js programming. Keep your request-specific state in your function arguments or on the req or res object itself. That's how you prevent overwritting the data from one request with the data from another while they are being processed.

我看到您仍然有一个模块级变量,您不应该有:currentDataRowParser。需要将适当的解析器作为参数传递给ParseMySqlRowsData(),而不是使用模块级别的共享变量,从而替换这一点。我将把它作为一种练习,供您修改。在任何服务器编程中,特别是在节点中,在特定请求状态下操作的共享变量都不是一个好主意。js编程。在函数参数或req或res对象本身中保持特定于请求的状态。这就是防止在处理一个请求时用来自另一个请求的数据重写数据的方法。