node.js入門及express.js框架


node.js介紹javascript

javascript本來只是用來處理前端,Node使得javascript編寫服務端程序成爲可能。因而前端開發者也能夠藉此輕鬆進入後端開發領域。
Node是基於Google的V8引擎封裝的,並提供了一些編寫服務器程序的經常使用接口,例如文件流的處理。Node的目的是提供一種簡單的途徑來編寫高性能的網絡程序。css

Node.js特性
* 單線程
* 非阻塞IO
* Google V8
* 事件驅動html

node.js安裝前端

對於在windows下的開發環境,node.js已經提供了windows安裝包的支持,不用再像開始同樣苦逼的各類配置操做了。而且也集成了npm管理。其餘系統下的安裝請參考官網。java

工具:npmnode

npm用來管理依賴模塊
npm search 能夠查看可代安裝的模塊列表mysql

框架:expressjquery

安裝express
npm install express -gd
查看express版本
express -vgit

工具:markdowngithub

geek必備的寫博利器。
npm install markdown

工具:jade

模塊引擎
npm install jade

入門 app.js

 

原生 node.js

var http=require('http').createServer(function(req,res){
res.send('hello world');
}).listen(2012);


express.js語法

// create a server
var app=require('express').createServer();
// url 路由
app.get('/',function(req,res){
res.send('hello world');
});
console.log('It works');
app.listen(2012);

 


運行

node app.js

 

 

----------------------------------------------------------------------------------------------

 

1、咱們建立項目目錄。

> md hello-world

2、進入此目錄,定義項目配置文件package.json。
爲了準肯定義,可使用命令:
D:\tmp\node\hello-world> npm info express version
npm http GET https://registry.npmjs.org/express
npm http 200 https://registry.npmjs.org/express
3.2.1

如今知道ExpressJS框架的最新版本爲3.2.1,那麼配置文件爲:
[javascript] view plaincopyprint?
{
"name": "hello-world",
"description": "hello world test app",
"version": "0.0.1",
"private": true,
"dependencies": {
"express": "3.2.1"
}
}

3、使用npm安裝項目依賴的包。
> npm install

一旦npm安裝依賴包完成,項目根目錄下會出現node_modules的子目錄。項目配置所需的express包都存放於這裏。若是相驗證,能夠執行命令:
> npm ls
[javascript] view plaincopyprint?
PS D:\tmp\node\hello-world> npm ls
npm WARN package.json hello-world@0.0.1 No README.md file found!
hello-world@0.0.1 D:\tmp\node\hello-world
└─┬ express@3.2.1
├── buffer-crc32@0.2.1
├── commander@0.6.1
├─┬ connect@2.7.7
│ ├── bytes@0.2.0
│ ├── formidable@1.0.13
│ └── pause@0.0.1
├── cookie@0.0.5
├── cookie-signature@1.0.1
├── debug@0.7.2
├── fresh@0.1.0
├── methods@0.0.1
├── mkdirp@0.3.4
├── qs@0.6.1
├── range-parser@0.0.4
└─┬ send@0.1.0
└── mime@1.2.6

此命令顯示了express包及其依賴關係。

4、建立應用程序

如今開始建立應用程序自身。建立一個名爲app.js或server.js的文件,看你喜歡,任選一個。引用express,並使用express()建立一個新應用:

[javascript] view plaincopyprint?
// app.js
var express = require('express');
var app = express();

接着,咱們可使用app.動詞()定義路由。
好比使用"GET /"響應"Hello World"字符串,由於res、req都是Node提供的準確的對象,所以你能夠調用res.pipe()或req.on('data', callback)或者其它。

[javascript] view plaincopyprint?
app.get('/hello.txt', function(req, res){
var body = 'Hello World';
res.setHeader('Content-Type', 'text/plain');
res.setHeader('Content-Length', body.length);
res.end(body);
});

ExpressJS框架提供了更高層的方法,好比res.send(),它能夠省去諸如添加Content-Length之類的事情。以下:

[javascript] view plaincopyprint?
app.get('/hello.txt', function(req, res){
res.send('Hello World');
});

如今能夠綁定和監聽端口了,調用app.listen()方法,接收一樣的參數,好比:

[javascript] view plaincopyprint?
app.listen(3000);
console.log('Listening on port 3000');

5、運行程序

如今運行程序,執行命令:
> node app.js

用瀏覽器訪問地址:http://localhost:3000/hello.txt
能夠看到輸出結果:
Hello World

 

---------------------------------------------------------------------------------------

 

6、使用express(1)產生應用
Express框架綁定了一個可執行腳本,名爲express(1)。若是使用npm對Express框架進行全局安裝,那麼express處處都能使用。

> npm install -g express

express(1)工具提供了簡單的產生應用程序骨架的方式,並且它有必定的使用範圍。好比它只支持有限的幾個模板引擎,而Express自身是支持任意Node構建的模板引擎。可使用下面的命令進行查看:

> express --help

PS D:\tmp\node\hello-world> express --help

Usage: express [options]

Options:

-h, --help output usage information
-V, --version output the version number
-s, --sessions add session support
-e, --ejs add ejs engine support (defaults to jade)
-J, --jshtml add jshtml engine support (defaults to jade)
-H, --hogan add hogan.js engine support
-c, --css <engine> add stylesheet <engine> support (less|stylus) (defaults to plain css)
-f, --force force on non-empty directory

若是想生成支持EJS、Stylus、和會話Session的應用,那麼該這樣作:
> express --sessions --css stylus --ejs myapp
D:\tmp\node\hello-world>express --sessions --css stylus --ejs myapp

create : myapp
create : myapp/package.json
create : myapp/app.js
create : myapp/public
create : myapp/public/javascripts
create : myapp/public/images
create : myapp/public/stylesheets
create : myapp/public/stylesheets/style.styl
create : myapp/routes
create : myapp/routes/index.js
create : myapp/routes/user.js
create : myapp/views
create : myapp/views/index.ejs

install dependencies:
$ cd myapp && npm install

run the app:
$ node app

與其它Node應用同樣,必須安裝依賴包。
> cd myapp
> npm install

而後運行myapp,執行:
> node myapp.js

使用瀏覽器訪問地址:http://localhost:3000/
能夠看到:
Express
Welcome to Express

這就是建立簡單應用的全過程。要記住,Express框架並無綁定任何指定的目錄結構,

7、Express框架說明
Express框架是Node.js第三方框架中比較好的Web開發框架。Express除了爲HTTP模塊提供了更高層的接口外,還實現了不少功能,其中包括:
1)路由控制;
2)模板解析支持;
3)動態視圖;
4)用戶會話;
5)CSRF保護;
6)靜態文件服務;
7)錯誤控制器;
8)訪問日誌;
9)緩存;
10)插件支持。

要說明一點,Express並非無所不包的全能框架(像Rails或Django那樣實現了模板引擎甚至是ORM),它只是一個輕量級的Web框架,其主要功能只是對HTTP協議中經常使用操做的封裝,更多的功能須要插件或整合其它模塊來完成。

好比:
[javascript] view plaincopyprint?
var express = require('express');

var app = express();
app.use(express.bodyParser());
app.all('/', function(req, res){
res.send(req.body.title + req.body.text);
});
app.listen(3000);

 

--------------------------------------------------------------------------------------------------

 

 

在Node.js語言中,包和模塊並無本質的不一樣,包是在模塊的基礎上更深一步的抽象,包將某個獨立的功能封裝起來,用於發佈、更新、依賴管理和進行版本控制。Node.js根據CommonJS規範實現了包機制,開發了npm來解決包的發佈和獲取需求。

Node.js的包是一個目錄,其中包含JSON格式的包說明文件package.json。Node.js的包基本遵循CommonJS規範,所以具有如下特徵:

CommonJS規範定義的包特性:
1)頂層目錄包含package.json文件;
2)bin目錄存放二進制文件;
3)lib目錄存放JavaScript文件;
4)doc目錄存放文檔;
5)test目錄存放單元測試。

Node.js的模塊與文件是一一對應的,文件不只能夠是JavaScript源碼文件或二進制文件,還能夠是目錄。最簡單的包,就是一個目錄的模塊。

Node.js的包一般是一些模塊的集合,在模塊的基礎上提供了更高層的抽象,至關於提供了一些固定接口的函數庫。
經過定製package.json,咱們能夠建立更復雜、更完善、更符合規範的包用於發佈。

Node.js在調用包時,首先會檢查包中的package.json文件的main字段,將其做爲包的接口模塊,若是package.json文件的main字段不存在,那麼Node.js會嘗試尋找index.js或index.node做爲包的接口。

package.json文件是CommonJS規範用於描述包的文件,徹底符合規範的package.json文件應該包含如下字段:
1)name:包名。包名是惟一的,由小寫字母、數字和下劃線組成,不能含空格。
2)description:包說明。對包進行簡要描述。
3)version:版本號。知足《語義化版本識別》規範的版本字符串。
4)keywords:關鍵字數組,一般用於搜索。
5)maintainers:維護者數組。每一個元素包含name、email(可選)、web(可選)字段。
6)contributors:貢獻者數組。格式與maintainer數組相同。包做者應該是貢獻者數組的第一個元素。
7)bugs:提交bug的地址,能夠是網址或電郵地址。
8)licenses:許可證數組。每一個元素要包含type(許可證名稱)和url(連接到許可證文本的地址)字段。
9)repositories:倉庫託管地址數組。每一個元素要包含type(倉庫的類型,如Git)、url(倉庫地址)和path(相對於倉庫的路徑,可選)字段。
10)dependencies:包依賴。是一個關聯數組,由包名和版本號組成。

注:《語義化版本識別》規範是國外提出的一套版本命名規範,最初目的是解決各類各樣的版本號大小比較的問題,目前被許多包管理系統所採用。

下面是一個徹底符合CommonJS規範的package.json例子:
[javascript] view plaincopyprint?
{
"name": "testpackage",
"description": "My package for CommonJS.",
"version": "0.1.0",
"keywords": [
"testpackage",
"liq"
],
"maintainers": [
{
"name": "liq",
"email": "liq@hotmail.com",
}
],
"contributors": [
{
"name": "liq",
"web": "http://blog.csdn.net/chszs"
}
],
"bugs": {
"mail": "liq@hotmail.com",
"web": "http://blog.csdn.net/chszs"
},
"licenses": [
{
"type": "Apache License v2",
"url": "http://www.apache.org/licenses/apache2.html"
}
],
"repositories": [
{
"type": "git",
"url": "http://github.com/chszs/packagetest.git"
}
],
"dependencies": {
"webkit": "1.2",
"ssl": {
"gnutls": ["1.0", "2.0"],
"openssl": "0.9.8"
}
}
}

 

--------------------------------------------------------------------------------------------

 

最近研究了下node.js, 這個東西好, 就如毛主席說的。

做者在C++層包裝了一個解析器,讓js擁有了指揮C C++操做socket htpp協議的原力。

jser在繼移動端,如今又進軍到網絡和文件的底層。

 

js是個好東西, 弱類型靈活,JSON的發明讓js更是如虎添翼。

由於js操縱JSON徹底就是在操縱本身的原生對象同樣簡單和有效。

js能夠靈活的處理其餘強類型語言須要一大堆代碼才能判斷正確的內容。

 

Express :  直接說結果, 省卻廢話

1.首先 routes文件夾不屬於express模塊,

他真的只是個文件夾, 是做者默認給使用者設置的寫控制器C的位置。

應該始終將控制器邏輯寫在類似的目錄文件裏,方便管理維護和建立新的對象。

 

2.express 並不算是實現MVC框架, 

(1)首先M不須要他提供, http+數據庫這一部分, 數據庫提供了M。

數據庫包括SELECT檢索(get),  添加INSERT INTO, 刪除DELETE。。。

Model所具有的許多特性。

不過數據庫不是js語言編寫的, 可是已經有人寫好了mysql模塊, 做爲與M通訊的適配器。

也就是咱們用mysql模塊的query(),能夠調用MYSQL的SELECT INSERT 。。。

 

(2)由於服務器端的緣由,並不能遵循定義式的MVC

MVC的定式, M要負責通知V,可是這裏M是脫離了語言的數據庫,沒法通知V。

能夠在C中讓控制器負責這個任務, render()方法。

 

另外V與C的分離,實際上是藉助ejs jade模板引擎。

 

服務器端不存在真正的視圖。

這個道理很明顯。express的視圖作了這樣的任務:

寫好html結構, 負責生成字符串。

依然是控制器最後調取View中的字符串,而後render->write 給瀏覽器。

(MVC定義的視圖是負責調取數據,顯示)

(而這裏是負責生成控制器須要的字符串格式, 由控制器發送)

 

總之,就是:控制器負責了一切。

 

3.express是怎麼工做的

在MVC意義上,是這樣工做的:

一個子視圖控件v-----[綁定]------>一個子控制器c         發送消息

子控制器c---------[綁定]--------->模型M                 一系列複雜的邏輯調用

模型M---------->[觀察者]--------->觀察視圖控件u,x     update從新獲取數據自我刷新

 

express基於服務器, 大部分只有get  post兩個消息,

沒有子視圖能夠綁定控制器, 如何來創建這條消息通道。

 

express採用這樣的方法,  經過request對象能夠獲取到 get/post消息類型  url字符串-pathname路徑

因此if檢測 pathname 和get/post 類型來調用須要的子控制器。

 

怎麼創建這個檢測?

在express中設置一個methods之類的棧之類的集合, 存儲子控制器的指針。

這個「指針」包括: 路徑名字  請求類型  子控制器的方法指針。

而後提供add方法(get, post)向methods中添加新的子控制器。

 

當服務器接收到request事件的時候, 觸發app(express)函數監聽器。

app函數會遍歷本身的methods, if 傳送來的請求get/post類型 和 pathname路徑,

來調取符合的子控制器。

 

客戶端-----------request:   get/post    url----------->服務器

服務器------onrequest監聽器觸發(app函數)-------->遍歷app內部集合methods[]--------->

if (請求get/post  和pathname 符合methods內部存儲的子控制器)---------------->

運行這條子控制器--->

->讀取mysql,

->調用【指定路徑】視圖(ejs引擎)的字符串內容

->組合mysql數據和模板引擎生成的字符串內容 , render (調取nodejs的write) 發送回瀏覽器。

 

-------------------------------------------------------------------------------------------------------------

 

 

Express.js 中文入門指引手冊

Express 是基於 Node.js,高性能、一流的web開發框架。

本手冊由一回於 2011-4-24 首次翻譯,現在已通過去接近一年,express 最新的版本已經與當時翻譯有些脫節,爲了方便內容更新,將使用 csser 開發的貼板功能進行維護,也方便你們針對細節進行討論,並請指出翻譯不當之處。



express 的安裝

$ npm install express
或者

$ npm install -g express

 

建立服務器

要建立 express.HTTPServer 的實例,只需簡單的調用 createServer() 方法便可。經過 HTTPServer 實例 app 咱們能夠定義基於 HTTP 動做(HTTP verbs)的路由(routes),本例中爲 app.get()。

var app = require('express').createServer();

app.get('/', function(req, res){
res.send('hello world from csser.com!');
});

app.listen(3000);

 

建立 HTTPS 服務器

要初始化一個express.HTTPSServer實例,與上例類似,不一樣的是在createServer方法咱們傳入一個對象做爲配置參數,該對象接受 key, cert 和其它在NodeJS https文檔中提到的配置參數。

var app = require('express').createServer({ key: ... });

 


配置

Express支持多工做環境,好比生產環境和開發環境等。開發者可使用configure()方法根據當前環境的須要進行設置,當configure()沒有傳入環境名稱時,它會在各環境以前被調用(一回注:至關於被各個明確環境所共享)。

下面的示例咱們只拋出異常(dumpException),而且在開發模式對異常堆棧的輸出作出響應,可是不論對開發或者生產環境咱們都使用了methodOverride和bodyParser。特別注意對app.router的使用,它能夠被用來設置應用的路由(可選),不然首次對app.get()、app.post()等的調用會設置路由。

// 定義共享環境
app.configure(function(){
app.use(express.methodOverride());
app.use(express.bodyParser());
app.use(app.router);
});

// 定義開發環境
app.configure('development', function(){
app.use(express.static(__dirname + '/public'));
app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
});

// 定義生產環境
app.configure('production', function(){
var oneYear = 31557600000;
app.use(express.static(__dirname + '/public', { maxAge: oneYear }));
app.use(express.errorHandler());
});
對於內部和多重設置(internal和arbitrary),Express提供了 set(key[, val]), enable(key), disable(key)等方法:

app.configure(function(){
app.set('views', __dirname + '/views');
app.set('views');
// => "/absolute/path/to/views"

app.enable('some feature');
// 與 app.set('some feature', true); 相同

app.disable('some feature');
// 與 app.set('some feature', false); 相同

app.enabled('some feature')
// => false
});
要修改環境,能夠經過設置NODE_ENV環境變量來實現,例如:

$ NODE_ENV=production node app.js
這很重要,由於許多的緩存機制只有在生產環境纔會啓用。

 



設置

Express 支持如下設置項:

home爲res.redirect()提供應用的基準路徑,透明的處理已安裝的應用。
views視圖(views)層的根目錄,默認指向CWD/views
view engine默認的視圖(view)引擎的名字,不包含擴展名。
view options用於設置全局視圖選項的對象



路由

Express利用HTTP動做提供了有意義並富有表現力的URL映射API,例如咱們可能想讓用戶賬號的URL看起來像/user/12的樣子,下面的例子就能實現這樣的路由,其中與佔位標識符(本例爲:id)相關的值能夠被req.params獲取到。

app.get('/user/:id', function(req, res){
res.send('user ' + req.params.id);
});
上例中當咱們訪問/user/12時返回「user 12」, csser注:app.get至關於在服務器註冊了一個監聽get請求事件的偵聽器,當請求的URL知足第一個參數時,執行後面的回調函數,該過程是異步的。

路由是一個能夠被內部編譯成正則表達式的簡單字符串,好比當/user/:id被編譯後,被內部編譯後的正則表達式字符串看起來會是下面的樣子(簡化後):

\/user\/([^\/]+)\/?
要實現複雜點的,咱們能夠傳入正則表達式直接量,由於正則捕獲組是匿名的所以咱們能夠經過req.params進行訪問,第一個捕獲組應該是req.params[0],第二個應該是req.params[1],以此類推。

app.get(/^\/users?(?:\/(\d+)(?:\.\.(\d+))?)?/, function(req, res){
res.send(req.params);
});
經過Linux的curl命令來測試咱們定義的路由:

$ curl http://cssercom:3000/user
[null,null]
$ curl http://cssercom:3000/users
[null,null]
$ curl http://cssercom:3000/users/1
["1",null]
$ curl http://cssercom:3000/users/1..15
["1","15"]
下面是一些路由例子,以及與之相匹配的關聯路徑:

"/user/:id"
/user/12

"/users/:id?"
/users/5
/users

"/files/*"
/files/jquery.js
/files/javascripts/jquery.js

"/file/*.*"
/files/jquery.js
/files/javascripts/jquery.js

"/user/:id/:operation?"
/user/1
/user/1/edit

"/products.:format"
/products.json
/products.xml

"/products.:format?"
/products.json
/products.xml
/products

"/user/:id.:format?"
/user/12
/user/12.json
另外,咱們能夠經過POST方式提交json數據,而後利用bodyParser中間件解析json請求體並把json數據返回給客戶端:

var express = require('express')
, app = express.createServer();

app.use(express.bodyParser());

app.post('/', function(req, res){
res.send(req.body);
});

app.listen(3000);
一般咱們所使用的佔位符(好比/user/:id)都沒有任何限制,即用戶能夠傳入各類各樣數據類型的id值,若是咱們但願限制用戶id爲數字,能夠這樣寫「/user/:id(\d+)」,這樣就能保證只有該佔位符數據類型爲數值類型纔會進行路由的相關處理。



路由控制

一個應用中能夠定義多個路由,咱們能夠控制以令其轉向下一個路由,Express提供了第三個參數即next()函數。當一個模式不被匹配時,控制將被轉回Connect(Express基於Connect模塊),同時中間件會繼續按照它們在use()中增長的順序來執行。當多個定義的路由均可能匹配同一個URL時也是如此,除非某個路由並不調用next()且已將響應輸出到客戶端,不然它們也將按順序執行。

app.get('/users/:id?', function(req, res, next){
var id = req.params.id;
if (id) {
// 一回注:若是在這裏就將響應內容輸出給客戶端,那麼後續的URL映射將不會被調用
} else {
next(); // 將控制轉向下一個符合URL的路由
}
});

app.get('/users', function(req, res){
// do something else
});
app.all()方法能夠對全部HTTP動做應用單一調用入口,這在有些狀況下頗有用。下面咱們使用該功能來從咱們的模擬數據庫中加載一個用戶,並把它分配給req.user。

var express = require('express')
, app = express.createServer();

var users = [{ name: 'www.csser.com' }];

app.all('/user/:id/:op?', function(req, res, next){
req.user = users[req.params.id];
if (req.user) {
next();
} else {
next(new Error('cannot find user ' + req.params.id));
}
});

app.get('/user/:id', function(req, res){
res.send('viewing ' + req.user.name);
});

app.get('/user/:id/edit', function(req, res){
res.send('editing ' + req.user.name);
});

app.put('/user/:id', function(req, res){
res.send('updating ' + req.user.name);
});

app.get('*', function(req, res){
res.send('what???', 404);
});

app.listen(3000);

 


中間件

中間件能夠經過Connect傳入express.createServer(),就像正常的鏈接服務器同樣,好比:

var express = require('express');

var app = express.createServer(
express.logger(),
express.bodyParser()
);
另外,在configure()函數塊中利用use()函數增長中間件,也是一種很好的方式。

app.use(express.logger({ format: ':method :uri' }));
一般鏈接中間件能夠經過require("connect")的方式,如:

var connect = require('connect');
app.use(connect.logger());
app.use(connect.bodyParser());
這讓人感受有些不太爽,因而express從新輸出了(re-exports)這些中間件屬性,可是在使用上保持了一致性:

app.use(express.logger());
app.use(express.bodyParser());



路由中間件

這裏路徑映射也能夠理解爲路由的意思,路由經過傳入一個或多個附加的回調函數(或數組)到方法中,從而能夠利用特定路由的中間件。該功能對限制訪問以及加載路由使用的數據很是有用。

一般狀況下異步數據的查詢看起來像下面的樣子,這裏咱們使用:id參數,並嘗試獲取一個用戶。

app.get('/user/:id', function(req, res, next){
loadUser(req.params.id, function(err, user){
if (err) return next(err);
res.send('Viewing user of csser.com ' + user.name);
});
});
爲了保持代碼整潔而且提升可讀性,咱們能夠在中間件內部應用該邏輯。正如你所看到的,將邏輯抽象到中間件裏讓咱們達到必定程度的複用,同時代碼更乾淨。

function loadUser(req, res, next) {
// 這裏提供假數據,你能夠從數據庫中獲取真實用戶信息
var user = users[req.params.id];
if (user) {
req.user = user;
next();
} else {
next(new Error('不存在的用戶 ' + req.params.id));
}
}

app.get('/user/:id', loadUser, function(req, res){
res.send('正在查看用戶 ' + req.user.name);
});
一回注:看到了嗎?上面的路徑映射的回調函數參數是能夠支持多個的。

多重路由中間件能夠被按照順序來執行,從而能夠實現更復雜的邏輯,好比限制訪問某個用戶的訪問權限,下面的代碼將只容許認證用戶才能夠編輯其賬號信息。

function andRestrictToSelf(req, res, next) {
req.authenticatedUser.id == req.user.id
? next()
: next(new Error('無權限'));

}

app.get('/user/:id/edit', loadUser, andRestrictToSelf, function(req, res){
res.send('開始編輯用戶 ' + req.user.name);
});
請記住中間件是簡單的函數,咱們還能定義返回中間件的函數,從而能夠建立一個更有表現力和更易用的以下方案:

function andRestrictTo(role) {
return function(req, res, next) {
req.authenticatedUser.role == role
? next()
: next(new Error('無權限'));

}
}

app.del('/user/:id', loadUser, andRestrictTo('admin'), function(req, res){
res.send('已刪除用戶 ' + req.user.name);
});
一回注:app.del的第三個參數之因此能夠這樣寫,是由於其返回的是一個函數,而該函數能夠訪問’admin’的值,這裏涉及到閉包的概念,若有疑問請在CSSer.com查找閉包相關文章。

一般使用的中間件的「棧」能夠被做爲數組(遞歸應用)傳入,如此能夠被混合並能匹配更復雜的功能。

var a = [middleware1, middleware2]
, b = [middleware3, middleware4]
, all = [a, b];

app.get('/foo', a, function(){});
app.get('/bar', a, function(){});

app.get('/', a, middleware3, middleware4, function(){});
app.get('/', a, b, function(){});
app.get('/', all, function(){});
能夠去express源碼倉庫查看完整的路由中間件示例。



HTTP 方法

在CSSer前面的文章中咱們已經接觸過app.get()屢次了,同時Express也提供了對其它HTTP動做的封裝,如app.post(), app.del()等。

對於POST最多見的例子,就是當咱們提交一個表單時,下面咱們在HTML中將表單的method特性設置爲「post」,而後須要在服務端定義對該表單提交的路由控制。

<form method="post" action="/">
<input type="text" name="user[name]" />
<input type="text" name="user[email]" />
<input type="submit" value="Submit" />
</form>
默認狀況下Express並不知道該如何處理該請求體,所以咱們須要增長bodyParser中間件,用於分析application/x-www-form-urlencoded和application/json請求體,並把變量存入req.body。咱們能夠像下面的樣子來「使用」中間件:

app.use(express.bodyParser());
接下來下面的路由就能夠訪問req.body.user對象了,該對象包含客戶端提交的name和email屬性。

app.post('/', function(req, res){
console.log(req.body.user);
res.redirect('back');
});
要在表單中使用PUT的HTTP方法,咱們能夠利用名爲_method的隱藏表單域,它能改變HTTP方法。而在服務端,咱們首先須要利用methodOverride中間件,把它放在bodyParser中間件下方,從而能夠利用包含表單值的req.body。

app.use(express.bodyParser());
app.use(express.methodOverride());
之因此須要這樣作,是由於這些處理並不老是默認進行的,緣由很簡單,由於這些對Express的總體功能來講並非必需的,依據應用的具體需求,你並不必定須要這些功能,若是客戶端直接支持PUT和DELETE方法也能夠被直接訪問到,同時methodOverride爲表單提供了強大的解決方案,下面咱們展現下PUT的使用:

<form method="post" action="/">
<input type="hidden" name="_method" value="put" />
<input type="text" name="user[name]" />
<input type="text" name="user[email]" />
<input type="submit" value="Submit" />
</form>

app.put('/', function(){
console.log(req.body.user);
res.redirect('back');
});

 


錯誤處理

Express提供了app.error()方法來接收路由或傳入next(err)的異常,下面的示例爲不一樣的頁面提供專門的NotFound異常服務:

function NotFound(msg){
this.name = 'NotFound in csser.com';
Error.call(this, msg);
Error.captureStackTrace(this, arguments.callee);
}

NotFound.prototype.__proto__ = Error.prototype;

app.get('/404', function(req, res){
throw new NotFound;
});

app.get('/500', function(req, res){
throw new Error('keyboard cat!');
});
像下面同樣,咱們能夠屢次調用app.error(),這裏咱們檢查若是是NotFound實例就顯示404頁面,不然將其傳入下一個錯誤處理。

注意這些處理能夠定義在任何地方,它們能夠放在路由能夠listen()之處。這也容許在configure()塊內作定義,因而咱們就能夠以不一樣的基於環境的方式處理異常。

app.error(function(err, req, res, next){
if (err instanceof NotFound) {
res.render('404.jade');
} else {
next(err);
}
});
下面的演示咱們假設全部錯誤都爲500錯誤,但你能夠根據喜愛選擇。例如當node在處理文件系統調用時,就有可能接收到這樣的錯誤對象,其ENOENT的error.code爲「no such file or directory」,這時咱們能夠在錯誤處理函數中進行處理而後顯示特定的頁面給用戶。

app.error(function(err, req, res){
res.render('500.jade', { error: err });
});
咱們的應用也能夠利用Connect errorHandler中間件來彙報異常信息。例如咱們想在「開發」環境輸出異常到stderr:

app.use(express.errorHandler({ dumpExceptions: true }));
同時在開發期間咱們想用好看的HTML頁面顯示異常信息時,能夠設置showStack的值爲true:

app.use(express.errorHandler({ showStack: true, dumpExceptions: true }));
若是請求頭Accept: application/json,errorHandler中間件也能以json方式作出響應,這對依賴於客戶端Javascript的應用開發頗有益處。

 



路由參數預處理

路由參數預處理經過隱式的數據處理,能夠大幅提升應用代碼的可讀性和請求URL的驗證。假如你常常性的從幾個路由獲取通用數據,如經過/user/:id加載用戶信息,一般咱們可能會這樣作:

app.get('/user/:userId', function(req, res, next){
User.get(req.params.userId, function(err, user){
if (err) return next(err);
res.send('user ' + user.name);
});
});
利用預處理後參數能夠被映射到回調函數,從而能夠提供諸如驗證、強制性改變值,甚至從數據庫中加載數據等功能。下面咱們將調用app.param()並傳入咱們但願映射到某個中間件的參數,能夠看到咱們接收了包含佔位符(:userId)值的id參數。在這裏能夠與日常同樣進行用戶數據加載以及錯誤處理,並能簡單的經過調用next()將控制權轉向下一個預處理或路由(路徑控制)。

app.param('userId', function(req, res, next, id){
User.get(id, function(err, user){
if (err) return next(err);
if (!user) return next(new Error('failed to find user'));
req.user = user;
next();
});
});
這樣作,不只向上面提到的能夠大幅提升路由的可讀性,還能在整個應用中共享該部分的邏輯實現,達到複用目的。

app.get('/user/:userId', function(req, res){
res.send('CSSer用戶爲 ' + req.user.name);
});
對於簡單的狀況如路由佔位符驗證和強迫改變值,只須要傳入1個參數(支持1個參數),期間拋出的異常將自動傳入next(err)。

app.param('number', function(n){ return parseInt(n, 10); });
也能夠同時將回調函數應用到多個佔位符,好比路由/commits/:from-:to來講,:from和:to都是數值類型,咱們能夠將它們定義爲數組:

app.param(['from', 'to'], function(n){ return parseInt(n, 10); });

 


視圖渲染

視圖的文件名默認需遵循「<name>.<engine>」的形式,這裏<engine>是要被加載的模塊的名字。好比視圖layout.ejs就是在告訴視圖系統要require("ejs"),被加載的模塊必須輸出exports.compile(str, options)方法,並要返回一個函數來遵照Express的模板接口約定。咱們也可使用app.register()來映射模板引擎到其它文件擴展名,從而實現更靈活的模板引擎行爲,如此一來就能夠實現「csser.html」能夠被ejs引擎所渲染。

下面咱們將用Jade引擎來渲染index.html,由於咱們沒有設置layout:false,index.jade渲染後的內容將被做爲body本地變量傳入layout.jade。

app.get('/', function(req, res){
res.render('index.jade', { title: 'CSSer, 關注Web前端技術!' });
});
新增的view engine設置能夠指定默認模板引擎,若是咱們想使用jade能夠這樣設置:

app.set('view engine', 'jade');
因而咱們就能夠經過下面的方式:

res.render('index');
代替以下方式:

res.render('index.jade');
當view engine設置後,模板的擴展名就成了可選項,同時咱們還能夠混合匹配多模板引擎:

res.render('another-page.ejs');
Express同時提供了視圖選項(view options)設置,這些設置會在每次視圖渲染後應用,好比你並不常用layouts,就能夠這樣設置:

app.set('view options', {
layout: false
});
若是須要,這些設置能夠在後續的res.render()調用中被覆蓋:

res.render('csser-view.ejs', { layout: true });
能夠經過指定一個路徑的方式來實現用本身的layout來代替系統默認的,好比若是咱們將「view engine」設置爲jade而且自定義了一個名爲「./views/mylayout.jade」的layout,咱們能夠這樣使用它:

res.render('page', { layout: 'mylayout' });
不然必須指定擴展名:

res.render('page', { layout: 'mylayout.jade' });
這些路徑也能夠是絕對路徑:

res.render('page', { layout: __dirname + '/../../mylayout.jade' });
這方面較好的例子就是自定義ejs模板的開始和關閉的標記:

app.set('view options', {
open: '{{',
close: '}}'
});

 


局部視圖

Express視圖系統原生支持局部和集合視圖,這稱做微型視圖,主要用於渲染一個文檔片斷。好比與其在視圖中循環顯示評論,不如使用局部集合(partial collection):

partial('comment', { collection: comments });
若是不須要其它選項或本地變量,咱們能夠省略對象而簡單的傳入評論數組,這和上面的示例是同樣的:

partial('comment', comments);
當使用局部集合時,支持一些「魔術」本地變量:

firstInCollection 當爲第一個對象時該值爲true
indexInCollection 集合中對象的索引值
lastInCollection 當爲最後一個對象時爲true
collectionLength 集合的長度
傳入(或生成)的本地變量優先,但傳入父視圖的本地變量在子視圖仍有效。所以若是咱們用partial(‘blog/post’, post)來渲染博客日誌時,將生成post的本地變量,但調用本函數的視圖擁有本地用戶,它在blog/post視圖依然有效。

性能提示:當使用局部集合渲染100長度的數組就意味着須要渲染100次視圖,對於簡單的集合你能夠將循環內聯,而不要使用局部集合,這樣能夠減小系統開銷。

 



視圖查找

視圖查找是相對於父視圖進行的,好比咱們有一個名爲「views/user/list.jade」的頁面視圖,若是在該視圖中調用partial(‘edit’),視圖系統將會嘗試查找並加載「views/user/edit.jade」,而partial(‘../messages’)將加載「views/messages.jade」。

視圖系統還支持索引模板,這樣你就可使用一個同名的目錄。好比,在一個路由中咱們執行res.render(‘users’),這將指向「views/users.jade」或者「views/users/index.jade」。

當使用上面的索引視圖時,咱們能夠經過partial(‘users’)從同名目錄下引用「views/users/index.jade」,同時視圖系統會嘗試「../users/index」,這能減小咱們調用partial(‘index’)的須要。

 


模板引擎

Express支持許多模板引擎,經常使用的有:

Haml haml 的實現
Jade haml.js 接替者,同時也是Express的默認模板引擎
EJS 嵌入JavaScript模板
CoffeeKup 基於CoffeeScript的模板引擎
jQuery Templates 的NodeJS版本

 


Session Support

能夠在Express中經過增長Connect的session中間件來開啓Session支持,固然前提是須要在這以前使用cookieParser中間件,用於分析和處理req.cookies的cookie數據(咱們知道session會利用cookie進行通訊保持的)。

app.use(express.cookieParser());
app.use(express.session({ secret: "keyboard cat" }));
默認session中間件使用Connect綁定的內存存儲,但也有另外的實現方式。好比connect-redis就提供了一個Redis的session存儲方案:

var RedisStore = require('connect-redis');
app.use(express.cookieParser());
app.use(express.session({ secret: "CSSer加密字符串", store: new RedisStore }));
如今req.session和req.sessionStore屬性就能夠被全部路由及下級中間件所訪問,req.session的屬性會伴隨着每一次響應發送給客戶端,下面是一個購物車的例子:

var RedisStore = require('connect-redis');
app.use(express.bodyParser());
app.use(express.cookieParser());
app.use(express.session({ secret: "keyboard cat", store: new RedisStore }));

app.post('/add-to-cart', function(req, res){
// 利用bodyParser()中間件處理POST提交的表單數據
var items = req.body.items;
req.session.items = items;
res.redirect('back');
});

app.get('/add-to-cart', function(req, res){ // 當頁面回到返回並經過GET請求/add-to-cart 時 // 咱們能夠檢查req.session.items && req.session.items.length,而後將信息打印到頁面 if (req.session.items && req.session.items.length) { req.flash('info', 'You have %s items in your cart', req.session.items.length); } res.render('shopping-cart');});req.session對象還擁有許多其它方法,如Session#touch(), Session#destroy(), Session#regenerate()等用於session處理,更多信息請查看Connect Session文檔。

相關文章
相關標籤/搜索