[Node.JS] NestJS. Загрузка файлов в S3 хранилище (minio)
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
NestJS — фреймворк для создания эффективных, масштабируемых серверных приложений на платформе Node.js. Вы можете встретить утверждение, что NestJS является платформо-независимым фреймворком. Имеется в виду, что он может работать на базе одного из двух фрейморков по Вашему выбору: NestJS+Express или NestJS+Fastify. Это действительно так, или почти так. Эта платформо-независимость заканчивается, на обработке запросов Content-Type: multipart/form-data. То есть практически на второй день разработки. И это не является большой проблемой, если Вы используете платформу NestJS+Express — в документации есть пример работы для Content-Type: multipart/form-data. Для NestJS+Fastify такого примера нет, и примеров в сети не так уж и много. И некоторые из этих примеров идут по весьма усложненному пути.
Выбирая между платформой NestJS+Fastify и NestJS+Express я сделал выбор в сторону NestJS+Fastify. Зная склонность разработчиков в любой непонятной ситуации вешать на объект req в Express дополнительные свойства и так общаться между разными частями приложения, я твердо решил что Express в следующем проекте не будет.
Только нужно было решить технический вопрос с Content-Type: multipart/form-data. Также полученные через запросы Content-Type: multipart/form-data файлы я планировал сохранять в хранилище S3. В этом плане реализация запросов Content-Type: multipart/form-data на платформе NestJS+Express меня смущала тем, что не работала с потоками.
Запуск локального хранилища S3
S3 — это хранилище данных (можно сказать, хотя не совсем строго, хранилище файлов), доступное по протоколу http. Изначально S3 предоставлялся AWS. В настоящее время API S3 поддерживается и другими облачными сервисами. Но не только. Появились реализации серверов S3, которые Вы можете поднять локально, чтобы использовать их во время разработки, и, возможно, поднять свои серверы S3 для работы на проде.
Для начала нужно определиться с мотивацией использования S3 хранилища данных. В некоторых случаях это позволяет снизить затраты. Например, для хранения резервных копий можно взять самые медленные и дешевые хранилища S3. Быстрые хранилища с высоким трафиком (трафик тарифицируется отдельно) на загрузку данных из хранилища, возможно, будут стоить сравнимо с SSD дисками того же объема.
Более весомым мотивом является 1) расширяемость — Вам не нужно думать о том, что место на диске может закончиться, и 2) надежность — сервера работают в кластере и Вам не нужно думать о резервном копировании, так как необходимое количество копий есть всегда.
Для поднятия реализации серверов S3 — minio — локально нужен только установленный на компьютере docker и docker-compose. Соответсвующий файл docker-compose.yml:
version: '3'
services:
minio1:
image: minio/minio:RELEASE.2020-08-08T04-50-06Z
volumes:
- ./s3/data1-1:/data1
- ./s3/data1-2:/data2
ports:
- '9001:9000'
environment:
MINIO_ACCESS_KEY: minio
MINIO_SECRET_KEY: minio123
command: server http://minio{1...4}/data{1...2}
healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost:9000/minio/health/live']
interval: 30s
timeout: 20s
retries: 3
minio2:
image: minio/minio:RELEASE.2020-08-08T04-50-06Z
volumes:
- ./s3/data2-1:/data1
- ./s3/data2-2:/data2
ports:
- '9002:9000'
environment:
MINIO_ACCESS_KEY: minio
MINIO_SECRET_KEY: minio123
command: server http://minio{1...4}/data{1...2}
healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost:9000/minio/health/live']
interval: 30s
timeout: 20s
retries: 3
minio3:
image: minio/minio:RELEASE.2020-08-08T04-50-06Z
volumes:
- ./s3/data3-1:/data1
- ./s3/data3-2:/data2
ports:
- '9003:9000'
environment:
MINIO_ACCESS_KEY: minio
MINIO_SECRET_KEY: minio123
command: server http://minio{1...4}/data{1...2}
healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost:9000/minio/health/live']
interval: 30s
timeout: 20s
retries: 3
minio4:
image: minio/minio:RELEASE.2020-08-08T04-50-06Z
volumes:
- ./s3/data4-1:/data1
- ./s3/data4-2:/data2
ports:
- '9004:9000'
environment:
MINIO_ACCESS_KEY: minio
MINIO_SECRET_KEY: minio123
command: server http://minio{1...4}/data{1...2}
healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost:9000/minio/health/live']
interval: 30s
timeout: 20s
retries: 3
Запускаем — и без проблем получаем кластер из 4 серверов S3.
NestJS + Fastify + S3
Работу с сервером NestJS опишу с самых первых шагов, хотя часть этого материала отлично описана в документации. Устанавливается CLI NestJS:
npm install -g @nestjs/cli
Создается новый проект NestJS:
nest new s3-nestjs-tut
Инсталлируются необходимые пакеты (включая те что нужны для работы с S3):
npm install --save @nestjs/platform-fastify fastify-multipart aws-sdk sharp
npm install --save-dev @types/fastify-multipart @types/aws-sdk @types/sharp
По умолчанию в проекте устанавливается платформа NestJS+Express. Как установить Fastify описано в документации docs.nestjs.com/techniques/performance. Дополнительно нам нужно установить плагин для обработки Content-Type: multipart/form-data — fastify-multipart
import { NestFactory } from '@nestjs/core';
import {
FastifyAdapter,
NestFastifyApplication,
} from '@nestjs/platform-fastify';
import fastifyMultipart from 'fastify-multipart';
import { AppModule } from './app.module';
async function bootstrap() {
const fastifyAdapter = new FastifyAdapter();
fastifyAdapter.register(fastifyMultipart, {
limits: {
fieldNameSize: 1024, // Max field name size in bytes
fieldSize: 128 * 1024 * 1024 * 1024, // Max field value size in bytes
fields: 10, // Max number of non-file fields
fileSize: 128 * 1024 * 1024 * 1024, // For multipart forms, the max file size
files: 2, // Max number of file fields
headerPairs: 2000, // Max number of header key=>value pairs
},
});
const app = await NestFactory.create<NestFastifyApplication>(
AppModule,
fastifyAdapter,
);
await app.listen(3000, '127.0.0.1');
}
bootstrap();
Теперь опишем сервис, загружающий файлы в хранилище S3, сократив код по обработке некоторых видов ошибок (полный текст есть в репозитарии статьи):
import { Injectable, HttpException, BadRequestException } from '@nestjs/common';
import { S3 } from 'aws-sdk';
import fastify = require('fastify');
import { AppResponseDto } from './dto/app.response.dto';
import * as sharp from 'sharp';
@Injectable()
export class AppService {
async uploadFile(req: fastify.FastifyRequest): Promise<any> {
const promises = [];
return new Promise((resolve, reject) => {
const mp = req.multipart(handler, onEnd);
function onEnd(err) {
if (err) {
reject(new HttpException(err, 500));
} else {
Promise.all(promises).then(
data => {
resolve({ result: 'OK' });
},
err => {
reject(new HttpException(err, 500));
},
);
}
}
function handler(field, file, filename, encoding, mimetype: string) {
if (mimetype && mimetype.match(/^image\/(.*)/)) {
const imageType = mimetype.match(/^image\/(.*)/)[1];
const s3Stream = new S3({
accessKeyId: 'minio',
secretAccessKey: 'minio123',
endpoint: 'http://127.0.0.1:9001',
s3ForcePathStyle: true, // needed with minio?
signatureVersion: 'v4',
});
const promise = s3Stream
.upload(
{
Bucket: 'test',
Key: `200x200_${filename}`,
Body: file.pipe(
sharp()
.resize(200, 200)
[imageType](),
),
}
)
.promise();
promises.push(promise);
}
const s3Stream = new S3({
accessKeyId: 'minio',
secretAccessKey: 'minio123',
endpoint: 'http://127.0.0.1:9001',
s3ForcePathStyle: true, // needed with minio?
signatureVersion: 'v4',
});
const promise = s3Stream
.upload({ Bucket: 'test', Key: filename, Body: file })
.promise();
promises.push(promise);
}
});
}
}
Из особенностей следует отметить, что мы пишем входной поток в два выходных потока, если загружается картинка. Один из потоков сжимает картинку до размеров 200х200. Во всех случаях используется стиль работы с потоками (stream). Но для того, чтобы отловить возможные ошибки и вернуть их в контроллер, мы вызываем метод promise(), который определен в библиотеке aws-sdk. Полученные промисы накапливаем в массиве promises:
const promise = s3Stream
.upload({ Bucket: 'test', Key: filename, Body: file })
.promise();
promises.push(promise);
И, далее, ожидаем их разрешение в методе Promise.all(promises).
Код контроллера, в котором таки пришлось пробросить FastifyRequest в сервис:
import { Controller, Post, Req } from '@nestjs/common';
import { AppService } from './app.service';
import { FastifyRequest } from 'fastify';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Post('/upload')
async uploadFile(@Req() req: FastifyRequest): Promise<any> {
const result = await this.appService.uploadFile(req);
return result;
}
}
Запускается проект:
npm run start:dev
Репозитарий статьи github.com/apapacy/s3-nestjs-tut
apapacy@gmail.com
13 августа 2020 года
===========
Источник:
habr.com
===========
Похожие новости:
- [Информационная безопасность, Системное администрирование, Серверное администрирование, DevOps] Как незакрытый Docker API и публичные образы от сообщества используются для распространения майнеров криптовалют (перевод)
- [Разработка веб-сайтов, JavaScript, Node.JS] Архитектура современных корпоративных Node.js-приложений
- [Информационная безопасность, Серверное администрирование, DevOps, Kubernetes] Изучаем (отсутствующую) безопасность типичных установок Docker и Kubernetes (перевод)
- [Big Data, Исследования и прогнозы в IT, Управление персоналом] HR-аналитика: как правильно применять метод 360
- [PHP, Symfony, ReactJS] SSR: рендеринг ReactJS приложения на бекэнде используя PHP
- [JavaScript, Node.JS] 11 Years Of Node.JS: Timeline & Significant Contributions
- [PostgreSQL, Node.JS, Яндекс API, Angular, TypeScript] Пишем full stack монолит с помощью Angular Universal + NestJS + PostgreSQL
- [Администрирование баз данных, Хранение данных, Хранилища данных] Архитектура S3: 3 года эволюции Mail.ru Cloud Storage
- [Open source, Виртуализация, Разработка под Linux, Openshift] Современные приложения на OpenShift, часть 2: связанные сборки chained builds
- [Разработка веб-сайтов, Разработка игр, Разработка мобильных приложений, Разработка под Linux, Разработка под Windows] Свободная веб-энциклопедия для любых IT-проектов на собственном движке
Теги для поиска: #_node.js, #_nestjs, #_nodejs, #_fastify, #_s3, #_awss3, #_minio, #_docker, #_dockercompose, #_express, #_expressjs, #_node.js
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 12:40
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
NestJS — фреймворк для создания эффективных, масштабируемых серверных приложений на платформе Node.js. Вы можете встретить утверждение, что NestJS является платформо-независимым фреймворком. Имеется в виду, что он может работать на базе одного из двух фрейморков по Вашему выбору: NestJS+Express или NestJS+Fastify. Это действительно так, или почти так. Эта платформо-независимость заканчивается, на обработке запросов Content-Type: multipart/form-data. То есть практически на второй день разработки. И это не является большой проблемой, если Вы используете платформу NestJS+Express — в документации есть пример работы для Content-Type: multipart/form-data. Для NestJS+Fastify такого примера нет, и примеров в сети не так уж и много. И некоторые из этих примеров идут по весьма усложненному пути. Выбирая между платформой NestJS+Fastify и NestJS+Express я сделал выбор в сторону NestJS+Fastify. Зная склонность разработчиков в любой непонятной ситуации вешать на объект req в Express дополнительные свойства и так общаться между разными частями приложения, я твердо решил что Express в следующем проекте не будет. Только нужно было решить технический вопрос с Content-Type: multipart/form-data. Также полученные через запросы Content-Type: multipart/form-data файлы я планировал сохранять в хранилище S3. В этом плане реализация запросов Content-Type: multipart/form-data на платформе NestJS+Express меня смущала тем, что не работала с потоками. Запуск локального хранилища S3 S3 — это хранилище данных (можно сказать, хотя не совсем строго, хранилище файлов), доступное по протоколу http. Изначально S3 предоставлялся AWS. В настоящее время API S3 поддерживается и другими облачными сервисами. Но не только. Появились реализации серверов S3, которые Вы можете поднять локально, чтобы использовать их во время разработки, и, возможно, поднять свои серверы S3 для работы на проде. Для начала нужно определиться с мотивацией использования S3 хранилища данных. В некоторых случаях это позволяет снизить затраты. Например, для хранения резервных копий можно взять самые медленные и дешевые хранилища S3. Быстрые хранилища с высоким трафиком (трафик тарифицируется отдельно) на загрузку данных из хранилища, возможно, будут стоить сравнимо с SSD дисками того же объема. Более весомым мотивом является 1) расширяемость — Вам не нужно думать о том, что место на диске может закончиться, и 2) надежность — сервера работают в кластере и Вам не нужно думать о резервном копировании, так как необходимое количество копий есть всегда. Для поднятия реализации серверов S3 — minio — локально нужен только установленный на компьютере docker и docker-compose. Соответсвующий файл docker-compose.yml: version: '3'
services: minio1: image: minio/minio:RELEASE.2020-08-08T04-50-06Z volumes: - ./s3/data1-1:/data1 - ./s3/data1-2:/data2 ports: - '9001:9000' environment: MINIO_ACCESS_KEY: minio MINIO_SECRET_KEY: minio123 command: server http://minio{1...4}/data{1...2} healthcheck: test: ['CMD', 'curl', '-f', 'http://localhost:9000/minio/health/live'] interval: 30s timeout: 20s retries: 3 minio2: image: minio/minio:RELEASE.2020-08-08T04-50-06Z volumes: - ./s3/data2-1:/data1 - ./s3/data2-2:/data2 ports: - '9002:9000' environment: MINIO_ACCESS_KEY: minio MINIO_SECRET_KEY: minio123 command: server http://minio{1...4}/data{1...2} healthcheck: test: ['CMD', 'curl', '-f', 'http://localhost:9000/minio/health/live'] interval: 30s timeout: 20s retries: 3 minio3: image: minio/minio:RELEASE.2020-08-08T04-50-06Z volumes: - ./s3/data3-1:/data1 - ./s3/data3-2:/data2 ports: - '9003:9000' environment: MINIO_ACCESS_KEY: minio MINIO_SECRET_KEY: minio123 command: server http://minio{1...4}/data{1...2} healthcheck: test: ['CMD', 'curl', '-f', 'http://localhost:9000/minio/health/live'] interval: 30s timeout: 20s retries: 3 minio4: image: minio/minio:RELEASE.2020-08-08T04-50-06Z volumes: - ./s3/data4-1:/data1 - ./s3/data4-2:/data2 ports: - '9004:9000' environment: MINIO_ACCESS_KEY: minio MINIO_SECRET_KEY: minio123 command: server http://minio{1...4}/data{1...2} healthcheck: test: ['CMD', 'curl', '-f', 'http://localhost:9000/minio/health/live'] interval: 30s timeout: 20s retries: 3 Запускаем — и без проблем получаем кластер из 4 серверов S3. NestJS + Fastify + S3 Работу с сервером NestJS опишу с самых первых шагов, хотя часть этого материала отлично описана в документации. Устанавливается CLI NestJS: npm install -g @nestjs/cli
Создается новый проект NestJS: nest new s3-nestjs-tut
Инсталлируются необходимые пакеты (включая те что нужны для работы с S3): npm install --save @nestjs/platform-fastify fastify-multipart aws-sdk sharp
npm install --save-dev @types/fastify-multipart @types/aws-sdk @types/sharp По умолчанию в проекте устанавливается платформа NestJS+Express. Как установить Fastify описано в документации docs.nestjs.com/techniques/performance. Дополнительно нам нужно установить плагин для обработки Content-Type: multipart/form-data — fastify-multipart import { NestFactory } from '@nestjs/core';
import { FastifyAdapter, NestFastifyApplication, } from '@nestjs/platform-fastify'; import fastifyMultipart from 'fastify-multipart'; import { AppModule } from './app.module'; async function bootstrap() { const fastifyAdapter = new FastifyAdapter(); fastifyAdapter.register(fastifyMultipart, { limits: { fieldNameSize: 1024, // Max field name size in bytes fieldSize: 128 * 1024 * 1024 * 1024, // Max field value size in bytes fields: 10, // Max number of non-file fields fileSize: 128 * 1024 * 1024 * 1024, // For multipart forms, the max file size files: 2, // Max number of file fields headerPairs: 2000, // Max number of header key=>value pairs }, }); const app = await NestFactory.create<NestFastifyApplication>( AppModule, fastifyAdapter, ); await app.listen(3000, '127.0.0.1'); } bootstrap(); Теперь опишем сервис, загружающий файлы в хранилище S3, сократив код по обработке некоторых видов ошибок (полный текст есть в репозитарии статьи): import { Injectable, HttpException, BadRequestException } from '@nestjs/common';
import { S3 } from 'aws-sdk'; import fastify = require('fastify'); import { AppResponseDto } from './dto/app.response.dto'; import * as sharp from 'sharp'; @Injectable() export class AppService { async uploadFile(req: fastify.FastifyRequest): Promise<any> { const promises = []; return new Promise((resolve, reject) => { const mp = req.multipart(handler, onEnd); function onEnd(err) { if (err) { reject(new HttpException(err, 500)); } else { Promise.all(promises).then( data => { resolve({ result: 'OK' }); }, err => { reject(new HttpException(err, 500)); }, ); } } function handler(field, file, filename, encoding, mimetype: string) { if (mimetype && mimetype.match(/^image\/(.*)/)) { const imageType = mimetype.match(/^image\/(.*)/)[1]; const s3Stream = new S3({ accessKeyId: 'minio', secretAccessKey: 'minio123', endpoint: 'http://127.0.0.1:9001', s3ForcePathStyle: true, // needed with minio? signatureVersion: 'v4', }); const promise = s3Stream .upload( { Bucket: 'test', Key: `200x200_${filename}`, Body: file.pipe( sharp() .resize(200, 200) [imageType](), ), } ) .promise(); promises.push(promise); } const s3Stream = new S3({ accessKeyId: 'minio', secretAccessKey: 'minio123', endpoint: 'http://127.0.0.1:9001', s3ForcePathStyle: true, // needed with minio? signatureVersion: 'v4', }); const promise = s3Stream .upload({ Bucket: 'test', Key: filename, Body: file }) .promise(); promises.push(promise); } }); } } Из особенностей следует отметить, что мы пишем входной поток в два выходных потока, если загружается картинка. Один из потоков сжимает картинку до размеров 200х200. Во всех случаях используется стиль работы с потоками (stream). Но для того, чтобы отловить возможные ошибки и вернуть их в контроллер, мы вызываем метод promise(), который определен в библиотеке aws-sdk. Полученные промисы накапливаем в массиве promises: const promise = s3Stream
.upload({ Bucket: 'test', Key: filename, Body: file }) .promise(); promises.push(promise); И, далее, ожидаем их разрешение в методе Promise.all(promises). Код контроллера, в котором таки пришлось пробросить FastifyRequest в сервис: import { Controller, Post, Req } from '@nestjs/common';
import { AppService } from './app.service'; import { FastifyRequest } from 'fastify'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Post('/upload') async uploadFile(@Req() req: FastifyRequest): Promise<any> { const result = await this.appService.uploadFile(req); return result; } } Запускается проект: npm run start:dev
Репозитарий статьи github.com/apapacy/s3-nestjs-tut apapacy@gmail.com 13 августа 2020 года =========== Источник: habr.com =========== Похожие новости:
|
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 12:40
Часовой пояс: UTC + 5