Knooppunt js werkt met bestandscodering. KnooppuntJS. Bestanden schrijven en lezen. Iets nuttigs doen: met bestanden werken

Dag Allemaal! In dit artikel bekijken we hoe u bestanden in NodeJS schrijft en leest.

Om dit te doen zullen we de FS(bestandssysteem) module moeten gebruiken. var fs =vereist(`fs`)-

Om het lezen van de inhoud van bestanden te demonstreren, maken we een bestand met de naam readme.txt.

Video: node.js »fs-module» schrijven naar bestand

// inhoud van het readme.txt-bestand
Hier is wat inhoud van het bestand

var tekst = fs.readFileSync(`readme.txt`,`utf8`)-
console.log(tekst)-

We gebruiken een methode waarbij we de bestandsnaam als eerste parameter doorgeven, en de codering als tweede. Zoals de naam al doet vermoeden, is deze methode synchroon. Dit betekent dat alle code die volgt pas wordt uitgevoerd als het volledige bestand is gelezen. Vervolgens schrijven we de ontvangen gegevens eenvoudigweg in een variabele, die we vervolgens op het scherm weergeven.

Video: Node.js - een Excel-bestand maken met behulp van een sjabloon

Laten we nu proberen de inhoud van het bestand dat we lezen in een nieuw bestand te schrijven.

Om dit te doen, zullen we het volgende schrijven:

fs.writeFileSync(`writeme.txt`, tekst)-

Nu, na het uitvoeren van de code, zul je zien dat er een nieuw bestand is gemaakt met de naam writeme.txt, dat de inhoud bevat die naar de tekstvariabele is geschreven vanuit het bestand readme.txt.

Video: Node.JS-basisprincipes. 2. Werken met bestanden

Laten we eens kijken hoe we methoden asynchroon kunnen gebruiken.

Laten we bijvoorbeeld eens kijken naar het readme.txt-bestand:


console.log(gegevens)-
})-

console.log(`wordt uitgevoerd vóór de gegevens uit het bestand`)-

Het gebruik is vrijwel hetzelfde, maar nu geven we de functie ook door als de derde parameter, waarbij het eerste argument de fout is, en het tweede de inhoud van het bestand, dat we vervolgens uitvoeren.

Hieronder heb ik nog een tekstuitvoer geschreven om te laten zien dat de methode inderdaad asynchroon is, dus terwijl het bestand wordt gelezen, wordt de onderstaande code uitgevoerd en pas daarna wordt de tekst uit het bestand uitgevoerd.

Video: Werken met bestanden, fs-module

Laten we nu de inhoud van het readme.txt-bestand opnieuw lezen en deze naar het writeme.txt-bestand schrijven, maar deze keer asynchroon.

fs.readFile(`readme.txt`,`utf8`,function(fout, data)(
fs.writeFile(`writeme.txt`, data)-
})-

En dat is alles voor mij vandaag. Bedankt voor uw aandacht!

(Nog geen beoordelingen)
  • Vertaling

Vandaag zullen we in het negende deel van de vertaling van de Node.js-gids praten over het werken met bestanden. In het bijzonder zullen we het hebben over de fs- en padmodules - over bestandsdescriptors, over paden naar bestanden, over het verkrijgen van informatie over bestanden, over het lezen en schrijven ervan, over het werken met mappen.

Werken met bestandsdescriptors in Node.js

Voordat u kunt communiceren met bestanden die zich op het bestandssysteem van uw server bevinden, moet u een bestandsingang verkrijgen.

De handle kan worden verkregen door de asynchrone open()-methode van de fs-module te gebruiken om het bestand te openen:

Const fs = require("fs") fs.open("/Users/flavio/test.txt", "r", (err, fd) => ( //fd is een bestandsdescriptor ))
Let op de tweede parameter, r, die wordt gebruikt bij het aanroepen van de methode fs.open(). Dit is een vlag die het systeem vertelt dat het bestand wordt geopend om te worden gelezen. Hier zijn enkele andere vlaggen die vaak worden gebruikt bij het werken met deze en enkele andere methoden:

  • r+ - open een bestand om te lezen en te schrijven.
  • w+ - open een bestand om te lezen en te schrijven, waarbij de streamaanwijzer naar het begin van het bestand wordt ingesteld. Als het bestand niet bestaat, wordt het gemaakt.
  • a - open een bestand om te schrijven, waarbij de streampointer naar het einde van het bestand wordt geplaatst. Als het bestand niet bestaat, wordt het gemaakt.
  • a+ - open een bestand om te lezen en te schrijven, waarbij de streampointer naar het einde van het bestand wordt geplaatst. Als het bestand niet bestaat, wordt het gemaakt.
Bestanden kunnen ook worden geopend met behulp van de synchrone fs.openSync()-methode, die, in plaats van een bestandsdescriptor op te geven in de callback, deze retourneert:

Const fs = require("fs") try ( const fd = fs.openSync("/Users/flavio/test.txt", "r") ) catch (err) ( console.error(err) )
Nadat u de descriptor hebt ontvangen met behulp van een van de hierboven beschreven methoden, kunt u er de nodige bewerkingen mee uitvoeren.

Bestandsgegevens

Aan elk bestand is een reeks gegevens gekoppeld; u kunt deze gegevens verkennen met Node.js. Dit kan in het bijzonder worden gedaan met behulp van de stat()-methode uit de fs-module.

Roep deze methode aan, geef het pad naar het bestand door en nadat Node.js de benodigde informatie over het bestand heeft, wordt de callback aangeroepen die is doorgegeven aan de stat() -methode. Dit is hoe het eruit ziet:

Const fs = require("fs") fs.stat("/Users/flavio/test.txt", (err, stats) => ( if (err) ( console.error(err) return ) //bestandsinformatie aanwezig in het `stats`-argument))
Node.js heeft de mogelijkheid om bestandsinformatie synchroon op te halen. Met deze aanpak wordt de hoofdthread geblokkeerd totdat de bestandseigenschappen zijn opgehaald:

Const fs = require("fs") try ( const stats = fs.statSync ("/Users/flavio/test.txt") ) catch (err) ( console.error(err) )
Informatie over het bestand wordt in de statistiekconstante opgenomen. Wat voor soort informatie is dit? In feite biedt het overeenkomstige object ons een groot aantal nuttige eigenschappen en methoden:

  • Met de methoden .isFile() en .isDirectory() kunt u respectievelijk achterhalen of het bestand dat wordt onderzocht een gewoon bestand of een map is.
  • Met de methode .isSymbolicLink() kunt u achterhalen of een bestand een symbolische link is.
  • De bestandsgrootte kan worden gevonden met behulp van de eigenschap .size.
Er zijn nog andere methoden, maar dit zijn de meest gebruikte. Hier ziet u hoe u ze kunt gebruiken:

Const fs = require("fs") fs.stat("/Users/flavio/test.txt", (err, stats) => ( if (err) ( console.error(err) return ) stats.isFile() //true stats.isDirectory() //false stats.isSymbolicLink() //false stats.size //1024000 //= 1MB ))

Bestandspaden in Node.js en de padmodule

Het pad naar een bestand is het adres van de locatie in het bestandssysteem waar het zich bevindt.

Op Linux en macOS kan het pad er als volgt uitzien:

/users/flavio/bestand.txt
Op Windows zien de paden er iets anders uit:

C:\users\flavio\bestand.txt
Er moet rekening worden gehouden met verschillen in padopnameformaten bij het gebruik van verschillende besturingssystemen, waarbij rekening moet worden gehouden met het besturingssysteem dat wordt gebruikt om de Node.js-server te implementeren.

Node.js heeft een standaardpadmodule die is ontworpen om met bestandspaden te werken. Voordat u deze module in het programma kunt gebruiken, moet deze worden aangesloten:

▍Informatie verkrijgen over het pad naar een bestand

Als u een pad naar een bestand heeft, kunt u met behulp van de mogelijkheden van de padmodule details over dit pad achterhalen in een vorm die handig is voor perceptie en verdere verwerking. Het ziet er zo uit:

Const notes = "/users/flavio/notes.txt" path.dirname(notes) // /users/flavio path.basename(notes) // notes.txt path.extname(notes) // .txt
Hier wordt in de notitieregel het pad naar het bestand opgeslagen. De volgende methoden van de padmodule worden gebruikt om het pad te parseren:

  • dirname() - retourneert de bovenliggende map van het bestand.
  • basename() - retourneert de bestandsnaam.
  • extname() - retourneert de bestandsextensie.
U kunt de bestandsnaam zonder extensie achterhalen door de methode .basename() aan te roepen en deze een tweede argument door te geven dat de extensie vertegenwoordigt:

Pad.basisnaam(notities, pad.extnaam(notities)) //notities

▍Werken met bestandspaden

Meerdere paddelen kunnen worden samengevoegd met behulp van de path.join() methode:

Const naam = "flavio" path.join("/", "users", naam, "notes.txt") //"/users/flavio/notes.txt"
U kunt het absolute pad van een bestand vinden op basis van het relatieve pad met behulp van de path.resolve() methode:

Path.resolve("flavio.txt") //"/Users/flavio/flavio.txt" bij het uitvoeren vanuit mijn thuismap
In dit geval voegt Node.js eenvoudigweg /flavio.txt toe aan het pad dat naar de huidige werkmap leidt. Als u bij het aanroepen van deze methode een andere parameter doorgeeft die het pad naar de map vertegenwoordigt, gebruikt de methode deze als basis voor het bepalen van het absolute pad:

Path.resolve("tmp", "flavio.txt") // "/Users/flavio/tmp/flavio.txt" bij het uitvoeren vanuit mijn thuismap
Als het pad dat als eerste parameter wordt doorgegeven begint met een schuine streep, betekent dit dat het een absoluut pad is.

Path.resolve("/etc", "flavio.txt") // "/etc/flavio.txt"
Hier is nog een handige methode: path.normalize() . Hiermee kunt u het daadwerkelijke pad van een bestand vinden met behulp van een pad dat relatieve padspecificaties bevat, zoals een punt (.), twee punten (..) of twee schuine strepen:

Path.normalize("/users/flavio/..//test.txt") // /users/test.txt
De methoden solve() en normalize() controleren niet op het bestaan ​​van een map. Ze vinden eenvoudigweg een pad op basis van de gegevens die hen worden verstrekt.

Bestanden lezen in Node.js

De eenvoudigste manier om bestanden in Node.js te lezen is door de methode fs.readFile() te gebruiken, waarbij u het bestandspad doorgeeft en een callback die de bestandsgegevens (of een foutobject) doorgeeft:

Fs.readFile("/Users/flavio/test.txt", (err, data) => ( if (err) ( console.error(err) return ) console.log(data) ))
Indien nodig kunt u de synchrone versie van deze methode gebruiken - fs.readFileSync() :

Const fs = require("fs") try ( const data = fs.readFileSync("/Users/flavio/test.txt") console.log(data) ) catch (err) ( console.error(err) )
Bij het lezen van bestanden is de codering standaard utf8, maar u kunt de codering zelf instellen door de juiste parameter aan de methode door te geven.

De methoden fs.readFile() en fs.readFileSync() lezen de volledige inhoud van een bestand in het geheugen. Dit betekent dat het werken met grote bestanden met behulp van deze methoden ernstige gevolgen zal hebben voor het geheugenverbruik en de prestaties van uw applicatie. Als u met dergelijke bestanden moet werken, kunt u het beste streams gebruiken.

Bestanden schrijven in Node.js

In Node.js is de eenvoudigste manier om bestanden te schrijven het gebruik van de fs.writeFile() methode:

Const fs = require("fs") const content = "Sommige inhoud!" fs.writeFile("/Users/flavio/test.txt", content, (err) => ( if (err) ( console.error(err) return ) //bestand succesvol geschreven ))
Er is ook een synchrone versie van dezelfde methode - fs.writeFileSync() :

Const fs = require("fs") const content = "Sommige inhoud!" try (const data = fs.writeFileSync("/Users/flavio/test.txt", inhoud) //bestand succesvol geschreven) catch (err) ( console.error(err) )
Deze methoden vervangen standaard de inhoud van bestaande bestanden. U kunt hun standaardgedrag wijzigen door de bijbehorende vlag te gebruiken:

Fs.writeFile("/Users/flavio/test.txt", inhoud, ( vlag: "a+"), (err) => ())
De vlaggen die we al hebben vermeld in de sectie over descriptoren kunnen hier worden gebruikt. Details over de vlaggen zijn te vinden.

Gegevens aan een bestand toevoegen

De methode fs.appendFile() (en de synchrone versie ervan, fs.appendFileSync()) is handig voor het toevoegen van gegevens aan het einde van een bestand:

Const content = "Sommige inhoud!" fs.appendFile("file.log", content, (err) => ( if (err) ( console.error(err) return ) //klaar!))

Over het gebruik van threads

Hierboven hebben we methoden beschreven die, bij het schrijven naar een bestand, de volledige hoeveelheid gegevens schrijven die ernaar wordt overgedragen, waarna ze, als hun synchrone versies worden gebruikt, de controle over het programma teruggeven, en als er asynchrone versies worden gebruikt, callbacks worden aangeroepen. . Als u niet tevreden bent met deze gang van zaken, kunt u beter gebruik maken van streams.

Werken met mappen in Node.js

De fs-module biedt de ontwikkelaar veel handige methoden die kunnen worden gebruikt om met mappen te werken.

▍Het bestaan ​​van een map controleren

Om te controleren of een map bestaat en of Node.js er toegang toe heeft met de juiste machtigingen, kunt u de methode fs.access() gebruiken.

▍Een nieuwe map maken

Om nieuwe mappen te maken, kunt u de methoden fs.mkdir() en fs.mkdirSync() gebruiken:

Const fs = require("fs") const folderName = "/Users/flavio/test" try ( if (!fs.existsSync(dir))( fs.mkdirSync(dir) ) ) catch (err) ( console.error( fout))

▍De inhoud van een map lezen

Om de inhoud van een map te lezen, kunt u de methoden fs.readdir() en fs.readdirSync() gebruiken. Dit voorbeeld leest de inhoud van een map, dat wil zeggen welke bestanden en submappen deze bevat, en retourneert hun relatieve paden:

Const fs = require("fs") const path = require("path") const folderPath = "/Users/flavio" fs.readdirSync(folderPath)
Zo kunt u het volledige pad naar het bestand verkrijgen:

Fs.readdirSync(mappad).map(bestandsnaam => ( return path.join(mappad, bestandsnaam) )
De resultaten kunnen worden gefilterd om alleen bestanden terug te geven en mappen uit te sluiten van de uitvoer:

Const isFile = bestandsnaam => ( retourneert fs.lstatSync(bestandsnaam).isFile() ) fs.readdirSync(mappad).map(bestandsnaam => ( retourneert pad.join(mappad, bestandsnaam)).filter(isBestand) )

▍De naam van een map wijzigen

Om de naam van een map te wijzigen, kunt u de methoden fs.rename() en fs.renameSync() gebruiken. De eerste parameter is het huidige mappad, de tweede is het nieuwe:

Const fs = require("fs") fs.rename("/Users/flavio", "/Users/roger", (err) => ( if (err) ( console.error(err) return ) // klaar ) )
U kunt de naam van een map ook wijzigen met behulp van de synchrone fs.renameSync()-methode:

Const fs = require("fs") try ( fs.renameSync("/Users/flavio", "/Users/roger") ) catch (err) ( console.error(err) )

▍Een map verwijderen

Om een ​​map te verwijderen, kunt u de methoden fs.rmdir() of fs.rmdirSync() gebruiken. Opgemerkt moet worden dat het verwijderen van een map die iets bevat een iets moeilijkere taak is dan het verwijderen van een lege map. Als u dergelijke mappen moet verwijderen, gebruikt u het fs-extra-pakket, dat behoorlijk populair is en goed wordt ondersteund. Het is een vervanging voor de fs-module, waardoor de mogelijkheden ervan worden uitgebreid.

De remove() methode uit het fs-extra pakket kan mappen verwijderen die al iets bevatten.

U kunt deze module als volgt installeren:

Npm installeer fs-extra
Hier is een voorbeeld van het gebruik ervan:

Const fs = require("fs-extra") const map = "/Users/flavio" fs.remove(map, err => ( console.error(err) ))
De methoden kunnen als beloften worden gebruikt:

Fs.remove(map).then(() => ( //done )).catch(err => ( console.error(err) ))
Het is ook mogelijk om de async/await-constructie te gebruiken:

Asynchrone functie removeFolder(map) ( try (wacht op fs.remove(map) // klaar ) catch (err) ( console.error(err) ) ) const map = "/Users/flavio" removeFolder(map)

fs-module

Hierboven zijn we al enkele methoden van de fs-module tegengekomen die worden gebruikt bij het werken met het bestandssysteem. Sterker nog, er staan ​​nog veel meer nuttige dingen in. Laten we u eraan herinneren dat het geen installatie vereist; om het in het programma te gebruiken, hoeft u het alleen maar aan te sluiten:

Const fs = vereisen("fs")
Hierna heeft u toegang tot de methoden ervan, waaronder we het volgende opmerken, waarvan sommige u al bekend zijn:

  • fs.access() : Controleert of een bestand bestaat en toegankelijk is op basis van machtigingen.
  • fs.appendFile() : Voegt gegevens toe aan een bestand. Als het bestand niet bestaat, wordt het aangemaakt.
  • fs.chmod() : Wijzigt de machtigingen voor het opgegeven bestand. Vergelijkbare methoden: fs.lchmod() , fs.fchmod() .
  • fs.chown() : Verandert de eigenaar en groep voor het gegeven bestand. Vergelijkbare methoden: fs.fchown() , fs.lchown() .
  • fs.close() : Sluit een bestandsingang.
  • fs.copyFile() : Kopieert een bestand.
  • fs.createReadStream() : Creëert een bestandsleesstroom.
  • fs.createWriteStream() : Creëert een bestandsschrijfstroom.
  • fs.link() : Creëert een nieuwe harde link naar een bestand.
  • fs.mkdir() : Creëert een nieuwe map.
  • fs.mkdtemp() : Creëert een tijdelijke map.
  • fs.open() : Opent een bestand.
  • fs.readdir() : Leest de inhoud van een map.
  • fs.readFile() : Leest de inhoud van een bestand. Soortgelijke methode: fs.read() .
  • fs.readlink() : Leest de waarde van een symbolische link.
  • fs.realpath() : Maakt een relatief bestandspad mogelijk dat is opgebouwd met behulp van symbolen. en.., helemaal.
  • fs.rename() : Hernoemt een bestand of map.
  • fs.rmdir() : Verwijdert een map.
  • fs.stat() : Retourneert bestandsinformatie. Vergelijkbare methoden: fs.fstat() , fs.lstat() .
  • fs.symlink() : Creëert een nieuwe symbolische link naar een bestand.
  • fs.truncate() : Kapt het bestand af tot de opgegeven lengte. Soortgelijke methode: fs.ftruncate() .
  • fs.unlink() : Verwijdert een bestand of symbolische link.
  • fs.unwatchFile() : Schakelt het bekijken van bestandswijzigingen uit.
  • fs.utimes() : Wijzigt de tijdstempel van een bestand. Soortgelijke methode: fs.futimes() .
  • fs.watchFile() : Maakt het mogelijk om te kijken naar bestandswijzigingen. Soortgelijke methode: fs.watch() .
  • fs.writeFile() : Schrijft gegevens naar een bestand. Soortgelijke methode: fs.write() .
Een interessant kenmerk van de fs-module is het feit dat al zijn methoden standaard asynchroon zijn, maar er zijn ook synchrone versies van, waarvan de namen worden verkregen door het woord Sync toe te voegen aan de namen van de asynchrone methoden.

Bijvoorbeeld:

  • fs.rename()
  • fs.renameSync()
  • fs.schrijven()
  • fs.writeSync()
Het gebruik van synchrone methoden heeft een ernstige impact op de manier waarop het programma werkt.

Node.js 10 biedt experimentele ondersteuning voor deze op beloftes gebaseerde API's.

Laten we de methode fs.rename() verkennen. Hier is een asynchrone versie van deze methode met behulp van callbacks:

Const fs = require("fs") fs.rename("before.json", "after.json", (err) => ( if (err) ( return console.error(err) ) //done ))
Bij gebruik van de synchrone versie wordt een try/catch-constructie gebruikt om fouten af ​​te handelen:

Const fs = require("fs") try ( fs.renameSync("before.json", "after.json") //done ) catch (err) ( console.error(err) )
Het belangrijkste verschil tussen deze opties voor het gebruik van deze methode is dat in het tweede geval de uitvoering van het script wordt geblokkeerd totdat de bestandsbewerking is voltooid.

padmodule

De padmodule, waarvan we enkele mogelijkheden al hebben besproken, bevat veel handige hulpmiddelen waarmee u met het bestandssysteem kunt communiceren. Zoals eerder gezegd, is het niet nodig om het te installeren, omdat het deel uitmaakt van Node.js. Om het te gebruiken, hoeft u het alleen maar aan te sluiten:

Const pad = vereisen("pad")
De eigenschap path.sep van deze module levert het teken dat wordt gebruikt om padsegmenten te scheiden (\ op Windows en/op Linux en macOS), en de eigenschap path.delimiter levert het teken dat wordt gebruikt om meerdere paden te scheiden ( ; op Windows en: op Linux en macOS).

Laten we enkele methoden van de padmodule bekijken en met voorbeelden illustreren.

▍pad.basisnaam()

Retourneert het laatste fragment van het pad. Door de tweede parameter aan deze methode door te geven, kunt u de bestandsextensie verwijderen.

Require("pad").basename("/test/iets") //iets require("pad").basename("/test/iets.txt") //iets.txt require("pad").basename ("/test/iets.txt", ".txt") //iets

▍pad.mapnaam()

Retourneert het deel van het pad dat de mapnaam vertegenwoordigt:

Require("pad").mapnaam("/test/iets") // /test require("pad").mapnaam("/test/iets/bestand.txt") // /test/iets

▍pad.extnaam()

Retourneert het deel van het pad dat de bestandsextensie vertegenwoordigt:

Require("pad").extnaam("/test/iets") // "" require("pad").extnaam("/test/iets/bestand.txt") // ".txt"

▍pad.isAbsoluut()

Retourneert waar als het pad absoluut is:

Require("pad").isAbsolute("/test/iets") // true require("pad").isAbsolute("./test/iets") // false

▍pad.join()

Verbindt verschillende delen van het pad:

Const name = "flavio" require("path").join("/", "users", name, "notes.txt") //"/users/flavio/notes.txt"

▍pad.normalize()

Probeert het echte pad te achterhalen op basis van een pad dat tekens bevat die worden gebruikt bij het construeren van relatieve paden zoals. , .. En // :

Require("pad").normalize("/users/flavio/..//test.txt") ///users/test.txt

▍pad.parse()

Converteert een pad naar een object waarvan de eigenschappen de afzonderlijke delen van het pad vertegenwoordigen:
  • root: hoofdmap.
  • dir: pad naar het bestand, beginnend vanuit de hoofdmap
  • base: bestandsnaam en extensie.
  • naam: bestandsnaam.
  • ext: bestandsextensie.
Hier is een voorbeeld van het gebruik van deze methode:

Require("pad").parse("/users/test.txt")
Het resultaat van zijn werk is het volgende object:

( root: "/", map: "/users", base: "test.txt", ext: ".txt", naam: "test" )

▍pad.relative()

Accepteert 2 paden als argumenten. Retourneert het relatieve pad van het eerste pad naar het tweede, gebaseerd op de huidige werkmap:

Require("pad").relative("/Users/flavio", "/Users/flavio/test.txt") //"test.txt" require("pad").relative("/Users/flavio", "/Users/flavio/iets/test.txt") //"iets/test.txt"

▍pad.resolve()

Vindt een absoluut pad op basis van het relatieve pad dat eraan is doorgegeven:

Path.resolve("flavio.txt") //"/Users/flavio/flavio.txt" bij het uitvoeren vanuit mijn thuismap.

Resultaten

Vandaag hebben we gekeken naar de Node.js fs- en path-modules, die worden gebruikt om met het bestandssysteem te werken. In het volgende deel van deze serie, waar het eindigt, zullen we de os, events, http-modules bespreken, praten over het werken met threads en het werken met databasebeheersystemen in Node.js.

Beste lezers! Welke npm-pakketten gebruikt u bij het werken met het bestandssysteem in Node.js?

Een student vroeg: “De programmeurs van vroeger gebruikten alleen eenvoudige computers en programmeerden zonder talen, maar ze maakten prachtige programma’s. Waarom gebruiken we complexe computers en programmeertalen?” Fu-Tzu antwoordde: “De bouwers van vroeger gebruikten alleen stokken en klei, maar ze maakten prachtige hutten.”

Meester Yuan-Ma, “Boek van Programmeren”

Tot nu toe heb je JavaScript geleerd en in één enkele omgeving gebruikt: de browser. In dit en het volgende hoofdstuk introduceren we kort Node.js, een programma waarmee je JavaScript-vaardigheden buiten de browser kunt gebruiken. Hiermee kun je alles schrijven, van opdrachtregelhulpprogramma's tot dynamische HTTP-servers.

Deze hoofdstukken zijn gericht op het onderwijzen van de belangrijke ideeën waaruit Node.js bestaat en zijn bedoeld om u voldoende informatie te geven om u in staat te stellen nuttige programma's in het raamwerk te schrijven. Ze proberen geen uitgebreide referenties te zijn voor Node.

Je zou de code uit voorgaande hoofdstukken rechtstreeks in de browser kunnen schrijven en uitvoeren, maar de code uit dit hoofdstuk is geschreven voor Node en zal niet werken in de browser.

Als je de code uit dit hoofdstuk meteen wilt uitvoeren, begin dan met het installeren van Node van nodejs.org voor jouw besturingssysteem. Ook op deze site vindt u documentatie over Node en de ingebouwde modules.

Invoering

Een van de moeilijkste problemen bij het schrijven van systemen die via een netwerk communiceren, is het verwerken van invoer en uitvoer. Gegevens lezen en schrijven van en naar het netwerk, schijf en andere apparaten. Het verplaatsen van gegevens kost tijd, en een goede planning voor deze activiteit kan een grote invloed hebben op de systeemresponstijd voor gebruikers- of netwerkverzoeken.

Bij traditionele invoer- en uitvoerverwerking begint een functie zoals readFile met het lezen van het bestand en keert pas terug als het bestand volledig is gelezen. Dit wordt synchrone I/O (invoer/uitvoer) genoemd.

Node is ontworpen om asynchrone I/O eenvoudiger en gebruiksvriendelijker te maken. We hebben al asynchrone interfaces gezien, zoals het browserobject XMLHttpRequest, besproken in hoofdstuk 17. Zo'n interface zorgt ervoor dat het script blijft draaien terwijl de interface zijn werk doet, en roept een callback-functie aan als deze klaar is. Dit is hoe alle I/O werkt in Node.

JavaScript past gemakkelijk in een systeem als Node. Dit is een van de weinige talen waarin geen I/O-systeem is ingebouwd. JavaScript past dus gemakkelijk in Node's nogal excentrieke benadering van I/O en leidt uiteindelijk niet tot twee verschillende invoer- en uitvoersystemen. In 2009, bij de ontwikkeling van Node, gebruikten mensen al callback-gebaseerde I/O in de browser, dus de gemeenschap rond de taal was gewend aan een asynchrone programmeerstijl.

Asynchronie

Ik zal proberen het verschil in synchrone en asynchrone benaderingen van I/O te illustreren met een klein voorbeeld, waarbij een programma twee bronnen van internet moet ontvangen en vervolgens iets met de gegevens moet doen.

In een synchrone omgeving is de voor de hand liggende manier om het probleem op te lossen het opeenvolgend maken van query's. Deze methode heeft een minpunt: het tweede verzoek start pas nadat het eerste is voltooid. De totale tijd bedraagt ​​minimaal de som van de tijd die nodig is om twee verzoeken te verwerken. Dit is een inefficiënt gebruik van de computer, die het grootste deel van de tijd inactief zal zijn terwijl gegevens via het netwerk worden overgedragen.

De oplossing voor het probleem in een synchroon systeem is het starten van extra controlethreads voor de uitvoering van programma's (we hebben deze al besproken in hoofdstuk 14). Een tweede thread kan een tweede verzoek uitvoeren, en vervolgens zullen beide threads wachten tot het resultaat wordt geretourneerd, waarna ze opnieuw worden gesynchroniseerd om het werk in één resultaat te convergeren.

In het diagram geven de dikke lijnen de normale werkingstijd van het programma aan, en geven de dunne lijnen de I/O-wachttijd aan. In het synchrone model wordt de tijd besteed aan I/O opgenomen in het tijdschema van elke thread. Bij asynchroon leidt het starten van een actie via I/O tot een vertakking van de tijdlijn. De thread die de I/O heeft gestart, gaat door met de uitvoering en de I/O wordt parallel uitgevoerd, waarbij na voltooiing een functie-callback wordt uitgevoerd.

Programmastroom voor synchrone en asynchrone I/O

Een andere manier om dit verschil uit te drukken: in het synchrone model is het wachten op het einde van I/O impliciet, maar in het asynchrone model is het expliciet en valt het onder onze directe controle. Maar asynchronie werkt twee kanten op. Het maakt het gemakkelijker om niet-lineaire programma's uit te drukken, maar maakt het moeilijker om lineaire programma's uit te drukken.

In hoofdstuk 17 heb ik al gewezen op het feit dat callbacks veel ruis veroorzaken en een programma minder georganiseerd maken. Of deze aanpak over het algemeen een goed idee is, valt te betwisten. Het kost in ieder geval tijd om eraan te wennen.

Maar voor een op JavaScript gebaseerd systeem zou ik zeggen dat het zinvol is om async te gebruiken met callbacks. Een van de sterke punten van JavaScript is de eenvoud, en het toevoegen van meerdere threads aan een programma zou tot veel complexiteit leiden. Hoewel callbacks code niet eenvoudig maken, is het idee erachter heel eenvoudig en toch krachtig genoeg om u in staat te stellen krachtige webservers te schrijven.

knooppunt commando

Wanneer Node.js op uw systeem is geïnstalleerd, beschikt u over een programma met de naam node dat JavaScript-bestanden uitvoert. Stel dat u een hello.js-bestand hebt met de volgende code:

Var message = "Hallo wereld"; console.log(bericht);

U kunt uw programma uitvoeren vanaf de opdrachtregel:

$node hello.js Hallo wereld

De console.log-methode in Node werkt op dezelfde manier als in de browser. Geeft een stukje tekst weer. Maar in Node wordt tekst afgedrukt naar standaarduitvoer in plaats van naar de JavaScript-console in de browser.

Als u node zonder bestand uitvoert, krijgt u een queryreeks waarin u JavaScript-code kunt schrijven en het resultaat kunt krijgen.

$ knooppunt > 1 + 1 2 > [-1, -2, -3].map(Math.abs) > ​​proces.exit(0) $

De procesvariabele is, net als console, wereldwijd beschikbaar in Node. Het biedt verschillende manieren om een ​​programma te inspecteren en te manipuleren. De exit-methode beëindigt het proces en er kan een programma-exitstatuscode worden doorgegeven, die het programma dat het knooppunt heeft gestart (in dit geval de shell) vertelt of het programma succesvol is afgesloten (nulcode) of mislukt (een ander nummer).

Om toegang te krijgen tot de opdrachtregelargumenten die aan het programma zijn doorgegeven, kunt u de stringarray process.argv lezen. Het bevat ook de naam van het knooppuntcommando en de naam van uw script, dus de argumentenlijst begint bij index 2. Als het bestand showargv.js alleen een console.log(process.argv)-instructie bevat, kunt u het als volgt uitvoeren :

$ node showargv.js één --en twee ["node", "/home/marijn/showargv.js", "one", "--and", "two"]

Alle standaard globale JavaScript-variabelen - Array, Math, JSON, zijn ook beschikbaar in de Node-omgeving. Maar er is geen browsergerelateerde functionaliteit, zoals document of waarschuwing.

Het globale scope-object, dat in de browser venster wordt genoemd, wordt in Node betekenisvoller globaal genoemd.

Modules

Afgezien van een paar genoemde variabelen zoals console en proces, behoudt Node weinig functionaliteit in de globale reikwijdte. Om toegang te krijgen tot de rest van de ingebouwde mogelijkheden, moet u toegang krijgen tot het modulesysteem.

Het CommonJS-systeem, gebaseerd op de require-functie, werd beschreven in hoofdstuk 10. Dit systeem is ingebouwd in Node en wordt gebruikt om alles te laden, van ingebouwde en gedownloade bibliotheken tot bestanden die deel uitmaken van uw programma.

Wanneer u require Node aanroept, moet u de gegeven string naar een bestandsnaam converteren. Paden die beginnen met "/", "./" of "../" worden geconverteerd naar paden ten opzichte van het huidige pad. "./" betekent de huidige map, "../" betekent de map erboven, en "/" betekent de hoofdmap van het bestandssysteem. Als je "./world/world" opvraagt ​​uit het bestand /home/marijn/elife/run.js, zal Node proberen het bestand /home/marijn/elife/world/world.js te laden. De .js-extensie kan worden weggelaten.

Wanneer een tekenreeks wordt doorgegeven die geen relatief of absoluut pad lijkt te zijn, wordt aangenomen dat het een ingebouwde module is of een module die is geïnstalleerd in de map node_modules. Require("fs") geeft u bijvoorbeeld een ingebouwde module voor het werken met het bestandssysteem, en require("elife") zal proberen de bibliotheek te laden vanuit node_modules/elife/. De typische methode voor het installeren van bibliotheken is het gebruik van NPM, waar ik later op terugkom.

Laten we, om dit te demonstreren, een eenvoudig project met twee bestanden maken. De eerste heet main.js en definieert een script dat wordt aangeroepen vanaf de opdrachtregel en is ontworpen om tekenreeksen te vervormen.

Var verminken = vereisen("./verminken"); // Index 2 bevat het eerste programmaargument van de opdrachtregel var argument = process.argv; console.log(verminking(argument));

Het bestand garble.js definieert een bibliotheek voor het verminken van tekenreeksen die zowel kan worden gebruikt door het opdrachtregelprogramma dat u eerder hebt gedefinieerd als door andere scripts die directe toegang tot de verminkingsfunctie nodig hebben.

Module.exports = function(string) ( return string.split("").map(function(ch) ( return String.fromCharCode(ch.charCodeAt(0) + 5); )).join(""); ) ;

Door module.exports te vervangen in plaats van er eigenschappen aan toe te voegen, kunnen we een specifieke waarde uit een module exporteren. In dit geval zal het resultaat van het verzoek van onze module de vervormingsfunctie zelf zijn.

De functie splitst een string op in karakters door middel van splitsen met een lege string, en vervangt vervolgens alle karakters door andere met een code die 5 eenheden hoger is. Vervolgens wordt het resultaat samengevoegd tot een string.

Nu kunnen we onze tool aanroepen:

$ knooppunt main.js JavaScript Of(fXhwnuy

Installatie via NPM

NPM, kort genoemd in hoofdstuk 10, is een online opslagplaats van JavaScript-modules, waarvan er vele specifiek voor Node zijn geschreven. Wanneer u Node op uw computer installeert, krijgt u een npm-programma dat een handige interface voor deze repository biedt.

Een van de NPM-modules heet bijvoorbeeld figlet en zet tekst om in 'ASCII-kunst', tekeningen die zijn opgebouwd uit teksttekens. Zo installeer je het:

$ npm installeer figlet npm GET https://registry.npmjs.org/figlet npm 200 https://registry.npmjs.org/figlet npm GET https://registry.npmjs.org/figlet/-/figlet-1.0. 9.tgz npm 200 https://registry.npmjs.org/figlet/-/figlet-1.0.9.tgz [e-mailadres beveiligd] node_modules/figlet $ node > var figlet = require("figlet"); > figlet.text("Hallo wereld!", function(error, data) ( if (error) console.error(error); else console.log(data); )); _ _ _ _ _ _ _ | | | | ___| | | ___ __ ______ _ __| | __| | | | |_| |/ _ \ | |/ _ \ \ \ /\ / / _ \| "__| |/ _` | | | _ | __/ | | (_) | \ V V / (_) | | | | (_| |_| |_| |_|\___|_|_|\ ___/ \_/\_/ \___/|_| |_|\__,_(_)

Na het uitvoeren van npm install zal NPM een map node_modules maken. Daarin bevindt zich een figlet-map met de bibliotheek. Wanneer we node starten en require(“figlet”) aanroepen, wordt de bibliotheek geladen en kunnen we de tekstmethode aanroepen om grote, mooie letters af te drukken.

Wat interessant is, is dat figlet.text, in plaats van simpelweg een string met hoofdletters terug te geven, een callback-functie accepteert waaraan het het resultaat doorgeeft. Het geeft daar ook een ander argument door, error, dat een error-object zal bevatten in geval van een fout, en null in geval van succes.

Dit werkingsprincipe wordt overgenomen in Node. Om letters te maken, moet figlet een bestand van de schijf lezen dat letters bevat. Het lezen van een bestand is een asynchrone bewerking in Node, dus figlet.text kan het resultaat niet onmiddellijk retourneren. Asynchronie is besmettelijk: elke functie die een asynchrone aanroept, wordt zelf asynchroon.

NPM is meer dan alleen npm-installatie. Het leest package.json-bestanden, die JSON-informatie bevatten over het programma of de bibliotheek, in het bijzonder op welke bibliotheken het is gebaseerd. Als u npm install uitvoert in de map die zo'n bestand bevat, worden automatisch alle afhankelijkheden geïnstalleerd, en op hun beurt hun afhankelijkheden. De npm-tool wordt ook gebruikt om bibliotheken op de NPM online repository te hosten, zodat andere mensen ze kunnen vinden, downloaden en gebruiken.

We zullen niet verder ingaan op de details van het gebruik van NPM. Zie npmjs.org voor documentatie over zoeken in bibliotheken.

bestandssysteemmodule

Een van de meest populaire ingebouwde Node-modules is de ‘fs’-module, wat staat voor ‘bestandssysteem’. De module biedt functionaliteit voor het werken met bestanden en mappen.

Er is bijvoorbeeld een functie readFile die een bestand leest en terugbelt met de inhoud van het bestand.

Var fs = vereisen("fs"); fs.readFile("file.txt", "utf8", function(error, text) ( if (error) throw error; console.log("En het bestand bevatte:", tekst); ));

Het tweede argument voor readFile specificeert de tekencodering waarin de inhoud van het bestand naar een tekenreeks moet worden geconverteerd. Tekst kan op veel manieren naar binaire gegevens worden geconverteerd, maar de nieuwste is UTF-8. Als u geen reden heeft om aan te nemen dat het bestand tekst in een andere codering bevat, kunt u veilig de parameter “utf8” doorgeven. Als u geen codering opgeeft, geeft Node u de binair gecodeerde gegevens als een Buffer-object in plaats van als een tekenreeks. Dit is een array-achtig object dat bytes uit een bestand bevat.

Var fs = vereisen("fs"); fs.readFile("file.txt", function(error, buffer) ( if (error) throw error; console.log("Het bestand bevatte ", buffer.length, " bytes.", "Eerste byte:", buffer ); ));

Een soortgelijke functie, writeFile, wordt gebruikt om een ​​bestand naar schijf te schrijven.

Var fs = vereisen("fs"); fs.writeFile("graffiti.txt", "Node was here", function(err) ( if (err) console.log("Niets werkte, en dit is waarom:", err); else console.log("Schrijven is gelukt Iedereen is vrij."); ));

Het is niet nodig om de codering hier in te stellen, omdat writeFile ervan uitgaat dat als het een string krijgt om te schrijven, en niet een Buffer-object, het als tekst moet worden uitgevoerd met de standaardcodering van UTF-8.

De “fs”-module bevat veel nuttige dingen: de readdir-functie retourneert een lijst met bestanden in een map als een array van tekenreeksen, stat retourneert informatie over een bestand, hernoemen hernoemt een bestand, ontkoppelt verwijderingen, enz. Zie documentatie op nodejs.org

Veel fs-functies hebben zowel synchrone als asynchrone opties. Er is bijvoorbeeld een synchrone versie van de readFile-functie genaamd readFileSync.

Var fs = vereisen("fs"); console.log(fs.readFileSync("bestand.txt", "utf8"));

Synchrone functies zijn gemakkelijker te gebruiken en nuttiger voor eenvoudige scripts waarbij de toegevoegde snelheid van een asynchrone methode niet belangrijk is. Maar houd er rekening mee dat terwijl de synchrone actie wordt uitgevoerd, uw programma volledig stopt. Als het moet reageren op gebruikersinvoer of andere programma's via het netwerk, veroorzaakt het wachten op synchrone I/O vervelende vertragingen.

HTTP-module

Een andere hoofdmodule is “http”. Het biedt functionaliteit voor het maken van HTTP-servers en HTTP-verzoeken.

Hier is alles wat u nodig heeft om een ​​eenvoudige HTTP-server te starten:

Var http = vereisen("http"); var server = http.createServer(function(request, response) ( response.writeHead(200, ("Content-Type": "text/html")); response.write("

Hallo!

Je verzocht " + verzoek.url + "

"); respons.end(); )); server.listen(8000);

Door het script op uw machine uit te voeren, kunt u uw browser naar localhost :8000/hello verwijzen, waardoor een verzoek naar de server wordt gecreëerd. Het zal reageren met een kleine HTML-pagina.

De functie die als argument voor createServer wordt doorgegeven, wordt aangeroepen telkens wanneer wordt geprobeerd verbinding te maken met de server. De aanvraag- en antwoordvariabelen zijn objecten die invoer- en uitvoergegevens vertegenwoordigen. De eerste bevat informatie over het verzoek, de eigenschap url bevat bijvoorbeeld de verzoek-URL.

Om iets terug te sturen worden de methoden van het responsobject gebruikt. De eerste, writeHead, schrijft de antwoordheaders (zie hoofdstuk 17). Je geeft het een statuscode (in dit geval 200 voor “OK”) en een object met de headerwaarden. Hier vertellen we de klant dat hij moet wachten op een HTML-document.

Dan komt de antwoordtekst (het document zelf), verzonden via response.write. Deze methode kan meerdere keren worden aangeroepen als u het antwoord in delen wilt verzenden, bijvoorbeeld door gegevens te streamen zodra deze binnenkomen. Ten slotte signaleert response.end het einde van het antwoord.

Door server.listen aan te roepen, luistert de server naar verzoeken op poort 8000. Daarom moet u in uw browser naar localhost:8000 gaan, en niet alleen naar localhost (waar de standaardpoort 80 is).

Om een ​​Node-script te stoppen dat niet automatisch wordt beëindigd omdat het wacht op verdere gebeurtenissen (in dit geval verbindingen), moet u op Ctrl-C drukken.

Een echte webserver doet veel meer dan wat in het voorbeeld wordt beschreven. Er wordt gekeken naar de verzoekmethode (de methode-eigenschap) om te begrijpen welke actie de client probeert uit te voeren, en naar de verzoek-URL om te begrijpen op welke bron die actie moet worden uitgevoerd. Vervolgens ziet u een meer geavanceerde versie van de server.

Om een ​​HTTP-client te maken, kunnen we de “http”-verzoekmodulefunctie gebruiken.

Var http = vereisen("http"); var request = http.request(( hostnaam: "eloquentjavascript.net", pad: "/20_node.html", methode: "GET", headers: (Accepteer: "text/html")), functie (antwoord) (console .log("De service heeft gereageerd met code ", response.statusCode); )); verzoek.end();

Het eerste verzoekargument configureert het verzoek en vertelt Node met welke server we zullen communiceren, welk pad het verzoek zal volgen, welke methode we moeten gebruiken, enz. De tweede is functie. die aan het einde van het verzoek moet worden opgeroepen. Er wordt een responsobject doorgegeven dat alle informatie over het antwoord bevat, bijvoorbeeld de statuscode.

Net als het antwoordobject van de server, kunt u met het op verzoek geretourneerde object gegevens doorgeven met de schrijfmethode en het verzoek beëindigen met de eindmethode. In het voorbeeld wordt geen gebruik gemaakt van schrijven omdat GET-verzoeken geen gegevens in de hoofdtekst mogen bevatten.

Voor verzoeken om URL's (HTTPS) te beveiligen, biedt Node de https-module aan, die een eigen verzoekfunctie heeft, vergelijkbaar met http.request.

Stromen

We hebben twee voorbeeldstreams gezien in de HTTP-voorbeelden: het responsobject, waarnaar de server kan schrijven, en het verzoekobject, dat wordt geretourneerd door http.request

Beschrijfbare streams zijn een populair concept in Node-interfaces. Alle threads hebben een schrijfmethode, waaraan een string of een Buffer-object kan worden doorgegeven. De end-methode sluit de stream en als er een argument is, wordt er een stukje gegevens afgedrukt voordat deze wordt gesloten. Beide methoden kunnen via een extra argument een callback-functie krijgen, die ze zullen aanroepen wanneer de opname is voltooid of de stream wordt gesloten.

Het is mogelijk om een ​​stream te maken die naar een bestand verwijst met behulp van de functie fs.createWriteStream. U kunt vervolgens de schrijfmethode gebruiken om in delen naar het bestand te schrijven in plaats van als geheel, zoals in fs.writeFile.

Leesbare streams zullen iets ingewikkelder zijn. Zowel de aanvraagvariabele die wordt doorgegeven aan de callback-functie op de HTTP-server als de responsvariabele die wordt doorgegeven in de HTTP-client zijn leesbare streams. (De server leest het verzoek en schrijft vervolgens antwoorden, en de client schrijft het verzoek en leest het antwoord). Het lezen uit een stream gebeurt via gebeurtenishandlers, niet via methoden.

Objecten die gebeurtenissen in Node creëren, hebben een on-methode, vergelijkbaar met de addEventListener-methode van de browser. Je geeft het een gebeurtenisnaam en een functie, en het registreert die functie zodat deze onmiddellijk wordt aangeroepen wanneer de gebeurtenis plaatsvindt.

Leesbare streams hebben 'data'- en 'end'-gebeurtenissen. De eerste vindt plaats wanneer gegevens binnenkomen, de tweede vindt plaats na voltooiing. Dit model is geschikt voor het streamen van gegevens die direct verwerkt kunnen worden, ook als niet het gehele document ontvangen wordt. Het bestand kan als stream worden gelezen via fs.createReadStream.

Met de volgende code wordt een server gemaakt die de aanvraagteksten leest en terugstuurt als een stroom hoofdletters.

Var http = vereisen("http"); http.createServer(function(request, response) ( response.writeHead(200, ("Content-Type": "text/plain")); request.on("data", function(chunk) ( response.write(chunk .toString().toUpperCase()); )); request.on("end", function() ( response.end(); )); )).listen(8000);

De chunk-variabele die aan de gegevenshandler wordt doorgegeven, zal een binaire buffer zijn, die kan worden geconverteerd naar een string door de toString-methode aan te roepen, die deze decodeert vanuit de standaardcodering (UTF-8).

De volgende code zal, wanneer deze gelijktijdig met de server wordt uitgevoerd, een verzoek naar de server sturen en het ontvangen antwoord afdrukken:

Var http = vereisen("http"); var request = http.request(( hostnaam: "localhost", poort: 8000, methode: "POST"), function(response) ( response.on("data", function(chunk) ( process.stdout.write(chunk .toString()); )); )); request.end("Hallo server");

In het voorbeeld wordt naar process.stdout geschreven (de standaarduitvoer van het proces, wat een beschrijfbare stream is) in plaats van naar console.log. We kunnen console.log niet gebruiken omdat het na elk stukje code een extra regeleinde toevoegt. Dit is hier niet nodig.

Eenvoudige bestandsserver

Laten we onze nieuwe kennis over HTTP-servers en het werken met het bestandssysteem combineren en een brug ertussen bouwen: een HTTP-server die externe toegang tot bestanden biedt. Er zijn veel gebruiksscenario's voor zo'n server. Hiermee kunnen webapplicaties gegevens opslaan en delen, of een groep mensen toegang geven tot een reeks bestanden.

Wanneer we bestanden behandelen als HTTP-bronnen, kunnen de methoden GET, PUT en DELETE worden gebruikt om bestanden te lezen, schrijven en verwijderen. We interpreteren het pad in het verzoek als het pad naar het bestand.

We hoeven niet het hele bestandssysteem openbaar te maken, dus interpreteren we deze paden als relatief ten opzichte van de hoofdmap, en dit zal de startmap van het script zijn. Als ik de server start vanuit /home/marijn/public/ (of C:\Users\marijn\public\ op Windows), dan zou een verzoek aan /file.txt moeten verwijzen naar /home/marijn/public/file.txt ( of C:\Gebruikers\marijn\public\bestand.txt).

We zullen het programma geleidelijk opbouwen, waarbij we het object methoden gebruiken om functies op te slaan die verschillende HTTP-methoden verwerken.

Var http = vereisen("http"), fs = vereisen("fs"); var methoden = Object.create(null); http.createServer(function(request, response) ( function respond(code, body, type) ( if (!type) type = "text/plain"; response.writeHead(code, ("Content-Type": type)) ; if (body && body.pipe) body.pipe(respons); else response.end(body); ) if (request.method in methoden) methoden(urlToPath(request.url), respond, request); else respond( 405, "Methode " + request.method + " niet toegestaan."); )).listen(8000);

Deze code zorgt ervoor dat de server 405-fouten retourneert. Deze code wordt gebruikt om aan te geven dat de gevraagde methode niet door de server wordt ondersteund.

De responsfunctie wordt doorgegeven aan functies die verschillende methoden verwerken en fungeert als callback om het verzoek te voltooien. Het accepteert een HTTP-statuscode, een hoofdtekst en mogelijk een inhoudstype. Als het doorgegeven lichaam een ​​leesbare stream is, zal het een pipe-methode hebben die wordt gebruikt om een ​​leesbare stream door te geven aan een beschrijfbare stream. Als dit niet het geval is, wordt aangenomen dat het nul is (de hoofdtekst is leeg) of een tekenreeks, en wordt het rechtstreeks doorgegeven aan de eindantwoordmethode.

Om het pad van een URL in een verzoek op te halen, gebruikt de functie urlToPath de ingebouwde "url" -module van Node om de URL te parseren. Er is een padnaam voor nodig, zoiets als /file.txt, decodeert om %20 escape-codes te verwijderen, en voegt een voorlooppunt in om het pad relatief aan de huidige map te krijgen.

Denkt u dat de urlToPath-functie onveilig is? Je hebt gelijk. In de oefeningen komen we hierop terug.

We zullen de GET-methode ontwerpen om een ​​lijst met bestanden terug te geven bij het lezen van een map, en de inhoud van een bestand bij het lezen van een bestand.

De echte vraag is welk type Content-Type header we moeten retourneren bij het lezen van een bestand. Omdat een bestand van alles kan zijn, kan de server niet zomaar voor iedereen hetzelfde type retourneren. Maar NPM kan hierbij helpen. De mime-module (indicatoren voor bestandsinhoudstypen zoals tekst/plain worden ook wel MIME-typen genoemd) kent het juiste type voor een groot aantal bestandsextensies.

Door de volgende npm-opdracht uit te voeren in de map waar het serverscript zich bevindt, kunt u require("mime") gebruiken om de typebibliotheek te doorzoeken.

$ npm mime installeren npm http GET https://registry.npmjs.org/mime npm http 304 https://registry.npmjs.org/mime [e-mailadres beveiligd] knooppunt_modules/mime

Als het opgevraagde bestand niet bestaat, is de juiste foutcode voor dit geval 404. We zullen fs.stat gebruiken om bestandsinformatie terug te sturen om erachter te komen of een dergelijk bestand bestaat en of het een map is.

Methods.GET = function(path, respond) ( fs.stat(path, function(error, stats) ( if (error && error.code == "ENOENT") respond(404, "Bestand niet gevonden"); else if (fout) reageren(500, fout.toString()); else if (stats.isDirectory()) fs.readdir(pad, functie(fout, bestanden) ( if (fout) reageren(500, fout.toString()) ; else respond(200, files.join("\n")); )); else respond(200, fs.createReadStream(path), require("mime").lookup(path)); )); );

Omdat schijfquery's tijd vergen, wordt fs.stat asynchroon uitgevoerd. Wanneer het bestand niet bestaat, zal fs.stat een foutobject met de code-eigenschap "ENOENT" doorgeven aan de callback-functie. Het zou geweldig zijn als Node verschillende fouttypen voor verschillende fouten zou definiëren, maar dat is niet het geval. In plaats daarvan spuugt het verwarrende codes in Unix-stijl uit.

We geven alle onverwachte fouten weer met code 500, wat aangeeft dat er een probleem is op de server - in tegenstelling tot codes die beginnen met 4, die duiden op een probleem met het verzoek. In sommige situaties zal dit niet helemaal nauwkeurig zijn, maar voor een klein voorbeeldprogramma zal het voldoende zijn.

Het stats-object dat door fs.stat wordt geretourneerd, vertelt ons alles over het bestand. Grootte is bijvoorbeeld de bestandsgrootte, mtime is de wijzigingsdatum. Hier moeten we uitzoeken of dit een map of een gewoon bestand is - de isDirectory-methode zal ons dit vertellen.

Om een ​​lijst met bestanden in een map te lezen, gebruiken we fs.readdir, en via een andere callback sturen we deze terug naar de gebruiker. Voor reguliere bestanden creëren we een leesbare stream via fs.createReadStream en geven deze door in het antwoord, samen met het inhoudstype dat de mime-module voor dat bestand heeft geproduceerd.

De DELETE-verwerkingscode zal eenvoudiger zijn:

Methods.DELETE = function(path, respond) ( fs.stat(path, function(error, stats) ( if (error && error.code == "ENOENT") respond(204); else if (error) respond(500 , error.toString()); else if (stats.isDirectory()) fs.rmdir(pad, respondErrorOrNothing(reageer)); else fs.unlink(pad, respondErrorOrNothing(reageer)); )); );

U vraagt ​​zich misschien af ​​waarom het verwijderen van een bestand dat niet bestaat een 204-status retourneert in plaats van een fout. We kunnen zeggen dat wanneer u een niet-bestaand bestand probeert te verwijderen, het verzoek al is uitgevoerd, aangezien het bestand er niet meer is. De HTTP-standaard moedigt mensen aan om idempotente verzoeken te doen, dat wil zeggen verzoeken waarbij het meerdere keren herhalen van dezelfde actie geen verschillende resultaten oplevert.

Functie respondErrorOrNothing(reageren) ( return function(error) ( if (error) respond(500, error.toString()); else respond(204); ); )

Wanneer het HTTP-antwoord geen gegevens bevat, kunt u de statuscode 204 (“geen inhoud”) gebruiken. Omdat we callback-functies moeten bieden die in verschillende situaties fouten rapporteren of een 204-antwoord retourneren, heb ik een speciale functie respondErrorOrNothing geschreven die zo'n callback creëert.

Hier is de PUT-verzoekhandler:

Methods.PUT = function(path, respond, request) ( var outStream = fs.createWriteStream(path); outStream.on("error", function(error) ( respond(500, error.toString()); )); outStream.on("finish", function() (respond(204); )); request.pipe(outStream); );

Hier hoeven we niet te controleren of het bestand bestaat; als het bestaat, overschrijven we het gewoon. Opnieuw gebruiken we pipe om gegevens over te dragen van een leesstream naar een schrijfstream, in ons geval van een verzoek naar een bestand. Als de thread niet kan worden aangemaakt, wordt er een “error”-gebeurtenis gegenereerd, die we in het antwoord rapporteren. Wanneer de gegevens succesvol zijn overgedragen, sluit pipe beide streams, waardoor de gebeurtenis “finish” wordt geactiveerd. En daarna kunnen we succes melden met code 204.

Het volledige serverscript vindt u hier: eloquentjavascript.net/code/file_server.js. Het kan worden gedownload en uitgevoerd via Node. Uiteraard kan het worden gewijzigd en aangevuld om oefeningen of experimenten op te lossen.

Het opdrachtregelprogramma curl, publiekelijk beschikbaar op Unix-systemen, kan worden gebruikt om HTTP-verzoeken te maken. Het volgende fragment test onze server. –X wordt gebruikt om de aanvraagmethode op te geven, en –d wordt gebruikt om de hoofdtekst van het verzoek op te nemen.

$ curl http://localhost:8000/file.txt Bestand niet gevonden $ curl -X PUT -d hallo http://localhost:8000/file.txt $ curl http://localhost:8000/file.txt hallo $ curl -X DELETE http://localhost:8000/file.txt $ curl http://localhost:8000/file.txt Bestand niet gevonden

Het eerste verzoek aan file.txt mislukt omdat het bestand nog niet bestaat. Het PUT-verzoek maakt het bestand aan, en zie, het volgende verzoek haalt het met succes op. Nadat u het via DELETE hebt verwijderd, ontbreekt het bestand opnieuw.

Foutverwerking

Er zijn zes plaatsen in de bestandsservercode waar we uitzonderingen omleiden als we niet weten hoe we met fouten moeten omgaan. Omdat uitzonderingen niet automatisch worden doorgegeven aan callback-functies, maar als argumenten, moeten ze elke keer afzonderlijk worden afgehandeld. Dit doet het voordeel van exception handling teniet, namelijk de mogelijkheid om fouten centraal af te handelen.

Wat gebeurt er als iets daadwerkelijk een uitzondering in het systeem genereert? We gebruiken geen try-blokken, dus deze worden helemaal bovenaan de call-stack doorgegeven. In Node zorgt dit ervoor dat het programma wordt beëindigd en de uitzonderingsinformatie (samen met een stacktrace) naar de standaarduitvoer afdrukt.

Daarom zal onze server crashen als er problemen zijn in de code - in tegenstelling tot problemen met asynchronie, die als argumenten aan de call-functie zullen worden doorgegeven. Als we alle uitzonderingen moeten afhandelen die optreden tijdens het verwerken van een verzoek, zodat we nauwkeurig een antwoord kunnen sturen, moeten we try/catch-blokken toevoegen aan elke callback.

Dit is slecht. Veel Node-programma's zijn zo geschreven dat er zo min mogelijk uitzonderingen worden afgehandeld, wat impliceert dat als er een uitzondering optreedt, het programma deze niet aankan en daarom moet crashen.

Een andere benadering is het gebruik van beloftes, die in hoofdstuk 17 zijn beschreven. Ze vangen uitzonderingen op die door callback-functies worden gegenereerd en geven deze door als fouten. In Node kunt u de beloftebibliotheek laden en deze gebruiken om asynchrone oproepen af ​​te handelen. Er zijn maar weinig Node-bibliotheken die beloftes integreren, maar deze zijn meestal vrij eenvoudig in te pakken. De uitstekende “promise” -module van NPM bevat een functie genaamd denodeify, die een asynchrone functie zoals fs.readFile gebruikt en omzet in een functie die een belofte retourneert.

Var Belofte = vereisen("belofte"); var fs = vereisen("fs"); var readFile = Belofte.denodeify(fs.readFile); readFile("file.txt", "utf8").then(function(content) ( console.log("Het bestand bevatte: " + content); ), function(error) ( console.log("Kan bestand niet lezen : " + fout); ));

Ter vergelijking heb ik een andere versie van een bestandsserver geschreven met behulp van beloftes, die te vinden is op eloquentjavascript.net/code/file_server_promises.js. Het is overzichtelijker omdat functies nu resultaten kunnen retourneren in plaats van callbacks toe te wijzen, en uitzonderingen impliciet worden doorgegeven.

Ik citeer daar een paar regels om het verschil in stijlen aan te tonen.

Het fsp-object dat in de code wordt gebruikt, bevat varianten van de fs-functies met beloften die zijn verpakt met behulp van Promise.denodeify. Het object dat door de methode-handler wordt geretourneerd, met de code- en body-eigenschappen, wordt het eindresultaat van de belofteketen en wordt gebruikt om te bepalen welk antwoord naar de client moet worden verzonden.

Methods.GET = function(path) ( return inspectPath(path).then(function(stats) ( if (!stats) // Bestaat niet return (code: 404, body: "Bestand niet gevonden"); else if ( stats.isDirectory()) return fsp.readdir(path).then(function(files) ( return (code: 200, body: files.join("\n")); )); else return (code: 200, type: require("mime").lookup(pad), body: fs.createReadStream(pad)); )); ); function inspectPath(path) ( return fsp.stat(path).then(null, function(error) ( if (error.code == "ENOENT") return null; else throw error; )); )

De functie inspectPath is een eenvoudige wrapper rond fs.stat die het geval afhandelt wanneer een bestand niet wordt gevonden. In dit geval vervangen we de fout door succes, dat null retourneert. Alle andere fouten kunnen worden verzonden. Wanneer de belofte van deze handlers wordt verbroken, reageert de server met code 500.

Kortom

Node is een geweldig, eenvoudig systeem waarmee je JavaScript buiten de browser kunt uitvoeren. Het was oorspronkelijk ontworpen om via een netwerk te werken, om de rol van een knooppunt in het netwerk te spelen. Maar je kunt er veel mee doen, en als je graag in JavaScript programmeert, werkt het automatiseren van dagelijkse taken met Node prima.

NPM biedt bibliotheken voor alles wat u maar kunt bedenken (en zelfs voor sommige dingen die u niet kent), en u kunt ze met een eenvoudige opdracht downloaden en installeren. Node wordt ook geleverd met een reeks ingebouwde modules, waaronder “fs” voor het werken met het bestandssysteem, en “http” voor het uitvoeren van HTTP-servers en het doen van HTTP-verzoeken.

Alle invoer en uitvoer in Node gebeurt asynchroon, tenzij u een expliciet synchrone variant van de functie gebruikt, zoals fs.readFileSync. U levert callback-functies, en Node roept deze op het juiste moment aan wanneer de I/O-bewerkingen zijn voltooid.

Opdrachten

En weer eens over de inhoud
In hoofdstuk 17 was de eerste oefening het maken van verzoeken aan eloquentjavascript.net/author waarin om verschillende soorten inhoud werd gevraagd door verschillende Accept-headers door te geven.

Doe het opnieuw met behulp van de http.request-functie van Node. Vraag minimaal de typen text/plain, text/html en application/json aan. Houd er rekening mee dat verzoekheaders kunnen worden doorgegeven als object in de headers-eigenschap, het eerste argument voor http.request.

Druk de inhoud van elk antwoord af.

Lekken repareren

Om de toegang tot bestanden te vereenvoudigen, heb ik de server op mijn computer laten draaien, in de directory /home/marijn/public. Op een dag ontdekte ik dat iemand toegang had tot al mijn wachtwoorden die ik in de browser had opgeslagen. Wat is er gebeurd?

Als dit u niet duidelijk is, onthoud dan de functie urlToPath, die als volgt is gedefinieerd:

Functie urlToPath(url) ( var path = require("url").parse(url).padnaam; return "." + decodeURIComponent(pad); )

Bedenk nu dat de paden die aan de functie “fs” worden doorgegeven, relatief kunnen zijn. Ze kunnen een pad “../” naar de bovenste map bevatten. Wat gebeurt er als de client verzoeken verzendt naar URL's zoals de volgende:

mijnhostnaam:8000/../.config/config/google-chrome/Default/Web%20Data
mijnhostnaam :8000/../.ssh/id_dsa
mijnhostnaam :8000/../../../etc/passwd

Wijzig de urlToPath-functie om dit probleem op te lossen. Houd er rekening mee dat op Windows Node zowel forward als backslashes de mogelijkheid hebben om paden te specificeren.

Denk er ook over na dat zodra u een grof systeem aan het internet blootstelt, bugs in het systeem tegen u en uw computer kunnen worden gebruikt.

Mappen maken
Hoewel de DELETE-methode ook werkt bij het verwijderen van mappen (via fs.rmdir), biedt de server nog niet de mogelijkheid om mappen aan te maken.

Voeg ondersteuning toe voor de MKCOL-methode, die een map moet maken via fs.mkdir. MKCOL is geen kern-HTTP-methode, maar bestaat wel voor dat doel in de WebDAV-standaard, die uitbreidingen op HTTP bevat, zodat deze kan worden gebruikt voor het schrijven van bronnen in plaats van ze alleen maar te lezen.

Openbare plaats op internet
Omdat de bestandsserver alle bestanden aanbiedt en zelfs de juiste Content-Type header retourneert, kan deze worden gebruikt om een ​​website te bedienen. Omdat iedereen hiermee bestanden kan verwijderen en vervangen, zou dit een interessante site zijn - een site die kan worden gewijzigd, beschadigd en verwijderd door iedereen die een geldig HTTP-verzoek kan maken. Maar het zou nog steeds een website zijn.

Schrijf een eenvoudige HTML-pagina met een eenvoudig JavaScript-bestand. Plaats ze in een map die door de server wordt beheerd en open ze in een browser.

Combineer vervolgens als geavanceerde oefening alles wat u uit het boek hebt geleerd om een ​​gebruiksvriendelijkere interface te bouwen voor het wijzigen van een website vanuit de site zelf.

Gebruik een HTML-formulier (hoofdstuk 18) om de bestanden waaruit de site bestaat te bewerken, zodat de gebruiker deze op de server kan bijwerken via HTTP-verzoeken, zoals beschreven in hoofdstuk 17.

Begin met één bestand dat u kunt bewerken. Zorg er vervolgens voor dat u het bestand kunt selecteren dat u wilt bewerken. Profiteer van het feit dat onze bestandsserver lijsten met bestanden retourneert wanneer daarom wordt gevraagd door een map.

Wijzig de bestanden niet rechtstreeks in de bestandsservercode. Als u een fout maakt, zullen deze bestanden waarschijnlijk beschadigd raken. Werk in een map die niet van buitenaf toegankelijk is en kopieer ze daar na het testen.

Als uw computer rechtstreeks verbinding maakt met internet, zonder firewall, router of andere apparaten, kunt u een vriend uitnodigen voor uw site. Om dit te controleren, gaat u naar whatismyip.com, kopieert u het IP-adres naar de adresbalk en voegt u:8000 toe om de gewenste poort te selecteren. Als u zich op uw site bevindt, is deze voor iedereen zichtbaar.

Er werd nagedacht over de overgang van synchrone naar asynchrone code. In het tweede deel zal ik kijken naar de overgang van byte-voor-byte naar regel-voor-regel lezen uit een bestand. Omdat de handeling van het coderen van de ene codering naar de andere tamelijk triviaal is, zal ik wat meer vertellen over de theoretische kant van de kwestie.

Deel 2: Een regel uit een bestand lezen

Fs.readFile("large.txt", ( codering: "utf8"), (err, data) => ( if (err) throw err; data.split("\n").forEach(line => ( doSomethingWithLine (lijn); )); ));

Hij is misschien wel de snelste. Maar het vereist ook het meeste geheugen: van 100% tot 200% van de bestandsgrootte. 200% is tegelijkertijd het meest voorkomende geval, aangezien de geheugencodering van de string UTF-16 is en daarom de grootte van het vereiste geheugen met twee wordt vermenigvuldigd als het bestand voornamelijk tekens uit het single-byte UTF-8-bereik bevat .

Bovendien raden Node.js-ontwikkelaars niet aan om veel gegevens in een Node.js-proces te laden (zie Wat is de geheugenlimiet voor een knooppuntproces?). Dit gebeurt niet erg elegant - zelfs als er voldoende fysiek geheugen is, wordt er een uitzondering gegenereerd wanneer u een bestand probeert te downloaden dat groter is dan 1 Gb:

This.parent = nieuwe SlowBuffer(this.length); ^ RangeError: lengte> kMaxLengte

Als het bestand kleiner is, kunt u zoiets als dit krijgen:

FATAL ERROR: CALL_AND_RETRY_0 Toewijzing mislukt - proces onvoldoende geheugen

Het enige dat overblijft is het bestand in delen te verwerken. Om dit te doen, moet je het in delen lezen en Node.js biedt hiervoor minimaal 5 manieren:

  1. Gebruik “oude” streams - open een stream en abonneer u op het “data” evenement.
  2. Gebruik "nieuwe" streams - abonneer u op de "readable" gebeurtenis en gebruik de read() -methode.
  3. Maak uw eigen WritableStream en stuur er een bestandsstream naartoe met behulp van de “pipe()” -methode.
  4. Gebruik bestandsdescriptors en een reeks open(), read(), close() methoden.
  5. Gebruik synchrone opties - openSync(), readSync(), closeSync().

Opties 1-3 zijn eleganter, omdat ze werken met een handige abstractie: een stream. Hierdoor kunt u een programma beschouwen als een gegevensstroomdiagram en termen als samenvoegen, splitsen en transformatie gebruiken bij het ontwerpen van een architectuur.

Ook verschillen opties 1 en 2 in de mogelijkheid om tekens uit een bestand te lezen. Bij opties 3 en 4 worden gegevens uit het bestand naar een buffer geschreven en moeten vervolgens naar tekst worden geconverteerd.

// Optie #1 - "oude" streams var stream = fs.createReadStream(file, (codering: "utf8" )); stream.on("data", (_, data) => processData(data)); stream.on("einde", klaar); // Optie #2 - "nieuwe" streams var stream = fs.createReadStream(file, (codering: "utf8" )); stream.on("leesbaar", () => processData(stream.read())); stream.on("einde", klaar); // Optie #3 - pipe var stream = fs.createReadStream(file, (codering: "utf8" )); var writeStream = new Writable(); writeStream._write = (chunk, codering, callback) => ( processData(chunk); callback(); ); writeStream.on("einde", klaar); stream.pipe(schrijfStream); // Optie #4 - asynchrone fs-methoden fs.open(file, "r", (err, fd) => ( var buffer = new Buffer(1000*1000); (function next() ( fs.read(fd, buffer, 0, buffer.length, null, (err, bytesRead, buffer) => ( if (bytesRead === 0) ( fs.close(fd, done); ) else ( processData(buffer); next(); ) )); )()); ));

Het meer conceptuele verschil zit in het ophalen van gegevens uit een bestand. Opties 1-2 ontvangen het volgende fragment zodra de gebeurtenishandler van het huidige fragment is voltooid. In het geval van asynchrone code in de handler is de volgorde van uitvoering onvoorspelbaar:

Functie processData(chunk) ( console.log("eerste") setImmediate(() => ( console.log("tweede"); setImmediate(() => console.log("derde")); )); ) var stream = fs.createReadStream(bestand, (codering: "utf8" )); stream.on("leesbaar", () => processData(stream.read())); ... eerste derde tweede derde eerste tweede ...

De situatie kan worden gecorrigeerd met behulp van de methoden pauze()/resume().

Functie processData(chunk, done) ( console.log("eerste") setImmediate(() => ( console.log("tweede"); setImmediate(() => ( console.log("derde"); done( ); )); )); ) var stream = fs.createReadStream(bestand, (codering: "utf8" )); stream.on("leesbaar", () => ( stream.pause(); processData(stream.read(), () => stream.resume()); )); ... eerste tweede derde eerste tweede derde ...

Bij opties 3 en 4 wordt het volgende fragment pas ontvangen na overdracht van zeggenschap (optie 3) of verzoek (optie 4).

Ik denk dat er voldoende informatie is om de functie createTextReader() uit het eerste deel van het artikel te implementeren. Van alle opties is de vierde de meest geschikte, omdat de besturingsstroom vergelijkbaar is met de interface (request-callback).

Functie createTextReader(file, options, done) ( var lengte, codering, scheidingsteken; if ("function" === type opties) ( done = opties; opties = ( ); ) lengte = 4 * 1024; encoding = opties.encoding || "utf8"; separator = (options.separator || "\n"); fs.open(file, "r", (err, fd) => ( var eof, tail, buffer, decoder, lines; if (err) ( done(err); return; ) eof = false; buffer = nieuwe buffer(lengte); tail = ""; lines = ; decoder = nieuwe StringDecoder(codering); done(null, ( readLine: done => ( var line; if (lines.length > 0) ( line = lines.shift(); done(null, line); ) else if (eof) ( done(null, null); ) else ( (functie read() ( fs.read(fd, buffer, 0, lengte, null, functie (err, bytesRead, buffer) ( var index, position; if (bytesRead === 0) ( eof = true; done(null, tail); ) else ( tail = tail + decoder.write(buffer.slice(0, bytesRead)); index = -1; while (-1 !== (positie = tail.indexOf(separator, index))) ( lines.push( tail.substring(index, positie));index = positie + scheidingsteken.lengte; ) staart = staart.substring(index); if (lines.length === 0) ( read(); ) else ( line = lines.shift(); done(null, line); ) ) )); )()); ) ), close: done => ( fs.close(fd, () => ( if (done) ( done(err || null); ) )); ) )); )); )

Nawoord

In twee delen van dit artikel heb ik geprobeerd alles te schetsen wat voor mij nuttig was bij het maken van de module https://github.com/AlexAtNet/async-read-lines. Helaas bleef er veel buiten de scope en was er niet genoeg tijd voor alles. Dus als u een fout of typefout vindt, schrijf dan in privéberichten. Als u vragen heeft over het onderwerp van het artikel, beantwoord ik deze graag in de reacties. Als je bugs in de module ziet, maak dan een verzoek in github-problemen. U kunt persoonlijk contact met mij opnemen via de website alexatnet.com.

Veel succes met programmeren!

Over de auteur: Alexander Netkachev is een senior ontwikkelaar in C# en F#. Onderhoudt de website alexatnet.com, geeft webinars (Code&Coffe), helpt beginnende ontwikkelaars met code (CodeReview4U).

Er is tegenwoordig geen tekort aan Node.js-tutorials, maar de meeste behandelen specifieke gebruiksscenario's of onderwerpen die van toepassing zijn zodra Node.js actief is. Hier en daar zie ik opmerkingen als "Ik heb Node.js gedownload, wat nu? Dit artikel beantwoordt deze vraag en legt uit hoe je vanaf het allereerste begin kunt beginnen.

Wat is Node.js?

Veel verwarring bij degenen die nieuw zijn bij Node.js komt doordat ze niet begrijpen wat het eigenlijk is. En de beschrijving op nodejs.org helpt niet veel om het te begrijpen.

Het is belangrijk om te begrijpen dat Node geen webserver is. Op zichzelf doet het niets. Dit is geen Apache. Er is geen configuratiebestand dat het pad naar de HTML-bestanden specificeert. Als u een HTTP-server nodig heeft, moet u een HTTP-server schrijven (met behulp van ingebouwde bibliotheken). Node.js is gewoon een andere manier om code op uw computer uit te voeren. Het is gewoon een omgeving voor het uitvoeren van JavaScript.

Knooppunt installatie

Het installeren van Node.js is heel eenvoudig. Als u Windows of Mac gebruikt, zijn de installatiebestanden beschikbaar op de downloadpagina.

Ik heb Node geïnstalleerd, wat nu?

Direct na de installatie is het nieuwe knooppuntcommando voor u beschikbaar. Het kan op twee verschillende manieren worden gebruikt. De eerste manier is zonder argumenten. Hierdoor wordt een interactieve shell geopend (REPL: read-eval-print-loop) waar u reguliere JavaScript-code kunt uitvoeren.

$ node > console .log("Hallo wereld"); Hallo wereld ongedefinieerd

In het bovenstaande voorbeeld schreef ik console.log("Hello World") in de shell en drukte op Enter. Node.js zal deze code uitvoeren en we zullen een bericht zien. undefined wordt erna afgedrukt omdat de shell de retourwaarde van elke opdracht weergeeft, en console.log retourneert niets.

Bovendien kunnen we een JavaScript-bestand doorgeven aan Node voor uitvoering. Dit is wat je vrijwel altijd zult doen.

hallo.js

console .log("Hallo wereld" );

Laten we het nu in de terminal uitvoeren:

$node hello.js Hallo wereld

In dit voorbeeld heb ik console.log naar een bestand verplaatst, dat ik vervolgens als argument aan de node-opdracht heb doorgegeven. Node voert vervolgens JavaScript uit vanuit dit bestand en voert Hello World uit.

Iets nuttigs doen: met bestanden werken

Gewoon JavaScript-code uitvoeren is leuk en zo, maar niet erg nuttig. Daarom bevat Node.js ook een krachtige set bibliotheken (modules) voor serieuze taken. In dit eerste voorbeeld ga ik een logbestand openen en verwerken.

voorbeeld_log.txt

2013-08-09T13 :50 :33.166Z A 2 2013-08-09T13 :51 :33.166Z B 1 2013-08-09T13 :52 :33.166Z C 6 2013-08-09T13 :53 :33.166Z B 8 201 3-08-09 T13 : 54:33.166Z B 5

Het maakt niet uit wat de gegevens in de logs betekenen, maar in wezen bevat elk bericht een datum, een letter en een waarde. Laten we zeggen dat ik de waarden voor elke letter wil optellen.

mijn_parser.js

var fs = vereisen("fs"); functie (err, logData){ // Als er een fout optreedt, genereren we een uitzondering // en de applicatie eindigt als (fout) gooi fout; // logData is van het buffertype, converteer het naar string var tekst = logData.toString(); ));

Werken met bestanden in Node.js is heel eenvoudig dankzij de ingebouwde fs-bestandssysteemmodule. Deze module bevat de functie readFile, die een bestandspad en een callback als argumenten gebruikt. De callback wordt aangeroepen wanneer het bestand klaar is met lezen. De gegevens uit het bestand hebben de vorm van een Buffer-object, dat in wezen een array van bytes is. We kunnen het naar een string converteren met de functie toString().

Laten we nu beginnen met het verwerken van de logboeken. Dit is een vrij eenvoudige JavaScript-code, dus ik zal niet in detail treden.

mijn_parser.js

var fs = vereisen("fs"); // Lees de bestandsinhoud in het geheugen fs.readFile("voorbeeld_log.txt" , functie (err, logData){ // Als er een fout optreedt, genereren we een uitzondering, // en de applicatie zal eindigen. als (fout) gooi fout; // logData is van het type Buffer, converteer het naar een string var tekst = logData.toString(); var resultaten = (); // Het bestand regel voor regel splitsen var lijnen = tekst.split("\n" ); lijnen.forEach( functie(lijn)( var parts = line.split(" "); var letter = parts; var count = parseInt (parts); if (!results) ( results = 0 ; ) results += parseInt (count); )); console .log(resultaten); // (A: 2, B: 14, C: 6)));

Wanneer u dit bestand nu als argument aan het knooppunt doorgeeft, wordt het resultaat afgedrukt en afgesloten.

$node mijn_parser.js (A: 2, B: 14, C: 6)

Ik gebruik Node.js vaak voor dit soort taken. Het is een eenvoudig en krachtig alternatief voor bash-scripts.

Asynchrone terugbelgesprekken

Zoals u in het vorige voorbeeld kunt zien, is een typisch patroon in Node.js het gebruik van asynchrone callbacks. In wezen zeg je dat het iets moet doen, en als het klaar is, roep je je functie aan (callback). Dit komt omdat Node.js single-threaded is. Terwijl u wacht tot de callback wordt aangeroepen, kan Node.js afgeleid raken en andere dingen doen in plaats van de thread te blokkeren terwijl u wacht tot het verzoek is verwerkt.

Dit is vooral belangrijk voor webservers. Toegang tot databases in moderne webapplicaties is gebruikelijk. Terwijl u wacht op een reactie van de database, kan Node.js meer verzoeken verwerken. Hierdoor kunt u duizenden gelijktijdige verbindingen verwerken met zeer weinig overhead, vergelijkbaar met het maken van een afzonderlijke thread voor elke verbinding.

Iets nuttigs doen: HTTP-server

Zoals ik al eerder zei, doet Node.js niets kant-en-klaar. Met een van de ingebouwde modules kun je eenvoudig een eenvoudige HTTP-server maken, zoals weergegeven in het voorbeeld op de Node.js-site.

mijn_web_server.js

var http = vereisen("http"); http.createServer( functie (req, res)( res.writeHead(200, ("Content-Type" : "text/plain" )); res.end("Hallo wereld\n" ); )).listen(8080); console .log( "Server draait op poort 8080.");

Als ik simpel zeg, betekent het ook simpel. Dit is geen mooie HTTP-server. Het werkt niet met HTML of afbeeldingen. Wat je ook vraagt, het zal Hello World teruggeven. U kunt het echter wel uitvoeren, ga naar http://localhost:8080 in uw browser en bekijk deze tekst.

$node mijn_web_server.js

Mogelijk merkt u een klein verschil: uw applicatie stopt niet. Dit gebeurt omdat je de server hebt gemaakt, en nu blijft deze werken en reageren op verzoeken totdat je het knooppunt zelf afsluit.

Als je van deze applicatie een volwaardige webserver wilt maken, zul je hard moeten werken. U moet controleren wat er wordt gevraagd, de bijbehorende bestanden lezen en de inhoud terugsturen. Het goede nieuws is echter dat anderen dit harde werk al voor je hebben gedaan.