Как повторить запрос на основе заголовков ответов, используя request и nodejs?

#node.js #typescript #request #response-headers

#node.js #typescript #запрос #заголовки ответа

Вопрос:

У меня есть небольшой скрипт nodejs, и я хочу загрузить zip-файл:

 const fs = requrie("fs");
const request = require("requestretry");

export class FileFetcher {
    public async fetchFile(url: string): Promise<string> {
        const fileName = "./download.zip";
        return new Promise<string>(resolve => {
            request(url)
                .pipe(fs.createWriteStream(fileName))
                .on("close", function () {
                    resolve(fileName);
                });
        });
    }
}
  

Я использую requestretry библиотеку, оболочку вокруг request , поскольку файл может существовать только в будущем и, следовательно, скорее всего, потерпит неудачу в первые пару раз.

Тем не менее, я должен адаптироваться к моей странно ведущей себя внешней конечной точке. Он всегда возвращает a 200 OK вместо ожидаемого 404 Not Found .

Следовательно, единственный способ определить — это Content-Length заголовок в ответе. Если Content-Length: 0 тогда нет файла.

Похоже, существуют разные стратегии повторных requestretry попыток, но все они, похоже, предполагают хорошо обработанный API, например request.RetryStrategies.HTTPOrNetworkError . Поскольку он получает 200, он никогда не повторит попытку и «успешно» загрузит пустой zip-файл.

Я не привязан к использованию requestretry , мне интересно, как повторить запрос на основе заголовков ответов.

Ответ №1:

Вы могли бы определить свою собственную логику повторных попыток с помощью RetryStrategy, например:

 request({
  url: '...',
  retryStrategy: function(err, res, body) {
    return res.headers['Content-Length'] === 0;
  }
});
  

Ответ №2:

В итоге я отказался от requestretry библиотеки request и самостоятельно внедрил механизм повторных попыток.

Одна из проблем заключалась в получении фактических данных тела ответа. Я ожидал, что он будет доступен через response событие, но у него есть только заголовок.

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

Еще одна ошибка заключалась в понимании того, как запрос должен быть отправлен, поскольку encoding должен быть нулевым, или загруженный файл преобразуется в строку и, следовательно, поврежден.

Мой класс downloader выглядит так:

 export class FileFetcher {
    public async downloadWithRetry(url: string, maxRetries: number, timeout: number): Promise<Buffer> {
        while (true) {
            try {
                const buffer = await this.fetchFile(url);
                return new Promise<Buffer>(resolve => {
                    resolve(buffer);
                });
            } catch (e) {
                maxRetries = maxRetries - 1;

                if (maxRetries <= 0) {
                    throw new Error("Too many requests");
                }

                console.warn(`No file at url:${url}, waiting ...`);
                await this.wait(timeout);
                console.log(`Continue.`);
            }
        }
    }

    private async wait(timeout: number): Promise<any> {
        timeout *= 1e3;
        console.log(timeout);
        return new Promise(resolve => {
            setTimeout(resolve, timeout);
        });
    }

    private async fetchFile(url: string): Promise <Buffer> {
        return new Promise<Buffer>((resolve, reject) => {
            let data = [];
            request.get({
                encoding: null,
                url: url,
            }).on("data", function (chunk) {
                data.push(chunk);
            }).on("response", function (response) {
                /**
                 * Server always returns 200 OK even if file does not exist yet. Hence checking for content-lenth head
                 */
                if (!(response.headers["content-length"] > 0)) {
                    reject("Empty response!");
                }
            }).on("end", function () {
                const body = Buffer.concat(data);
                if (body.length > 0) {
                    resolve(body);
                    return;
                }

                reject("Empty response");
            });
        });
    }
}
  

Этот буфер может быть записан через fs.writeFile(file, buffer) .

Недостатком является то, что я больше не использую потоковый подход, поскольку я хотел передавать данные. Тем не менее, этот подход, по крайней мере, правильно извлекает файл.