如何使用请求和 nodejs 根据响应 headers 重试请求?
How to retry a request based on response headers using request and nodejs?
我有一个小的 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
library, a wrapper around request
,因为该文件可能只在将来存在,因此很可能会在前几次失败。
但是我必须适应我行为异常的外部端点。它总是 returns 一个 200 OK
而不是预期的 404 Not Found
.
因此,唯一的确定方法是响应中的Content-Length
header。如果 Content-Length: 0
则没有文件。
requestretry
中似乎有不同的重试策略,但它们似乎都假设 well-beaved api,例如request.RetryStrategies.HTTPOrNetworkError
。因为它得到一个 200,它永远不会重试并且 "successfully" 下载一个空的 zip 文件。
我不固定使用 requestretry
,我想知道如何根据响应 headers 重试请求。
您可以使用 retryStrategy 定义自己的重试逻辑,例如:
request({
url: '...',
retryStrategy: function(err, res, body) {
return res.headers['Content-Length'] === 0;
}
});
我最终放弃了 requestretry
库并使用 request
并自己实现了重试机制。
一个问题是获取实际响应 body 数据。我希望它可以通过 response
事件获得,但它只有 header.
可以通过多个数据事件获取数据并解决结束事件的承诺。
另一个问题是理解如何发送请求,因为 encoding
需要为 null 或下载的文件被强制转换为字符串并因此损坏。
我的下载器 class 看起来像:
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)
.
写入该缓冲区
缺点是不再使用流方法,因为我想通过管道传输数据。然而,这种方法至少可以正确获取文件。
我有一个小的 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
library, a wrapper around request
,因为该文件可能只在将来存在,因此很可能会在前几次失败。
但是我必须适应我行为异常的外部端点。它总是 returns 一个 200 OK
而不是预期的 404 Not Found
.
因此,唯一的确定方法是响应中的Content-Length
header。如果 Content-Length: 0
则没有文件。
requestretry
中似乎有不同的重试策略,但它们似乎都假设 well-beaved api,例如request.RetryStrategies.HTTPOrNetworkError
。因为它得到一个 200,它永远不会重试并且 "successfully" 下载一个空的 zip 文件。
我不固定使用 requestretry
,我想知道如何根据响应 headers 重试请求。
您可以使用 retryStrategy 定义自己的重试逻辑,例如:
request({
url: '...',
retryStrategy: function(err, res, body) {
return res.headers['Content-Length'] === 0;
}
});
我最终放弃了 requestretry
库并使用 request
并自己实现了重试机制。
一个问题是获取实际响应 body 数据。我希望它可以通过 response
事件获得,但它只有 header.
可以通过多个数据事件获取数据并解决结束事件的承诺。
另一个问题是理解如何发送请求,因为 encoding
需要为 null 或下载的文件被强制转换为字符串并因此损坏。
我的下载器 class 看起来像:
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)
.
缺点是不再使用流方法,因为我想通过管道传输数据。然而,这种方法至少可以正确获取文件。