摘要:原生開發入門完全教程微信公眾號開發企業級產品全棧開發速成周末班首期班號正式開班,歡迎搶座一關于本篇文章參考了入門并從零到壹操作了一遍,感謝原作者,同時也強烈推薦大家移步到原文給予原文作者一個贊賞支持。
Node.js原生開發入門完全教程
(Node+Vue+React+微信公眾號開發)企業級產品全棧開發速成周末班首期班(10.28號正式開班,歡迎搶座)
一、關于本篇文章參考了Node入門https://www.nodebeginner.org/index-zh-cn.html#about并從零到壹操作了一遍,感謝原作者,同時也強烈推薦大家移步到原文給予原文作者一個贊賞支持。
二、代碼狀態所有代碼為春哥親測,全部正確通過。
三、閱讀文章的對象1.有編程基礎
2.想轉向Node.js后端的技術愛好者
3.Node.js新手
請直接移步Node.js官網,如下圖所示,直接點擊最新版下載并進行安裝。
Node.js安裝完畢后,打開終端,在終端分別輸入如下命令,檢測是否安裝成功。
Last login: Tue Jun 27 09:19:38 on console liyuechun:~ yuechunli$ node -v v8.1.3 liyuechun:~ yuechunli$ npm -v 5.0.3 liyuechun:~ yuechunli$
如果能夠正確顯示node和npm的版本,說明Node.js安裝成功。
2."Hello World"第一種輸出方式
好了,“廢話”不多說了,馬上開始我們第一個Node.js應用:“Hello World”。
liyuechun:~ yuechunli$ node > console.log("Hello World!"); Hello World! undefined > console.log("從零到壹全棧部落!"); 從零到壹全棧部落! undefined > process.exit() liyuechun:~ yuechunli$
在終端里面直接輸入命令node,接下來輸入一句console.log("Hello World!"); ,回車,即可輸出Hello World。
簡單解釋一下為什么每一次打印后面都出現了一個undefined,原因是因為你輸入js代碼并按下回車后,node會輸出執行完該代碼后的返回值,如果沒有返回值,就會顯示undefined,這個跟Chrome的調試工具相似。
如上代碼所示,當輸入process.exit()并回車時,即可退出node模式。
第二種輸出方式
Last login: Thu Jun 29 18:17:27 on ttys000 liyuechun:~ yuechunli$ ls Applications Downloads Pictures Creative Cloud Files Library Public Desktop Movies Documents Music liyuechun:~ yuechunli$ cd Desktop/ liyuechun:Desktop yuechunli$ mkdir nodejs入門 liyuechun:Desktop yuechunli$ pwd /Users/liyuechun/Desktop liyuechun:Desktop yuechunli$ cd nodejs入門/ liyuechun:nodejs入門 yuechunli$ pwd /Users/liyuechun/Desktop/nodejs入門 liyuechun:nodejs入門 yuechunli$ vi helloworld.js liyuechun:nodejs入門 yuechunli$ cat helloworld.js console.log("Hello World!"); liyuechun:nodejs入門 yuechunli$ node helloworld.js Hello World! liyuechun:nodejs入門 yuechunli$
命令解釋:
ls:查看當前路徑下面的文件和文件夾。
pwd:查看當前所在路徑。
cd Desktop:切換到桌面。
mkdir nodejs入門:在當前路徑下面創建nodejs入門文件夾。
cd nodejs入門:進入nodejs入門文件夾。
vi helloworld.js:創建一個helloworld.js文件,并在文件里面輸入console.log("Hello World!"),保存并退出。
cat helloworld.js:查看helloworld.js文件內容。
node helloworld.js:在當前路徑下面執行helloworld.js文件。
PS:如果對命令行不熟悉的童鞋,可以用其他編輯器創建一個helloworld.js文件,在里面輸入console.log("Hello World!"),將文件保存到桌面,然后打開終端,直接將helloworld.js文件拖拽到終端,直接在終端中執行node helloworld.js即可在終端輸出Hello World!。
好吧,我承認這個應用是有點無趣,那么下面我們就來點“干貨”。
下面我們將通過VSCode來進行Node.js的編碼。
五、一個完整的基于Node.js的web應用 1.用例我們來把目標設定得簡單點,不過也要夠實際才行:
用戶可以通過瀏覽器使用我們的應用。
當用戶請求http://domain/start時,可以看到一個歡迎頁面,頁面上有一個文件上傳的表單。
用戶可以選擇一個圖片并提交表單,隨后文件將被上傳到http://domain/upload,該頁面完成上傳后會把圖片顯示在頁面上。
差不多了,你現在也可以去Google一下,找點東西亂搞一下來完成功能。但是我們現在先不做這個。
更進一步地說,在完成這一目標的過程中,我們不僅僅需要基礎的代碼而不管代碼是否優雅。我們還要對此進行抽象,來尋找一種適合構建更為復雜的Node.js應用的方式。
2.應用不同模塊分析我們來分解一下這個應用,為了實現上文的用例,我們需要實現哪些部分呢?
我們需要提供Web頁面,因此需要一個HTTP服務器
對于不同的請求,根據請求的URL,我們的服務器需要給予不同的響應,因此我們需要一個路由,用于把請求對應到請求處理程序(request handler)
當請求被服務器接收并通過路由傳遞之后,需要可以對其進行處理,因此我們需要最終的請求處理程序
路由還應該能處理POST數據,并且把數據封裝成更友好的格式傳遞給請求處理入程序,因此需要請求數據處理功能
我們不僅僅要處理URL對應的請求,還要把內容顯示出來,這意味著我們需要一些視圖邏輯供請求處理程序使用,以便將內容發送給用戶的瀏覽器
最后,用戶需要上傳圖片,所以我們需要上傳處理功能來處理這方面的細節
現在我們就來開始實現之路,先從第一個部分--HTTP服務器著手。
六、構建應用的模塊 1.一個基礎的HTTP服務器用VSCode創建一個server.js的文件,將文件保存到桌面的nodejs入門文件夾里面。
在server.js文件里面寫入以下內容:
let http = require("http"); http.createServer(function(request, response) { response.writeHead(200, {"Content-Type": "text/plain"}); response.write("Hello World"); response.end(); }).listen(8888);
上面的代碼就是一個完整的Node.js服務器,如下圖所示,點擊VSCode左下腳按鈕,打開VSCode終端,在終端中輸入node server.js來進行驗證。
如上圖所示,一個基礎的HTTP服務器搞定。
2.HTTP服務器原理解析上面的案例中,第一行請求(require)Node.js自帶的 http 模塊,并且把它賦值給 http 變量。
接下來我們調用http模塊提供的函數: createServer 。這個函數會返回一個對象,這個對象有一個叫做 listen 的方法,這個方法有一個數值參數,指定這個HTTP服務器監聽的端口號。
咱們暫時先不管 http.createServer 的括號里的那個函數定義。
我們本來可以用這樣的代碼來啟動服務器并偵聽8888端口:
var http = require("http"); var server = http.createServer(); server.listen(8888);
這段代碼只會啟動一個偵聽8888端口的服務器,它不做任何別的事情,甚至連請求都不會應答。
3.進行函數傳遞舉例來說,你可以這樣做:
Last login: Thu Jun 29 20:03:25 on ttys001 liyuechun:~ yuechunli$ node > function say(word) { ... console.log(word); ... } undefined > > function execute(someFunction, value) { ... someFunction(value); ... } undefined > > execute(say, "Hello"); Hello undefined >
請仔細閱讀這段代碼!在這里,我們把 say 函數作為execute函數的第一個變量進行了傳遞。這里傳遞的不是 say 的返回值,而是 say 本身!
這樣一來, say 就變成了execute 中的本地變量 someFunction ,execute可以通過調用 someFunction() (帶括號的形式)來使用 say 函數。
當然,因為 say 有一個變量, execute 在調用 someFunction 時可以傳遞這樣一個變量。
我們可以,就像剛才那樣,用它的名字把一個函數作為變量傳遞。但是我們不一定要繞這個“先定義,再傳遞”的圈子,我們可以直接在另一個函數的括號中定義和傳遞這個函數:
Last login: Thu Jun 29 20:04:35 on ttys001 liyuechun:~ yuechunli$ node > function execute(someFunction, value) { ... someFunction(value); ... } undefined > > execute(function(word){ console.log(word) }, "Hello"); Hello undefined >
我們在 execute 接受第一個參數的地方直接定義了我們準備傳遞給 execute 的函數。
用這種方式,我們甚至不用給這個函數起名字,這也是為什么它被叫做 匿名函數 。
這是我們和我所認為的“進階”JavaScript的第一次親密接觸,不過我們還是得循序漸進。現在,我們先接受這一點:在JavaScript中,一個函數可以作為另一個函數接收一個參數。我們可以先定義一個函數,然后傳遞,也可以在傳遞參數的地方直接定義函數。
4.函數傳遞是如何讓HTTP服務器工作的帶著這些知識,我們再來看看我們簡約而不簡單的HTTP服務器:
var http = require("http"); http.createServer(function(request, response) { response.writeHead(200, {"Content-Type": "text/plain"}); response.write("Hello World"); response.end(); }).listen(8888); console.log("請在瀏覽器中打開 http://127.0.0.1:8888...");
現在它看上去應該清晰了很多:我們向 createServer 函數傳遞了一個匿名函數。
用這樣的代碼也可以達到同樣的目的:
/** * 從零到壹全棧部落,添加小精靈微信(ershiyidianjian) */ //請求(require)Node.js自帶的 http 模塊,并且把它賦值給 http 變量。 let http = require("http"); //箭頭函數 let onRequest = (request, response) => { response.writeHead(200, {"Content-Type": "text/plain"}); response.write("Hello World"); response.end(); } //把函數當作參數傳遞 http.createServer(onRequest).listen(8888); console.log("請在瀏覽器中打開 http://127.0.0.1:8888...");
也許現在我們該問這個問題了:我們為什么要用這種方式呢?
5.基于事件驅動的回調事件驅動是Node.js原生的工作方式,這也是它為什么這么快的原因。
當我們使用http.createServer方法的時候,我們當然不只是想要一個偵聽某個端口的服務器,我們還想要它在服務器收到一個HTTP請求的時候做點什么。
我們創建了服務器,并且向創建它的方法傳遞了一個函數。無論何時我們的服務器收到一個請求,這個函數就會被調用。
這個就是傳說中的回調 。我們給某個方法傳遞了一個函數,這個方法在有相應事件發生時調用這個函數來進行回調 。
我們試試下面的代碼:
/** * 從零到壹全棧部落,添加小精靈微信(ershiyidianjian) */ //請求(require)Node.js自帶的 http 模塊,并且把它賦值給 http 變量。 let http = require("http"); //箭頭函數 let onRequest = (request, response) => { console.log("Request received."); response.writeHead(200, {"Content-Type": "text/plain;charset=utf-8"}); response.write("添加小精靈微信(ershiyidianjian),加入全棧部落"); response.end(); } //把函數當作參數傳遞 http.createServer(onRequest).listen(8888); console.log("Server has started."); console.log("請在瀏覽器中打開 http://127.0.0.1:8888...");
在上圖中,當我們執行node server.js命令時,Server has started.正常往下執行。
我們看看當我們在瀏覽器里面打開http://127.0.0.1:8888時會發生什么。
大家會發現在瀏覽器中打開http://127.0.0.1:8888時,在終端會輸出Request received.,瀏覽器會輸出添加小精靈微信(ershiyidianjian),加入全棧部落這一句話。
請注意,當我們在服務器訪問網頁時,我們的服務器可能會輸出兩次“Request received.”。那是因為大部分瀏覽器都會在你訪問 http://localhost:8888/ 時嘗試讀取 http://localhost:8888/favicon... )
6.服務器是如何處理請求的好的,接下來我們簡單分析一下我們服務器代碼中剩下的部分,也就是我們的回調函數onRequest()的主體部分。
當回調啟動,我們的onRequest()函數被觸發的時候,有兩個參數被傳入:request 和response 。
它們是對象,你可以使用它們的方法來處理HTTP請求的細節,并且響應請求(比如向發出請求的瀏覽器發回一些東西)。
所以我們的代碼就是:當收到請求時,使用response.writeHead()函數發送一個HTTP狀態200和HTTP頭的內容類型(content-type),使用response.write()函數在HTTP相應主體中發送文本添加小精靈微信(ershiyidianjian),加入全棧部落。
最后,我們調用 response.end() 完成響應。
目前來說,我們對請求的細節并不在意,所以我們沒有使用 request 對象。
7.服務端模塊化何為模塊?
let http = require("http"); ... http.createServer(...);
在上面的代碼中,Node.js中自帶了一個叫做“http”的模塊,我們在我們的代碼中請求它并把返回值賦給一個本地變量。
這把我們的本地變量變成了一個擁有所有 http 模塊所提供的公共方法的對象。
給這種本地變量起一個和模塊名稱一樣的名字是一種慣例,但是你也可以按照自己的喜好來:
var foo = require("http"); ... foo.createServer(...);
如何自定義模塊
將server.js文件的內容改成下面的內容。
/** * 從零到壹全棧部落,添加小精靈微信(ershiyidianjian) */ //請求(require)Node.js自帶的 http 模塊,并且把它賦值給 http 變量。 let http = require("http"); //用一個函數將之前的內容包裹起來 let start = () => { //箭頭函數 let onRequest = (request, response) => { console.log("Request received."); response.writeHead(200, {"Content-Type": "text/plain;charset=utf-8"}); response.write("添加小精靈微信(ershiyidianjian),加入全棧部落"); response.end(); } //把函數當作參數傳遞 http.createServer(onRequest).listen(8888); console.log("Server has started."); console.log("請在瀏覽器中打開 http://127.0.0.1:8888..."); } //導出`server`對象,對象中包含一個start函數 //對象格式為 /** * { * start * } */ //這個對象導入到其他文件中即可使用,可以用任意的名字來接收這個對象 exports.start = start;
在server.js當前的文件路徑下新建一個index.js文件。內容如下:
/** * 從零到壹全棧部落,添加小精靈微信(ershiyidianjian) */ //從`server`模塊中導入server對象 let server = require("./server"); //啟動服務器 server.start();
如下圖所示運行index.js文件。
一切運行正常,上面的案例中,server.js就是自定義的模塊。
8.如何來進行請求的“路由”我們要為路由提供請求的URL和其他需要的GET及POST參數,隨后路由需要根據這些數據來執行相應的代碼(這里“代碼”對應整個應用的第三部分:一系列在接收到請求時真正工作的處理程序)。
因此,我們需要查看HTTP請求,從中提取出請求的URL以及GET/POST參數。這一功能應當屬于路由還是服務器(甚至作為一個模塊自身的功能)確實值得探討,但這里暫定其為我們的HTTP服務器的功能。
我們需要的所有數據都會包含在request對象中,該對象作為onRequest()回調函數的第一個參數傳遞。但是為了解析這些數據,我們需要額外的Node.JS模塊,它們分別是url和querystring模塊。
url.parse(string).query | url.parse(string).pathname | | | | | ------ ------------------- http://localhost:8888/start?foo=bar&hello=world --- ----- | | | | querystring.parse(string)["foo"] | | querystring.parse(string)["hello"]
當然我們也可以用querystring模塊來解析POST請求體中的參數,稍后會有演示。
現在我們來給onRequest()函數加上一些邏輯,用來找出瀏覽器請求的URL路徑:
接下來我在終端執行node index.js命令,如下所示:
bogon:如何來進行請求的“路由” yuechunli$ node index.js Server has started. 請在瀏覽器中打開 http://127.0.0.1:8888...
我先在Safari瀏覽器中打開http://127.0.0.1:8888,瀏覽器展示效果如下:
控制臺效果如下:
bogon:如何來進行請求的“路由” yuechunli$ node index.js Server has started. 請在瀏覽器中打開 http://127.0.0.1:8888... Request for / received.
接著我在Google瀏覽器里面打開 http://127.0.0.1:8888... ,瀏覽器效果圖如下:
控制臺效果如下:
為什么在Safari瀏覽器中進行請求時,只打印了一個Request for / received.,而在Google瀏覽器中訪問時,會多打印一個Request for /favicon.ico received.,如上圖所示,原因是因為在Google瀏覽器中,瀏覽器的原因會去嘗試請求favicon.ico小圖標。
為了演示效果,還有不受Google瀏覽器的favicon.ico請求的干擾,我接著在Safari里面請求http://127.0.0.1:8888/start和http://127.0.0.1:8888/upload,我們看看控制臺展示的內容是什么。
bogon:如何來進行請求的“路由” yuechunli$ node index.js Server has started. 請在瀏覽器中打開 http://127.0.0.1:8888... Request for /start received. Request for /upload received.
好了,我們的應用現在可以通過請求的URL路徑來區別不同請求了--這使我們得以使用路由(還未完成)來將請求以URL路徑為基準映射到處理程序上。
在我們所要構建的應用中,這意味著來自/start和/upload的請求可以使用不同的代碼來處理。稍后我們將看到這些內容是如何整合到一起的。
現在我們可以來編寫路由了,建立一個名為router.js的文件,添加以下內容:
/** * 從零到壹全棧部落,添加小精靈微信(ershiyidianjian) */ function route(pathname) { console.log("About to route a request for " + pathname); } exports.route = route;
如你所見,這段代碼什么也沒干,不過對于現在來說這是應該的。在添加更多的邏輯以前,我們先來看看如何把路由和服務器整合起來。
首先,我們來擴展一下服務器的start()函數,以便將路由函數作為參數傳遞過去:
/** * 從零到壹全棧部落,添加小精靈微信(ershiyidianjian) */ //請求(require)Node.js自帶的 http 模塊,并且把它賦值給 http 變量。 let http = require("http"); let url = require("url"); //用一個函數將之前的內容包裹起來 let start = (route) => { //箭頭函數 let onRequest = (request, response) => { let pathname = url.parse(request.url).pathname; console.log("Request for " + pathname + " received."); route(pathname); response.writeHead(200, {"Content-Type": "text/plain;charset=utf-8"}); response.write("添加小精靈微信(ershiyidianjian),加入全棧部落"); response.end(); } //把函數當作參數傳遞 http.createServer(onRequest).listen(8888); console.log("Server has started."); console.log("請在瀏覽器中打開 http://127.0.0.1:8888..."); } exports.start = start;
同時,我們會相應擴展index.js,使得路由函數可以被注入到服務器中:
/** * 從零到壹全棧部落,添加小精靈微信(ershiyidianjian) */ //從`server`模塊中導入server對象 let server = require("./server"); let router = require("./router"); //啟動服務器 server.start(router.route);
在這里,我們傳遞的函數依舊什么也沒做。
如果現在啟動應用(node index.js,始終記得這個命令行),隨后請求一個URL,你將會看到應用輸出相應的信息,這表明我們的HTTP服務器已經在使用路由模塊了,并會將請求的路徑傳遞給路由:
bogon:如何來進行請求的“路由” v2.0 yuechunli$ node index.js Server has started. 請在瀏覽器中打開 http://127.0.0.1:8888... Request for / received. About to route a request for /9.路由給真正的請求處理程序
現在我們的HTTP服務器和請求路由模塊已經如我們的期望,可以相互交流了,就像一對親密無間的兄弟。
當然這還遠遠不夠,路由,顧名思義,是指我們要針對不同的URL有不同的處理方式。例如處理/start的“業務邏輯”就應該和處理/upload的不同。
在現在的實現下,路由過程會在路由模塊中“結束”,并且路由模塊并不是真正針對請求“采取行動”的模塊,否則當我們的應用程序變得更為復雜時,將無法很好地擴展。
我們暫時把作為路由目標的函數稱為請求處理程序。現在我們不要急著來開發路由模塊,因為如果請求處理程序沒有就緒的話,再怎么完善路由模塊也沒有多大意義。
應用程序需要新的部件,因此加入新的模塊 -- 已經無需為此感到新奇了。我們來創建一個叫做requestHandlers的模塊,并對于每一個請求處理程序,添加一個占位用函數,隨后將這些函數作為模塊的方法導出:
/** * 從零到壹全棧部落,添加小精靈微信(ershiyidianjian) */ function start() { console.log("Request handler "start" was called."); } function upload() { console.log("Request handler "upload" was called."); } exports.start = start; exports.upload = upload;
現在我們將一系列請求處理程序通過一個對象來傳遞,并且需要使用松耦合的方式將這個對象注入到route()函數中。
我們先將這個對象引入到主文件index.js中:
/** * 從零到壹全棧部落,添加小精靈微信(ershiyidianjian) */ //從`server`模塊中導入server對象 let server = require("./server"); let router = require("./router"); let requestHandlers = require("./requestHandlers"); //對象構造 var handle = {} handle["/"] = requestHandlers.start; handle["/start"] = requestHandlers.start; handle["/upload"] = requestHandlers.upload; //啟動服務器 server.start(router.route, handle);
雖然handle并不僅僅是一個“東西”(一些請求處理程序的集合),我還是建議以一個動詞作為其命名,這樣做可以讓我們在路由中使用更流暢的表達式,稍后會有說明。
正如所見,將不同的URL映射到相同的請求處理程序上是很容易的:只要在對象中添加一個鍵為"/"的屬性,對應requestHandlers.start即可,這樣我們就可以干凈簡潔地配置/start和"/"的請求都交由start這一處理程序處理。
在完成了對象的定義后,我們把它作為額外的參數傳遞給服務器,為此將server.js修改如下:
/** * 從零到壹全棧部落,添加小精靈微信(ershiyidianjian) */ //請求(require)Node.js自帶的 http 模塊,并且把它賦值給 http 變量。 let http = require("http"); let url = require("url"); //用一個函數將之前的內容包裹起來 let start = (route,handle) => { //箭頭函數 let onRequest = (request, response) => { let pathname = url.parse(request.url).pathname; console.log("Request for " + pathname + " received."); route(handle,pathname); response.writeHead(200, {"Content-Type": "text/plain;charset=utf-8"}); response.write("添加小精靈微信(ershiyidianjian),加入全棧部落"); response.end(); } //把函數當作參數傳遞 http.createServer(onRequest).listen(8888); console.log("Server has started."); console.log("請在瀏覽器中打開 http://127.0.0.1:8888..."); } exports.start = start;
這樣我們就在start()函數里添加了handle參數,并且把handle對象作為第一個參數傳遞給了route()回調函數。
然后我們相應地在route.js文件中修改route()函數:
有了這些,我們就把服務器、路由和請求處理程序在一起了。現在我們啟動應用程序并在瀏覽器中訪問http://127.0.0.1:8888/start,以下日志可以說明系統調用了正確的請求處理程序:
bogon:路由給真正的請求處理程序 yuechunli$ node index.js Server has started. 請在瀏覽器中打開 http://127.0.0.1:8888... Request for /start received. About to route a request for /start Request handler "start" was called.
并且在瀏覽器中打開http://127.0.0.1:8888/可以看到這個請求同樣被start請求處理程序處理了:
bogon:路由給真正的請求處理程序 yuechunli$ node index.js Server has started. 請在瀏覽器中打開 http://127.0.0.1:8888... Request for / received. About to route a request for / Request handler "start" was called.10.讓請求處理程序作出響應
很好。不過現在要是請求處理程序能夠向瀏覽器返回一些有意義的信息而并非全是添加小精靈微信(ershiyidianjian),加入全棧部落,那就更好了。
這里要記住的是,瀏覽器發出請求后獲得并顯示的添加小精靈微信(ershiyidianjian),加入全棧部落信息仍是來自于我們server.js文件中的onRequest函數。
其實“處理請求”說白了就是“對請求作出響應”,因此,我們需要讓請求處理程序能夠像onRequest函數那樣可以和瀏覽器進行“對話”。
11.不好的實現方式修改requestHandler.js文件內容如下:
/** * 從零到壹全棧部落,添加小精靈微信(ershiyidianjian) */ function start() { console.log("Request handler "start" was called."); return "Hello Start"; } function upload() { console.log("Request handler "upload" was called."); return "Hello Upload"; } exports.start = start; exports.upload = upload;
好的。同樣的,請求路由需要將請求處理程序返回給它的信息返回給服務器。因此,我們需要將router.js修改為如下形式:
/** * 從零到壹全棧部落,添加小精靈微信(ershiyidianjian) */ function route(handle, pathname) { console.log("About to route a request for " + pathname); if (typeof handle[pathname] === "function") { return handle[pathname](); } else { console.log("No request handler found for " + pathname); return "404 Not found"; } } exports.route = route;
正如上述代碼所示,當請求無法路由的時候,我們也返回了一些相關的錯誤信息。
最后,我們需要對我們的server.js進行重構以使得它能夠將請求處理程序通過請求路由返回的內容響應給瀏覽器,如下所示:
/** * 從零到壹全棧部落,添加小精靈微信(ershiyidianjian) */ //請求(require)Node.js自帶的 http 模塊,并且把它賦值給 http 變量。 let http = require("http"); let url = require("url"); //用一個函數將之前的內容包裹起來 let start = (route,handle) => { //箭頭函數 let onRequest = (request, response) => { let pathname = url.parse(request.url).pathname; console.log("Request for " + pathname + " received."); route(handle,pathname); response.writeHead(200, {"Content-Type": "text/plain;charset=utf-8"}); var content = route(handle, pathname) response.write(content); response.end(); } //把函數當作參數傳遞 http.createServer(onRequest).listen(8888); console.log("Server has started."); } exports.start = start;
如果我們運行重構后的應用,一切都會工作的很好:
請求http://localhost:8888/start,瀏覽器會輸出Hello Start。
請求http://localhost:8888/upload會輸出Hello Upload,
而請求http://localhost:8888/foo 會輸出404 Not found。
好,那么問題在哪里呢?簡單的說就是: 當未來有請求處理程序需要進行非阻塞的操作的時候,我們的應用就“掛”了。
沒理解?沒關系,下面就來詳細解釋下。
12.阻塞與非阻塞我們先不解釋這里阻塞與非阻塞,我們來修改下start請求處理程序,我們讓它等待10秒以后再返回Hello Start。因為,JavaScript中沒有類似sleep()這樣的操作,所以這里只能夠來點小Hack來模擬實現。
讓我們將requestHandlers.js修改成如下形式:
/** * 從零到壹全棧部落,添加小精靈微信(ershiyidianjian) */ function start() { console.log("Request handler "start" was called."); 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 called."); return "Hello Upload"; } exports.start = start; exports.upload = upload;
上述代碼中,我先調用了upload(),會和此前一樣立即返回。當函數start()被調用的時候,Node.js會先等待10秒,之后才會返回“Hello Start”。如下圖所示,等待中:
(當然了,這里只是模擬休眠10秒,實際場景中,這樣的阻塞操作有很多,比方說一些長時間的計算操作等。)
接下來就讓我們來看看,我們的改動帶來了哪些變化。
如往常一樣,我們先要重啟下服務器。為了看到效果,我們要進行一些相對復雜的操作(跟著我一起做): 首先,打開兩個瀏覽器窗口或者標簽頁。在第一個瀏覽器窗口的地址欄中輸入http://localhost:8888/start, 但是先不要打開它!
在第二個瀏覽器窗口的地址欄中輸入http://localhost:8888/upload, 同樣的,先不要打開它!
接下來,做如下操作:在第一個窗口中(“/start”)按下回車,然后快速切換到第二個窗口中(“/upload”)按下回車。
注意,發生了什么: /start URL加載花了10秒,這和我們預期的一樣。但是,/upload URL居然也花了10秒,而它在對應的請求處理程序中并沒有類似于sleep()這樣的操作!
這到底是為什么呢?原因就是start()包含了阻塞操作。形象的說就是“它阻塞了所有其他的處理工作”。
這顯然是個問題,因為Node一向是這樣來標榜自己的:“在node中除了代碼,所有一切都是并行執行的”。
這句話的意思是說,Node.js可以在不新增額外線程的情況下,依然可以對任務進行并行處理 —— Node.js是單線程的。它通過事件輪詢(event loop)來實現并行操作,對此,我們應該要充分利用這一點 —— 盡可能的避免阻塞操作,取而代之,多使用非阻塞操作。
然而,要用非阻塞操作,我們需要使用回調,通過將函數作為參數傳遞給其他需要花時間做處理的函數(比方說,休眠10秒,或者查詢數據庫,又或者是進行大量的計算)。
對于Node.js來說,它是這樣處理的:“嘿,probablyExpensiveFunction()(譯者注:這里指的就是需要花時間處理的函數),你繼續處理你的事情,我(Node.js線程)先不等你了,我繼續去處理你后面的代碼,請你提供一個callbackFunction(),等你處理完之后我會去調用該回調函數的,謝謝!”
(如果想要了解更多關于事件輪詢細節,可以閱讀Mixu的博文——理解node.js的事件輪詢。)
接下來,我們會介紹一種錯誤的使用非阻塞操作的方式。
和上次一樣,我們通過修改我們的應用來暴露問題。
這次我們還是拿start請求處理程序來“開刀”。將其修改成如下形式:
/** * 從零到壹全棧部落,添加小精靈微信(ershiyidianjian) */ //我們引入了一個新的Node.js模塊,child_process。之所以用它,是為了實現一個既簡單又實用的非阻塞操作:exec()。 var exec = require("child_process").exec; function start() { console.log("Request handler "start" was called."); /** * exec()做了什么呢? * 它從Node.js來執行一個shell命令。 * 在本例子中,我們用它來獲取當前目錄下所有的文件(“ls -lah”) * 然后,當`/start` URL請求的時候將文件信息輸出到瀏覽器中。 * 下面的代碼非常直觀的: * 創建了一個新的變量content(初始值為“empty”)。 * 執行“ls -lah”命令,將結果賦值給content,最后將content返回。 */ var content = "empty"; exec("ls -lah", function (error, stdout, stderr) { content = stdout; }); return content; } function upload() { console.log("Request handler "upload" was called."); return "Hello Upload"; } exports.start = start; exports.upload = upload;
和往常一樣,我們啟動服務器,然后訪問“http://localhost:8888/start” 。
載入一個漂亮的web頁面,其內容為“empty”。怎么回事?
如果想要證明這一點,可以將“ls -lah”換成比如“find /”這樣更耗時的操作來效果。
然而,針對瀏覽器顯示的結果來看,我們并不滿意我們的非阻塞操作,對吧?
好,接下來,我們來修正這個問題。在這過程中,讓我們先來看看為什么當前的這種方式不起作用。
問題就在于,為了進行非阻塞工作,exec()使用了回調函數。
在我們的例子中,該回調函數就是作為第二個參數傳遞給exec()的匿名函數:
function (error, stdout, stderr) { content = stdout; }
現在就到了問題根源所在了:我們的代碼是同步執行的,這就意味著在調用exec()之后,Node.js會立即執行 return content ;在這個時候,content仍然是“empty”,因為傳遞給exec()的回調函數還未執行到——因為exec()的操作是異步的。
我們這里“ls -lah”的操作其實是非常快的(除非當前目錄下有上百萬個文件)。這也是為什么回調函數也會很快的執行到 —— 不過,不管怎么說它還是異步的。
為了讓效果更加明顯,我們想象一個更耗時的命令: “find /”,它在我機器上需要執行1分鐘左右的時間,然而,盡管在請求處理程序中,我把“ls -lah”換成“find /”,當打開/start URL的時候,依然能夠立即獲得HTTP響應 —— 很明顯,當exec()在后臺執行的時候,Node.js自身會繼續執行后面的代碼。并且我們這里假設傳遞給exec()的回調函數,只會在“find /”命令執行完成之后才會被調用。
那究竟我們要如何才能實現將當前目錄下的文件列表顯示給用戶呢?
好,了解了這種不好的實現方式之后,我們接下來來介紹如何以正確的方式讓請求處理程序對瀏覽器請求作出響應。
13.以非阻塞操作進行請求響應我剛剛提到了這樣一個短語 —— “正確的方式”。而事實上通常“正確的方式”一般都不簡單。
不過,用Node.js就有這樣一種實現方案: 函數傳遞。下面就讓我們來具體看看如何實現。
到目前為止,我們的應用已經可以通過應用各層之間傳遞值的方式(請求處理程序 -> 請求路由 -> 服務器)將請求處理程序返回的內容(請求處理程序最終要顯示給用戶的內容)傳遞給HTTP服務器。
現在我們采用如下這種新的實現方式:相對采用將內容傳遞給服務器的方式,我們這次采用將服務器“傳遞”給內容的方式。 從實踐角度來說,就是將response對象(從服務器的回調函數onRequest()獲取)通過請求路由傳遞給請求處理程序。 隨后,處理程序就可以采用該對象上的函數來對請求作出響應。
原理就是如此,接下來讓我們來一步步實現這種方案。
先從server.js開始:
/** * 從零到壹全棧部落,添加小精靈微信(ershiyidianjian) */ //請求(require)Node.js自帶的 http 模塊,并且把它賦值給 http 變量。 let http = require("http"); let url = require("url"); //用一個函數將之前的內容包裹起來 let start = (route,handle) => { //箭頭函數 let onRequest = (request, response) => { let pathname = url.parse(request.url).pathname; console.log("Request for " + pathname + " received."); route(handle, pathname, response); } //把函數當作參數傳遞 http.createServer(onRequest).listen(8888); console.log("Server has started."); } exports.start = start;
相對此前從route()函數獲取返回值的做法,這次我們將response對象作為第三個參數傳遞給route()函數,并且,我們將onRequest()處理程序中所有有關response的函數調都移除,因為我們希望這部分工作讓route()函數來完成。
下面就來看看我們的router.js:
同樣的模式:相對此前從請求處理程序中獲取返回值,這次取而代之的是直接傳遞response對象。
如果沒有對應的請求處理器處理,我們就直接返回“404”錯誤。
最后,我們將requestHandler.js修改為如下形式:
/** * 從零到壹全棧部落,添加小精靈微信(ershiyidianjian) */ //我們引入了一個新的Node.js模塊,child_process。之所以用它,是為了實現一個既簡單又實用的非阻塞操作:exec()。 var exec = require("child_process").exec; function start(response) { console.log("Request handler "start" was called."); 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("Hello Upload"); response.end(); } exports.start = start; exports.upload = upload;
我們的處理程序函數需要接收response參數,為了對請求作出直接的響應。
start處理程序在exec()的匿名回調函數中做請求響應的操作,而upload處理程序仍然是簡單的回復“Hello World”,只是這次是使用response對象而已。
這時再次我們啟動應用(node index.js),一切都會工作的很好。
在瀏覽器中打開 http:127.0.0.0:8888/start 效果圖如下所示:
在瀏覽器中打開 http:127.0.0.0:8888/upload 效果圖如下所示:
如果想要證明/start處理程序中耗時的操作不會阻塞對/upload請求作出立即響應的話,可以將requestHandlers.js修改為如下形式:
var exec = require("child_process").exec; function start(response) { console.log("Request handler "start" was called."); exec("find /", { timeout: 10000, maxBuffer: 20000*1024 }, 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("Hello Upload"); response.end(); } exports.start = start; exports.upload = upload;
這樣一來,當請求http://localhost:8888/start的時候,會花10秒鐘的時間才載入,而當請求http://localhost:8888/upload的時候,會立即響應,縱然這個時候/start響應還在處理中。
14.更有用的場景到目前為止,我們做的已經很好了,但是,我們的應用沒有實際用途。
服務器,請求路由以及請求處理程序都已經完成了,下面讓我們按照此前的用例給網站添加交互:用戶選擇一個文件,上傳該文件,然后在瀏覽器中看到上傳的文件。 為了保持簡單,我們假設用戶只會上傳圖片,然后我們應用將該圖片顯示到瀏覽器中。
好,下面就一步步來實現,鑒于此前已經對JavaScript原理性技術性的內容做過大量介紹了,這次我們加快點速度。
要實現該功能,分為如下兩步: 首先,讓我們來看看如何處理POST請求(非文件上傳),之后,我們使用Node.js的一個用于文件上傳的外部模塊。之所以采用這種實現方式有兩個理由。
第一,盡管在Node.js中處理基礎的POST請求相對比較簡單,但在這過程中還是能學到很多。
第二,用Node.js來處理文件上傳(multipart POST請求)是比較復雜的,它不在本文的范疇,但是,如何使用外部模塊卻是在本書涉獵內容之內。
考慮這樣一個簡單的例子:我們顯示一個文本區(textarea)供用戶輸入內容,然后通過POST請求提交給服務器。最后,服務器接受到請求,通過處理程序將輸入的內容展示到瀏覽器中。
/start請求處理程序用于生成帶文本區的表單,因此,我們將requestHandlers.js修改為如下形式:
/** * 從零到壹全棧部落,添加小精靈微信(ershiyidianjian) */ //我們引入了一個新的Node.js模塊,child_process。之所以用它,是為了實現一個既簡單又實用的非阻塞操作:exec()。 var exec = require("child_process").exec; function start(response) { console.log("Request handler "start" was called."); let body = ""+ ""+ ""+ ""+ ""+ ""+ ""+ ""; response.writeHead(200, {"Content-Type": "text/html;charset=utf-8"}); response.write(body); response.end(); } function upload(response) { console.log("Request handler "upload" was called."); response.writeHead(200, {"Content-Type": "text/plain;charset=utf-8"}); response.write("Hello Upload"); response.end(); } exports.start = start; exports.upload = upload;
瀏覽器請求http://127.0.0.1:8888/start,效果圖如下:
余下的篇幅,我們來探討一個更有趣的問題: 當用戶提交表單時,觸發/upload請求處理程序處理POST請求的問題。
現在,我們已經是新手中的專家了,很自然會想到采用異步回調來實現非阻塞地處理POST請求的數據。
這里采用非阻塞方式處理是明智的,因為POST請求一般都比較“重” —— 用戶可能會輸入大量的內容。用阻塞的方式處理大數據量的請求必然會導致用戶操作的阻塞。
為了使整個過程非阻塞,Node.js會將POST數據拆分成很多小的數據塊,然后通過觸發特定的事件,將這些小數據塊傳遞給回調函數。這里的特定的事件有data事件(表示新的小數據塊到達了)以及end事件(表示所有的數據都已經接收完畢)。
我們需要告訴Node.js當這些事件觸發的時候,回調哪些函數。怎么告訴呢? 我們通過在request對象上注冊監聽器(listener) 來實現。這里的request對象是每次接收到HTTP請求時候,都會把該對象傳遞給onRequest回調函數。
如下所示:
request.addListener("data", function(chunk) { // called when a new chunk of data was received }); request.addListener("end", function() { // called when all chunks of data have been received });
問題來了,這部分邏輯寫在哪里呢? 我們現在只是在服務器中獲取到了request對象 —— 我們并沒有像之前response對象那樣,把 request 對象傳遞給請求路由和請求處理程序。
在我看來,獲取所有來自請求的數據,然后將這些數據給應用層處理,應該是HTTP服務器要做的事情。因此,我建議,我們直接在服務器中處理POST數據,然后將最終的數據傳遞給請求路由和請求處理器,讓他們來進行進一步的處理。
因此,實現思路就是: 將data和end事件的回調函數直接放在服務器中,在data事件回調中收集所有的POST數據,當接收到所有數據,觸發end事件后,其回調函數調用請求路由,并將數據傳遞給它,然后,請求路由再將該數據傳遞給請求處理程序。
還等什么,馬上來實現。先從server.js開始:
/** * 從零到壹全棧部落,添加小精靈微信(ershiyidianjian) */ //請求(require)Node.js自帶的 http 模塊,并且把它賦值給 http 變量。 let http = require("http"); let url = require("url"); //用一個函數將之前的內容包裹起來 let start = (route,handle) => { //箭頭函數 let onRequest = (request, response) => { let postData = ""; let pathname = url.parse(request.url).pathname; console.log("Request for " + pathname + " received."); request.setEncoding("utf8"); request.addListener("data", function(postDataChunk) { postData += postDataChunk; console.log("Received POST data chunk ""+ postDataChunk + ""."); }); request.addListener("end", function() { route(handle, pathname, response, postData); }); } //把函數當作參數傳遞 http.createServer(onRequest).listen(8888); console.log("Server has started."); } exports.start = start;
上述代碼做了三件事情: 首先,我們設置了接收數據的編碼格式為UTF-8,然后注冊了“data”事件的監聽器,用于收集每次接收到的新數據塊,并將其賦值給postData 變量,最后,我們將請求路由的調用移到end事件處理程序中,以確保它只會當所有數據接收完畢后才觸發,并且只觸發一次。我們同時還把POST數據傳遞給請求路由,因為這些數據,請求處理程序會用到。
上述代碼在每個數據塊到達的時候輸出了日志,這對于最終生產環境來說,是很不好的(數據量可能會很大,還記得吧?),但是,在開發階段是很有用的,有助于讓我們看到發生了什么。
我建議可以嘗試下,嘗試著去輸入一小段文本,以及大段內容,當大段內容的時候,就會發現data事件會觸發多次。
再來點酷的。我們接下來在/upload頁面,展示用戶輸入的內容。要實現該功能,我們需要將postData傳遞給請求處理程序,修改router.js為如下形式:
/** * 從零到壹全棧部落,添加小精靈微信(ershiyidianjian) */ function route(handle, pathname, response, postData) { console.log("About to route a request for " + pathname); if (typeof handle[pathname] === "function") { handle[pathname](response, postData); } else { console.log("No request handler found for " + pathname); response.writeHead(404, {"Content-Type": "text/plain"}); response.write("404 Not found"); response.end(); } } exports.route = route;
然后,在requestHandlers.js中,我們將數據包含在對upload請求的響應中:
/** * 從零到壹全棧部落,添加小精靈微信(ershiyidianjian) */ //我們引入了一個新的Node.js模塊,child_process。之所以用它,是為了實現一個既簡單又實用的非阻塞操作:exec()。 var exec = require("child_process").exec; function start(response, postData) { console.log("Request handler "start" was called."); var body = ""+ ""+ ""+ ""+ ""+ ""+ ""+ ""; response.writeHead(200, {"Content-Type": "text/html"}); response.write(body); response.end(); } function upload(response, postData) { console.log("Request handler "upload" was called."); response.writeHead(200, {"Content-Type": "text/plain"}); response.write("You"ve sent: " + postData); response.end(); } exports.start = start; exports.upload = upload;
好了,我們現在可以接收POST數據并在請求處理程序中處理該數據了。
我們最后要做的是: 當前我們是把請求的整個消息體傳遞給了請求路由和請求處理程序。我們應該只把POST數據中,我們感興趣的部分傳遞給請求路由和請求處理程序。在我們這個例子中,我們感興趣的其實只是text字段。
我們可以使用此前介紹過的querystring模塊來實現:
/** * 從零到壹全棧部落,添加小精靈微信(ershiyidianjian) */ //我們引入了一個新的Node.js模塊,child_process。之所以用它,是為了實現一個既簡單又實用的非阻塞操作:exec()。 var exec = require("child_process").exec; var querystring = require("querystring"); function start(response, postData) { console.log("Request handler "start" was called."); var body = ""+ ""+ ""+ ""+ ""+ ""+ ""+ ""; response.writeHead(200, {"Content-Type": "text/html"}); response.write(body); response.end(); } function upload(response, postData) { console.log("Request handler "upload" was called."); response.writeHead(200, {"Content-Type": "text/plain"}); response.write("You"ve sent the text: "+querystring.parse(postData).text); response.end(); } exports.start = start; exports.upload = upload;
下面我們瀏覽器中訪問http://127.0.0.1:8888/start,如下圖所示:
點擊Submit text按鈕,將跳轉到http://127.0.0.1:8888/upload,效果圖如下:
好了,這就是完整的POST請求。
15.處理文件上傳最后,我們來實現我們最終的用例:允許用戶上傳圖片,并將該圖片在瀏覽器中顯示出來。
我們通過它能學到這樣兩件事情:
如何安裝外部Node.js模塊
以及如何將它們應用到我們的應用中
這里我們要用到的外部模塊是Felix Geisend?rfer開發的node-formidable模塊。它對解析上傳的文件數據做了很好的抽象。 其實說白了,處理文件上傳“就是”處理POST數據 —— 但是,麻煩的是在具體的處理細節,所以,這里采用現成的方案更合適點。
使用該模塊,首先需要安裝該模塊。Node.js有它自己的包管理器,叫NPM。它可以讓安裝Node.js的外部模塊變得非常方便。
首先在當前項目路徑下面通過npm init創建package.json文件:
PS:在終端輸入npm init后,一路回車即可。新增的package.json文件的內容如下:
{ "author" : "liyuechun", "description" : "", "license" : "ISC", "main" : "index.js", "name" : "fileupload", "scripts" : { "test" : "echo "Error: no test specified" && exit 1" }, "version" : "1.0.0" }
接下來,在終端輸入如下命令安裝formidable外部模塊。
如下所示:
liyuechun:fileupload yuechunli$ ls index.js requestHandlers.js server.js package.json router.js liyuechun:fileupload yuechunli$ npm install formidable npm notice created a lockfile as package-lock.json. You should commit this file. npm WARN fileupload@1.0.0 No description npm WARN fileupload@1.0.0 No repository field. + formidable@1.1.1 added 1 package in 1.117s liyuechun:fileupload yuechunli$
package.json文件變化如下:
{ "author": "liyuechun", "description": "", "license": "ISC", "main": "index.js", "name": "fileupload", "scripts": { "test": "echo "Error: no test specified" && exit 1" }, "version": "1.0.0", "dependencies": { "formidable": "^1.1.1" } }
項目整體變化如下圖所示:
現在我們就可以用formidable模塊了——使用外部模塊與內部模塊類似,用require語句將其引入即可:
let formidable = require("formidable");
這里該模塊做的就是將通過HTTP POST請求提交的表單,在Node.js中可以被解析。我們要做的就是創建一個新的IncomingForm,它是對提交表單的抽象表示,之后,就可以用它解析request對象,獲取表單中需要的數據字段。
node-formidable官方的例子展示了這兩部分是如何融合在一起工作的:
let formidable = require("formidable"), http = require("http"), util = require("util"); http.createServer(function(req, res) { if (req.url == "/upload" && req.method.toLowerCase() == "post") { // parse a file upload let form = new formidable.IncomingForm(); form.parse(req, function(err, fields, files) { res.writeHead(200, {"content-type": "text/plain"}); res.write("received upload: "); res.end(util.inspect({fields: fields, files: files})); }); return; } // show a file upload form res.writeHead(200, {"content-type": "text/html"}); res.end( "" ); }).listen(8888);
如果我們將上述代碼,保存到一個文件中,并通過node來執行,就可以進行簡單的表單提交了,包括文件上傳。然后,可以看到通過調用form.parse傳遞給回調函數的files對象的內容,如下所示:
received upload: { fields: { title: "Hello World" }, files: { upload: { size: 1558, path: "./tmp/1c747974a27a6292743669e91f29350b", name: "us-flag.png", type: "image/png", lastModifiedDate: Tue, 21 Jun 2011 07:02:41 GMT, _writeStream: [Object], length: [Getter], filename: [Getter], mime: [Getter] } } }
為了實現我們的功能,我們需要將上述代碼應用到我們的應用中,另外,我們還要考慮如何將上傳文件的內容(保存在./tmp目錄中)顯示到瀏覽器中。
我們先來解決后面那個問題: 對于保存在本地硬盤中的文件,如何才能在瀏覽器中看到呢?
顯然,我們需要將該文件讀取到我們的服務器中,使用一個叫fs的模塊。
我們來添加/showURL的請求處理程序,該處理程序直接硬編碼將文件./tmp/test.png內容展示到瀏覽器中。當然了,首先需要將該圖片保存到這個位置才行。
將requestHandlers.js修改為如下形式:
/** * 從零到壹全棧部落,添加小精靈微信(ershiyidianjian) */ var querystring = require("querystring"), fs = require("fs"); function start(response, postData) { console.log("Request handler "start" was called."); var body = ""+ ""+ ""+ ""+ ""+ ""+ ""+ ""; response.writeHead(200, {"Content-Type": "text/html"}); response.write(body); response.end(); } function upload(response, postData) { console.log("Request handler "upload" was called."); response.writeHead(200, {"Content-Type": "text/plain"}); response.write("You"ve sent the text: "+ querystring.parse(postData).text); response.end(); } function show(response, postData) { console.log("Request handler "show" was called."); fs.readFile("./tmp/test.png", "binary", function(error, file) { if(error) { response.writeHead(500, {"Content-Type": "text/plain"}); response.write(error + " "); response.end(); } else { response.writeHead(200, {"Content-Type": "image/png"}); response.write(file, "binary"); response.end(); } }); } exports.start = start; exports.upload = upload; exports.show = show;
我們還需要將這新的請求處理程序,添加到index.js中的路由映射表中:
/** * 從零到壹全棧部落,添加小精靈微信(ershiyidianjian) */ //從`server`模塊中導入server對象 let server = require("./server"); let router = require("./router"); let requestHandlers = require("./requestHandlers"); //對象構造 var handle = {} handle["/"] = requestHandlers.start; handle["/start"] = requestHandlers.start; handle["/upload"] = requestHandlers.upload; handle["/show"] = requestHandlers.show; //啟動服務器 server.start(router.route, handle);
重啟服務器之后,通過訪問http://localhost:8888/show看看效果:
原因是當前項目路徑下面沒有./tmp/test.png圖片,我們在當前項目路徑下面添加tmp文件夾,在往里面拖拽一張圖片,命名為test.png。
再重新啟動服務器,訪問http://localhost:8888/show查看效果如下:
咱繼續,從server.js開始 —— 移除對postData的處理以及request.setEncoding (這部分node-formidable自身會處理),轉而采用將request對象傳遞給請求路由的方式:
/** * 從零到壹全棧部落,添加小精靈微信(ershiyidianjian) */ //請求(require)Node.js自帶的 http 模塊,并且把它賦值給 http 變量。 let http = require("http"); let url = require("url"); //用一個函數將之前的內容包裹起來 let start = (route,handle) => { //箭頭函數 let onRequest = (request, response) => { let pathname = url.parse(request.url).pathname; console.log("Request for " + pathname + " received."); route(handle, pathname, response, request); } //把函數當作參數傳遞 http.createServer(onRequest).listen(8888); console.log("Server has started."); } exports.
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/83773.html
摘要:菜鳥教程框架中文手冊入門目標使用搭建通過對數據增刪查改沒了純粹占行用的拜 后端API入門學習指北 了解一下一下概念. RESTful API標準] 所有的API都遵循[RESTful API標準]. 建議大家都簡單了解一下HTTP協議和RESTful API相關資料. 阮一峰:理解RESTful架構 阮一峰:RESTful API 設計指南 RESTful API指南 依賴注入 D...
閱讀 3290·2021-09-09 11:39
閱讀 1237·2021-09-09 09:33
閱讀 1139·2019-08-30 15:43
閱讀 555·2019-08-29 14:08
閱讀 1741·2019-08-26 13:49
閱讀 2386·2019-08-26 10:09
閱讀 1553·2019-08-23 17:13
閱讀 2291·2019-08-23 12:57