Загрузка файла по частям из React в Node-Express
Практически в каждом веб-приложении мы сталкиваемся с ситуацией, когда нам нужно загрузить файлы на сервер.
Одним из самых простых способов является использование input type="file"
и отправка на сервер в виде блоба.
Но иногда отправка файлов по частям может быть полезна для производительности, безопасности и других целей, например, для отображения процента завершения загрузки.
В этом руководстве мы будем загружать файл из приложения React на сервер в Node и Express.
Шаг 1: Настройка приложения React
Я использую react-formvik для быстрой настройки формы, я создам input type="file"
.
import React from 'react';
import { Form } from 'react-formvik';
const App = () => {
return <Form
config={
{
fields: [
{
field: 'avatar',
css: {
containerClass: 'mt-4',
inputClass: 'form-control',
labelClass: 'form-label'
},
inputProps: {
type: 'file'
},
label: 'Upload'
}
],
css: {
formContainerClass: 'container'
}
}
}
onChange={async ({ avatar }) => {
//We will add code here in further steps
}
}
export default App;
Я также установил бутстрап для стилизации нашего поля ввода.
Добавьте нижеприведенную строку в index.js
или можете импортировать ее в css-файл.
import 'bootstrap/dist/css/bootstrap.css';
Предварительный просмотр
Шаг 2: Настройка сервера
В приложении Express нам понадобится мультер, npm i -S multer
.
Добавьте следующую строку в файл вашего сервера
const multer = require('multer');
const storage = multer.memoryStorage();
const upload = multer({ storage });
Убедитесь, что в приложении Express настроен парсер тел.
Создайте один маршрут для загрузки
app.post('upload', (req, res) => {
//We will add code here in further steps
});
Шаг 3: Понимание подхода к разбитию файлов на части
Итак, мы выполним следующие шаги:
- Мы нажмем кнопку
browse
на пользовательском интерфейсе и загрузим файл (изображение). - В компоненте React в методе
onChange
мы получим файл-блоб. - Мы сохраним одну константу для размера чанка (скажем, 1024 байта).
- Зная размер чанка (
chunkSize
) и размер файла (File Size
), мы можем определить, сколько чанков нам нужно создать. - Мы создадим один цикл и нарежем блоб по размеру чанка, и на каждой итерации будем отправлять очередной чанк на сервер.
- Это означает, что мы будем обращаться к серверу несколько раз для одного и того же маршрута "
/upload
". О подходе к бэкенду (узлу) мы поговорим в следующих шагах.
Шаг 4: Внедрение чанкинга в метод onChange
Теперь, когда мы поняли, как будем реализовывать, можно приступать к написанию кода.
Сначала нам нужно получить информацию о файле в событии onChange
.
onChange={async ({ avatar }) => {
const chunkSize = 1024;
if (avatar) {
// we will proceed if we have files here
}
}
Далее давайте определим общее количество необходимых нам чанков.
onChange={async ({ avatar }) => {
const chunkSize = 1024;
if (avatar) {
const totalChunks = Math.ceil(avatar[0].size/chunkSize);
}
}
Создайте цикл для итерации по каждому чанку.
onChange={async ({ avatar }) => {
const chunkSize = 1024* 100;
if (avatar) {
const totalChunks = Math.ceil(avatar[0].size/chunkSize);
for(let i=0; i<totalChunks; i++) {
const start = i*chunkSize;
const end = (i + 1) * chunkSize;
await sendChunk(start,end);
}
}
}
Создадим метод sendChunk
для отправки чанка на сервер.
onChange={async ({ avatar }) => {
const chunkSize = 1024;
if (avatar) {
const totalChunks = Math.ceil(avatar[0].size/chunkSize);
const sendChunk = async (start,end) =>{
const formData = new FormData();
const blobSlice = avatar[0].slice(start, end);
formData.append('file', blobSlice, avatar[0].name);
return await fetch('http://localhost:3000/upload', {
method: 'POST',
body: formData
});
}
for(let i=0; i<totalChunks; i++) {
const start = i*chunkSize;
const end = (i + 1) * chunkSize;
await sendChunk(start,end);
}
}
}
Шаг 5: Реализация сервера
app.post('/upload', upload.single('file'), (req, res) => {
const { buffer, originalname } = req.file;
const writeStream = fs.createWriteStream(originalname, { flags: 'a' });
writeStream.write(buffer);
writeStream.end();
writeStream.on('finish', () => {
res.status(200).send('File received successfully.');
});
// Event listener for any errors during the write operation
writeStream.on('error', (err) => {
console.error(err);
res.status(500).send('Internal Server Error');
});
Давайте разберемся в приведенном выше коде,
upload.single('file')
- промежуточное программное обеспечение для мультера, которое ожидает данные формы с именем файлаfile
.- В
fs.createWriteStream
мы добавили флаг "a
", который будет фактически добавлять буфер в конец файла каждый раз, когда он получает запрос.
Шаг 6: Тестирование
Консоль сервера:
Сетевые запросы:
Загруженный файл:
Может быть много других подходов, если вы знаете какой-то, поделитесь со мной.
Спасибо, что прочитали это!