Это довольно просто. Используя этот код, любой загруженный файл изображения будет поврежден и не может быть открыт. PDF-файлы выглядят нормально, но я заметил, что он вводит значения в текстовые файлы. Это правильный размер файла в s3, а не нулевой, как будто что-то пошло не так. Я не уверен, что это проблема с Express, SDK или их комбинацией? Это Почтальон? Я построил нечто подобное в рабочем проекте в марте этого года, и оно работало безупречно. У меня больше нет доступа к этому коду для сравнения.
Никаких ошибок, никаких признаков каких-либо проблем.
const aws = require("aws-sdk");
const stream = require("stream");
const express = require("express");
const router = express.Router();
const AWS_ACCESS_KEY_ID = "XXXXXXXXXXXXXXXXXXXX";
const AWS_SECRET_ACCESS_KEY = "superSecretAccessKey";
const BUCKET_NAME = "my-bucket";
const BUCKET_REGION = "us-east-1";
const s3 = new aws.S3({
region: BUCKET_REGION,
accessKeyId: AWS_ACCESS_KEY_ID,
secretAccessKey: AWS_SECRET_ACCESS_KEY
});
const uploadStream = key => {
let streamPass = new stream.PassThrough();
let params = {
Bucket: BUCKET_NAME,
Key: key,
Body: streamPass
};
let streamPromise = s3.upload(params, (err, data) => {
if (err) {
console.error("ERROR: uploadStream:", err);
} else {
console.info("INFO: uploadStream:", data);
}
}).promise();
return {
streamPass: streamPass,
streamPromise: streamPromise
};
};
router.post("/upload", async (req, res) => {
try {
let key = req.query.file_name;
let { streamPass, streamPromise } = uploadStream(key);
req.pipe(streamPass);
await streamPromise;
res.status(200).send({ result: "Success!" });
} catch (e) {
res.status(500).send({ result: "Fail!" });
}
});
module.exports = router;
Вот мой package.json:
{
"name": "expresss3streampass",
"version": "0.0.0",
"private": true,
"scripts": {
"start": "node ./bin/www"
},
"dependencies": {
"aws-sdk": "^2.812.0",
"cookie-parser": "~1.4.4",
"debug": "~2.6.9",
"express": "~4.16.1",
"morgan": "~1.9.1"
}
}
ОБНОВЛЯТЬ:
После дальнейшего тестирования я заметил, что Postman изменяет текстовые файлы. Например, этот исходный файл:
{
"question_id": null,
"position_type_id": 1,
"question_category_id": 1,
"position_level_id": 1,
"question": "Do you test your code before calling it \"done\"?",
"answer": "Candidate should respond that they at least happy path test every feature and bug fix they write.",
"active": 1
}
... выглядит так после попадания в ведро:
----------------------------472518836063077482836177
Content-Disposition: form-data; name = "file"; filename = "question.json"
Content-Type: application/json
{
"question_id": null,
"position_type_id": 1,
"question_category_id": 1,
"position_level_id": 1,
"question": "Do you test your code before calling it \"done\"?",
"answer": "Candidate should respond that they at least happy path test every feature and bug fix they write.",
"active": 1
}
----------------------------472518836063077482836177--
Я должен думать, что это проблема. Почтальон — единственное, что изменилось в этом уравнении с тех пор, как этот код впервые заработал у меня. Заголовки моих запросов выглядят так:
Я был тем, кто изначально добавил заголовок «application/x-www-form-urlencoded». Если я использую это сейчас, я получаю файл с 0 байтами в ведре.
@relief.melone Да, это потоковая передача файлов, а не вынос контейнера с файлом, который может быть слишком большим, или блокировка потока при большой загрузке.
Вы также можете использовать потоки с multer вместо того, чтобы сначала загружать его в контейнер. Я не уверен, но я был бы уверен, что multer-s3-storage делает именно это, и я делаю то же самое в механизме хранения, который я написал для multer gitlab.com/relief-melone/multer-s3-sharp- изменение размера. Однако я до сих пор не уверен, что вызывает проблемы с вашим кодом, поскольку мой подход почти такой же, как у вас (и я также использую Postman для тестирования). Я все равно посмотрю на это поближе, как только у меня будет время. Я понимаю, что вы не только хотите, чтобы все заработало, но и понимаете, что идет не так.
Цель вашего сервера - только забрать файл у пользователя и поместить его на S3?
Насколько я могу судить, Postman ведет себя так, как должен — «вставка текста» на самом деле является веб-стандартом, используемым для идентификации/разграничения файлов при загрузке. Пожалуйста, ознакомьтесь с этим веб-документом MDN , а также этим, чтобы понять почему.
На самом деле эта часть внедряется независимо от типа файла:
let streamPass = new stream.PassThrough();
// adding this
const chunks = [];
streamPass.on('data', (chunk) => chunks.push(chunk) );
streamPass.on("end", () => {
body = Buffer.concat(chunks).toString();
console.info(chunks, chunks.length)
console.info("finished", body); // <-- see it here
});
Я попробовал несколько методов, чтобы контролировать/изменить это, но не повезло с простым методом - со стороны Postman, я не думаю, что это параметр, который можно изменить, и со стороны NodeJS... Я имею в виду, что это возможно, но решение, скорее всего, будет неуклюжим/сложным, что, как я подозреваю, вам не нужно. (хотя могу ошибаться...)
Учитывая вышеизложенное, я присоединяюсь к @relief.melone и рекомендую multer как простое решение.
Если вы хотите использовать multer с streams, попробуйте следующее: (я указал, где я внес изменения в ваш код):
// const uploadStream = (key) => {
const uploadStream = (key, mime_type) => { // <- adding the mimetype
let streamPass = new stream.PassThrough();
let params = {
Bucket: BUCKET_NAME,
Key: key,
Body: streamPass,
ACL: 'public-read', // <- you can remove this
ContentType: mime_type // <- adding the mimetype
};
let streamPromise = s3.upload(params, (err, data) => {
if (err) {
console.error("ERROR: uploadStream:", err);
} else {
console.info("INFO: uploadStream:", data);
}
}).promise();
return {
streamPass: streamPass,
streamPromise: streamPromise
};
};
// router.post("/upload", async (req, res) => {
router.post("/upload", multer().single('file'), async (req, res) => { // <- we're adding multer
try {
let key = req.query.file_name;
// === change starts here
// console.info(req.file); // <- if you want to see, uncomment this file
let { streamPass, streamPromise } = uploadStream(key, req.file.mimetype); // adding the mimetype
var bufferStream = new stream.PassThrough();
bufferStream.end(req.file.buffer);
bufferStream.pipe(streamPass); // no longer req.pipe(streamPass);
// === change ends here
await streamPromise;
res.status(200).send({ result: "Success!" });
} catch (e) {
console.info(e)
res.status(500).send({ result: "Fail!" });
}
});
Отличный ответ, спасибо! Это отлично сработало для меня, однако я все еще немного обеспокоен тем, что это не полностью потоковая передача. Как отметил Ричард Данн выше, не потребуется ли специальный обработчик для фактической потоковой передачи данных?
Рад, что сработало 🎉. Я на пороге пользовательского обработчика (кстати, я также нашел эту статью, где кто-то использовал аналогичную настройку, но для потоковой передачи в Google Cloud: medium.com/better-programming/…) Но @Richard Dunn может быть прав. Поскольку Multer теперь выглядит хорошо для вас, я бы порекомендовал прочитать репозиторий Multer (он хорошо объяснен) и продолжить тестирование, чтобы увидеть, что работает для вас.
Мультер - это то, что нужно.
Он предоставляет несколько различных режимов, но, насколько я могу судить, вам нужно написать собственный обработчик хранилища, чтобы получить доступ к базовому потоку, иначе он будет буферизовать все данные в памяти и выполнять обратный вызов только после того, как это будет сделано. .
Если вы отметите req.file в своем обработчике маршрута, Multer обычно предоставляет буфер под полем buffer, но его больше нет, так как я ничего не передаю в обратном вызове, поэтому я достаточно уверен, что поток идет так, как ожидалось.
Ниже приведено рабочее решение.
Примечание: parse.single('image') передается в обработчик маршрута. Это относится к имени поля из нескольких частей, которое я использовал.
const aws = require('aws-sdk');
const stream = require('stream');
const express = require('express');
const router = express.Router();
const multer = require('multer')
const AWS_ACCESS_KEY_ID = "XXXXXXXXXXXXXXXXXXXX";
const AWS_SECRET_ACCESS_KEY = "superSecretAccessKey";
const BUCKET_NAME = "my-bucket";
const BUCKET_REGION = "us-east-1";
const s3 = new aws.S3({
region: BUCKET_REGION,
accessKeyId: AWS_ACCESS_KEY_ID,
secretAccessKey: AWS_SECRET_ACCESS_KEY
});
const uploadStream = key => {
let streamPass = new stream.PassThrough();
let params = {
Bucket: BUCKET_NAME,
Key: key,
Body: streamPass
};
let streamPromise = s3.upload(params, (err, data) => {
if (err) {
console.error('ERROR: uploadStream:', err);
} else {
console.info('INFO: uploadStream:', data);
}
}).promise();
return {
streamPass: streamPass,
streamPromise: streamPromise
};
};
class CustomStorage {
_handleFile(req, file, cb) {
let key = req.query.file_name;
let { streamPass, streamPromise } = uploadStream(key);
file.stream.pipe(streamPass)
streamPromise.then(() => cb(null, {}))
}
}
const storage = new CustomStorage();
const parse = multer({storage});
router.post('/upload', parse.single('image'), async (req, res) => {
try {
res.status(200).send({ result: 'Success!' });
} catch (e) {
console.info(e)
res.status(500).send({ result: 'Fail!' });
}
});
module.exports = router;
Решение на основе Multer , которое я представил выше, немного хакерское. Поэтому я заглянул под капот, чтобы увидеть как это работает . Это решение просто использует Busboy для анализа и потоковой передачи файла. Multer на самом деле является просто оболочкой для этого с некоторыми удобными функциями дискового ввода-вывода.
const aws = require('aws-sdk');
const express = require('express');
const Busboy = require('busboy');
const router = express.Router();
const AWS_ACCESS_KEY_ID = "XXXXXXXXXXXXXXXXXXXX";
const AWS_SECRET_ACCESS_KEY = "superSecretAccessKey";
const BUCKET_NAME = "my-bucket";
const BUCKET_REGION = "us-east-1";
const s3 = new aws.S3({
region: BUCKET_REGION,
accessKeyId: AWS_ACCESS_KEY_ID,
secretAccessKey: AWS_SECRET_ACCESS_KEY
});
function multipart(request){
return new Promise(async (resolve, reject) => {
const headers = request.headers;
const busboy = new Busboy({ headers });
// you may need to add cleanup logic using 'busboy.on' events
busboy.on('error', err => reject(err))
busboy.on('file', function (fieldName, fileStream, fileName, encoding, mimeType) {
const params = {
Bucket: BUCKET_NAME,
Key: fileName,
Body: fileStream
};
s3.upload(params).promise().then(() => resolve());
})
request.pipe(busboy)
})
}
router.post('/upload', async (req, res) => {
try {
await multipart(req)
res.status(200).send({ result: 'Success!' });
} catch (e) {
console.info(e)
res.status(500).send({ result: 'Fail!' });
}
});
module.exports = router;
Лучшее решение сейчас не работает с AWS, оно возвращает ошибку: «Предоставленный вами заголовок подразумевает функциональность, которая не реализована». Может какие-то данные заголовка пропущены при разрешении Busboy??
Есть ли конкретная причина, по которой вы не хотите использовать multer?