




版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡介
\hNode與Express開發(fā)(第2版)目錄\h第1章Express是什么\h1.1JavaScript的革命\h1.2Express簡介\h1.3服務(wù)器端應(yīng)用和客戶端應(yīng)用\h1.4Express簡史\h1.5Node:另一種Web服務(wù)器\h1.6Node生態(tài)系統(tǒng)\h1.7開源協(xié)議\h1.8小結(jié)\h第2章Node的世界\h2.1獲取Node\h2.2使用終端\h2.3編輯器\h2.4npm\h2.5用Node做一個(gè)簡單的Web服務(wù)器\h2.5.1Helloworld\h2.5.2事件驅(qū)動(dòng)編程\h2.5.3路由\h2.5.4提供靜態(tài)資源\h2.6進(jìn)入Express\h第3章Express的方式\h3.1腳手架\h3.2草地鷚旅游網(wǎng)站\h3.3初始工作\h3.4視圖與布局\h3.5靜態(tài)文件與視圖\h3.6視圖中的動(dòng)態(tài)內(nèi)容\h3.7小結(jié)\h第4章項(xiàng)目整頓\h4.1項(xiàng)目文件與目錄結(jié)構(gòu)\h4.2最佳實(shí)踐\h4.3版本控制\h4.4在本書學(xué)習(xí)中如何使用Git\h4.4.1親手錄入\h4.4.2使用官方版本庫\h4.5npm包\h4.6項(xiàng)目元數(shù)據(jù)\h4.7Node模塊\h4.8小結(jié)\h第5章質(zhì)量保證\h5.1QA計(jì)劃\h5.2QA:是否值得\h5.3邏輯與表示\h5.4測試類型\h5.5QA技術(shù)\h5.6安裝和配置Jest\h5.7單元測試\h5.7.1模擬\h5.7.2為可測試性而重構(gòu)應(yīng)用\h5.7.3寫第一個(gè)測試\h5.7.4測試維護(hù)\h5.7.5代碼覆蓋率\h5.8集成測試\h5.9Linting\h5.10持續(xù)集成\h5.11小結(jié)\h第6章request和response對(duì)象\h6.1URL的各個(gè)組成部分\h6.2HTTP請(qǐng)求方法\h6.3請(qǐng)求頭\h6.4響應(yīng)頭\h6.5互聯(lián)網(wǎng)媒體類型\h6.6請(qǐng)求的Body\h6.7request對(duì)象\h6.8response對(duì)象\h6.9深入源代碼\h6.10按功能歸納\h6.10.1渲染內(nèi)容\h6.10.2處理表單\h6.10.3API服務(wù)\h6.11小結(jié)\h第7章視圖模板——使用Handlebars\h7.1何時(shí)使用模板\h7.2選擇模板引擎\h7.3Pug:另辟蹊徑\h7.4Handlebars基礎(chǔ)\h7.4.1注釋\h7.4.2代碼塊\h7.4.3服務(wù)器端模板\h7.4.4視圖和布局\h7.4.5在Express中使用(或不使用)布局\h7.4.6sections\h7.4.7partial模板\h7.4.8完善模板\h7.5小結(jié)\h第8章表單處理\h8.1把客戶端數(shù)據(jù)發(fā)送到服務(wù)器\h8.2HTML表單\h8.3表單的編碼\h8.4處理表單的不同做法\h8.5使用Express處理表單\h8.6使用fetch發(fā)送表單數(shù)據(jù)\h8.7文件上傳\h使用fetch做文件上傳\h8.8提升文件上傳的UI\h8.9小結(jié)\h第9章Cookie和Session\h9.1提取敏感信息\h9.2Express中的Cookie\h9.3查看Cookie\h9.4Session\h9.4.1內(nèi)存存儲(chǔ)\h9.4.2使用Session\h9.5使用Session實(shí)現(xiàn)flash消息\h9.6Session的用途\h9.7小結(jié)\h第10章中間件\h10.1基本原理\h10.2中間件示例\h10.3常用中間件\h10.4第三方中間件\h10.5小結(jié)\h第11章發(fā)送郵件\h11.1SMTP、MSA和MTA\h11.2接收郵件\h11.3郵件頭\h11.4郵件格式\h11.5HTML郵件\h11.6Nodemailer\h11.6.1發(fā)送郵件\h11.6.2發(fā)送給多個(gè)收件人\h11.7群發(fā)郵件更好的選擇\h11.8發(fā)送HTML郵件\h11.8.1HTML郵件中的圖片\h11.8.2使用視圖來發(fā)送HTML郵件\h11.8.3封裝郵件功能\h11.9小結(jié)\h第12章考慮生產(chǎn)環(huán)境中的問題\h12.1運(yùn)行環(huán)境\h12.2特定環(huán)境的配置\h12.3運(yùn)行Node進(jìn)程\h12.4網(wǎng)站的擴(kuò)展\h12.4.1使用應(yīng)用集群實(shí)現(xiàn)水平擴(kuò)展\h12.4.2處理未捕獲的異常\h12.4.3使用多臺(tái)服務(wù)器完成水平擴(kuò)展\h12.5監(jiān)控網(wǎng)站的運(yùn)行\(zhòng)h第三方在線監(jiān)控\h12.6壓力測試\h12.7小結(jié)\h第13章持久化\h13.1文件系統(tǒng)持久化\h13.2云持久化\h13.3數(shù)據(jù)庫持久化\h13.3.1關(guān)于性能的提醒\h13.3.2數(shù)據(jù)庫層抽象\h13.3.3設(shè)置MongoDB\h13.3.4Mongoose\h13.3.5使用Mongoose連接數(shù)據(jù)庫\h13.3.6創(chuàng)建模式和模型\h13.3.7使用種子數(shù)據(jù)初始化\h13.3.8獲取數(shù)據(jù)\h13.3.9更新數(shù)據(jù)\h13.3.10PostgreSQL\h13.3.11新增數(shù)據(jù)\h13.4使用數(shù)據(jù)庫存儲(chǔ)Session\h13.5小結(jié)\h第14章路由\h14.1路由與SEO\h14.2子域名\h14.3路由處理函數(shù)也是中間件\h14.4路由路徑和正則表達(dá)式\h14.5路由參數(shù)\h14.6組織路由\h14.7在模塊中聲明路由\h14.8合乎邏輯地分組路由\h14.9自動(dòng)化渲染視圖\h14.10小結(jié)\h第15章RESTAPI和JSON\h15.1JSON和XML\h15.2我們的API\h15.3API錯(cuò)誤報(bào)告\h15.4跨域資源共享\h15.5測試\h15.6使用Express提供API\h15.7小結(jié)\h第16章單頁應(yīng)用\h16.1Web應(yīng)用開發(fā)簡史\h16.2SPA技術(shù)選擇\h16.3創(chuàng)建React應(yīng)用\h16.4React基本概念\h16.4.1主頁\h16.4.2路由\h16.4.3度假產(chǎn)品頁——可視化設(shè)計(jì)\h16.4.4度假產(chǎn)品頁——跟服務(wù)器端集成\h16.4.5向服務(wù)器發(fā)送信息\h16.4.6狀態(tài)管理\h16.4.7部署選擇\h16.5小結(jié)\h第17章靜態(tài)內(nèi)容\h17.1性能上的考量\h17.2內(nèi)容分發(fā)網(wǎng)絡(luò)(CDN)\h17.3為CDN而設(shè)計(jì)\h17.3.1服務(wù)器端渲染的網(wǎng)站\h17.3.2單頁應(yīng)用\h17.4緩存靜態(tài)資源\h17.5變更靜態(tài)內(nèi)容\h17.6小結(jié)\h第18章安全\h18.1HTTPS\h18.1.1生成自己的證書\h18.1.2使用免費(fèi)的證書中心\h18.1.3購買證書\h18.1.4為Express應(yīng)用啟用HTTPS\h18.1.5有關(guān)端口的說明\h18.1.6HTTPS與代理\h18.2跨站請(qǐng)求偽造\h18.3認(rèn)證\h18.3.1認(rèn)證與授權(quán)\h18.3.2使用密碼認(rèn)證的問題\h18.3.3第三方認(rèn)證\h18.3.4在數(shù)據(jù)庫里存儲(chǔ)用戶信息\h18.3.5認(rèn)證與注冊(cè)及用戶體驗(yàn)\h18.3.6Passport\h18.3.7基于角色的授權(quán)\h18.3.8增加認(rèn)證提供者\(yùn)h18.4小結(jié)\h第19章集成第三方API\h19.1社交媒體\h19.1.1社交媒體插件與網(wǎng)站性能\h19.1.2搜索推文\h19.1.3展現(xiàn)推文\h19.2地理編碼\h19.2.1使用谷歌生成地理編碼\h19.2.2為你的數(shù)據(jù)做地理編碼\h19.2.3顯示地圖\h19.3天氣數(shù)據(jù)\h19.4小結(jié)\h第20章調(diào)試\h20.1調(diào)試原則第一條\h20.2利用REPL和控制臺(tái)\h20.3使用Node的內(nèi)建調(diào)試器\h20.4Node調(diào)試客戶端\h20.5調(diào)試異步函數(shù)\h20.6調(diào)試Express\h20.7小結(jié)\h第21章上線\h21.1域名注冊(cè)與托管\h21.1.1域名系統(tǒng)\h21.1.2安全\h21.1.3頂級(jí)域名\h21.1.4子域名\h21.1.5域名服務(wù)器\h21.1.6托管服務(wù)\h21.1.7部署\h21.2小結(jié)\h第22章維護(hù)\h22.1維護(hù)的原則\h22.1.1長遠(yuǎn)規(guī)劃\h22.1.2使用源代碼控制\h22.1.3使用問題跟蹤系統(tǒng)\h22.1.4保持良好的“衛(wèi)生習(xí)慣”\h22.1.5不要拖延\h22.1.6例行QA核查\h22.1.7監(jiān)控分析\h22.1.8優(yōu)化性能\h22.1.9優(yōu)先跟蹤潛在客戶\h22.1.10避免“不可見”的故障\h22.2代碼重用與重構(gòu)\h22.2.1私有npm倉庫\h22.2.2中間件\h22.3小結(jié)\h第23章更多資源\h23.1在線文檔\h23.2期刊\h23.3StackOverflow\h23.4對(duì)Express做貢獻(xiàn)\h23.5小結(jié)注:原文檔電子版(非掃描),需要的請(qǐng)下載本文檔后留言謝謝。第1章Express是什么1.1JavaScript的革命在進(jìn)入本書的主題之前,非常有必要先提供一點(diǎn)兒歷史和背景信息。這意味著我們要談一談JavaScript和Node。JavaScript的時(shí)代已經(jīng)到來了。當(dāng)初JavaScript作為客戶端腳本語言,并不受人待見,可到了現(xiàn)在,它不僅完全統(tǒng)治了客戶端,而且早已被用作服務(wù)器端語言,這一切多虧了Node。全JavaScript技術(shù)棧的前景是顯而易見的:再也沒有頻繁的語境切換了!你再也不需要從JavaScript切換到PHP、C#、Ruby或Python(或任何其他服務(wù)器端語言)了。而且,全JavaScript技術(shù)棧提升了前端工程師們的能力,使他們可以轉(zhuǎn)到服務(wù)器端編程。這并非說服務(wù)器端編程只是語言的事,關(guān)于服務(wù)器端編程還是有很多東西要學(xué)的。但是選擇使用JavaScript,至少語言不會(huì)是一個(gè)障礙了。本書適合所有看到了JavaScript技術(shù)棧廣闊前景的人。或許你是個(gè)前端工程師,想繼續(xù)拓展到后端開發(fā)。或許你是個(gè)像我一樣有經(jīng)驗(yàn)的后端開發(fā)者,在目前諸多根深蒂固的服務(wù)器端語言之外,把JavaScript看作另一個(gè)可行的選擇。如果你做軟件工程師的年頭和我一樣長,就會(huì)發(fā)現(xiàn)很多語言、框架和API曾風(fēng)靡一時(shí)。有些的確發(fā)展起來了,而有的最終銷聲匿跡。你或許自豪于自己快速學(xué)習(xí)新語言、新系統(tǒng)的能力。每一種新語言你都覺得有些熟悉:某塊知識(shí),你在大學(xué)學(xué)習(xí)某種語言時(shí)就學(xué)過了;另一塊知識(shí),你在幾年前的工作中就學(xué)過了。這種感覺當(dāng)然不錯(cuò),但也會(huì)讓人疲憊。有時(shí)你不過是想把任務(wù)完成,并不想學(xué)一種全新的技術(shù),也不想把擱置了幾個(gè)月甚至幾年的技能重新?lián)炱饋怼F鸪酰琂avaScript看起來沒有什么勝算。我也是這么看的,毋庸置疑。如果在2007年你告訴我說,你不僅會(huì)考慮把JavaScript作為首選語言,而且會(huì)寫一本關(guān)于JavaScript的書,我會(huì)說“你瘋了”。我也和大家一樣對(duì)JavaScript有過偏見,認(rèn)為它只是一種“玩具”語言,是只有外行和半吊子才會(huì)來搗鼓的東西。平心而論,JavaScript為編程愛好者降低了門檻,加上那時(shí)有問題的JavaScript代碼又太多了,這些都不利于這門語言的名聲。用一句流行的話來說:“可恨的是玩家,不是游戲。”遺憾的是人們受到這種偏見的影響,沒能發(fā)現(xiàn)JavaScript這門語言是多么強(qiáng)大、靈活和優(yōu)雅。我們現(xiàn)在知道,JavaScript早在1996年就出現(xiàn)了(盡管它很多吸引人的特性是2005年加上的),然而,很多人直到現(xiàn)在才開始認(rèn)真地看待它。在你拿起本書時(shí),你可能沒有這種偏見:或許像我一樣,偏見成了過去式;又或許是你從來就沒有過偏見。不管怎樣,你是幸運(yùn)的,我很樂意為你介紹Express。造就Express的,正是JavaScript這門給人帶來驚喜的語言。到2009年,距人們開始認(rèn)識(shí)到JavaScript作為瀏覽器腳本語言的威力和表達(dá)力已經(jīng)有幾年了,RyanDahl看到了JavaScript作為服務(wù)器端語言的潛力,于是Node.js誕生了。此時(shí)正是互聯(lián)網(wǎng)技術(shù)蓬勃發(fā)展的時(shí)代。Ruby以及RubyonRails將學(xué)院派計(jì)算機(jī)科學(xué)的一些優(yōu)秀思想,同它們自己的一些原創(chuàng)思想融合起來,向世界展示了一種開發(fā)網(wǎng)站和Web應(yīng)用的更快的方式。微軟公司為了證明自己在互聯(lián)網(wǎng)時(shí)代的不可或缺而不懈努力,一方面大量地從學(xué)術(shù)殿堂汲取營養(yǎng),另一方面從Ruby和JavaScript的成功及Java的失誤中學(xué)到了不少,于是在.NET上取得了卓越的成就。今天,多虧了像Babel這樣的轉(zhuǎn)譯技術(shù),Web開發(fā)者才得以放心地使用JavaScript語言的最新特性,不用擔(dān)心阻隔了使用老瀏覽器的用戶。在管理Web應(yīng)用的依賴和保證性能方面,Webpack成了通用的解決方案。像React、Angular和Vue這樣的框架,正在改變?nèi)藗冞M(jìn)行Web開發(fā)的方式,使得聲明式DOM操作庫(例如jQuery)慢慢地變得不再重要,成了明日黃花。現(xiàn)在是參與互聯(lián)網(wǎng)技術(shù)發(fā)展的激動(dòng)人心的時(shí)刻。讓人驚奇的新想法(或重新恢復(fù)活力的舊想法)隨處可見。很多年以來,創(chuàng)新精神和興奮感從沒有像今天這樣強(qiáng)烈。1.2Express簡介Express的官網(wǎng)上把Express描述為“最小而又靈活的Node.jsWeb應(yīng)用框架,為Web和移動(dòng)應(yīng)用提供了一個(gè)健壯的特性集”。可這到底是什么意思?我們把這句話分解一下。最小這是Express最吸引人的一個(gè)方面。很多時(shí)候,框架開發(fā)者忘記了通常“少即是多”。Express的哲學(xué)就是,在你的大腦與Web服務(wù)器之間提供最小的一層。這并不意味著它就不夠健壯,或是沒有太多有用的特性。相反,這意味著它在為你提供有用東西的同時(shí),對(duì)你的限制更少,能夠讓你的想法得到更充分的表達(dá)。Express給你提供一個(gè)最小的框架,你可以根據(jù)需要加入Express的各部分功能,替換任何不符合你需要的功能。這給框架帶來了可操作的空間。太多的框架想要包辦一切,你甚至還沒開始寫一行代碼,你的項(xiàng)目就已經(jīng)變得臃腫、神秘而又復(fù)雜。項(xiàng)目的第一項(xiàng)任務(wù),往往就是砍掉不需要的功能或替換不符合需求的功能,結(jié)果時(shí)間都浪費(fèi)了。Express反其道而行之,允許你在需要的時(shí)候再加入東西。靈活最小的結(jié)果就是,Express所做的事情是非常簡單的:從客戶端(可以是瀏覽器、移動(dòng)設(shè)備、另一個(gè)服務(wù)器、桌面應(yīng)用……只要能理解HTTP)接收HTTP請(qǐng)求,然后返回HTTP響應(yīng)。這個(gè)基本模式幾乎可以描述任何到互聯(lián)網(wǎng)的連接,使得Express的應(yīng)用極其靈活。Web應(yīng)用框架或許更精確的說法是“Web應(yīng)用框架的服務(wù)器端部分”。時(shí)至今日,當(dāng)你想到“Web應(yīng)用框架”這個(gè)概念的時(shí)候,想到的多半是像React、Angular或Vue這樣的單頁應(yīng)用框架。不過,除少數(shù)獨(dú)立應(yīng)用外,大多數(shù)Web應(yīng)用需要分享數(shù)據(jù)和集成其他服務(wù)。這些工作一般需要一套WebAPI來完成,這套API就可以看作Web應(yīng)用框架的服務(wù)器端組件。要注意,要構(gòu)建整個(gè)只有服務(wù)器端渲染的應(yīng)用還是可以的(有時(shí)也需要如此),這種情況下,Express就是完整的Web應(yīng)用框架。關(guān)于Express的特性,除了官方描述中明確提到的,我還想再加兩點(diǎn)。高性能隨著Express成為Node.js開發(fā)的首選Web框架,它也吸引了很多大公司的注意,這些大公司運(yùn)行著高性能、高流量的網(wǎng)站。這給Express團(tuán)隊(duì)帶來了壓力,促使他們特別關(guān)注性能。現(xiàn)在Express可以為高流量網(wǎng)站提供一流的性能。中立JavaScript生態(tài)系統(tǒng)的一個(gè)特征就是它的規(guī)模和多樣性。盡管Express常常位于Node.jsWeb開發(fā)的中心,還是有數(shù)百個(gè)(如果在讀者閱讀時(shí)還沒有達(dá)到數(shù)千個(gè)的話)來自社區(qū)的包可以用到Express應(yīng)用里。Express團(tuán)隊(duì)認(rèn)識(shí)到了這個(gè)生態(tài)系統(tǒng)的多樣性,于是提供了一個(gè)極其靈活的中間件體系,使你可以很容易地選擇組件來創(chuàng)建自己的應(yīng)用。你可以看到,在Express本身的開發(fā)過程中,它摒棄了內(nèi)建組件的做法,選擇了可配置中間件的機(jī)制。前面提到Express是Web應(yīng)用框架的“服務(wù)器端部分”,因此或許我們應(yīng)該談?wù)劮?wù)器端應(yīng)用和客戶端應(yīng)用之間的關(guān)系。1.3服務(wù)器端應(yīng)用和客戶端應(yīng)用服務(wù)器端應(yīng)用就是應(yīng)用中的頁面都在服務(wù)器上生成(如HTML、CSS、圖片和其他多媒體資源,以及JavaScript),然后發(fā)送給客戶端。而客戶端應(yīng)用的大部分用戶界面是從一個(gè)資源包渲染的,這個(gè)資源包只需要在最開始的時(shí)候接收一次。也就是說,一旦瀏覽器接收到初始的HTML(通常也非常小),它就使用JavaScript來動(dòng)態(tài)地修改DOM,不需要依靠服務(wù)器來顯示新頁面(雖然原始數(shù)據(jù)通常還是來自服務(wù)器)。在1999年之前,服務(wù)器端應(yīng)用是標(biāo)準(zhǔn)。事實(shí)上,Web應(yīng)用這個(gè)術(shù)語是在1999年正式引入的。我認(rèn)為大約1999年到2012年這段時(shí)間是Web2.0時(shí)代,在這個(gè)時(shí)期,最終成為客戶端應(yīng)用的技術(shù)正在開發(fā)之中。到2012年,隨著智能手機(jī)的穩(wěn)固發(fā)展,通過網(wǎng)絡(luò)發(fā)送盡可能少的信息成為一種普遍做法,這種做法更青睞客戶端應(yīng)用。服務(wù)器端應(yīng)用常常被稱為服務(wù)器端渲染(SSR)應(yīng)用,客戶端應(yīng)用常常被稱為單頁應(yīng)用(SPA)。客戶端應(yīng)用完全在諸如React、Angular和Vue等框架中實(shí)現(xiàn)。我總是覺得“單頁”的叫法有點(diǎn)兒不妥,因?yàn)樵谟脩艨磥盹@然有很多個(gè)頁面。唯一的區(qū)別是,頁面是服務(wù)器發(fā)過來的還是在客戶端動(dòng)態(tài)渲染的。在實(shí)際中,服務(wù)器端應(yīng)用和客戶端應(yīng)用之間有很多模糊的界線。很多客戶端應(yīng)用有2個(gè)或3個(gè)HTML資源包可以發(fā)給客戶端(例如公開可見的界面和登錄可見的界面,或普通界面和管理界面)。而且,SPA常常跟SSR結(jié)合以便提升第一頁加載的性能,這樣做也有助于搜索引擎優(yōu)化(SEO)。一般來說,如果服務(wù)器只發(fā)送少量的HTML文件(一般1至3個(gè)),而此時(shí)用戶可以獲得基于動(dòng)態(tài)DOM操作的豐富的多視圖體驗(yàn),我們就認(rèn)為這是客戶端渲染。各個(gè)視圖的數(shù)據(jù)(通常是JSON格式)和多媒體資源通常還是要從網(wǎng)絡(luò)傳過來。當(dāng)然,對(duì)Express來說,它并不怎么關(guān)心你是在開發(fā)一個(gè)服務(wù)器端應(yīng)用還是客戶端應(yīng)用,兩種角色它都能完成得很好。你要提供1個(gè)HTML資源包還是100個(gè),對(duì)它來說都一樣。目前SPA毫無疑問已經(jīng)成了統(tǒng)治性的Web應(yīng)用架構(gòu)。盡管如此,本書還是從符合服務(wù)器端應(yīng)用的示例開始。服務(wù)器端應(yīng)用還是有意義的,而且,提供1個(gè)HTML資源包還是很多HTML資源包,在概念上的差異是很小的。第16章會(huì)提供一個(gè)SPA的示例。1.4Express簡史Express的創(chuàng)造者TJHolowaychuk把Express描述為一個(gè)受Sinatra啟發(fā)的Web框架。Sinatra是一個(gè)基于Ruby的Web框架。Express借鑒一個(gè)用Ruby寫的框架并不奇怪:在Web開發(fā)方面,Ruby孵化出了大量卓越的新思路、新方法,使Web開發(fā)變得更快捷、更高效和更好維護(hù)。就像深受Sinatra啟發(fā)一樣,Express還跟Connect深度交織在一起,這是Node應(yīng)用的一個(gè)插件庫。Connect最初使用中間件這個(gè)術(shù)語來描述各種能在不同程度上處理Web請(qǐng)求的可插拔的Node模塊。2014年,4.0版本的Node移除了對(duì)Connect的依賴,不過仍然把其“中間件”概念歸功于Connect。從版本2.x到3.0,Express經(jīng)歷了大規(guī)模的重寫;從3.x到4.0,亦如此。本書關(guān)注的是版本4.0。1.5Node:另一種Web服務(wù)器某種程度上,Node跟其他幾個(gè)流行的Web服務(wù)器,如微軟的IIS或Apache,有很多共同之處。不過,更有意思的是它們究竟有哪些不同。讓我們從這里開始講起。對(duì)于Web服務(wù)器,Node的做法也是最小化,這一點(diǎn)跟Express一樣。不像IIS或Apache那樣,需要花上數(shù)年時(shí)間去掌握,Node是很容易搭建和配置的。這并不是說在生產(chǎn)環(huán)境中為達(dá)到性能最大化去調(diào)優(yōu)Node服務(wù)器是一件很簡單的事情,而是說比起那些服務(wù)器,Node的配置選項(xiàng)要簡單多了。Node跟傳統(tǒng)Web服務(wù)器的另一個(gè)顯著差異就是,Node是單線程的。乍一看去,這似乎是一步倒退。而事實(shí)上,這是明智之舉。單線程極大地簡化了Web應(yīng)用的編程,而當(dāng)需要達(dá)到多線程應(yīng)用的性能時(shí),你只需簡單地多開幾個(gè)Node實(shí)例,就能獲得多線程的好處。敏銳的讀者可能會(huì)想,這聽起來像是糊弄人呢。畢竟,通過服務(wù)器并行(跟應(yīng)用內(nèi)的并行相對(duì))實(shí)現(xiàn)的多線程,只是把復(fù)雜性轉(zhuǎn)移了,并沒有消除它呀。或許如此,但根據(jù)我的經(jīng)驗(yàn),這種做法是把復(fù)雜性轉(zhuǎn)移到了它本來就該在的地方。況且,隨著云計(jì)算越來越受歡迎,越來越多的人把服務(wù)器資源看作普通商品,這種做法就越發(fā)顯得合理了。IIS和Apache的確很強(qiáng)大,它們從設(shè)計(jì)上就是要“榨干”今天強(qiáng)大硬件的最后“一滴”性能。可這是有代價(jià)的,要達(dá)到那樣的性能,就必須有相當(dāng)專業(yè)的人員來搭建和調(diào)優(yōu)。從編程來說,Node應(yīng)用更像PHP或Ruby應(yīng)用,而不是.NET或Java應(yīng)用。雖然Node所使用的JavaScript引擎(谷歌的V8)是把JavaScript編譯成了原生機(jī)器代碼(像C或C++那樣),但這是透明地完成的1,所以從用戶的角度來看,它就像純粹解釋型語言一樣。由于沒有單獨(dú)的編譯步驟,維護(hù)和部署更省事了:你只需更新JavaScript文件,所做的變更會(huì)自動(dòng)生效。1常被稱作即時(shí)(JIT)編譯。Node應(yīng)用還有一個(gè)很吸引人的好處:Node是平臺(tái)獨(dú)立的。它不是第一項(xiàng)或唯一一項(xiàng)平臺(tái)獨(dú)立的服務(wù)器技術(shù),然而,平臺(tái)獨(dú)立遠(yuǎn)不只是二進(jìn)制包的問題。例如,因?yàn)橛辛薓ono,你可以在Linux服務(wù)器上運(yùn)行.NET應(yīng)用,但是由于文檔的缺失和系統(tǒng)不兼容,這會(huì)是一項(xiàng)痛苦的工作。同樣,你可以在Windows服務(wù)器上運(yùn)行PHP應(yīng)用,但是通常來說搭建它沒有在Linux服務(wù)器上那么容易。而在各大主流操作系統(tǒng)(Windows、macOS和Linux)上搭建Node都輕而易舉,不同操作系統(tǒng)協(xié)作起來也很容易。在各個(gè)網(wǎng)站設(shè)計(jì)小組中,混合使用PC和Mac是尋常事。某些開發(fā)平臺(tái),如.NET,就給前端開發(fā)和設(shè)計(jì)人員帶來了不少的挑戰(zhàn)(因?yàn)樗麄兂3J褂肕ac),嚴(yán)重影響了他們的協(xié)作和工作效率。只要幾分鐘(甚至幾秒鐘)就可以在任何操作系統(tǒng)上運(yùn)行起來一個(gè)能正常工作的服務(wù)器,這個(gè)美夢已經(jīng)成真了。1.6Node生態(tài)系統(tǒng)自然,Node位于這個(gè)技術(shù)棧的核心位置。正是Node,使得JavaScript可以運(yùn)行于服務(wù)器之上,脫離瀏覽器而存在,從而允許使用用JavaScript編寫的框架(如Express)。另一個(gè)重要的組件就是數(shù)據(jù)庫,這將在第13章深入討論。除非是最簡單的Web應(yīng)用,否則都需要一個(gè)數(shù)據(jù)庫。有幾種數(shù)據(jù)庫在Node中比在其他語言中還要好用。所有主要的關(guān)系型數(shù)據(jù)庫(MySQL、MariaDB、PostgreSQL、Oracle、SQLServer)都有Node的接口,這不奇怪。這些數(shù)據(jù)庫經(jīng)過多年發(fā)展已經(jīng)非常強(qiáng)大,忽略它們是愚蠢的。盡管如此,Node時(shí)代的到來,讓一種新數(shù)據(jù)庫存儲(chǔ)方式重新煥發(fā)了活力:所謂的NoSQL數(shù)據(jù)庫。把某個(gè)東西定義為不是什么,無助于人們理解,所以我們補(bǔ)充一下,這些NoSQL數(shù)據(jù)庫或許叫作“文檔數(shù)據(jù)庫”或“鍵值對(duì)數(shù)據(jù)庫”更為合適。對(duì)于數(shù)據(jù)存儲(chǔ),它們提供了一種概念上更簡單的方法。這類NoSQL數(shù)據(jù)庫有很多,而MongoDB是它們的領(lǐng)跑者,所以我們將MongoDB作為本書的NoSQL數(shù)據(jù)庫。構(gòu)建一個(gè)實(shí)用的網(wǎng)站需要依賴多個(gè)技術(shù)板塊,因此對(duì)于網(wǎng)站所基于的技術(shù)棧,人們想出了各種縮寫詞來描畫它們。例如,Linux、Apache、MySQL和PHP的組合就被叫作LAMP技術(shù)棧。MongoDB的一位工程師ValeriKarpov造了一個(gè)縮寫詞MEAN:Mongo、Express、Angular和Node。它很易記,這是肯定的,但也有局限:對(duì)于數(shù)據(jù)庫和應(yīng)用框架來說,有太多的選擇了,“MEAN”并不能體現(xiàn)這個(gè)生態(tài)的多樣性(何況它沒有把渲染引擎包含進(jìn)來,這是我認(rèn)為很重要的一個(gè)組件)。想出一個(gè)具有包容性的縮寫詞是一項(xiàng)有意思的練習(xí)。當(dāng)然,其中不可或缺的一個(gè)組件就是Node。盡管還有其他的服務(wù)器端JavaScript容器,但Node自問世以來便統(tǒng)治了一切。Express同樣不是唯一可用的Web應(yīng)用框架,但它的統(tǒng)治力也接近Node了。對(duì)Web應(yīng)用開發(fā)來說,還有兩個(gè)常常至關(guān)重要的組件,就是數(shù)據(jù)庫服務(wù)器和渲染引擎(或者是模板引擎,如Handlebars;或者是SPA框架,如React)。對(duì)于這兩者,沒有那么多明顯的領(lǐng)跑者,我認(rèn)為進(jìn)行限制是有害無益的。把所有這些技術(shù)捆綁到一起的,就是JavaScript。所以為了更具包容性,我會(huì)把它叫作JavaScript技術(shù)棧。就本書來說,它指的是Node、Express和MongoDB(第13章還有關(guān)系型數(shù)據(jù)庫的示例)。1.7開源協(xié)議開發(fā)Node應(yīng)用的時(shí)候,你可能會(huì)發(fā)現(xiàn),自己必須要比以前更加留心開源協(xié)議(我當(dāng)然也是)。Node生態(tài)系統(tǒng)的一個(gè)美妙之處就是有大量的包可用。可是,每個(gè)包都有自己的授權(quán)方式,更麻煩的是,每個(gè)包都可能依賴一些其他的包,這意味著要理解應(yīng)用各個(gè)部分的授權(quán)方式會(huì)十分困難。好在有一些好消息。MIT協(xié)議是Node包用得最多的協(xié)議之一。這是一個(gè)非常寬松的協(xié)議,你幾乎可以做任何事,包括把這個(gè)包用到閉源軟件里。不過你不能設(shè)想你用的每個(gè)包都采用MIT協(xié)議。在npm中有好幾個(gè)包,可以嘗試找出你的項(xiàng)目中每個(gè)依賴的授權(quán)方式。請(qǐng)?jiān)趎pm中搜索nlf或license-report。除了MIT這個(gè)最常見的協(xié)議,你可能還會(huì)遇到以下開源協(xié)議。GNU通用公共許可(GPL)GPL是一個(gè)很受歡迎的開源協(xié)議,為了保持軟件的開源,它經(jīng)過了精心設(shè)計(jì)。這意味著如果你在項(xiàng)目中使用了GPL授權(quán)的代碼,你的項(xiàng)目必須也要以GPL來授權(quán)。自然,你的項(xiàng)目就不能是閉源的。Apache2.0像MIT一樣,這個(gè)協(xié)議允許你的項(xiàng)目采用一種不同的協(xié)議,包括閉源的協(xié)議。不過對(duì)于那些采用Apache2.0許可的組件,你必須在項(xiàng)目協(xié)議中把它們的授權(quán)聲明都包含進(jìn)來。BSD許可類似于Apache許可,這個(gè)協(xié)議允許你的項(xiàng)目采用任何你想用的協(xié)議,只要你把采用BSD許可的組件的授權(quán)聲明都包含進(jìn)來。有時(shí)候軟件是雙重許可的(按兩種不同的協(xié)議來授權(quán))。這樣做的一個(gè)常見原因是,讓軟件既可以用在GPL項(xiàng)目中,也可以用在采用更寬松許可的項(xiàng)目中(如果一個(gè)組件要用到GPL軟件中,那么這個(gè)組件必須是GPL授權(quán)的)。在我自己的項(xiàng)目中,我經(jīng)常采用這種授權(quán)模式:GPL和MIT的雙重許可。最后,如果你在寫自己的包,你應(yīng)該做一個(gè)社區(qū)的好公民,選擇合適的協(xié)議,并進(jìn)行適當(dāng)?shù)恼f明。對(duì)開發(fā)者來說,在使用別人的包的時(shí)候,如果需要深挖代碼以便確定所采用的協(xié)議,甚至找了半天卻發(fā)現(xiàn)根本沒有采用許可協(xié)議,就太讓人沮喪了。1.8小結(jié)對(duì)于Express是什么以及它在更大的Node和JavaScript生態(tài)系統(tǒng)中處于什么位置,希望本章讓你有了更深入的理解。同時(shí),希望本章也闡明了服務(wù)器端Web應(yīng)用和客戶端Web應(yīng)用的關(guān)系。對(duì)于Express確切來說是什么,如果你仍舊感到迷惑,也不用擔(dān)心:有時(shí)候直接上手使用一個(gè)東西更有助于理解它。本書會(huì)幫助你使用Express來構(gòu)建Web應(yīng)用。不過在使用Express之前,在下一章我們先游覽一番Node的世界。要理解Express是如何工作的,這是很重要的一步。第2章Node的世界如果你還沒有Node開發(fā)經(jīng)驗(yàn),本章就是為你寫的。要理解Express及其用處,首先要對(duì)Node有基本的理解。如果你已經(jīng)有使用Node構(gòu)建Web應(yīng)用的經(jīng)驗(yàn),則可以放心地跳過本章。本章將使用Node構(gòu)建一個(gè)最小的Web應(yīng)用,下一章將使用Express來實(shí)現(xiàn)同樣的構(gòu)建。2.1獲取Node將Node安裝到系統(tǒng)上再容易不過了。為確保所有主要系統(tǒng)中的安裝過程都簡單直接,Node團(tuán)隊(duì)已經(jīng)付出了很多努力。打開Node官網(wǎng),點(diǎn)擊寫著版本號(hào)及“LTS(RecommendedforMostUsers)”的綠色大按鈕。LTS代表長期支持(Long-TermSupport),某種程度上比Current版本更穩(wěn)定,后者包含更多的最新特性和性能提升。對(duì)于Windows和macOS用戶來說,點(diǎn)擊上述按鈕就可以下載安裝程序,內(nèi)有安裝指導(dǎo)。而對(duì)于Linux用戶來說,使用包管理器安裝和運(yùn)行或許會(huì)更快。如果你是想要使用包管理器的Linux用戶,確保遵循上述網(wǎng)頁中的步驟。不增加合適的包倉庫配置的話,很多Linux發(fā)行版安裝的將是非常老的Node版本。如果你想在組織內(nèi)分發(fā)Node,也可以下載一個(gè)獨(dú)立安裝程序。2.2使用終端我是那種迷戀于終端(也叫控制臺(tái)或命令行)的威力和生產(chǎn)率而無法自拔的人。本書中的所有示例都假設(shè)你使用終端。如果還不熟悉所選用的終端,強(qiáng)烈建議你花一點(diǎn)兒時(shí)間熟悉一下。本書使用的很多工具有相應(yīng)的GUI,如果你實(shí)在不想使用終端,也可以選擇別的,但就得自己想辦法了。如果你使用的是macOS或Linux,那么就會(huì)有好多可貴的shell(終端的命令解釋器)可供選擇。目前為止最流行的是bash,不過zsh也有很多擁護(hù)者。我更青睞bash的主要原因(除了長期的“親密接觸”外)是它的無處不在。如果你坐到一臺(tái)裝有Unix系統(tǒng)的計(jì)算機(jī)前,99%的情況下,默認(rèn)shell是bash。如果你是Windows用戶,事情就沒有那么美好了。因?yàn)槲④洀膩頉]有對(duì)提供一種舒服的終端體驗(yàn)上過心,所以你得自己做些工作。Git體貼地附帶了一個(gè)叫“Gitbash”的shell,提供了類似Unix的終端體驗(yàn)(雖然它只包含Unix上可用命令行工具的一個(gè)小子集,但很有用)。不過,Gitbash提供的是一個(gè)最小的bashshell,仍然要使用Windows內(nèi)建的控制臺(tái)應(yīng)用,所以有時(shí)會(huì)給你帶來一些麻煩(即便是像調(diào)整窗口大小、選擇文本、剪切和復(fù)制這樣簡單的功能,用起來都有些別扭)。為此,推薦安裝一個(gè)更高級(jí)的終端,例如ConsoleZ或ConEmu。對(duì)于Windows高級(jí)用戶,尤其是.NET開發(fā)者以及系統(tǒng)管理員和網(wǎng)絡(luò)管理員,還有另一種選擇:使用微軟自己的PowerShell。PowerShell名副其實(shí),它可以把工作完成得非常出色,一個(gè)熟練的PowerShell用戶完全不輸于一個(gè)Unix命令行老手。不過,如果要在macOS/Linux和Windows之間切換,建議你還是使用比較通用的Gitbash。如果你使用的是Windows10或更新版本,則可以直接在Windows上安裝UbuntuLinux。UbuntuLinux不是雙系統(tǒng)或虛擬化的結(jié)果,而是微軟開源團(tuán)隊(duì)所做的卓越工作,給Windows帶來了Linux的體驗(yàn)。可以通過微軟應(yīng)用商店安裝Ubuntu。Windows用戶的最后選擇是虛擬化。得益于現(xiàn)代計(jì)算機(jī)的強(qiáng)大性能和體系結(jié)構(gòu),現(xiàn)在虛擬機(jī)(VM)的性能跟真實(shí)機(jī)器相差無幾。我就幸運(yùn)地遇到過免費(fèi)的OracleVirtualBox。最后,無論在什么系統(tǒng)中,都可以使用非常好用的云平臺(tái)開發(fā)環(huán)境,比如Cloud9(現(xiàn)在是AWS的一個(gè)產(chǎn)品)。Cloud9會(huì)給你啟動(dòng)一個(gè)新的Node開發(fā)環(huán)境,讓你可以快速上手Node。選定了一個(gè)覺得很舒服的shell之后,建議你花點(diǎn)兒時(shí)間了解它的基本用法。網(wǎng)上有很多非常好的課程材料(“TheBashGuide”是很好的新手指導(dǎo))。現(xiàn)在學(xué)一點(diǎn)兒可以為將來免去很多麻煩。至少,你應(yīng)該知道如何切換目錄,如何復(fù)制、移動(dòng)和刪除文件,以及如何中斷一個(gè)命令行程序(通常是Ctrl-C)。如果想成為一位終端“忍者”,建議你學(xué)一下如何搜索文件中的文本、如何搜索文件和目錄、如何把多個(gè)命令鏈接起來(傳說中的“UNIX哲學(xué)”),以及如何重定向輸出。在很多類Unix系統(tǒng)中,Ctrl-S有特別的含義——它會(huì)“凍結(jié)”終端(因此曾被用來暫停快速滾動(dòng)的輸出)。由于它也是常見的“保存”快捷鍵,因此用戶很容易不假思索地按下去,從而導(dǎo)致很多人很困惑(雖然不想承認(rèn),但這種事情經(jīng)常發(fā)生在我身上)。要想從“凍結(jié)”中恢復(fù),只需按下Ctrl-Q。因此,如果你的終端好像突然凍結(jié)了,你也沒明白怎么回事,試著按下Ctrl-Q看看能不能恢復(fù)。2.3編輯器在程序員中,很少有話題能像編輯器的選擇那樣引發(fā)激烈的爭論。這很合理,畢竟編輯器是你首要的工具。我選擇的編輯器是vi(或支持vi模式的編輯器)。1vi未必適合所有人(每當(dāng)我對(duì)同事們說,要是我用vi來完成他們的工作會(huì)多么輕而易舉,他們總會(huì)沖我翻白眼),但尋找一款強(qiáng)大的編輯器并學(xué)會(huì)使用它,可以大幅提升你的工作效率,而且我敢說,這樣也可以大大增加工作的樂趣。我特別喜歡vi的一個(gè)原因(雖然不是最重要的原因)是,和bash一樣,它無處不在。只要能使用Unix系統(tǒng),就能使用vi。很多流行的編輯器有“vi模式”,可以讓你使用vi的鍵盤命令。一旦你習(xí)慣了vi,就很難再去使用別的編輯器了。雖然vi剛用起來有些難,但為其付出努力是值得的。1現(xiàn)今,vi基本上成了vim的同義詞。在大部分系統(tǒng)中,命令vi是vim的別名,但我一般都輸入vim,以確保用的是vim。如果你像我一樣,看到了熟悉一個(gè)無處不在的編輯器的好處,還可以選擇Emacs。雖然我用不慣Emacs(人們一般要么習(xí)慣使用Emacs,要么習(xí)慣使用vi),但對(duì)于它的強(qiáng)大和靈活,我絕對(duì)是信服的。如果vi的分模式編輯方式不適合你,推薦你仔細(xì)看看Emacs。盡管掌握一個(gè)控制臺(tái)編輯器(例如vi或Emacs)會(huì)非常方便,你可能還是想要掌握一個(gè)更現(xiàn)代的編輯器。一個(gè)流行的選擇是VisualStudioCode(不要跟不帶Code的“VisualStudio”混淆)。誠心推薦VisualStudioCode,它設(shè)計(jì)得非常好:運(yùn)行快速、工作效率高,完全適合Node和JavaScript開發(fā)。另一個(gè)流行的選擇是Atom,它在JavaScript社區(qū)也很受歡迎。這兩個(gè)編輯器在Windows、macOS和Linux中均可免費(fèi)使用,而且都支持vi模式。現(xiàn)在有了編輯代碼的好工具,讓我們把注意力轉(zhuǎn)移到npm上來。npm可以幫助我們獲取別人寫好的包,這樣就可以利用這個(gè)龐大而活躍的JavaScript社區(qū)了。2.4npmnpm就是那個(gè)時(shí)常見到的管理Node包的包管理器(也用來獲取和安裝Express)。npm繼承了PHP、GNU、WINE等的幽默傳統(tǒng),它不是一個(gè)首字母縮寫詞(這就是它不用大寫的原因),相反,它是“npmisnotanacronym”(npm不是縮寫)的遞歸縮寫。大體來說,包管理器的兩個(gè)主要職責(zé)就是安裝包和管理包依賴關(guān)系。npm是一個(gè)快速、能干、易用的包管理器,Node生態(tài)能發(fā)展得如此迅速和多姿多彩,我想其中有npm很大的功勞。另外還有一個(gè)很流行的相競爭的包管理器叫Yarn,它跟npm使用同一個(gè)包數(shù)據(jù)庫。我們將在第16章使用Yarn。如果你是照著先前的步驟安裝Node,npm就會(huì)一起安裝了,因此,你已經(jīng)獲得npm了。現(xiàn)在可以開始工作了。你在npm中使用的主要命令將是install(一點(diǎn)兒也不奇怪)。比如說要安裝nodemon(一個(gè)流行的工具,用于修改代碼時(shí)自動(dòng)重啟Node應(yīng)用),你就在控制臺(tái)中運(yùn)行以下命令:npminstall-gnodemon-g參數(shù)告訴npm,這個(gè)包要全局安裝,意味著在系統(tǒng)中全局可用。等到討論package.json文件的時(shí)候,這個(gè)差別就會(huì)更清楚了。就現(xiàn)在來說,經(jīng)驗(yàn)法則是JavaScript工具(例如nodemon)一般應(yīng)該全局安裝,而那些特定于Web應(yīng)用或項(xiàng)目的包就不需要這樣了。不像Python等語言(Python從2.0到3.0經(jīng)歷了大變更,需要一種辦法來為切換環(huán)境提供便利),Node平臺(tái)足夠新,很可能你運(yùn)行的就是最新版的Node。不過如果你覺得需要支持多版本的Node,可以看看nvm或n,它們支持切換環(huán)境。想知道你的計(jì)算機(jī)上安裝的是什么版本的Node,鍵入node--version即可。2.5用Node做一個(gè)簡單的Web服務(wù)器如果你曾搭建過靜態(tài)HTML網(wǎng)站,或者有PHP或ASP背景,很可能熟悉Web服務(wù)器(例如Apache或IIS)這個(gè)概念。Web服務(wù)器提供靜態(tài)文件服務(wù),以便瀏覽器通過網(wǎng)絡(luò)讀取。例如,如果你創(chuàng)建了文件about.html并將其放到合適的目錄,那么在瀏覽器中就可以導(dǎo)航到http://localhost/about.html。取決于你的Web服務(wù)器配置,你或許能夠省去.html,不過URL和文件名之間的關(guān)系是清楚的:Web服務(wù)器知道這個(gè)文件在計(jì)算機(jī)上的哪個(gè)位置,然后提供給瀏覽器。正如其名,localhost指的是你所使用的計(jì)算機(jī)。這是IPv4回送地址或IPv6回送地址::1的常見別名。雖然很常見,但本書中將使用localhost。如果你在使用遠(yuǎn)程計(jì)算機(jī)(例如正在使用SSH),要記得瀏覽localhost時(shí)不會(huì)連到那臺(tái)計(jì)算機(jī)上。比起傳統(tǒng)的Web服務(wù)器,Node提供了一種不同的范式:你所寫的應(yīng)用,其本身就是Web服務(wù)器。Node只是給你提供了構(gòu)建Web服務(wù)器的框架。你可能會(huì)說:“可是,我不想寫一個(gè)Web服務(wù)器。”這是很自然的反應(yīng):你只是想寫一個(gè)應(yīng)用,而不是Web服務(wù)器。但是,Node使得寫Web服務(wù)器變成了很簡單的事情(甚至只需幾行代碼),而你也獲得了對(duì)應(yīng)用的控制,兩全其美。所以就這么干吧。你已經(jīng)安裝了Node,熟悉了終端,現(xiàn)在可以繼續(xù)了。2.5.1Helloworld常見的編程入門示例都是平平無奇的“Helloworld”,我時(shí)常為此感到遺憾。不過要是悍然挑戰(zhàn)這一沉悶的傳統(tǒng),未免顯得十分不敬,因此我們還是先這樣開始,之后再創(chuàng)建更有趣的東西。在你喜歡的編輯器里,創(chuàng)建一個(gè)文件helloworld.js(版本庫的ch02/00-helloworld.js):consthttp=require('http')constport=process.env.PORT||3000constserver=http.createServer((req,res)=>{res.writeHead(200,{'Content-Type':'text/plain'})res.end('Helloworld!')})server.listen(port,()=>console.log(`serverstartedonport${port};`+'pressCtrl-Ctoterminate'))跟學(xué)習(xí)JavaScript的經(jīng)歷有關(guān),你可能會(huì)對(duì)示例代碼中缺少了分號(hào)感到不安。我曾是分號(hào)的“頑固分子”,后來做了一些React開發(fā)后才勉強(qiáng)不再使用分號(hào),畢竟React開發(fā)的習(xí)慣就是不用分號(hào)。不久之后,我終于想開了,奇怪為何自己過去對(duì)分號(hào)那么執(zhí)著。現(xiàn)在我是堅(jiān)定的“無分號(hào)”陣營的一員,書中的示例代碼也會(huì)體現(xiàn)這一點(diǎn)。這是個(gè)人選擇,如果你想用分號(hào),盡管用。確認(rèn)你位于helloworld.js所在的目錄下,然后輸入nodehelloworld.js。打開瀏覽器,導(dǎo)航到http://localhost:3000。好啦!這就是你的第一個(gè)Web服務(wù)器。這個(gè)服務(wù)器并沒有提供HTML,它只是在瀏覽器上顯示文本消息“Helloworld!”。如果你愿意,可以體驗(yàn)一下發(fā)送HTML:只需把text/plain改為text/html,并把'Helloworld!'改為合法的HTML字符串。不過,我沒有那么演示,因?yàn)槲以噲D避免在JavaScript里寫HTML,至于其中的原因,第7章會(huì)詳細(xì)討論。2.5.2事件驅(qū)動(dòng)編程N(yùn)ode背后的核心哲學(xué)就是事件驅(qū)動(dòng)編程。對(duì)你(程序員)就意味著,必須要知悉有什么事件可能會(huì)發(fā)生以及如何響應(yīng)。很多人是通過實(shí)現(xiàn)用戶界面才第一次認(rèn)識(shí)事件驅(qū)動(dòng)編程的:用戶點(diǎn)擊了某個(gè)東西,然后你處理一個(gè)點(diǎn)擊事件。事件驅(qū)動(dòng)編程是很好的比喻,因?yàn)楹茱@然程序員沒法控制用戶是否點(diǎn)擊以及何時(shí)點(diǎn)擊一個(gè)東西,因此,事件驅(qū)動(dòng)編程是非常直觀的。把這個(gè)概念推廣到服務(wù)器上,會(huì)略微有些難以理解,但基本原理一樣。在前面的示例代碼中,事件是隱含的:正在處理的事件就是一個(gè)HTTP請(qǐng)求。http.createServer方法需要一個(gè)函數(shù)作為參數(shù),每次創(chuàng)建新的HTTP請(qǐng)求時(shí),就會(huì)調(diào)用這個(gè)函數(shù)。簡單示例只是把內(nèi)容類型設(shè)置為普通文本,然后發(fā)送字符串“Helloworld!”。一旦你開始從事件驅(qū)動(dòng)編程的角度思考,就會(huì)發(fā)現(xiàn)到處都是事件。用戶使用你的應(yīng)用時(shí),從一頁導(dǎo)航到另一頁,或從一塊內(nèi)容導(dǎo)航到另一塊內(nèi)容,就是一個(gè)事件。你的應(yīng)用如何響應(yīng)這樣的導(dǎo)航事件,就叫作路由。2.5.3路由路由指的是對(duì)客戶端內(nèi)容請(qǐng)求的滿足機(jī)制。對(duì)基于Web的客戶端/服務(wù)器端應(yīng)用,客戶端在URL中指定了想要的內(nèi)容,具體來講就是URL中的路徑和查詢串(第6章將更詳細(xì)地討論URL的各個(gè)部分)。通常,服務(wù)器端的路由依賴于路徑和查詢串,但還有別的信息可以使用:HTTP頭、域名、IP地址,等等。這使得服務(wù)器端可以把更多的因素納入考慮,例如用戶的大概位置,或是用戶首選的語言。下面對(duì)前面的“Helloworld!”做一下擴(kuò)展,讓它做點(diǎn)兒更有意思的事情。做一個(gè)只包含主頁、“關(guān)于”頁和“頁面未找到”頁的小網(wǎng)站。繼續(xù)前面的例子,仍然只發(fā)送普通文本而不是HTML(版本庫的ch02/01-helloworld.js):consthttp=require('http')constport=process.env.PORT||3000constserver=http.createServer((req,res)=>{//對(duì)URL做規(guī)范化處理:移除查詢串、末尾可選的斜杠,并轉(zhuǎn)成小寫constpath=req.url.replace(/\/?(?:\?.**)?$/,'').toLowerCase()switch(path){case'':res.writeHead(200,{'Content-Type':'text/plain'})res.end('Homepage')breakcase'/about':res.writeHead(200,{'Content-Type':'text/plain'})res.end('About')breakdefault:res.writeHead(404,{'Content-Type':'text/plain'})res.end('NotFound')break}})server.listen(port,()=>console.log(`serverstartedonport${port};`+'pressCtrl-Ctoterminate'))把它運(yùn)行起來,你會(huì)發(fā)現(xiàn)現(xiàn)在可以瀏覽主頁(http://localhost:3000)和“關(guān)于”頁(http://localhost:3000/about)了。任何查詢串都會(huì)被忽略(因此http://localhost:3000/?foo=bar會(huì)轉(zhuǎn)到主頁),任何其他的URL(http://localhost:3000/foo)都會(huì)轉(zhuǎn)到“頁面未找到”頁。2.5.4提供靜態(tài)資源現(xiàn)在我們的簡單路由已經(jīng)起作用了,再來提供一些實(shí)際的HTML和一個(gè)logo圖片。這些被稱作靜態(tài)資源,因?yàn)樗鼈円话悴粫?huì)變化(與此相反的例子是股票行情頁——每次你刷新頁面,股票價(jià)格都可能會(huì)變化)。由Node來提供靜態(tài)資源,適用于開發(fā)階段和小項(xiàng)目,而對(duì)于稍大一些的項(xiàng)目,你可能希望使用代理服務(wù)器,例如NGINX或CDN。更多信息參見第17章。如果你用過Apache或IIS,只要?jiǎng)?chuàng)建一個(gè)HTML文件,然后從瀏覽器上訪問它,文件就會(huì)自動(dòng)發(fā)過來,可能你已經(jīng)習(xí)慣了這種方式。但Node的工作模式不是這樣的,像打開文件、讀取文件、把內(nèi)容發(fā)送到瀏覽器這些工作,必須我們自己來做。在項(xiàng)目下創(chuàng)建一個(gè)名為public的目錄(為什么不稱其為static,下一章將解釋)。在public目錄下,創(chuàng)建home.html、about.html、404.html、一個(gè)名為img的子目錄,以及一個(gè)名為img/logo.png的圖片。我把這些留給你來做,你在閱讀本書的時(shí)候應(yīng)該已經(jīng)知道如何編寫HTML文件和找圖片了。在HTML文件中,這樣來引入logo:<imgsrc="/img/logo.png"alt="logo">。現(xiàn)在修改一下helloworld.js(版本庫的ch02/02-helloworld.js):consthttp=require('http')constfs=require('fs')constport=process.env.PORT||3000functionserveStaticFile(res,path,contentType,responseCode=200){fs.readFile(__dirname+path,(err,data)=>{if(err){res.writeHead(500,{'Content-Type':'text/plain'})returnres.end('500-InternalError')}res.writeHead(responseCode,{'Content-Type':contentType})res.end(data)})}constserver=http.createServer((req,res)=>{//對(duì)URL做規(guī)范化處理:去除查詢串、末尾可選的斜杠,并轉(zhuǎn)成小寫constpath=req.url.replace(/\/?(?:\?.**)?$/,'').toLowerCase()switch(path){case'':serveStaticFile(res,'/public/home.html','text/html')breakcase'/about':serveStaticFile(res,'/public/about.html','text/html')breakcase'/img/logo.png':serveStaticFile(res,'/public/img/logo.png','image/png')breakdefault:serveStaticFile(res,'/public/404.html','text/html',404)break}})server.listen(port,()=>console.log(`serverstartedonport${port};`+'pressCtrl-Ctoterminate'))在這個(gè)例子中,我們的路由處理還比較缺乏想象力。如果你導(dǎo)航到http://localhost:3000/about,會(huì)得到public/about.html文件。你可以把路徑改成任何想要的樣子,也可以把文件換成任何想要的文件。舉個(gè)例子,如果對(duì)于一周中的每一天,你都有一個(gè)不同的“關(guān)于”頁,那么就會(huì)有這些文件:public/about_mon.html、public/about_tue.html,等等。然后你在路由中增加一些邏輯,這樣當(dāng)用戶訪問http://localhost:3000/about時(shí),就可以提供合適的HTML文件了。注意,我們創(chuàng)建了一個(gè)輔助函數(shù)serveStaticFile,它做了大量的工作。fs.readFile是一個(gè)讀取文件的異步方法,雖然它也有一個(gè)同步的版本fs.readFileSync,但我們最好還是早點(diǎn)兒以異步的方式來思考問題。fs.readFile函數(shù)采用了一種稱為回調(diào)的模式。你要提供一個(gè)叫回調(diào)函數(shù)的函數(shù),當(dāng)工作一完成,這個(gè)回調(diào)函數(shù)就會(huì)調(diào)用(所以叫“回調(diào)”)。在這里,fs.readFile讀取指定文件的內(nèi)容,讀完以后就執(zhí)行這個(gè)回調(diào)函數(shù)。如果該文件不存在或者讀取時(shí)出現(xiàn)權(quán)限問題,就會(huì)調(diào)用err這個(gè)變量,然后這個(gè)回調(diào)函數(shù)會(huì)向客戶端返回HTTP狀態(tài)碼500,指示一個(gè)服務(wù)器端錯(cuò)誤。如果能夠成功讀取文件,就把這個(gè)文件發(fā)送到客戶端,帶上指定的響應(yīng)碼和內(nèi)容類型。關(guān)于響應(yīng)碼的詳細(xì)信息,參見第6章。__dirname會(huì)被解析為當(dāng)前腳本所在的目錄。因此如果腳本位于/home/sites/app.js,__dirname將被解析為/home/sites。如果有可能,最好使用這個(gè)順手的全局變量。否則,如果從一個(gè)不同的目錄運(yùn)行應(yīng)用,就會(huì)出現(xiàn)難以診斷的錯(cuò)誤。2.6進(jìn)入Express目前為止,Node可能還沒有給你帶來多大的震撼,基本上只是重復(fù)了Apache或IIS為你自動(dòng)完成的工作。不過,現(xiàn)在你對(duì)Node的工作方式以及如何控制它,應(yīng)該有了更深入的理解。雖然我們還沒有做出特別震撼的東西,但是你應(yīng)該能看到,可以以此為起點(diǎn),做一些更高級(jí)的東西。如果繼續(xù)這么做下去,寫出越來越高級(jí)的Node應(yīng)用,到最后很可能你得到的東西跟Express很相像……幸運(yùn)的是,不需要那么做:Express已經(jīng)存在了,你不再需要花大量時(shí)間去實(shí)現(xiàn)各種基礎(chǔ)設(shè)施。現(xiàn)在我們已經(jīng)獲得了一些Node的經(jīng)驗(yàn),可以開始學(xué)習(xí)Express了。第3章Express的方式前一章學(xué)習(xí)了如何僅用Node來創(chuàng)建一個(gè)簡單的Web服務(wù)器。本章將使用Express來重新創(chuàng)建該服務(wù)器,這一方面可以為你介紹Express的基礎(chǔ)知識(shí),另一方面也為本書的后續(xù)內(nèi)容提供了一個(gè)起點(diǎn)。3.1腳手架腳手架不是什么新想法,但很多人(包括我自己)是首先通過Ruby接觸到這個(gè)概念的。腳手架這個(gè)想法很簡單:大部分項(xiàng)目需要某些所謂的樣板代碼,而誰會(huì)愿意每開始一個(gè)項(xiàng)目就重寫一遍那些代碼呢?一個(gè)簡單的辦法就是創(chuàng)建一個(gè)項(xiàng)目的框架,每次你需要一個(gè)新項(xiàng)目時(shí),就復(fù)制這個(gè)框架(或叫模板)。RubyOnRails在這個(gè)概念的基礎(chǔ)上更進(jìn)一步,它提供了一個(gè)程序,可以自動(dòng)生成腳手架代碼。比起從一組模板中選擇,這種做法可以生成更高級(jí)的框架,這就是它的優(yōu)勢。Express借鑒了RubyOnRails,它提供了一個(gè)工具來生成腳手架,幫助你開始一個(gè)Express項(xiàng)目。雖然Express腳手架工具很有用,但我認(rèn)為學(xué)習(xí)如何從頭搭建一個(gè)Express項(xiàng)目也很有價(jià)值。除了可以學(xué)習(xí)更多東西,你也可以對(duì)項(xiàng)目有更多的控制,包括要安裝什么包以及采用什么樣的目錄結(jié)構(gòu)。此外,Express的腳手架工具更適合服務(wù)器端HTML的生成,跟API和單頁應(yīng)用關(guān)系不大。雖然我們不使用腳手架工具,但是我鼓勵(lì)你在讀完本書后去看一看它。到那時(shí)你已經(jīng)掌握了各種必備的知識(shí)和技能,可以評(píng)估腳手架工具所生成的腳手架對(duì)你是否有用了。要了解更多信息,請(qǐng)查看express-generator的文檔。3.2草地鷚旅游網(wǎng)站貫穿本書,我們將使用一個(gè)持續(xù)更新的示例網(wǎng)站:為草地鷚旅游公司(MeadowlarkTravel)而做的一個(gè)假想的網(wǎng)站,這個(gè)公司是為到俄勒岡州旅行的人提供服務(wù)的。如果你對(duì)創(chuàng)建API更感興趣,不用擔(dān)心:這個(gè)網(wǎng)站除了提供功能正常的網(wǎng)頁之外,還會(huì)暴露一套API。3.3初始工作先創(chuàng)建一個(gè)新目錄,這將是你的項(xiàng)目的根目錄。在本書中,每當(dāng)說項(xiàng)目目錄、應(yīng)用目錄或應(yīng)用根目錄,都是指這個(gè)目錄。你可能想讓W(xué)eb應(yīng)用的文件跟通常屬于項(xiàng)目的其他文件(例如會(huì)議筆記、文檔等)分離。為此,推薦你把應(yīng)用目錄作為項(xiàng)目目錄的子目錄。例如,對(duì)于草地鷚旅游網(wǎng)站,我會(huì)把項(xiàng)目放在~/projects/meadowlark中,把應(yīng)用放在~/projects/meadowlark/site中。npm在一個(gè)名為package.json的文件里管理項(xiàng)目的依賴關(guān)系以及項(xiàng)目的元數(shù)據(jù)。創(chuàng)建這個(gè)文件的最簡單方式是運(yùn)行npminit:它會(huì)問你一系列問題,然后生成package.json文件以開始項(xiàng)目(對(duì)于“entrypoint”這個(gè)提問,按照項(xiàng)目名,使用meadowlark.js)。每次運(yùn)行npm,你可能都會(huì)收到一條警告信息,提示缺少項(xiàng)目描述或版本庫字段。忽略這些警告是沒問題的,不過如果你想消除它們,則需要編輯package.json文件,填上npm提示缺少的字段值。關(guān)于此文件各個(gè)字段的更多信息,請(qǐng)查看npm的package.json文檔。首先安裝Express。運(yùn)行以下npm命令:npminstallexpress運(yùn)行npminstall會(huì)把指定的包安裝到node_modules目錄下,并更新package.json文件。既然任何時(shí)候npm都可以重新生成node_modules目錄,那么就不應(yīng)該把這個(gè)目錄保存進(jìn)版本倉庫。為了確保不會(huì)無意地把node_modules目錄加進(jìn)版本倉庫,創(chuàng)建一個(gè)名為.gitignore的文件:#ignorepackagesinstalledbynpmnode_modules#putanyotherfilesyoudon'twanttocheckinhere,suchas.DS_Store#(OSX),*.bak,etc.接著創(chuàng)建一個(gè)名為meadowlark.js的文件。這將是項(xiàng)目的入口點(diǎn)(entrypoint)。本書中簡單地把這個(gè)文件叫作應(yīng)用文件(版本庫的ch03/00-meadowlark.js):constexpress=require('express')constapp=express()constport=process.env.PORT||3000//定制404頁app.use((req,res)=>{res.type('text/plain')res.status(404)res.send('404-NotFound')})//定制500頁app.use((err,req,res,next)=>{console.error(err.message)res.type('text/plain')res.status(500)res.send('500-ServerError')})app.listen(port,()=>console.log(`Expressstartedonhttp://localhost:${port};`+`pressCtrl-Ctoterminate.`))很多教程以及Express腳手架生成器推薦把主文件命名為app.js(有時(shí)候是index.js或server.js)。除非在使用某個(gè)托管服務(wù)或部署系統(tǒng),其要求你的項(xiàng)目主文件使用特定的文件名,否則我覺得沒有必要如此命名,我更喜歡按項(xiàng)目名來命名這個(gè)主文件。在編輯器中寫代碼時(shí),如果誰有過盯著一排標(biāo)簽頁看而它們?nèi)冀小癷ndex.html”的經(jīng)歷,那他就會(huì)立刻明白我的做法有多么明智。npminit命令默認(rèn)是index.js,如果你的應(yīng)用文件使用不同的文件名,請(qǐng)確保更新package.json文件的main屬性。現(xiàn)在你已經(jīng)有了一個(gè)小的Express服務(wù)器。你可以啟動(dòng)服務(wù)器(nodemeadowlark.js),在瀏覽器中打開http://localhost:3000。結(jié)果可能會(huì)讓你失望:你還沒有給Express提供路由,因此它只會(huì)給你一條泛泛的404信息,指示頁面不存在。注意我們是如何選擇想讓應(yīng)用運(yùn)行的端口的:constport=process.env.PORT||3000。這樣就可以在啟動(dòng)服務(wù)器之前先設(shè)置環(huán)境變量,覆蓋3000端口。如果在你啟動(dòng)之后,應(yīng)用沒有在3000端口上運(yùn)行,檢查看看是否已經(jīng)設(shè)置了PORT環(huán)境變量。讓我們給主頁和“關(guān)于”頁加上一些路由。在404處理函數(shù)前,增加兩個(gè)路由(版本庫的ch03/01-meadowlark.js):app.get('/',(req,res)=>{res.type('text/plain')res.send('MeadowlarkTravel');})app.get('/about',(req,res)=>{res.type('text/plain')res.send('AboutMeadowlarkTravel')})//定制404頁app.use((req,res)=>{res.type('text/plain')res.status(404)res.send('404-NotFound')})app.get就是用來增加路由的方法。在Express文檔中,你會(huì)看到app.METHOD。并不是說實(shí)際存在一個(gè)名為METHOD的方法,它只是你的(小寫形式的)HTTP動(dòng)詞(get和post最常見)的占位符。這個(gè)方法接收兩個(gè)參數(shù):一個(gè)路徑和一個(gè)函數(shù)。這個(gè)路徑就界定了一個(gè)路由。注意,app.METHOD方法為你做了很多事情:默認(rèn)情況下,它不區(qū)分大小寫,也不介意末尾有沒有斜杠,在匹配路徑時(shí)也不考慮查詢串部分。因此對(duì)于“關(guān)于”頁的路由,/about、/About、/about/、/about?foo=bar、/about/?foo=bar等URL都可以匹配。當(dāng)路由匹配成功時(shí),你提供的函數(shù)會(huì)得到執(zhí)行。傳入這個(gè)函數(shù)的參數(shù)是request和response對(duì)象,第6章將深入學(xué)習(xí)它們。目前,我們只是返回一條文本和一個(gè)狀態(tài)碼200(Express默認(rèn)的狀態(tài)碼就是200,你不需要明確指定)。強(qiáng)烈建議你安裝一個(gè)瀏覽器插件,這個(gè)插件可以向你顯示HTTP狀態(tài)碼以及可能發(fā)生的重定向。它讓你更容易發(fā)現(xiàn)代碼中重定向的問題或不正確的狀態(tài)碼,這些都是常常被忽視的。對(duì)于Chrome,Ayima的RedirectPath插件十分好用。在大多數(shù)瀏覽器中,你可以在開發(fā)者工具中的“網(wǎng)絡(luò)”部分查看狀態(tài)碼。我們不再使用Node的更低層的res.end方法,而是切換到了Express擴(kuò)展的res.send方法。也會(huì)把Node的res.writeHead替換為res.set和res.status。Express也提供了一個(gè)便利的方法,即res.type,它可以設(shè)置Content-Type頭信息。仍舊使用res.writeHead和res.send也可以,但沒必要或不推薦。注意定制的404頁和500頁的處理方式必須有些許不同。對(duì)于它們,我們不使用app.get,而是使用app.use。app.use就是Express用來增加中間件的方法。第10章將深入討論中間件,但就目前來說,你可以把這些中間件看作一個(gè)兜底的處理函數(shù),能夠處理任何在前面沒有得到路由匹配的請(qǐng)求。由此可以得出一個(gè)重要結(jié)論:在Express中,路由和中間件的加入順序至關(guān)重要。如果把404處理函數(shù)放在路由之前,主頁和“關(guān)于”頁將不再正常,那些URL都會(huì)“走”到404頁。現(xiàn)在,雖然我們的路由還十分簡單,但由于它們也支持通配符,因此也會(huì)產(chǎn)生順序的問題。舉個(gè)例子,如果想給“關(guān)于”頁增加子頁面,比如/about/contact和/about/directions,該怎么做?寫成下面這樣是不會(huì)按預(yù)期工作的:app.get('/about*',(req,res)=>{//發(fā)送內(nèi)容……})app.get('/about/contact',(req,res)=>{//發(fā)送內(nèi)容……})app.get('/about/directions',(req,res)=>{//發(fā)送內(nèi)容……})在這個(gè)例子中,/about/contact和/about/directions這兩個(gè)路由永遠(yuǎn)得不到匹配,因?yàn)榈谝粋€(gè)處理函數(shù)在路徑中使用了通配符:/about*。Express可以區(qū)分404和500這兩個(gè)處理函數(shù),依靠的是回調(diào)函數(shù)接收的參數(shù)個(gè)數(shù)。錯(cuò)誤處理路由將在第10章和第12章中進(jìn)一步討論。現(xiàn)在可以重新啟動(dòng)服務(wù)器,看看主頁和“關(guān)于”頁是否正常了。目前為止我們所做的事情雖然沒有Express也很容易完成,但是Express正在默默地提供一些幫助。回想前一章:為了確定請(qǐng)求的是什么資源,必須對(duì)req.url做規(guī)范化處理。我們不得不手動(dòng)去除URL的查詢串和末尾斜杠,并把它轉(zhuǎn)換成小寫。現(xiàn)在Express的路由自動(dòng)處理了這些細(xì)節(jié)。雖然看起來或許不是什
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 2025企業(yè)員工崗前安全培訓(xùn)考試試題答案考試直接用
- 2025公司項(xiàng)目負(fù)責(zé)人安全培訓(xùn)考試試題(B卷)
- 2025年中國一軸軸套市場調(diào)查研究報(bào)告
- 2025年中國PE纖維袋市場調(diào)查研究報(bào)告
- 2025年中國CO2激光高速切割打標(biāo)機(jī)市場調(diào)查研究報(bào)告
- 人工智能與醫(yī)療倫理共筑福祉之路
- 2025年船用推進(jìn)電機(jī)項(xiàng)目合作計(jì)劃書
- 臨床決策支持系統(tǒng)中的醫(yī)療大數(shù)據(jù)分析
- 企業(yè)與個(gè)人之間借款合同
- 微信服務(wù)合同微信服務(wù)協(xié)議
- 《花生膜下滴灌技術(shù)》課件
- 名片設(shè)計(jì)教程
- 森林消防員勞務(wù)派遣服務(wù)投標(biāo)方案技術(shù)標(biāo)
- 婦科學(xué)婦科感染病
- 《內(nèi)科常見病的診治》課件
- 離心泵有效汽蝕余量計(jì)算公式
- 第十一章計(jì)劃調(diào)控法律制度
- 《我的家鄉(xiāng)日喀則》課件
- 語文版一年級(jí)下冊(cè)語文閱讀理解(15篇)
- 華文版書法五年級(jí)下冊(cè) 第12課 同字框 教案
- 國網(wǎng)裝表接電(初級(jí))理論考試復(fù)習(xí)題庫(含答案)
評(píng)論
0/150
提交評(píng)論