Express 網站上是這樣介紹 Express 的: 「精簡的、靈活的 Node.js Web 程序框架,爲構建單頁、多頁及混合的 Web 程序提供了一系列健壯的功能特性。 」這到底是什麼意思呢?下面咱們來逐一解讀一下。html
精簡
這是 Express 最吸引人的特性之一。框架開發者常常會忘掉「少便是多」這一基本原則。Express 的哲學是在你的想法和服務器之間充當薄薄的一層。這並不意味着它不夠健壯,或者沒有足夠的有用特性,而是儘可能少干預你,讓你充分表達本身的思想,同時提供一些有用的東西。node
靈活
Express 哲學中的另外一個關鍵點是可擴展。Express 提供了一個很是精簡的框架,你能夠根據本身的須要添加 Express 功能中的不一樣部分,替換掉不能知足須要的部分。這種作法很新鮮。不少框架把什麼都給你了,一行代碼還沒寫,你擁有的就已是一個臃腫、神祕而複雜的項目了。一般,你的第一項任務就是把不須要的功能砍掉,或者替換掉不能知足需求的功能。Express 則採起了大相徑庭的方式,讓你在須要時纔去添加東西。git
Web程序框架
這裏須要琢磨一下語義了。什麼是 Web 程序?這意味着 Express 就不能作出網站或者網頁了嗎?不,網站是 Web 程序,網頁也是 Web 程序。但 Web 程序的含義不止這些,它還能夠向其餘 Web 程序提供功能(還有別的) 。通常而言, 「程序」是具備功能的,它不止是內容的靜態集合(儘管這也是很是簡單的 Web 程序) 。儘管如今「程序」 (在你的設備本地運行的東西)和「網頁」 (經過網絡爲你的設備服務的東西)之間有明顯的界限,但這種界限漸漸變得模糊了,這要感謝 PhoneGap 這樣的項目,同時也要感謝微軟容許 HTML5 像本地應用程序同樣在桌面上運行。不難想象,幾年以內程序和網站之間的界限將不復存在。程序員
單頁Web程序
單頁 Web 程序是比較新穎的想法。不像以前的網站,用戶每次訪問不一樣的頁面都要發起網絡請求,單頁 Web 程序把整個網站(或很大一部分)都下載到客戶端瀏覽器上。通過初始下載後,用戶訪問不一樣頁面的速度更快了,由於幾乎不須要或者只要不多的服務端通訊。單頁程序的開發可使用 Angular 或 Ember 等流行框架,Express 跟它們都
配合得很好。github
多頁和混合的Web程序
多頁 Web 程序是更傳統的方式。網站上的每一個頁面都是經過向服務器發起單獨的請求獲得的。這種方式確實比較傳統,但這並不意味着它沒有優勢,或者說單頁程序更好。只是如今有更多選擇了,你能夠決定哪些內容應該做爲單頁程序提供,哪些應該經過不一樣的請求提供。 「混合」說的就是同時使用這兩種方式的網站。
若是你仍是很困惑 Express 到底是什麼,不用擔憂。有時候只管把某些東西拿來用就行了,不用先理解它是什麼,本書將教你如何用 Express 開發 Web 程序。web
express官方遷移指南express
若是你以前曾經作過靜態的 HTML 網站,或者有 PHP 或 ASP 背景,可能習慣用 Web 服務器(好比 Apache 或 IIS)提供靜態文件服務,以便使用瀏覽器經過網絡查看這些文件。好比說,若是你建立了一個名爲 about.html 的文件,並把它放到了恰當的目錄下,而後就能夠訪問 http://localhost/about.html 查看這個文件。根據 Web 服務器的配置,你甚至能夠省略 .html,但 URL 和文件名之間的關係很清晰:Web 服務器知道文件在機器的哪一個地方,
並能把它返回給瀏覽器。
從 localhost 的名字就能看出來,它指的是你所在的機器。這是 IPv4 迴環地址 127.0.0.1 或者 IPv6 迴環地址 ::1 的經常使用別名。你應該更常見到 127.0.0.1,不過本書中用的是 localhost。若是你用的是遠程的機器(好比經過 SSH 訪問的) ,記得瀏覽 localhost 時訪問的不是你眼前的那臺機器。
Node 所提供的範式跟傳統的 Web 服務器不一樣:你寫的程序就是 Web 服務器。Node 只是給你提供了一個構建 Web 服務器的框架。你可能會說「但我不想寫 Web 服務器」 。這是很天然的反應:你想寫一個程序,而不是Web 服務器。然而在 Node 裏編寫 Web 服務器很是簡單(甚至只須要幾行代碼) ,而且你所以取得了對程序的控制權,這是很是值得的。那麼咱們開始吧。若是你已經安裝了 Node,也已經熟悉了終端,如今一切都準備好了。編程
我發現正規的編程入門範例老是輸出毫無創意的「Hello World」消息。但打破這樣的傳統彷佛是不敬之舉,因此咱們也從這裏開始吧,而後再去作一些更有趣的事情。用你喜歡的編輯器建立一個 helloWorld.js 文件:瀏覽器
var http = require('http'); http.createServer(function(req,res){ res.writeHead(200, { 'Content-Type': 'text/plain' }); res.end('Hello world!'); }).listen(3000); console.log('Server started on localhost:3000; press Ctrl-C to terminate....');
確保是和 helloWorld.js 在同一個目錄下,輸入 node hello World.js。而後打開瀏覽器訪問http://localhost:3000,你的第一個 Web 服務器就建成啦!這個服務器並無返回 HTML,而只是向你的瀏覽器傳遞了一條普通的文本消息「Hello world!」 。若是你想要嘗試發送HTML,能夠試驗一下:只要把 text/plain 換成 text/html,再把 'Hello world!' 換成一個包含有效 HTML 的字符串就好了。在這裏就不演示了,由於我要儘可能避免在 JavaScript裏寫 HTML。服務器
Node 的核心理念是事件驅動編程。這對程序員來講,意味着你必須知道有哪些事件,以及如何響應這些事件。不少人接觸事件驅動編程是從用戶界面開始的:用戶點擊了什麼,而後你處理「點擊事件」 。這個類比很好,由於程序員不能控制用戶什麼時間點擊或者是
否會點擊,因此事件驅動編程真的很直觀。在服務器上響應事件這種概念性的跳躍可能會
比較難,但原理是同樣的。在前面那個例子中,事件是隱含的:HTTP 請求就是要處理的事件。http.createServer 方
法將函數做爲一個參數,每次有 HTTP 請求發送過來就會調用那個函數。咱們這個簡單的程序只是把內容類型設爲普通文本,併發送字符串「Hello world!」 。
路由是指向客戶端提供它所發出的請求內容的機制。對基於 Web 的客戶端 / 服務器端程序而言,客戶端在 URL 中指明它想要的內容,具體來講就是路徑和查詢字符串(第 6 章會詳細講解 URL 的組成部分) 。
咱們擴展一下「Hello world!」那個例子,作些更有意思的事情。作一個有首頁、關於頁面和未找到頁面的極其簡單的網站。目前咱們還像以前那個例子同樣,不提供 HTML,只提供普通文本:
var http = require('http'); http.createServer(function(req,res){ // 規範化 url,去掉查詢字符串、可選的反斜槓,並把它變成小寫 var path = req.url.replace(/\/?(?:\?.*)?$/, '').toLowerCase(); switch(path) { case '': res.writeHead(200, { 'Content-Type': 'text/plain' }); res.end('Homepage'); break; case '/about': res.writeHead(200, { 'Content-Type': 'text/plain' }); res.end('About'); break; default: res.writeHead(404, { 'Content-Type': 'text/plain' }); res.end('Not Found'); break; } }).listen(3000); console.log('Server started on localhost:3000; press Ctrl-C to terminate....');
運行這段代碼,你會發現如今你能夠訪問首頁 (http://localhost: 3000)和關於頁面(http://localhost:3000/about) 。全部查詢字符串都會被忽略(因此 http://localhost:3000/?foo=bar 也是返回首頁) ,而且其餘全部 URL(http://localhost:3000/foo)返回的都是未找到頁面。
如今咱們有了一些可用的簡單路由,接下來咱們提供一些真正的 HTML 和 logo 圖片。由於這些內容不會變化,因此它們都被稱爲「靜態資源」 (相對於股票之類的內容,你每次刷新頁面,股價都會變化) 。
用 Node 提供靜態資源只適用於初期的小型項目,對於比較大的項目,你應該會想用 Nginx 或 CDN 之類的代理服務器來提供靜態資源。對此,第 16 章會有更多介紹。
若是你用過 Apache 或 IIS,可能習慣於只是建立一個 HTML 文件,訪問它,而後讓它自動發送到客戶端。Node 不是那樣的:咱們必須打開文件,讀取其中的內容,而後將這些內容發送給瀏覽器。因此咱們要在項目裏建立一個名爲 public 的目錄(在下一章中,你就會明白咱們爲何無論它叫 static) 。在這個目錄下建立文件 home.html、about.html、notfound.html,子目錄 img,以及一個名爲 img/logo.jpg 的圖片。以上這些工做就由你本身來完成了:既然你在閱讀這本書,那麼你應該知道怎麼編寫 HTML 文件和找張圖片。在你的 HTML 文件中這樣引用 logo:
<img href="/img/logo.jpg" alt="logo">
接下來修改 helloWorld.js:
var http = require('http'), fs = require('fs'); function serveStaticFile(res, path, contentType, responseCode) { if(!responseCode) responseCode = 200; fs.readFile(__dirname + path, function(err,data) { if(err) { res.writeHead(500, { 'Content-Type': 'text/plain' }); res.end('500 - Internal Error'); } else { res.writeHead(responseCode, { 'Content-Type': contentType }); res.end(data); } }); } http.createServer(function(req,res){ // 規範化 url,去掉查詢字符串、可選的反斜槓,並把它變成小寫 var path = req.url.replace(/\/?(?:\?.*)?$/, '') .toLowerCase(); switch(path) { case '': serveStaticFile(res, '/public/home.html', 'text/html'); break; case '/about': serveStaticFile(res, '/public/about.html', 'text/html'); break; case '/img/logo.jpg': serveStaticFile(res, '/public/img/logo.jpg', 'image/jpeg'); break; default: serveStaticFile(res, '/public/404.html', 'text/html', 404); break; } }).listen(3000); console.log('Server started on localhost:3000; press Ctrl-C to terminate....');
這 個 例 子 中, 我 們 的 路 由 是 非 常 缺 乏 想 象 力 的。 如 果 你 訪 問 http://localhost:3000/about,就返回 public/about.html 文件。你能夠隨意修改路由,也能夠隨意修改文件。好比說,若是你一週裏的每一天都要換一個關於頁
面,你可能會有 public/about_mon.html、public/about_tue.html 等之類的頁面,在你的路由中定義好邏輯,從而在用戶訪問 http://localhost:3000/about 時能提供恰當的頁面。注意,咱們建立了一個輔助函數 serveStaticFile,它完成了大部分工做。fs.readFile 是讀取文件的異步方法。這個函數有同步版本,fs.readFileSync,但這種異步思考問題的方式,你接觸得越早越好。這個函數不復雜:它調用 fs.readFile 讀取指定文件中的內容。fs.readFile 讀取完文件後執行回調函數,若是文件不存在,或者讀取文件時遇到許可權限方面的問題,會設定 err 變量,而且會返回一個 HTTP 500 的狀態碼代表服務器錯誤。若是文件讀取成功,文件會帶着特定的響應碼和內容類型發給客戶端。 *__dirname 會被解析爲正在執行的腳本所在的目錄。因此若是你的腳本放在/home/sites/app.js 中,則 __dirname 會被解析爲 /home/sites。無論何時,這個全局變量用起來都很方便。若是不這麼作,在不一樣的目錄中運行你的程序時極可能會出現難以診斷的錯誤。*