#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)
.
Недостатком является то, что я больше не использую потоковый подход, поскольку я хотел передавать данные. Тем не менее, этот подход, по крайней мере, правильно извлекает файл.