NestJS-yritysbackendien API:t · Oppitunti

Suurten vastausten suoratoisto StreamableFilella

Tarjoa suuret lataukset tehokkaasti StreamableFilen ja Noden luettavien virtojen avulla ilman puskurointia.

Oppitunti 2/413 vaihetta

Suurten vastausten suoratoisto StreamableFilella on ilmainen NestJS-yritysbackendien API:t-oppitunti CoddyKitissä. Tämä on oppitunti 2/4. Voit lukea koko oppitunnin alta ilmaiseksi ja harjoitella sen jälkeen käytännössä selaimessa sisäänrakennetulla koodieditorilla ja ympäri vuorokauden käytettävissä olevan tekoälytuutorin avulla. Oppitunti kuuluu NestJS-yritysbackendien API:t-oppimispolkuun, ja edistymisesi synkronoituu verkon ja CoddyKit-sovelluksen välillä. NestJS-yritysbackendien API:t-kurssilla on yhteensä 4 oppituntia.

Puskurointiongelma

Kun ohjain palauttaa suuren tiedoston, naiivi lähestymistapa on lukea koko tiedosto muistiin ja lähettää se takaisin:

  • fs.readFileSync('huge.zip') lataa jokaisen tavun RAM-muistiin ennen kuin ensimmäinen tavu saavuttaa asiakkaan.
  • 50 samanaikaiselle käyttäjälle tarjottu 2 GB:n vientitiedosto voi kuluttaa keon loppuun ja kaataa prosessin.

Suoratoisto ratkaisee ongelman: tiedosto luetaan pieninä paloina ja putkitetaan vastaukseen niiden saapuessa, jolloin muistin käyttö pysyy tasaisena tiedoston koosta riippumatta.

Mikä StreamableFile on

NestJS sisältää StreamableFile-luokan. Siihen kääritään Node.js:n Readable-tietovirta (tai Buffer), minkä jälkeen se palautetaan ohjaimen käsittelijästä.

  • Nest tunnistaa palautetun StreamableFile-arvon ja putkittaa taustalla olevan tietovirran HTTP-vastaukseen puolestanne.
  • Se toimii sekä Express- että Fastify-sovittimilla ilman, että käsittelette res.pipe()-kutsua itse.

Näin käsittelijä pysyy deklaratiivisena, vaikka tiedosto suoratoistetaan edelleen pala kerrallaan.

import { Controller, Get, StreamableFile } from '@nestjs/common';
import { createReadStream } from 'fs';
import { join } from 'path';

@Controller('files')
export class FilesController {
  @Get('report')
  getReport(): StreamableFile {
    const file = createReadStream(join(process.cwd(), 'report.pdf'));
    return new StreamableFile(file);
  }
}

Miten Readable-tietovirta etenee

Kulissien takana createReadStream lähettää 'data'-tapahtumia pieninä Buffer-paloina (oletuksena 64 kt). Vastaus käsittelee ne yksi kerrallaan.

Tässä on pelkällä Nodella tehty esimerkki paloittaisesta lukemisesta ilman frameworkia — huomaa, ettei koko tiedosto ole koskaan kerralla muistissa:

import { Readable } from 'stream';

// Simulate a large source as a stream of chunks
async function* generateChunks() {
  for (let i = 0; i < 5; i++) {
    yield `chunk-${i} `;
  }
}

const stream = Readable.from(generateChunks());

stream.on('data', (chunk: Buffer | string) => {
  console.log('received:', chunk.toString().trim());
});
stream.on('end', () => console.log('done streaming'));

Content-Typen ja tiedostonimen määrittäminen

Oletusarvoisesti selain ei tiedä, mitä streami sisältää. Välitä asetukset StreamableFile-oliolle, jotta Nest määrittää oikeat otsakkeet:

  • type määrittää Content-Type-otsakkeen.
  • disposition määrittää Content-Disposition-otsakkeen, jolloin selain lataa tiedoston annetulla nimellä sen sijaan, että näyttäisi sen selaimessa.
import { Controller, Get, StreamableFile } from '@nestjs/common';
import { createReadStream } from 'fs';
import { join } from 'path';

@Controller('files')
export class FilesController {
  @Get('invoice')
  getInvoice(): StreamableFile {
    const file = createReadStream(join(process.cwd(), 'invoice.pdf'));
    return new StreamableFile(file, {
      type: 'application/pdf',
      disposition: 'attachment; filename="invoice.pdf"',
    });
  }
}

Otsakkeet: @Header vai StreamableFile-asetukset

Voit määrittää otsakkeet myös @Header()-koristelijalla, mutta molempien käyttäminen voi aiheuttaa ristiriitoja. Suosi StreamableFile-asetuksia, koska Nest soveltaa niitä yhdenmukaisesti molemmilla adaptereilla.

  • Käytä @Header('Content-Type', ...)-määritystä vain, kun arvo on staattinen ja tiedossa käännösaikana.
  • Käytä StreamableFile-olion type/disposition-asetuksia, kun arvo lasketaan pyyntökohtaisesti (esimerkiksi dynaaminen tiedostonimi).
import { Controller, Get, Header, StreamableFile } from '@nestjs/common';
import { createReadStream } from 'fs';

@Controller('exports')
export class ExportsController {
  @Get('static')
  @Header('Content-Type', 'text/csv')
  @Header('Content-Disposition', 'attachment; filename="data.csv"')
  download(): StreamableFile {
    return new StreamableFile(createReadStream('data.csv'));
  }
}

Generoidun sisällön suoratoisto (tiedostoa ei ole levyllä)

StreamableFile ei rajoitu levyllä oleviin tiedostoihin. Mikä tahansa Readable toimii — myös lennossa tuotettu data. Tämä sopii erinomaisesti suurten CSV-vientien luomiseen rivi kerrallaan tietokannan kursorista.

Alla generaattori tuottaa CSV-rivejä laiskasti, joten koko aineistoa ei koskaan muodosteta kerralla muistiin.

import { Controller, Get, StreamableFile, Header } from '@nestjs/common';
import { Readable } from 'stream';

@Controller('exports')
export class CsvExportController {
  @Get('users.csv')
  @Header('Content-Type', 'text/csv')
  exportUsers(): StreamableFile {
    async function* rows() {
      yield 'id,name\n';
      for (let i = 1; i <= 100000; i++) {
        yield `${i},user_${i}\n`;
      }
    }
    return new StreamableFile(Readable.from(rows()));
  }
}

Backpressure: miksi suoratoisto säästää muistia

Backpressure on mekanismi, joka pitää suoratoiston turvallisena. Kun asiakas (tai verkko) on hidas, kirjoitettava puoli ilmoittaa luettavalle puolelle, että palojen tuottaminen on keskeytettävä.

  • Nopea levyltä lukeminen yhdistettynä hitaaseen asiakkaaseen ei täytä muistia gigatavuilla dataa.
  • Noden pipe() (jota Nest käyttää sisäisesti) käsittelee tämän automaattisesti — se pysäyttää lähteen ja jatkaa sen suorittamista tarpeen mukaan.

Juuri siksi kannattaa palauttaa streami valtavan Bufferin sijaan.

import { Writable, Readable } from 'stream';

const source = Readable.from(['a', 'b', 'c', 'd', 'e']);

const slowSink = new Writable({
  write(chunk, _enc, cb) {
    console.log('wrote:', chunk.toString());
    setTimeout(cb, 10); // simulate slow consumer -> triggers backpressure
  },
});

source.pipe(slowSink);
slowSink.on('finish', () => console.log('all chunks flushed safely'));

Stream-virheiden käsittely

Jos taustalla oleva streami antaa virheen sen jälkeen, kun otsakkeet on lähetetty, et voi enää lähettää JSON-muotoista virhevastausta. StreamableFile tarjoaa virheenkäsittelijän, jonka avulla voit kirjata virheen ja sulkea yhteyden hallitusti.

  • Käytä getStream().on('error', ...)-kutsua tai setErrorHandler()-menetelmää / virheenkäsittelyasetusta reagoidaksesi lukemisessa tapahtuviin virheisiin.
  • Ilman tätä puuttuva tiedosto voi jättää yhteyden odottamaan tai kaataa pyynnön käsittelyn.
import { Controller, Get, StreamableFile, Logger } from '@nestjs/common';
import { createReadStream } from 'fs';

@Controller('files')
export class SafeFilesController {
  private readonly logger = new Logger(SafeFilesController.name);

  @Get('archive')
  getArchive(): StreamableFile {
    const stream = createReadStream('archive.zip');
    const file = new StreamableFile(stream);
    file.setErrorHandler((err, response) => {
      this.logger.error(`Stream failed: ${err.message}`);
      response.statusCode = 404;
      response.end('File not available');
    });
    return file;
  }
}

Suoratoisto objektitallennuksesta (S3)

Yrityssovelluksissa tiedosto sijaitsee yleensä S3:ssa tai muussa objektitallennuksessa, ei paikallisella levyllä. S3 SDK palauttaa objektin sisällölle luettavan streamin — välitä se suoraan StreamableFile-oliolle.

  • Väliaikaista tiedostoa ei tarvita, eikä koko tiedostoa ladata API-palvelimen muistiin.
  • Tavut kulkevat reittiä S3 → oma API → asiakas.
import { Controller, Get, Param, StreamableFile } from '@nestjs/common';
import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3';
import { Readable } from 'stream';

@Controller('media')
export class MediaController {
  private s3 = new S3Client({ region: 'eu-central-1' });

  @Get(':key')
  async download(@Param('key') key: string): Promise<StreamableFile> {
    const obj = await this.s3.send(
      new GetObjectCommand({ Bucket: 'my-bucket', Key: key }),
    );
    return new StreamableFile(obj.Body as Readable);
  }
}

Raakavastauksen käyttäminen @Res({ passthrough })-parametrilla

Joskus tarvitset raakavastausolion tilakoodien tai lisäotsakkeiden määrittämiseen, mutta haluat silti Nestin välittävän StreamableFile-olion streamina. Käytä @Res({ passthrough: true })-määritystä, jotta Nest hallitsee edelleen vastauksen elinkaarta.

  • Ilman passthrough: true-asetusta @Res()-olion injektointi tekee sinusta vastuullisen vastauksen päättämisestä, eikä StreamableFile-olion palauttaminen enää toimi automaattisesti.
import { Controller, Get, Res, StreamableFile } from '@nestjs/common';
import type { Response } from 'express';
import { createReadStream, statSync } from 'fs';

@Controller('files')
export class RangeController {
  @Get('video')
  getVideo(@Res({ passthrough: true }) res: Response): StreamableFile {
    const { size } = statSync('movie.mp4');
    res.set({ 'Content-Length': size, 'Accept-Ranges': 'bytes' });
    return new StreamableFile(createReadStream('movie.mp4'));
  }
}

Streamin muuntaminen lennossa

Voit ketjuttaa muunnoksia ennen streamin välittämistä StreamableFile-oliolle. Yleinen käyttötapaus on suuren viennin pakkaaminen gzipillä, jolloin verkon yli siirretään vähemmän dataa — edelleen pala kerrallaan.

Tässä on itsenäinen esimerkki streamin välittämisestä muunnoksen läpi ilman palvelinta:

import { Readable, Transform } from 'stream';

const upper = new Transform({
  transform(chunk, _enc, cb) {
    cb(null, chunk.toString().toUpperCase());
  },
});

const source = Readable.from(['hello ', 'streamed ', 'world']);

source.pipe(upper).on('data', (c) => console.log(c.toString()));
upper.on('end', () => console.log('transform complete'));

Pikatarkistus

Testaa, ymmärrätkö StreamableFileen liittyvän keskeisen ratkaisun.

Kertaus

Tärkeimmät opit suurten vastausten suoratoistamisesta NestJS:ssä:

  • Palauta new StreamableFile(readable) sen sijaan, että puskuroisit kokonaiset tiedostot muistiin.
  • Voit kääriä minkä tahansa Readable-olion: createReadStream-streamin, S3-objektin sisällön tai generaattoriin perustuvan streamin.
  • Määritä type ja disposition (tai käytä @Header-määritystä), jotta asiakkaat saavat oikean MIME-tyypin ja ladattavan tiedostonimen.
  • Backpressure pitää muistin käytön tasaisena hitaiden asiakkaiden kanssa — tämä on koko suoratoiston tarkoitus.
  • Liitä setErrorHandler() käsittelemään lukuvirheitä, jotka tapahtuvat otsakkeiden lähettämisen jälkeen.
  • Käytä @Res({ passthrough: true })-määritystä, kun tarvitset raakavastauksen käyttöön mutta haluat säilyttää Nestin suorittaman streamin välityksen.
Aloita maksutta

Opi TypeScript tekoälytuutorin avulla — ilmaiseksi

Kirjoita ja suorita oikeaa koodia selaimessa, saa välitöntä apua tekoälytuutorilta ympäri vuorokauden ja jatka siitä, mihin jäit, verkossa tai sovelluksessa.

Kurssit
20
Oppitunnit
76

Usein kysytyt kysymykset

Onko oppitunti ”Suurten vastausten suoratoisto StreamableFilella” ilmainen?

Kyllä – oppitunnin ”Suurten vastausten suoratoisto StreamableFilella” koko tekstin voi lukea täällä verkossa ilmaiseksi. Jos haluat harjoitella interaktiivisesti sisäänrakennetulla koodieditorilla ja ympäri vuorokauden käytettävissä olevan tekoälytuutorin avulla sekä avata koko NestJS-yritysbackendien API:t-kurssin, päivitä CoddyKit PROhon. NestJS-yritysbackendien API:t-kurssilla on yhteensä 4 oppituntia.

Mitä opin oppitunnilla ”Suurten vastausten suoratoisto StreamableFilella”?

Tarjoa suuret lataukset tehokkaasti StreamableFilen ja Noden luettavien virtojen avulla ilman puskurointia. Harjoittelet NestJS-yritysbackendien API:t-aihetta koodilla, jonka suoritat suoraan selaimessa. Ympäri vuorokauden käytettävissä oleva tekoälytuutori vastaa kysymyksiisi oppitunnin aikana.

Tarvitsenko kokemusta aloittaakseni NestJS-yritysbackendien API:t-opiskelun?

Aiempi kokemus ei ole tarpeen. CoddyKitin NestJS-yritysbackendien API:t-oppimispolku sopii vasta-alkajista edistyneisiin, joten voit aloittaa tästä tai alusta ja edetä omaan tahtiisi. Tämä on oppitunti 2/4.

Kuinka kauan ”Suurten vastausten suoratoisto StreamableFilella”-oppitunnin suorittaminen kestää?

Useimmat CoddyKitin oppitunnit kestävät noin 5–10 minuuttia. Jokainen oppitunti on lyhyt ja interaktiivinen, joten edistyt tasaisesti ja voit jatkaa siitä, mihin jäit – sekä verkossa että sovelluksessa.

Voinko kirjoittaa ja suorittaa koodia tällä NestJS-yritysbackendien API:t-oppitunnilla?

Kyllä. Jokainen NestJS-yritysbackendien API:t-oppitunti sisältää sisäänrakennetun koodieditorin, joten voit kirjoittaa ja suorittaa oikeaa koodia suoraan selaimessa ja saada välitöntä palautetta tekoälyltä – paikallista asennusta ei tarvita.

Kaikki tämän kurssin oppitunnit

  1. Multipart-lataukset Multer-sieppaajilla
  2. Suurten vastausten suoratoisto StreamableFilella
  3. Suorat S3-lataukset allekirjoitetuilla URL-osoitteilla
  4. Kuvankäsittelyputket Sharpilla
← Takaisin: NestJS-yritysbackendien API:t