При запросе большого количества подключений к другому сайту с активным закрытием перекрестного источника ресурсов происходит повреждение данных. - PullRequest
0 голосов
/ 16 марта 2020

Я делаю приложение node.js и часть моего кода запрашивает данные из 193 различных URL-адресов для загрузки данных json с каждого URL-адреса. Вот один из этих URL: https://www.gemeentegeschiedenis.nl/gemeentenaam/json/Apeldoorn Для некоторых загруженные данные json в порядке и полны. Однако ближе к концу происходят искажения для некоторых файлов. Часть данных обнуляется, а некоторые из них содержат ошибки базы данных. Я думаю, что это связано с запросом данных из очень многих URL-адресов за короткий промежуток времени (именно поэтому я попробовал функцию «setTimeout» (но на самом деле это не работает)).

function writeToFile(url) {
    // get name to make each new file unique
    var name = url.split("json/")[1];
    var fileStream = fs.createWriteStream(`jsonFiles/${name}.json`);
    var options = {
        url: `${url}`,
        method: 'GET',
        headers: {
            'Accept': 'application/json',
            'Accept-Charset': 'utf-8',
            json: true
        }
    }
    //request the data from the site and download to the file.
    request.get(options).pipe(fileStream);
}
function getMunicipalityGeoJsonData(req, res) {
    //Get all the urls pointing to the JSON data for the province, Gelderland
    getGelderlandJsonUrls((err, jsonUrls) => {
        //for all those urls, write the data to files.
        for (url of jsonUrls) {
            console.log(url);
            writeToFile(url);
        }
    })
}
function getGelderlandJsonUrls(callback) {
    getMunicipalityJsonUrls("Gelderland", (err, data) => {
        jsonUrls = data;
        callback(null, jsonUrls);
    });
}
function getMunicipalityJsonUrls(provinceName, callback) {
    request({ uri: `https://www.gemeentegeschiedenis.nl/provincie/json/${provinceName}` }, (error, response, body) => {
        body = JSON.parse(body);
        // extracting each json URL from all the municipalities in Gelderland
        var jsonUrls = [];
        var numberMun = body.length;
        for (var i = 0; i < numberMun; i++) {
            var url = body[i].uri.naam;
            var urlSplit = url.split("gemeentenaam");
            var jsonUrl = urlSplit[0] + "gemeentenaam/json" + urlSplit[1];
            jsonUrl = jsonUrl.replace("http://", "https://");
            jsonUrls.push(jsonUrl);
        }
        callback(null, jsonUrls);
    });
}

Последние json данные, загруженные в файл в виде html страницы с ошибкой базы данных из URL: https://www.gemeentegeschiedenis.nl/gemeentenaam/json/Zutphen, которая на самом деле потребовалось чуть меньше 6 секунд для загрузки, глядя на вкладку сети на Chrome , у 1812 есть нулевое значение для своих свойств, когда он должен иметь группу координат https://www.gemeentegeschiedenis.nl/gemeentenaam/json/Winssen (загрузка заняла чуть более секунды на chrome

Я нуб на узле, но, пожалуйста, помогите мне решить эту проблему, возможно, с какой-то проверкой, повреждены ли данные или что-то в этом роде. Спасибо за помощь в продвинутом:)

РЕДАКТИРОВАТЬ: я пытаюсь сделать до 200 URL за один раз в течение l oop.

1 Ответ

0 голосов
/ 16 марта 2020

Прежде всего, добавьте правильную обработку ошибок в getMunicipalityJsonUrls() и getGelderlandJsonUrls(). Это означает:

  1. Проверять параметр err везде, где он присутствует, и передавать ошибку обратно вызывающей стороне.
  2. Получать возможные ошибки из JSON.parse()
  3. Проверить http statusCode.

Вот этот исправленный код:

function getMunicipalityJsonUrls(provinceName, callback) {
    request({ uri: `https://www.gemeentegeschiedenis.nl/provincie/json/${provinceName}` }, (error, response, body) => {
        if (err) {
            callback(err);
            return;
        }
        if (response.statusCode !== 200) {
            callback(new Error(`http status code ${response.statusCode}`));
            return;
        }
        try {
            const jsonUrls = JSON.parse(body).map(url => {
                let urlSplit = url.split("gemeentenaam");
                let jsonUrl = urlSplit[0] + "gemeentenaam/json" + urlSplit[1];
                return jsonUrl.replace("http://", "https://");
            });
            callback(null, jsonUrls);
        } catch(e) {
            callback(e);
        }
    });
}

function getGelderlandJsonUrls(callback) {
    getMunicipalityJsonUrls("Gelderland", (err, data) => {
        if (err) {
            callback(err);
        } else {
            callback(null, data);
        }
    });
}

Затем в writeToFile() добавьте обработку ошибок и мониторинг завершения, и я решил обернуть его в обещание, а не простой обратный вызов, потому что я хочу использовать его с некоторыми утилитами, которые работают с обещаниями.

function writeToFile(url) {
    return new Promise((resolve, reject) => {
        // get name to make each new file unique
        var name = url.split("json/")[1];
        var fileStream = fs.createWriteStream(`jsonFiles/${name}.json`);
        fileStream.on('error', (e) => {
            reject(e);
        });
        var options = {
            url: `${url}`,
            method: 'GET',
            headers: {
                'Accept': 'application/json',
                'Accept-Charset': 'utf-8',
                json: true
            }
        }
        //request the data from the site and download to the file.
        request.get(options).pipe(fileStream).on('error', (e) => {
            reject(e);
        }).on('finish', () => {
            resolve(url);
        });
    });
}

Теперь нам нужно решить, как l oop пройти по всем URL-адресам. Если какой-либо из URL-адресов когда-либо пытался записать в один и тот же файл (если это даже удаленная возможность), то вам нужно сериализовать URL-адреса, чтобы они не могли выполнять более одной асинхронной операции, пытаясь записать в один и тот же файл в в то же время, потому что это просто испортит этот файл. Итак, если бы это было так, вы могли бы сериализовать запись в файл следующим образом:

// option 1 - serialize writing to files
async function getMunicipalityGeoJsonData(req, res) {
    //Get all the urls pointing to the JSON data for the province, Gelderland
    getGelderlandJsonUrls((err, jsonUrls) => {
        if (err) {
            console.log(err);
            res.sendStatus(500);
        } else {
            try {
                //for all those urls, write the data to files.
                for (url of jsonUrls) {
                    console.log(url);
                    await writeToFile(url);
                }
                res.send("All done");
            } catch(e) {
                console.log(e);
                res.sendStatus(500);
            }
        }
    });
}

Если вы абсолютно уверены, что ни один из этих URL-адресов не приведет к записи в тот же файл, то вы можете запускайте N из них в то время, когда вы определяете, какое наименьшее значение N дает вам достойную производительность. Более высокие значения N потребляют больше пиковых ресурсов (памяти и файловых дескрипторов). Более низкие значения N запускают меньше вещей параллельно. Если целевые имена хостов являются одним и тем же сервером, то обычно вы не хотите, чтобы N было больше, чем около 5. Если целевые хосты, с которых вы извлекаете данные, все разные, вы можете поэкспериментировать со значениями N до, возможно, 20.

// option 2 - run N at a time in parallel
function getMunicipalityGeoJsonData(req, res) {
    //Get all the urls pointing to the JSON data for the province, Gelderland
    getGelderlandJsonUrls((err, jsonUrls) => {
        if (err) {
            console.log(err);
            res.sendStatus(500);
        } else {
            //for all those urls, write the data to files.
            const numConcurrent = 5;
            mapConcurrent(jsonUrls, numConcurrent, writeToFile).then(() => {
                res.send("All done");
            }).catch(err => {
                console.log(err);
                res.sendStatus(500);
            });
        }
    })
}

Функция mapConcurrent() взята из этого ответа Promise.all потребляет всю мою оперативную память и работает следующим образом. Он ожидает, что вы передадите ему массив элементов для итерации, максимальное количество, которое вы хотите в полете одновременно, и функцию, которая будет передавать элемент массива и будет возвращать обещание, связанное с ним, когда это будет сделано или возникнет ошибка:

function mapConcurrent(items, maxConcurrent, fn) {
    let index = 0;
    let inFlightCntr = 0;
    let doneCntr = 0;
    let results = new Array(items.length);
    let stop = false;

    return new Promise(function(resolve, reject) {

        function runNext() {
            let i = index;
            ++inFlightCntr;
            fn(items[index], index++).then(function(val) {
                ++doneCntr;
                --inFlightCntr;
                results[i] = val;
                run();
            }, function(err) {
                // set flag so we don't launch any more requests
                stop = true;
                reject(err);
            });
        }

        function run() {
            // launch as many as we're allowed to
            while (!stop && inflightCntr < maxConcurrent && index < items.length) {
                runNext();
            }
            // if all are done, then resolve parent promise with results
            if (doneCntr === items.length) {
                resolve(results);
            }
        }

        run();
    });
}

В Bluebird Promise.map() и в библиотеке Asyn c есть сопоставимые функции.


Итак, используя этот код, вы теперь можете контролировать, сколько ваших запросов / writeToFile () выполняются одновременно, и вы регистрируете и регистрируете все возможные ошибки. Делайте, вы можете настроить, сколько может быть в полете одновременно, для лучшей производительности и минимального использования ресурсов, и, если есть какие-либо ошибки, вы должны регистрировать эти ошибки, чтобы вы могли отлаживать.

Этот код в настоящее время настроен на прекращение обработки любых дальнейших URL-адресов, если получит ошибку. Вы можете изменить это, если хотите перейти к другим URL-адресам, если вы получили ошибку, настроив mapConcurrent(). Но я все равно должен убедиться, что вы регистрируете любые ошибки, чтобы вы знали, когда они есть, и можете выяснить, почему вы видите ошибки.


Еще одно примечание. Если бы это был мой код, я бы конвертировал все в обещания (без простых обратных вызовов) и использовал бы библиотеку got() вместо библиотеки , которая сейчас не поддерживается request(). Я не пишу новый код, используя библиотеку request().

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...