如何使用request和nodejs根据响应头重试请求?

时间:2016-09-29 15:27:46

标签: node.js typescript request response-headers

我有一个小的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的包装器,因为该文件可能仅在将来存在,因此很可能在前几次失败。

然而,我必须适应我奇怪的行为外部终点。它始终返回200 OK而不是预期的404 Not Found

因此,确定的唯一方法是响应中的Content-Length标头。如果Content-Length: 0则没有文件。

requestretry中似乎有不同的重试策略,但它们似乎都假设一个良好的api,例如request.RetryStrategies.HTTPOrNetworkError。由于它获得了200,它将永远不会重试并且成功地#34;下载一个空的zip文件。

我没有使用requestretry,我想知道如何根据响应标头重试请求。

2 个答案:

答案 0 :(得分:1)

您可以使用retryStrategy定义自己的重试逻辑,例如:

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

答案 1 :(得分:0)

我最后放弃了requestretry库,然后选择了request并自行实施了重试机制。

一个问题是获取实际的响应正文数据。我希望它可以通过response事件获得,但这只有标题。

可以通过多个数据事件获取数据并解析结束事件的承诺。

另一个问题是了解如何发送请求,因为encoding需要为null,或者下载的文件被转换为字符串并因此损坏。

我的下载程序类如下:

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)写入。

Downnside不再使用流方法,因为我想管道数据。然而,这种方法至少可以正确地获取文件。