本书状态
你正在阅读的已经是本书的最终版。因此,只有当进行错误更正以及针对新版本Node.js的改动进行对应的修正时,才会进行更新。
本书中的代码案例都在Node.js 0.6.11版本中测试过,可以正确工作。
读者对象
本书最适合与我有相似技术背景的读者: 至少对一门诸如Ruby、Python、PHP或者Java这样面向对象的语言有一定的经验;对JavaScript处于初学阶段,并且完全是一个Node.js的新手。
这里指的适合对其他编程语言有一定经验的开发者,意思是说,本书不会对诸如数据类型、变量、控制结构等等之类非常基础的概念作介绍。要读懂本书,这些基础的概念我都默认你已经会了。
然而,本书还是会对JavaScript中的函数和对象作详细介绍,因为它们与其他同类编程语言中的函数和对象有很大的不同。
本书结构
读完本书之后,你将完成一个完整的web应用,该应用允许用户浏览页面以及上传文件。
当然了,应用本身并没有什么了不起的,相比为了实现该功能书写的代码本身,我们更关注的是如何创建一个框架来对我们应用的不同模块进行干净地剥离。 是不是很玄乎?稍后你就明白了。
本书先从介绍在Node.js环境中进行JavaScript开发和在浏览器环境中进行JavaScript开发的差异开始。
紧接着,会带领大家完成一个最传统的“Hello World”应用,这也是最基础的Node.js应用。
最后,会和大家讨论如何设计一个“真正”完整的应用,剖析要完成该应用需要实现的不同模块,并一步一步介绍如何来实现这些模块。
可以确保的是,在这过程中,大家会学到JavaScript中一些高级的概念、如何使用它们以及为什么使用这些概念就可以实现而其他编程语言中同类的概念就无法实现。
该应用所有的源代码都可以通过 本书Github代码仓库:https://github.com/ManuelKiessling/NodeBeginnerBook/tree/master/code/application.
JavaScript与Node.js
JavaScript与你
抛开技术,我们先来聊聊你以及你和JavaScript的关系。本章的主要目的是想让你看看,对你而言是否有必要继续阅读后续章节的内容。
如果你和我一样,那么你很早就开始利用HTML进行“开发”,正因如此,你接触到了这个叫JavaScript有趣的东西,而对于JavaScript,你只会基本的操作——为web页面添加交互。
而你真正想要的是“干货”,你想要知道如何构建复杂的web站点 —— 于是,你学习了一种诸如PHP、Ruby、Java这样的编程语言,并开始书写“后端”代码。
与此同时,你还始终关注着JavaScript,随着通过一些对jQuery,Prototype之类技术的介绍,你慢慢了解到了很多JavaScript中的进阶技能,同时也感受到了JavaScript绝非仅仅是window.open() 那么简单。 .
不过,这些毕竟都是前端技术,尽管当想要增强页面的时候,使用jQuery总让你觉得很爽,但到最后,你顶多是个JavaScript用户,而非JavaScript开发者。
然后,出现了Node.js,服务端的JavaScript,这有多酷啊?
于是,你觉得是时候该重新拾起既熟悉又陌生的JavaScript了。但是别急,写Node.js应用是一件事情;理解为什么它们要以它们书写的这种方式来书写则意味着——你要懂JavaScript。这次是玩真的了。
问题来了: 由于JavaScript真正意义上以两种,甚至可以说是三种形态存在(从中世纪90年代的作为对DHTML进行增强的小玩具,到像jQuery那样严格意义上的前端技术,一直到现在的服务端技术),因此,很难找到一个“正确”的方式来学习JavaScript,使得让你书写Node.js应用的时候感觉自己是在真正开发它而不仅仅是使用它。
因为这就是关键: 你本身已经是个有经验的开发者,你不想通过到处寻找各种解决方案(其中可能还有不正确的)来学习新的技术,你要确保自己是通过正确的方式来学习这项技术。
当然了,外面不乏很优秀的学习JavaScript的文章。但是,有的时候光靠那些文章是远远不够的。你需要的是指导。
本书的目标就是给你提供指导。
简短申明
业界有非常优秀的JavaScript程序员。而我并非其中一员。
Ich bin die im vorherigen Abschnitt beschriebene Person. Ich war mit der Entwicklung von Backend-Webanwendungen vertraut, aber mit „echtem“ JavaScript und Node.js war ich neu. Ich habe erst kürzlich einige fortgeschrittene Konzepte von JavaScript gelernt und habe keine praktische Erfahrung.
Daher handelt es sich bei diesem Buch nicht um ein „Vom Einstieg zum Meister“-Buch, sondern eher um ein „Vom Anfänger zum Fortgeschrittenen“-Buch.
Wenn es gelingt, wird dieses Buch das Tutorial sein, das ich mir am meisten gewünscht habe, als ich anfing, Node.js zu lernen.
Serverseitiges JavaScript
JavaScript wurde zunächst im Browser ausgeführt. Der Browser stellte jedoch nur einen Kontext bereit, der definierte, was mit JavaScript getan werden kann, aber nicht viel darüber „sagte“, was die JavaScript-Sprache selbst tun kann. Tatsächlich ist JavaScript eine „vollständige“ Sprache: Sie kann in verschiedenen Kontexten verwendet werden und ihre Fähigkeiten sind genauso gut wie die anderer ähnlicher Sprachen.
Node.js ist eigentlich ein weiterer Kontext, der die Ausführung von JavaScript-Code im Backend (außerhalb der Browserumgebung) ermöglicht.
Um JavaScript-Code im Hintergrund auszuführen, muss der Code zuerst interpretiert und dann korrekt ausgeführt werden. Dies ist das Prinzip von Node.js. Es nutzt die virtuelle V8-Maschine von Google (die vom Chrome-Browser von Google verwendete JavaScript-Ausführungsumgebung), um JavaScript-Code zu interpretieren und auszuführen.
Darüber hinaus enthält Node.js viele nützliche Module, die viele wiederkehrende Aufgaben vereinfachen können, wie beispielsweise die Ausgabe von Strings an das Terminal.
Daher ist Node.js tatsächlich sowohl eine Laufzeitumgebung als auch eine Bibliothek.
Um Node.js verwenden zu können, müssen Sie es zunächst installieren. Auf die Installation von Node.js werde ich hier nicht näher eingehen. Sie können sich direkt auf die offizielle Installationsanleitung beziehen. Kommen Sie nach Abschluss der Installation noch einmal zurück und lesen Sie den Rest dieses Buches.
„Hallo Welt“
Okay, kein „Unsinn“ mehr, starten wir unsere erste Node.js-Anwendung: „Hello World“.
Öffnen Sie Ihren bevorzugten Editor und erstellen Sie eine helloworld.js-Datei. Was wir tun müssen, ist „Hello World“ an STDOUT auszugeben. Das Folgende ist der Code zum Implementieren dieser Funktion:
Speichern Sie die Datei und führen Sie sie über Node.js aus:
Wenn normal, wird Hello World im Terminal ausgegeben.
Okay, ich gebe zu, dass diese Anwendung etwas langweilig ist, also lasst uns ein paar „trockene Sachen“ besorgen.
Eine vollständige Webanwendung basierend auf Node.js
Anwendungsfälle
Machen wir das Ziel einfach, aber realistisch genug:
1. Benutzer können unsere Anwendung über den Browser verwenden.
2. Wenn der Benutzer http://domain/start anfordert, wird ihm eine Willkommensseite mit einem Datei-Upload-Formular angezeigt.
3. Der Benutzer kann ein Bild auswählen und das Formular absenden. Anschließend wird die Datei auf http://domain/upload hochgeladen. Nachdem die Seite hochgeladen wurde, wird das Bild auf der Seite angezeigt.
Das war's. Sie können jetzt auch zu Google gehen und etwas finden, mit dem Sie herumspielen können, um die Funktion abzuschließen. Aber lasst uns das jetzt nicht tun.
Um dieses Ziel zu erreichen, benötigen wir außerdem mehr als nur Basiscode, unabhängig davon, ob der Code elegant ist oder nicht. Wir müssen dies auch abstrahieren, um eine Möglichkeit zu finden, komplexere Node.js-Anwendungen zu erstellen.
Verschiedene Module zur Analyse anwenden
Welche Teile müssen wir implementieren, um die oben genannten Anwendungsfälle zu implementieren?
1. Wir müssen Webseiten bereitstellen, daher benötigen wir einen HTTP-Server
2. Für verschiedene Anfragen muss unser Server je nach angeforderter URL unterschiedliche Antworten geben, daher benötigen wir eine Route, um die Anfragen entsprechend weiterzuleiten an den Request-Handler
3. Nachdem die Anfrage vom Server empfangen und über die Route weitergeleitet wurde, muss sie verarbeitet werden, daher benötigen wir den endgültigen Request-Handler
4. Die Route sollte sie auch verarbeiten können POST-Daten, kapseln Sie die Daten in ein benutzerfreundlicheres Format und übergeben Sie sie an das Anforderungsverarbeitungsprogramm, sodass Sie die Datenverarbeitungsfunktion anfordern müssen
5. Wir müssen nicht nur die der URL entsprechende Anforderung verarbeiten, sondern auch Zeigt den Inhalt an, was bedeutet, dass wir eine Ansichtslogik für den Anforderungshandler benötigen, um Inhalte an den Browser des Benutzers zu senden
6. Schließlich muss der Benutzer Bilder hochladen, daher benötigen wir die Upload-Verarbeitungsfunktion, um die Details dieses Machen wir es zuerst. Überlegen Sie, wie wir diese Struktur mit PHP erstellen würden. Im Allgemeinen verwenden wir einen Apache-HTTP-Server mit dem Modul mod_php5.
Aus dieser Perspektive muss die gesamte Anforderung „HTTP-Anfrage empfangen und eine Webseite bereitstellen“ überhaupt nicht von PHP behandelt werden.
Es hört sich nach viel Arbeit an, aber dann werden wir nach und nach erkennen, dass es für Node.js keine lästige Sache ist.
Jetzt beginnen wir mit der Implementierung, beginnend mit dem ersten Teil-HTTP-Server.
Module zum Erstellen von Anwendungen
Ein einfacher HTTP-Server
Als ich mit dem Schreiben meiner ersten „echten“ Node.js-Anwendung beginnen wollte, wusste ich nicht nur nicht, wie man Node.js-Code schreibt, sondern auch nicht, wie ich ihn organisieren sollte.Soll ich alles in einer Datei zusammenfassen? Es gibt viele Tutorials im Internet, die Ihnen zeigen, wie Sie die gesamte Logik in einen einfachen, in Node.js geschriebenen HTTP-Server integrieren. Aber was ist, wenn ich mehr Inhalte hinzufügen und gleichzeitig den Code lesbar halten möchte?
Dieser Ansatz ermöglicht Ihnen eine saubere Hauptdatei, die Sie mit Node.js ausführen können, und Sie können saubere Module haben, die von der Hauptdatei und anderen Modulen aufgerufen werden können.
Also, jetzt erstellen wir eine Hauptdatei, die unsere Anwendung startet, und ein Modul, das unseren HTTP-Servercode enthält.
Meiner Meinung nach ist der Aufruf der Hauptdatei index.js mehr oder weniger ein Standardformat. Das Einfügen des Servermoduls in eine Datei namens server.js ist leicht zu verstehen.
Beginnen wir mit dem Servermodul. Erstellen Sie eine Datei namens server.js im Stammverzeichnis Ihres Projekts und schreiben Sie den folgenden Code:
Response.writeHead(200, {"Content-Type": "text/plain"});
Response.write("Hello World") ;
Response.end();
}).listen(8888);
Fertig! Sie haben gerade einen funktionierenden HTTP-Server fertiggestellt. Um dies zu beweisen, führen wir diesen Code aus und testen ihn. Führen Sie zunächst Ihr Skript mit Node.js aus:
node server.js
Öffnen Sie als Nächstes den Browser und besuchen Sie http://localhost:8888/. Sie sehen eine Webseite mit der Aufschrift „Hello World“.
Das ist interessant, nicht wahr? Lassen Sie uns zunächst über den HTTP-Server sprechen und die Organisation des Projekts außer Acht lassen. Was denken Sie? Ich verspreche, dass wir das später herausfinden werden.
HTTP-Server analysieren
Als nächstes analysieren wir die Zusammensetzung dieses HTTP-Servers.
Die erste Zeile fordert das mit Node.js gelieferte http-Modul an und weist es der http-Variablen zu.
Als nächstes rufen wir die vom http-Modul bereitgestellte Funktion auf: createServer. Diese Funktion gibt ein Objekt zurück. Dieses Objekt verfügt über eine Methode namens listen. Diese Methode verfügt über einen numerischen Parameter, der die Portnummer angibt, die der HTTP-Server überwacht.
Lassen Sie uns vorerst die Funktionsdefinition in den Klammern von http.createServer ignorieren.
Wir hätten Code wie diesen verwenden können, um den Server zu starten und Port 8888 abzuhören:
var server = http.createServer();
server.listen(8888);
Der interessanteste (und, wenn Sie an eine konservativere Sprache wie PHP gewöhnt sind, seltsamste) Teil ist das erste Argument für createSever(), eine Funktionsdefinition.
Tatsächlich ist diese Funktionsdefinition der erste und einzige Parameter von createServer(). Denn in JavaScript können Funktionen wie andere Variablen übergeben werden.
Funktionsübertragung durchführen
Zum Beispiel können Sie dies tun:
Funktion ausführen(someFunction, value) {
someFunction(value);
}
execute(sagen Sie „Hallo“);
Bitte lesen Sie diesen Code sorgfältig durch! Hier übergeben wir die Say-Funktion als erste Variable der Execute-Funktion. Was hier zurückgegeben wird, ist nicht der Rückgabewert von say, sondern say selbst!
Auf diese Weise wird say zur lokalen Variablen, someFunction in Execute kann die Funktion say verwenden, indem someFunction() (mit Klammern) aufgerufen wird.
Da say eine Variable hat, kann Execute natürlich eine solche Variable übergeben, wenn someFunction aufgerufen wird.
Wir können, wie gerade getan, eine Funktion anhand ihres Namens als Variable übergeben. Aber wir müssen diesen „Zuerst definieren, dann übergeben“-Kreis nicht umgehen. Wir können diese Funktion direkt in den Klammern einer anderen Funktion definieren und übergeben:
execute(function(word){ console.log(word) }, "Hello");
Wir definieren direkt die Funktion, die wir an die Ausführung übergeben werden, wobeiexecute den ersten Parameter akzeptiert.
Auf diese Weise müssen wir der Funktion nicht einmal einen Namen geben, weshalb sie als anonyme Funktion bezeichnet wird.
Dies ist unsere erste enge Begegnung mit dem, was ich als „fortgeschrittenes“ JavaScript bezeichne, aber wir müssen es noch Schritt für Schritt angehen. Lassen Sie uns zunächst Folgendes akzeptieren: In JavaScript kann eine Funktion einen Parameter als eine andere Funktion empfangen. Wir können zuerst eine Funktion definieren und sie dann übergeben, oder wir können die Funktion direkt dort definieren, wo die Parameter übergeben werden.
Wie die Funktionsübergabe dafür sorgt, dass HTTP-Server funktionieren
Mit diesem Wissen werfen wir einen Blick auf unseren einfachen, aber nicht einfachen HTTP-Server:
http.createServer(function(request, Response) {
Response.writeHead(200, {"Content-Type": "text/plain"});
Response.write("Hello World") ;
Response.end();
}).listen(8888);
Der gleiche Zweck kann mit Code wie diesem erreicht werden:
function onRequest(request, Response) {
Response.writeHead(200, {"Content-Type": "text/plain"});
Response.write("Hello World");
Response.end();
}
http.createServer(onRequest).listen(8888);
Ereignisgesteuerte Rückrufe
Diese Frage ist nicht einfach zu beantworten (zumindest für mich), aber so funktioniert Node.js nativ. Es ist ereignisgesteuert und daher so schnell.
Vielleicht möchten Sie sich einen Moment Zeit nehmen und Felix Geisendörfers Meisterwerk „Understanding node.js“ lesen, das etwas Hintergrundwissen vermittelt.
Alles läuft darauf hinaus, dass „Node.js ereignisgesteuert ist“. Na ja, eigentlich weiß ich nicht genau, was dieser Satz bedeutet. Aber ich werde versuchen zu erklären, warum es für uns sinnvoll ist, webbasierte Anwendungen mit Node.js zu schreiben.
Wenn wir die Methode http.createServer verwenden, möchten wir natürlich nicht nur, dass ein Server an einem bestimmten Port lauscht, sondern auch, dass er etwas tut, wenn der Server eine HTTP-Anfrage empfängt.
Das Problem ist, dass dies asynchron ist: Anfragen können jederzeit eintreffen, aber unser Server läuft in einem einzigen Prozess.
Wenn wir PHP-Anwendungen schreiben, machen wir uns darüber überhaupt keine Sorgen: Jedes Mal, wenn eine Anfrage eingeht, erstellt der Webserver (normalerweise Apache) einen neuen Prozess für diese Anfrage und beginnt mit der Ausführung der entsprechenden Prozesse von Anfang bis Ende. PHP-Skript.
Wie steuern wir also in unserem Node.js-Programm den Prozess, wenn eine neue Anfrage an Port 8888 eintrifft?
Nun, hier kann das ereignisgesteuerte Design von Node.js/JavaScript wirklich helfen – obwohl wir noch einige neue Konzepte lernen müssen, um es zu beherrschen. Sehen wir uns an, wie diese Konzepte auf unseren Servercode angewendet werden.
Wir haben den Server erstellt und eine Funktion an die Methode übergeben, die ihn erstellt hat. Immer wenn unser Server eine Anfrage erhält, wird diese Funktion aufgerufen.
Wir wissen nicht, wann dies geschehen wird, aber wir haben jetzt einen Ort, an dem wir die Anfrage bearbeiten können: Es ist die Funktion, an die wir sie übergeben haben. Dabei spielt es keine Rolle, ob es sich um eine vordefinierte Funktion oder eine anonyme Funktion handelt.
Das ist der legendäre Rückruf. Wir übergeben eine Funktion an eine Methode, und diese Methode ruft diese Funktion auf, um einen Rückruf durchzuführen, wenn ein entsprechendes Ereignis eintritt.
Zumindest für mich hat es einige Arbeit gekostet, es herauszufinden. Wenn Sie sich immer noch nicht sicher sind, lesen Sie den Blogbeitrag von Felix.
Lassen Sie uns dieses neue Konzept noch einmal betrachten. Wie beweisen wir, dass unser Code nach der Erstellung des Servers auch dann weiterhin gültig ist, wenn keine HTTP-Anfrage eingeht und unsere Callback-Funktion nicht aufgerufen wird? Versuchen wir Folgendes:
Funktion onRequest(Anfrage, Antwort) {
console.log("Anfrage erhalten.");
Response.writeHead(200, {"Content-Type": "text/plain"});
Response.write("Hello World");
Response.end();
}
http.createServer(onRequest).listen(8888);
console.log("Server wurde gestartet.");
Wenn wir node server.js wie gewohnt ausführen, wird in der Befehlszeile sofort „Server wurde gestartet“ ausgegeben. Wenn wir eine Anfrage an den Server stellen (besuchen Sie http://localhost:8888/ im Browser), erscheint die Meldung „Anfrage erhalten“.
Dies ist ereignisgesteuertes asynchrones serverseitiges JavaScript und seine Rückrufe!
(Bitte beachten Sie, dass unser Server beim Zugriff auf die Webseite auf dem Server möglicherweise zweimal „Anfrage empfangen“ ausgibt. Dies liegt daran, dass die meisten Server versuchen, zu lesen, wenn Sie auf http://localhost:8888/ zugreifen. Holen Sie sich http ://localhost:8888/favicon.ico )
Wie der Server Anfragen verarbeitet
Okay, analysieren wir kurz den verbleibenden Teil unseres Servercodes, der den Hauptteil unserer Rückruffunktion onRequest() darstellt.
Wenn der Rückruf startet und unsere onRequest()-Funktion ausgelöst wird, werden zwei Parameter übergeben: Anfrage und Antwort.
Es handelt sich um Objekte, deren Methoden Sie verwenden können, um die Details einer HTTP-Anfrage zu verarbeiten und auf die Anfrage zu antworten (z. B. indem Sie etwas an den anfragenden Browser zurücksenden).
Unser Code lautet also: Wenn Sie eine Anfrage erhalten, verwenden Sie die Funktion „response.writeHead()“, um einen HTTP-Status 200 und den Inhaltstyp des HTTP-Headers (content-type) zu senden, und verwenden Sie „response.write()“. Funktion zum Hinzufügen des entsprechenden HTTP-Textes Senden Sie den Text „Hello World“.
Zuletzt rufen wir „response.end()“ auf, um die Antwort zu vervollständigen.
Im Moment sind uns die Details der Anfrage egal, daher verwenden wir das Anfrageobjekt nicht.
Wo soll das Servermodul platziert werden
Okay, wie ich versprochen habe, können wir jetzt wieder damit beginnen, unsere Bewerbungen zu organisieren. Wir haben jetzt einen sehr einfachen HTTP-Servercode in der Datei server.js, und ich habe erwähnt, dass wir normalerweise eine Datei namens index.js haben, die andere Module der Anwendung aufruft (z. B. das HTTP-Servermodul in server.js Boot). und starten Sie die Anwendung.
Lassen Sie uns nun darüber sprechen, wie Sie server.js in ein echtes Node.js-Modul umwandeln, damit es von unserer (noch nicht gestarteten) index.js-Hauptdatei verwendet werden kann.
Vielleicht ist Ihnen aufgefallen, dass wir im Code Module verwendet haben. So:
...
http.createServer(...);
Dadurch wird unsere lokale Variable in ein Objekt mit allen öffentlichen Methoden, die vom http-Modul bereitgestellt werden.
Es ist eine Konvention, solchen lokalen Variablen den gleichen Namen wie dem Modulnamen zu geben, Sie können ihn aber auch nach Belieben verwenden:
...
foo.createServer(...);
Sie werden es herausfinden, wenn wir server.js in ein echtes Modul verwandeln.
Tatsächlich müssen wir nicht allzu viele Änderungen vornehmen. Wenn wir einen Codeabschnitt in ein Modul umwandeln, müssen wir den Teil seiner Funktionalität, den wir bereitstellen möchten, in das Skript exportieren, das das Modul anfordert.
Derzeit sind die Funktionen, die unser HTTP-Server exportieren muss, sehr einfach, da das Skript, das das Servermodul anfordert, nur den Server starten muss.
Wir fügen unser Serverskript in eine Funktion namens start ein, die wir dann exportieren.
Funktion start() {
Funktion onRequest(Anfrage, Antwort) {
console.log("Anfrage erhalten.");
Response.writeHead(200, {"Content-Type": " text/plain"});
Response.write("Hello World");
Response.end();
}
http.createServer(onRequest).listen(8888);
console.log("Server wurde gestartet.");
}
exports.start = start;
Erstellen Sie die Datei index.js und schreiben Sie den folgenden Inhalt:
server.start();
Okay. Wir können unsere Anwendung jetzt über unser Hauptskript starten, das immer noch dasselbe ist wie zuvor:
Wir haben immer noch nur den ersten Teil der gesamten Anwendung: Wir können HTTP-Anfragen empfangen. Aber wir müssen etwas tun – der Server sollte unterschiedlich auf verschiedene URL-Anfragen reagieren.
Für eine sehr einfache Anwendung können Sie dies direkt in der Callback-Funktion onRequest() tun. Aber wie gesagt, wir sollten einige abstrakte Elemente hinzufügen, um unser Beispiel etwas interessanter zu machen.
Die Verarbeitung verschiedener HTTP-Anfragen ist ein anderer Teil unseres Codes, der „Routing“ genannt wird – also erstellen wir als nächstes ein Modul namens Routing.
So „leiten“ Sie Anfragen weiter
Wir müssen die angeforderte URL und andere erforderliche GET- und POST-Parameter für die Route bereitstellen. Anschließend muss die Route den entsprechenden Code basierend auf diesen Daten ausführen (der „Code“ entspricht hier dem dritten Teil der gesamten Anwendung: Der Handler hat eine Reihe von Anfragen erhalten, die tatsächlich funktionieren.
Daher müssen wir uns die HTTP-Anfrage ansehen und die angeforderte URL und die GET/POST-Parameter extrahieren. Ob diese Funktion zum Routing oder Server gehört (oder sogar als Funktion des Moduls selbst), ist zwar diskussionswürdig, wird hier aber vorläufig als Funktion unseres HTTP-Servers betrachtet.
Alle von uns benötigten Daten sind im Anforderungsobjekt enthalten, das als erster Parameter der Rückruffunktion onRequest() übergeben wird. Um diese Daten zu analysieren, benötigen wir jedoch zusätzliche Node.JS-Module, nämlich die URL- und Querystring-Module.
Jetzt fügen wir der Funktion onRequest() etwas Logik hinzu, um den vom Browser angeforderten URL-Pfad herauszufinden:
var http = require("http");
var url = require("url");
Funktion start() {
Funktion onRequest(Anfrage, Antwort) {
var pathname = url.parse(request.url).pathname;
console.log("Anfrage für „Pfadname“ erhalten .");
Response.writeHead(200, {"Content-Type": "text/plain"});
Response.write("Hello World");
Response.end();
}
http.createServer(onRequest).listen(8888);
console.log("Server wurde gestartet.");
}
exports.start = start;
Okay, unsere Anwendung kann jetzt anhand des URL-Pfads der Anfrage zwischen verschiedenen Anfragen unterscheiden – dies ermöglicht uns, Routing (noch nicht abgeschlossen) zu verwenden, um die Anfrage an den URL-Pfad weiterzuleiten. Baselines werden Handlern zugeordnet.
In der Anwendung, die wir erstellen, bedeutet dies, dass Anfragen von /start und /upload mit unterschiedlichem Code verarbeitet werden können. Wir werden später sehen, wie das zusammenpasst.
Jetzt können wir die Route schreiben. Erstellen Sie eine Datei mit dem Namen router.js und fügen Sie den folgenden Inhalt hinzu:
function route(pathname) {
console.log("Im Begriff, eine Anfrage für " pathname weiterzuleiten);
}
exports.route = route;
Wie Sie sehen können, bewirkt dieser Code nichts, aber im Moment ist er so, wie er sein sollte. Bevor wir weitere Logik hinzufügen, schauen wir uns zunächst an, wie Routing und Server integriert werden.
Unsere Server sollten sich der Existenz von Routen bewusst sein und diese effektiv nutzen. Wir könnten diese Abhängigkeit natürlich fest zum Server codieren, aber die Erfahrung mit der Programmierung in anderen Sprachen zeigt uns, dass dies mühsam wäre, daher werden wir die Abhängigkeitsinjektion verwenden, um die Route lose hinzuzufügen (Sie können Martin Fowlers lesen). 'Meisterwerk zur Abhängigkeitsinjektion für Hintergrundwissen).
Erweitern wir zunächst die start()-Funktion des Servers, um die Routing-Funktion als Parameter zu übergeben:
function start(route) {
function onRequest(request, Response) {
var pathname = url.parse(request.url).pathname;
console.log("Request for " pathname " erhalten.");
Route(Pfadname);
Response.writeHead(200, {"Content-Type": "text/plain"});
Response.write("Hello World");
Response.end();
}
http.createServer(onRequest).listen(8888);
console.log("Server wurde gestartet.");
}
exports.start = start;
server.start(router.route);
Wenn Sie jetzt die Anwendung starten (Knoten index.js, denken Sie immer an diese Befehlszeile) und dann eine URL anfordern, sehen Sie, dass die Anwendung die entsprechenden Informationen ausgibt, die darauf hinweisen, dass unser HTTP-Server das Routing bereits verwendet Modul, und der angeforderte Pfad wird an die Route übergeben:
Verhaltensgesteuerte Ausführung
Bitte erlauben Sie mir, noch einmal vom Thema abzuweichen und hier über funktionale Programmierung zu sprechen.
Die Übergabe von Funktionen als Argumente hat nicht nur technische Gründe. Für das Softwaredesign ist dies eigentlich eine philosophische Frage. Stellen Sie sich dieses Szenario vor: In der Indexdatei können wir das Router-Objekt übergeben, und der Server kann dann die Routenfunktion dieses Objekts aufrufen.
Einfach so übergeben wir etwas, und dann verwendet der Server dieses Ding, um etwas zu vervollständigen. Hallo, das Ding namens Routing. Können Sie mir beim Routing helfen?
Aber der Server braucht so etwas eigentlich nicht. Es müssen einfach nur Dinge erledigt werden. Um Dinge zu erledigen, braucht man überhaupt keine Dinge, sondern Taten. Das heißt, Sie brauchen kein Substantiv, sondern ein Verb.
Nachdem ich die grundlegendste und grundlegendste ideologische Transformation in diesem Konzept verstanden hatte, verstand ich natürlich die funktionale Programmierung.
Ich habe die funktionale Programmierung verstanden, nachdem ich Steve Yegges Meisterwerk „Death Penalty in the Kingdom of Nouns“ gelesen hatte. Sie sollten dieses Buch wirklich auch lesen. Dies ist eines der Bücher über Software, die mir jemals Freude beim Lesen bereitet haben.
An den tatsächlichen Anforderungshandler weitergeleitet
Zurück zum Thema: Jetzt können unser HTTP-Server und das Anforderungsrouting-Modul wie erwartet miteinander kommunizieren, wie zwei enge Brüder.
Natürlich reicht das nicht aus. Routing bedeutet, wie der Name schon sagt, dass wir unterschiedliche URLs auf unterschiedliche Weise behandeln müssen. Beispielsweise sollte sich die „Geschäftslogik“ für die Verarbeitung /start von der für die Verarbeitung /upload unterscheiden.
Unter der aktuellen Implementierung „endet“ der Routing-Prozess im Routing-Modul, und das Routing-Modul ist nicht das Modul, das wirklich „Maßnahmen“ auf die Anfrage ergreift, andernfalls ist unsere Anwendung nicht mehr in der Lage, wenn sie komplexer wird gut zu skalieren.
Wir bezeichnen die Funktion vorübergehend als Routing-Ziel und als Anforderungshandler. Beeilen wir uns jetzt nicht mit der Entwicklung des Routing-Moduls, denn es macht wenig Sinn, das Routing-Modul zu verbessern, wenn der Request-Handler nicht bereit ist.
Anwendungen erfordern neue Widgets. Fügen Sie also neue Module hinzu – Sie müssen sich nicht mehr schick fühlen. Lassen Sie uns ein Modul namens „requestHandlers“ erstellen, für jeden Anforderungshandler eine Platzhalterfunktion hinzufügen und diese Funktionen dann als Modulmethoden exportieren:
Funktion upload() {
console.log("Anforderungshandler 'upload' wurde aufgerufen.");
}
exports.start = start;
exports.upload = upload;
Hier müssen wir eine Entscheidung treffen: Sollen wir das requestHandlers-Modul zur Verwendung fest in die Route codieren oder eine kleine Abhängigkeitsinjektion hinzufügen? Obwohl die Abhängigkeitsinjektion wie andere Muster nicht nur zur Verwendung verwendet werden sollte, kann die Verwendung der Abhängigkeitsinjektion in diesem Fall die Kopplung zwischen der Route und dem Anforderungshandler lockern und die Route somit wiederverwendbar machen.
Das bedeutet, dass wir Anforderungshandler vom Server an die Route übergeben müssen, aber das fühlt sich noch ungeheuerlicher an. Wir müssen eine Reihe von Anforderungshandlern von unserer Hauptdatei an den Server übergeben und sie dann übergeben vom Server zur Route.
Wie übergeben wir diese Anforderungshandler? Bedenken Sie nicht, dass wir jetzt nur noch 2 Handler haben. In einer realen Anwendung wird die Anzahl der Anfragehandler weiter zunehmen. Wir möchten die Anfrage auf der Route sicherlich nicht jedes Mal abschließen müssen Neue URL oder Anforderungshandler zuordnen und immer wieder werfen. Darüber hinaus gibt es im Routing viele if request == x then Call-Handler y, was das System ebenfalls hässlich macht.
Denken Sie sorgfältig darüber nach, es gibt viele Dinge, die jeweils einer Zeichenfolge (d. h. der angeforderten URL) zugeordnet werden müssen. Es scheint, dass ein assoziatives Array perfekt funktioniert.
Aber das Ergebnis ist etwas enttäuschend, JavaScript bietet keine assoziativen Arrays – kann man das denn behaupten? Tatsächlich sind es in JavaScript seine Objekte, die eine solche Funktionalität wirklich bereitstellen können.
In diesem Zusammenhang gibt es unter http://msdn.microsoft.com/en-us/magazine/cc163419.aspx eine gute Einführung, die ich hier auszugsweise wiedergeben werde:
Wenn wir in C oder C# über Objekte sprechen, beziehen wir uns auf Instanzen von Klassen oder Strukturen. Objekte haben je nach Vorlage (der sogenannten Klasse), aus der sie instanziiert werden, unterschiedliche Eigenschaften und Methoden. Aber Objekte sind in JavaScript nicht dieses Konzept. In JavaScript ist ein Objekt eine Sammlung von Schlüssel/Wert-Paaren – Sie können sich ein JavaScript-Objekt als Wörterbuch mit Zeichenfolgenschlüsseln vorstellen.
Aber wenn ein JavaScript-Objekt nur eine Sammlung von Schlüssel/Wert-Paaren ist, wie kann es dann Methoden haben? Nun, der Wert hier kann eine Zeichenfolge, eine Zahl oder ... eine Funktion sein!
Okay, kommen wir endlich zurück zum Code. Nachdem wir uns nun entschieden haben, eine Reihe von Anforderungshandlern über ein Objekt zu leiten, müssen wir dieses Objekt auf lose gekoppelte Weise in die Route()-Funktion einfügen.
Wir führen dieses Objekt zunächst in die Hauptdatei index.js ein:
var handle = {}
handle["/"] = requestHandlers.start;
handle["/start"] = requestHandlers.start;
handle["/upload"] = requestHandlers. hochladen;
server.start(router.route, handle);
Wie Sie sehen, ist die Zuordnung verschiedener URLs zum gleichen Anforderungshandler einfach: Fügen Sie dem Objekt einfach eine Eigenschaft mit dem Schlüssel „/“ hinzu, die requestHandlers.start entspricht, und wir können Anforderungen für die einfache Konfiguration von /start bereinigen und / werden vom Starthandler verarbeitet.
Nach Abschluss der Definition des Objekts übergeben wir es als zusätzlichen Parameter an den Server. Dazu wird server.js wie folgt geändert:
function start(route, handle) {
function onRequest(request, Response) {
var pathname = url.parse(request.url).pathname;
console.log("Request for " Pfadname „empfangen.“);
Route(Handle, Pfadname);
Response.writeHead(200, {"Content-Type": "text/plain"});
Response.write("Hello World");
Response.end();
}
http.createServer(onRequest).listen(8888);
console.log("Server wurde gestartet.");
}
exports.start = start;
Dann ändern wir die Funktion route() in der Datei route.js entsprechend:
exports.route = route;
Damit haben wir Server, Route und Request-Handler zusammen. Nun starten wir die Anwendung und rufen im Browser http://localhost:8888/start auf. Das folgende Protokoll kann zeigen, dass das System den richtigen Request-Handler aufruft:
Lassen Sie den Anforderungshandler antworten
Sehr gut. Aber jetzt wäre es schön, wenn der Request-Handler statt nur „Hallo Welt“ einige aussagekräftige Informationen an den Browser zurückgeben könnte.
Hier ist zu beachten, dass die „Hello World“-Informationen, die nach einer Anfrage des Browsers abgerufen und angezeigt werden, immer noch von der onRequest-Funktion in unserer server.js-Datei stammen.
Tatsächlich bedeutet „eine Anfrage verarbeiten“ einfach „auf eine Anfrage antworten“. Daher müssen wir dem Anfragehandler ermöglichen, mit dem Browser zu „sprechen“, wie die onRequest-Funktion.
Schlechte Implementierung
Für Entwickler wie uns mit PHP- oder Ruby-Technikhintergrund ist die einfachste Implementierungsmethode tatsächlich nicht sehr zuverlässig: Sie scheint effektiv zu sein, ist es aber möglicherweise nicht.
Was ich hier mit „einfacher Implementierung“ meine, ist, dass der Anforderungshandler die Informationen, die er dem Benutzer anzeigen möchte, direkt über die onRequest-Funktion zurückgibt (return()).
Lass es uns zuerst so umsetzen und dann schauen, warum dies keine gute Möglichkeit ist, es umzusetzen.
Beginnen wir damit, dass der Request-Handler die Informationen zurückgibt, die im Browser angezeigt werden müssen. Wir müssen requestHandler.js in die folgende Form ändern:
function upload() {
console.log("Request handler 'upload' was aufgerufen.");
return "Hello Upload";
}
exports.start = start;
exports.upload = upload;
exports.route = route;
Schließlich müssen wir unsere server.js so umgestalten, dass sie dem Browser mit dem vom Anforderungshandler über die Anforderungsroute zurückgegebenen Inhalt antwortet, etwa so:
function start(route, handle) {
function onRequest(request, Response) {
var pathname = url.parse(request.url).pathname;
console.log("Request for " Pfadname „empfangen.“);
Response.writeHead(200, {"Content-Type": "text/plain"});
var content = route(handle, pathname)
Response.write(content);
Antwort .end();
}
http.createServer(onRequest).listen(8888);
console.log("Server wurde gestartet.");
}
exports.start = start;
Okay, also was ist das Problem? Einfach ausgedrückt: Wenn ein Anforderungshandler in Zukunft nicht blockierende Vorgänge ausführen muss, bleibt unsere Anwendung „hängen“.
Nicht verstehen? Es spielt keine Rolle, wir erklären es weiter unten im Detail.
Blockierend und nicht blockierend
Wie bereits erwähnt, treten Probleme auf, wenn nicht blockierende Vorgänge in Anforderungshandler einbezogen werden. Bevor wir jedoch darüber sprechen, werfen wir zunächst einen Blick darauf, was Blockierungsvorgänge sind.
Ich möchte die spezifische Bedeutung von „Blockieren“ und „Nicht-Blockieren“ nicht erklären. Schauen wir uns einfach an, was passiert, wenn Blockierungsoperationen zum Anforderungshandler hinzugefügt werden.
Hier ändern wir den Startanforderungshandler. Wir lassen ihn 10 Sekunden warten, bevor wir „Hallo Start“ zurückgeben. Da es in JavaScript keine Operation wie „sleep()“ gibt, können wir die Implementierung nur mit einem kleinen Hack simulieren.
Lassen Sie uns requestHandlers.js in die folgende Form ändern:
function sleep(milliSeconds) {
var startTime = new Date().getTime();
while (new Date().getTime() < startTime milliSeconds);
}
sleep(10000);
return „Hello Start“;
}
function upload() {
console.log("Request handler 'upload' was aufgerufen.");
return "Hello Upload";
}
exports.start = start;
exports.upload = upload;
(Dies ist natürlich nur eine Simulation eines 10-sekündigen Schlafs. In tatsächlichen Szenarien gibt es viele solcher Blockiervorgänge, beispielsweise einige Langzeitberechnungsvorgänge.)
Werfen wir einen Blick darauf, welche Veränderungen unsere Veränderungen mit sich gebracht haben.
Wie üblich müssen wir zuerst den Server neu starten. Um den Effekt zu sehen, müssen wir einige relativ komplizierte Vorgänge ausführen (folgen Sie mir): Öffnen Sie zunächst zwei Browserfenster oder -registerkarten. Geben Sie http://localhost:8888/start in die Adressleiste des ersten Browserfensters ein, aber öffnen Sie es noch nicht!
Geben Sie http://localhost:8888/upload in die Adressleiste des zweiten Browserfensters ein. Öffnen Sie es noch nicht!
Als nächstes gehen Sie wie folgt vor: Drücken Sie im ersten Fenster („/start“) die Eingabetaste, wechseln Sie dann schnell zum zweiten Fenster („/upload“) und drücken Sie die Eingabetaste.
Beachten Sie, was passiert ist: Das Laden der /start-URL dauerte 10 Sekunden, was wir erwartet hatten. Allerdings dauerte die /upload-URL tatsächlich 10 Sekunden und es gab keine ähnliche Operation wie sleep() im entsprechenden Anforderungshandler!
Warum ist das so? Der Grund dafür ist, dass start() blockierende Operationen enthält. Im übertragenen Sinne „blockiert es alle anderen Verarbeitungsarbeiten.“
Das ist offensichtlich ein Problem, denn Node hat sich immer so beworben: „In Node wird bis auf den Code alles parallel ausgeführt.“
Dieser Satz bedeutet, dass Node.js weiterhin Aufgaben parallel verarbeiten kann, ohne zusätzliche Threads hinzuzufügen – Node.js ist Single-Threaded. Es implementiert parallele Operationen über eine Ereignisschleife, und wir sollten dies voll ausnutzen – blockierende Operationen so weit wie möglich vermeiden und stattdessen nicht blockierende Operationen verwenden.
Um jedoch nicht blockierende Vorgänge zu verwenden, müssen wir Rückrufe verwenden, indem wir die Funktion als Parameter an andere Funktionen übergeben, deren Verarbeitung einige Zeit in Anspruch nimmt (z. B. 10 Sekunden lang schlafen, die Datenbank abfragen oder einen großen Vorgang ausführen). Anzahl der Berechnungen).
Für Node.js wird es so gehandhabt: „Hey, wahrscheinlichExpensiveFunction() (Anmerkung des Übersetzers: Dies bezieht sich auf Funktionen, deren Verarbeitung einige Zeit in Anspruch nimmt), Sie kümmern sich weiterhin um Ihre Dinge, ich (Node.js-Thread) Ich werde den Code vorerst nicht weiter verarbeiten. Ich werde die Rückruffunktion aufrufen, nachdem Sie die Verarbeitung abgeschlossen haben
(Wenn Sie mehr Details über Event Polling erfahren möchten, können Sie Mixus Blogbeitrag „Understanding node.js Event Polling“ lesen.)Als nächstes stellen wir eine falsche Methode zur Verwendung nicht blockierender Operationen vor.
Wie beim letzten Mal haben wir unsere Anwendung geändert, um das Problem aufzudecken.
Dieses Mal verwenden wir immer noch den Start-Request-Handler zum „Betrieb“. Ändern Sie es in die folgende Form:
console.log("Anforderungshandler 'start' wurde aufgerufen.");
var content = "empty";
content = stdout;
});
}
console.log("Request handler 'upload' was aufgerufen.");
return "Hello Upload";
}
exports.upload = upload;
Im obigen Code haben wir ein neues Node.js-Modul eingeführt, child_process. Der Grund für seine Verwendung besteht darin, eine einfache und praktische nicht blockierende Operation zu implementieren: exec ().
Was macht exec()? Es führt einen Shell-Befehl von Node.js aus. Im obigen Beispiel verwenden wir es, um alle Dateien im aktuellen Verzeichnis abzurufen („ls -lah“) und geben dann die Dateiinformationen an den Browser aus, wenn /startURL angefordert wird.
Der obige Code ist sehr intuitiv: Erstellen Sie einen neuen Variableninhalt (Anfangswert ist „leer“), führen Sie den Befehl „ls -lah“ aus, weisen Sie das Ergebnis dem Inhalt zu und geben Sie schließlich den Inhalt zurück.
Wie gewohnt starten wir den Server und greifen auf „http://localhost:8888/start“ zu.
Eine schöne Webseite wird mit dem Inhalt „leer“ geladen. Was ist los?
Zu diesem Zeitpunkt haben Sie vielleicht schon grob vermutet, dass exec() eine magische Rolle beim Nichtblockieren spielt. Das ist eigentlich eine gute Sache, denn damit können wir sehr zeitaufwändige Shell-Operationen durchführen, ohne unsere Anwendung dazu zu zwingen, anzuhalten und auf die Operation zu warten.
(Wenn Sie dies beweisen möchten, können Sie „ls -lah“ durch eine zeitaufwändigere Operation wie „find /“ ersetzen, um den Effekt zu erzielen.)
Aber den vom Browser angezeigten Ergebnissen nach zu urteilen, sind wir mit unserem nicht blockierenden Betrieb nicht zufrieden, oder?
Okay, als nächstes beheben wir dieses Problem. Wenn wir schon dabei sind, werfen wir einen Blick darauf, warum der aktuelle Ansatz nicht funktioniert.
Das Problem besteht darin, dass exec() eine Rückruffunktion verwendet, um nicht blockierende Arbeiten auszuführen.
In unserem Fall ist die Rückruffunktion die anonyme Funktion, die als zweiter Parameter an exec() übergeben wird:
Der Vorgang von „ls -lah“ ist hier tatsächlich sehr schnell (es sei denn, das aktuelle Verzeichnis enthält Millionen von Dateien). Aus diesem Grund wird die Callback-Funktion schnell ausgeführt – sie ist aber trotzdem asynchron.
Um den Effekt deutlicher zu machen, stellen wir uns einen zeitaufwändigeren Befehl vor: „find /“, dessen Ausführung auf meinem Computer etwa 1 Minute dauert. Allerdings habe ich im Request-Handler „ls – lah“ eingegeben. durch „find /“ ersetzt wird, können Sie beim Öffnen der /start-URL immer noch sofort die HTTP-Antwort erhalten – offensichtlich führt Node.js selbst weiterhin den folgenden Code aus, wenn exec() im Hintergrund ausgeführt wird. Und wir gehen hier davon aus, dass die an exec() übergebene Callback-Funktion erst aufgerufen wird, nachdem der Befehl „find /“ ausgeführt wurde.
Wie können wir dem Benutzer also die Dateiliste im aktuellen Verzeichnis anzeigen?
Okay, jetzt, da wir diese schlechte Implementierung verstanden haben, wollen wir vorstellen, wie man den Request-Handler dazu bringt, auf Browser-Anfragen richtig zu reagieren.
Reaktion auf Anfragen mit nicht blockierenden Vorgängen
Ich habe gerade den Satz „auf die richtige Art und Weise“ erwähnt. Tatsächlich ist der „richtige Weg“ meist nicht einfach.
Es gibt jedoch eine solche Implementierungslösung mit Node.js: Funktionsübergabe. Werfen wir einen Blick darauf, wie wir dies im Detail umsetzen können.
Bisher kann unsere Anwendung bereits den vom Anforderungshandler zurückgegebenen Inhalt (der Anforderungshandler zeigt dem Benutzer schließlich Inhalte an) an den HTTP-Server übergeben.
Jetzt übernehmen wir die folgende neue Implementierungsmethode: Anstatt den Inhalt an den Server zu übergeben, verwenden wir dieses Mal die Methode, den Server an den Inhalt zu „übergeben“. Aus praktischer Sicht wird das Antwortobjekt (erhalten von der Rückruffunktion onRequest() des Servers) über das Anforderungsrouting an den Anforderungshandler übergeben. Der Handler kann dann auf die Anfrage antworten, indem er Funktionen für dieses Objekt verwendet.
Das ist das Prinzip, lasst uns diese Lösung Schritt für Schritt umsetzen.
Beginnen Sie mit server.js:
function start(route, handle) {
function onRequest(request, Response) {
var pathname = url.parse(request.url).pathname;
console.log("Request for " Pfadname „empfangen.“);
Route(Handle, Pfadname, Antwort);
}
http.createServer(onRequest).listen(8888);
console.log("Server wurde gestartet.");
}
exports.start = start;
Werfen wir einen Blick auf unsere router.js:
exports.route = route;
Wenn es keinen entsprechenden Anforderungshandler gibt, geben wir direkt einen „404“-Fehler zurück.
Schließlich ändern wir requestHandler.js in die folgende Form:
Funktionsstart (Antwort) {
console.log("Anforderungshandler 'start' wurde aufgerufen.");
exec("ls -lah", function (error, stdout, stderr) {
Response.writeHead(200, {"Content-Type": "text/plain"});
Response.write (stdout);
Response.end();
});
}
function upload(response) {
console.log("Request handler 'upload' was Called.");
Response.writeHead(200, {"Content-Type": "text/plain"} );
Response.write("Hallo Upload");
Response.end();
}
exports.start = start;
exports.upload = upload;
Der Start-Handler führt die Anfrage-Antwort-Operation in der anonymen Rückruffunktion von exec() aus, während der Upload-Handler immer noch einfach „Hallo Welt“ antwortet, dieses Mal jedoch das Antwortobjekt verwendet.
Jetzt starten wir die Anwendung (node index.js) erneut und alles wird gut funktionieren.
Wenn Sie nachweisen möchten, dass die zeitaufwändigen Vorgänge im /start-Handler die sofortige Antwort auf die /upload-Anfrage nicht blockieren, können Sie requestHandlers.js in die folgende Form ändern:
Funktionsstart (Antwort) {
console.log("Anforderungshandler 'start' wurde aufgerufen.");
exec("find /",
{ timeout: 10000, maxBuffer: 20000*1024 },
function (error, stdout, stderr) {
Response.writeHead(200, {"Content- Typ": "text/plain"});
Response.write(stdout);
Response.end();
});
}
function upload(response) {
console.log("Request handler 'upload' was Called.");
Response.writeHead(200, {"Content-Type": "text/plain"} );
Response.write("Hallo Upload");
Response.end();
}
exports.start = start;
exports.upload = upload;
Weitere nützliche Szenarien
Bisher haben wir uns gut geschlagen, unsere Anwendung hat jedoch keinen praktischen Nutzen.
Der Server, das Anforderungsrouting und der Anforderungshandler wurden fertiggestellt. Fügen wir der Website eine Interaktion gemäß dem vorherigen Anwendungsfall hinzu: Der Benutzer wählt eine Datei aus, lädt die Datei hoch und sieht dann die hochgeladene Datei im Browser. Der Einfachheit halber gehen wir davon aus, dass der Benutzer nur ein Bild hochlädt und unsere App dieses Bild im Browser anzeigt.
Okay, lass es uns Schritt für Schritt umsetzen. Da wir bereits viele JavaScript-Prinzipien und technische Inhalte eingeführt haben, lasst uns dieses Mal etwas schneller gehen.
Um diese Funktion zu implementieren, sind die folgenden zwei Schritte erforderlich: Schauen wir uns zunächst an, wie mit POST-Anfragen (Nicht-Datei-Uploads) umgegangen wird. Anschließend verwenden wir ein externes Modul von Node.js für Datei-Uploads. Für diese Umsetzung gibt es zwei Gründe.
Erstens: Obwohl die Handhabung grundlegender POST-Anfragen in Node.js relativ einfach ist, können Sie dabei dennoch viel lernen.
Zweitens ist die Verwendung von Node.js zur Verarbeitung von Datei-Uploads (mehrteilige POST-Anfragen) relativ komplex und liegt außerhalb des Rahmens dieses Buches. Die Verwendung externer Module liegt jedoch im Rahmen dieses Buchs.
Bearbeitung von POST-Anfragen
Betrachten Sie ein einfaches Beispiel: Wir zeigen einen Textbereich an, in dem der Benutzer Inhalte eingeben kann, und senden ihn dann über eine POST-Anfrage an den Server. Schließlich empfängt der Server die Anfrage und zeigt den Eingabeinhalt über den Handler dem Browser an.
Der Anforderungshandler/start wird zum Generieren eines Formulars mit einem Textbereich verwendet. Daher ändern wir requestHandlers.js in das folgende Formular:
Funktionsstart (Antwort) {
console.log("Anforderungshandler 'start' wurde aufgerufen.");
var body = ''
'
Response.write(body);
Response.end();
}
console.log("Request handler 'upload' was Called.");
Response.writeHead(200, {"Content-Type": "text/plain"} );
Response.write("Hallo Upload");
Response.end();
}
exports.upload = upload;
Okay, jetzt ist unsere Bewerbung sehr vollständig und kann sogar Webby Awards gewinnen, haha. (Anmerkung des Übersetzers: Die Webby Awards sind eine von der International Academy of Digital Arts and Sciences gesponserte Auszeichnung zur Auswahl der besten Websites der Welt. Einzelheiten finden Sie in der ausführlichen Beschreibung.) Sie können sie sehen, indem Sie http://localhost:8888 besuchen /start in Ihrem Browser Es ist ein einfaches Formular, denken Sie daran, den Server neu zu starten!
Im verbleibenden Raum werden wir ein interessanteres Thema besprechen: Wenn der Benutzer das Formular absendet, wird der /upload-Anforderungshandler ausgelöst, um die POST-Anfrage zu verarbeiten.
Da wir nun Experten unter Neulingen sind, ist es naheliegend, über die Verwendung asynchroner Rückrufe nachzudenken, um die Daten von POST-Anfragen nicht blockierend zu verarbeiten.
Hier wird nicht blockierend verwendet