En choisissant entre la plateforme NestJS + Fastify et NestJS + Express, j'ai fait un choix vers NestJS + Fastify. Connaissant l'inclination des développeurs dans toute situation incompréhensible à accrocher des propriétés supplémentaires sur l'objet req dans Express et ainsi communiquer entre les différentes parties de l'application, j'ai fermement décidé qu'Express ne serait pas dans le prochain projet.
Je n'avais besoin que de résoudre un problème technique avec Content-Type: multipart / form-data. De plus, j'avais prévu de sauvegarder les fichiers reçus via Content-Type: requêtes multipart / form-data dans le stockage S3. À cet égard, la mise en œuvre de Content-Type: demandes multipart / form-data sur la plate-forme NestJS + Express m'a confus que cela ne fonctionnait pas avec les flux.
Lancement du stockage local S3
S3 est un magasin de données (on pourrait dire, mais pas à proprement parler, un magasin de fichiers) accessible via le protocole http. S3 a été initialement fourni par AWS. L'API S3 est actuellement également prise en charge par d'autres services cloud. Mais pas seulement. Il existe des implémentations de serveur S3 que vous pouvez hisser localement pour les utiliser pendant le développement, et éventuellement faire fonctionner vos serveurs S3 en production.
Tout d'abord, vous devez décider de la motivation pour utiliser le stockage de données S3. Dans certains cas, cela peut réduire les coûts. Par exemple, vous pouvez utiliser le stockage S3 le plus lent et le moins cher pour stocker les sauvegardes. Les stockages rapides avec un trafic élevé (le trafic est facturé séparément) pour le chargement de données à partir du stockage coûteront probablement comparable à des disques SSD de même taille.
Un motif plus puissant est 1) l'évolutivité - vous n'avez pas besoin de penser au fait que l'espace disque peut manquer, et 2) la fiabilité - les serveurs fonctionnent dans un cluster et vous n'avez pas besoin de penser à la sauvegarde, car le nombre requis de copies est toujours disponible.
Pour augmenter l'implémentation des serveurs S3 - minio - localement, vous n'avez besoin que de docker et docker-compose installés sur l'ordinateur. Fichier docker-compose.yml correspondant:
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
Nous commençons - et sans aucun problème, nous obtenons un cluster de 4 serveurs S3.
NestJS + Fastify + S3
Je décrirai le travail avec le serveur NestJS dès les premières étapes, bien qu'une partie de ce matériel soit parfaitement décrite dans la documentation. Installe CLI NestJS:
npm install -g @nestjs/cli
Un nouveau projet NestJS est créé:
nest new s3-nestjs-tut
Les packages nécessaires sont installés (y compris ceux nécessaires pour fonctionner avec S3):
npm install --save @nestjs/platform-fastify fastify-multipart aws-sdk sharp
npm install --save-dev @types/fastify-multipart @types/aws-sdk @types/sharp
Par défaut, le projet installe la plate-forme NestJS + Express. La procédure d'installation de Fastify est décrite dans la documentation docs.nestjs.com/techniques/performance . De plus, nous devons installer un plugin pour gérer 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();
Nous allons maintenant décrire le service qui télécharge les fichiers dans le référentiel S3, après avoir réduit le code pour gérer certains types d'erreurs (le texte intégral se trouve dans le référentiel d'articles):
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);
}
});
}
}
Parmi les fonctionnalités, il convient de noter que nous écrivons un flux d'entrée dans deux flux de sortie si une image est chargée. L'un des flux compresse l'image à une taille de 200x200. Dans tous les cas, le style de flux est utilisé. Mais pour détecter d'éventuelles erreurs et les renvoyer au contrôleur, nous appelons la méthode promise (), qui est définie dans la bibliothèque aws-sdk. Nous accumulons les promesses reçues dans le tableau des promesses:
const promise = s3Stream
.upload({ Bucket: 'test', Key: filename, Body: file })
.promise();
promises.push(promise);
Et, en outre, nous attendons leur résolution dans la méthode
Promise.all(promises)
.
Le code du contrôleur, dans lequel je devais encore transmettre FastifyRequest au service:
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;
}
}
Le projet est lancé:
npm run start:dev
Article référentiel github.com/apapacy/s3-nestjs-tut
apapacy@gmail.com
13 Août, 2020