為了防止無良網站的爬蟲抓取文章,特此標識,轉載請注明文章出處。LaplaceDemon/SJQ。
http://www.cnblogs.com/shijiaqi1066/p/3821150.html
本文使用node.js v0.10.28 + express 4.2.0
1 Express概述
Express 是一個簡潔而靈活的node.js的MVC Web應用框架,提供一系列強大特性創建各種Web應用。
Express 不對 node.js 已有的特性進行二次抽象,我們只是在它之上擴展了Web應用所需的功能。
Expressd底層由Node.js的HTTP模塊實現。
1.1 express 4.x 安裝
express 4.x與之前的版本有了許多的變化,書里和網上的很多方法都不再適用。學習需要更多的參考官方文檔。
若需要用express 3.x版本,直接使用nmp 中的@字符確定版本,指令如下:
npm install -g express-generator@3
若需要使用4.x,注意的問題在4.x版本express 已經把命令行工具分離出來。
現在全局安裝只需要安裝這個命令行工具就可以,指令如下:
npm install -g express-generator
1.2 創建express工程
使用express命令
express [options]
選項:
-h, --help 輸出使用信息
-V, --version 輸出版本號
-e, --ejs 添加ejs的支持(默認使用jade)
-H, --hogan 添加hogan.js引擎的支持
-c, --css 添加css(less 或stylus 或compass)的支持
-f, --force 強迫接受非空目錄
1.2.1 創建hello-express的工程
使用命令:express hello-express ,默認創建基於jade的項目。
使用-e參數創建基於ejs的項目。使用命令:express -e hello-express
在當前目錄下會創建新的目錄。
目錄內容如下所示:
進入到工程目錄
1.2.2 安裝express及依賴
使用命令:npm install
該過程會自動下載網絡上的依賴包,請耐心等待命令結束。創建包后,可查看目下的package.json文件:
1.2.3 啟動工程
使用命令:npm start
注意:該實例無法以 node app.js 為啟動方式,而是用指令 npm start 作為啟動。原因如下所述。
npm執行的時候會讀取當前目錄的package.json文件,這個也就是我上面那個bug出現的原因
執行npm start其實是執行package.json中的script對應的對象中的start屬性所對應的命令行。
也就是執行bin/www文件,該文件無后綴名。使用文本工具打開,可以看到其中的腳本。npm start命令,就是執行的該腳本程序。
express默認端口為3000。瀏覽器訪問 http://127.0.0.1:3000/ 。顯示頁面Welcome to Express,即安裝成功。
1.3 目錄解釋
如果瀏覽這個子目錄,就會發現express自動生成了以下的子目錄和文件。
- node_modules子目錄:用於安裝本地模塊。
- public子目錄:用於存放用戶可以下載到的文件,比如圖片、腳本、樣式表等。
- routes子目錄:用於存放路由文件。
- views子目錄:用於存放網頁的模板。
- app.js文件:應用程序的啟動腳本。
- package.json文件:項目的配置文件。
當對express工程目錄和package.json文件熟悉后,可以手動創建工程,而不用依賴express命令。
1.4 解析express命令生成工程的代碼
bin/www
#!/usr/bin/env node var debug = require('debug')('hello-express'); var app = require('../app'); //引入app模塊 app.set('port', process.env.PORT || 3000); //設置’port’ var server = app.listen(app.get('port'), function() { //獲取’port’,並監聽。 debug('Express server listening on port ' + server.address().port); });
app.js
var express = require('express'); //引入express var path = require('path'); //引入path var favicon = require('static-favicon'); //引入static-favicon var logger = require('morgan'); //引入morgan var cookieParser = require('cookie-parser'); //引入cookie-parser var bodyParser = require('body-parser'); //引入body-parser var routes = require('./routes/index'); //引入路由index var users = require('./routes/users'); //引入路由users var app = express(); //創建一個app // 設置視圖引擎。 app.set('views', path.join(__dirname, 'views')); //設置視圖文件所在位置。 app.set('view engine', 'ejs'); //設置視圖引擎。 app.use(favicon()); app.use(logger('dev')); app.use(bodyParser.json()); app.use(bodyParser.urlencoded()); app.use(cookieParser()); app.use(express.static(path.join(__dirname, 'public'))); //指定靜態文件所在位置 /* 請求時,服務器端就到public目錄尋找這個文件。比如,瀏覽器發出如下的樣式表請求: <link href="/bootstrap/css/bootstrap.css" rel="stylesheet"> 服務器端就到public/bootstrap/css/目錄中尋找bootstrap.css文件。 */ app.use('/', routes); //對’/’設置路由。 app.use('/users', users); //對’/user’設置路由。 //捕獲404並對跳轉錯誤進行處理。 app.use(function(req, res, next) { var err = new Error('Not Found'); err.status = 404; next(err); }); // 開發環境錯誤處理。 if (app.get('env') === 'development') { app.use(function(err, req, res, next) { res.status(err.status || 500); res.render('error', { //渲染error.ejs。 message: err.message, error: err }); }); } // 生產環境錯誤處理。 // 沒有堆棧跟蹤泄露給用戶。 app.use(function(err, req, res, next) { res.status(err.status || 500); res.render('error', { message: err.message, error: {} }); }); module.exports = app; //將app設為模塊。
views/index.ejs
<!DOCTYPE html> <html> <head> <title><%= title %></title> <link rel='stylesheet' href='/stylesheets/style.css' /> <!--由於已經設置了靜態文件位置,服務器會自動在public目錄中尋找。--> </head> <body> <h1><%= title %></h1> <p>Welcome to <%= title %></p> </body> </html>
views/error.ejs
<h1><%= message %></h1> <h2><%= error.status %></h2> <pre><%= error.stack %></pre>
routes/index.js
var express = require('express'); var router = express.Router(); //獲取路由對象。 /* GET home page. */ router.get('/', function(req, res) { res.render('index', { title: 'Express' }); //頁面渲染index.ejs。 }); module.exports = router; //設置為模塊。
routes/users.js
var express = require('express'); var router = express.Router(); //獲取路由對象。 /* GET users listing. */ router.get('/', function(req, res) { res.send('respond with a resource'); //respones直接返回字符串。 }); module.exports = router; //將本路由設置為模塊。
2 Express基本組件
2.1 Express的中間件
Express中的中間件(middleware)類似於JavaEE中攔截器的概念。
Express中的中間件由函數表示:
function uselessMiddleware(req, res, next) { next(); }
每個中間件都可以對HTTP請求(request對象)做出回應。調用next方法會將request對象再傳給下一個中間件。next方法如果帶有參數,則代表拋出一個錯誤,參數為錯誤文本。
function uselessMiddleware(req, res, next) { next('出錯了!'); }
2.2 user()方法
user方法用於將路徑與中間件函數或路由對象綁定起來。默認路徑為”/”。
app.use([path], function)
var express = require('express'); var app = express(); // simple logger app.use(function(req, res, next){ console.log('%s %s', req.method, req.url); next(); }); // respond app.use(function(req, res, next){ res.send('Hello World'); }); app.listen(3000);
2.3 靜態資源
靜態資源的獲取是use()函數的典型應用。express.static()方法為靜態資源指定目錄。use()方法用於綁定攔截http請求,若請求的靜態資源存在,則返回靜態資源文件。
例:js、css、圖片分別位於工程路徑的public目錄中。
// GET /static/javascripts/jquery.js // GET /static/style.css // GET /static/favicon.ico app.use(express.static(__dirname + '/public'));
注意:
對不同url使用use()函數,各use()函數順序,先攔截到http請求的中間件函數會先被執行。而且,use()方法與路由方法的順序也需要注意。
一般的,use()方法位於路由方法之前。
例:當請求"GET /javascripts/jquery.js" 時,會先檢查 "./public/javascripts/jquery.js",若不存在,隨后的中間件會檢查 "./files/javascripts/jquery.js"。
app.use(express.static('public'));
app.use(express.static('files'));
2.4 異常處理
定義錯誤處理的中間件跟定義普通的中間件沒有什么區別,僅僅是參數必須定義為4個,定義如下 (err, req, res, next)
app.use(function(err, req, res, next){ console.error(err.stack); res.send(500, 'Something broke!'); });
2.5 請求路由
2.5.1 路由方法
app.VERB(url, callback(req, res))
app.VERB(url, callback1(req, res),callback2(req, res),......,callbackn(req, res))
app.VERB() 方法為Express提供路由方法,VERB 是指某一個HTTP 動作,比如,app.post()。
app.VERB() 方法可以提供多個callbacks,多個callbacks會被平等對待,它們的行為跟中間件類似,不同的是,若某一個callback執行了next('route'),此后的callback就被忽略。這種情形會應用在當滿足一個路由前綴,但是不需要處理這個路由,於是把它向后傳遞。
2.5.2 Router 路由
Express 4.x使用Router組件對URL進行響應處理。在Express 4.x 應該盡量多使用 express.Router 來代替app.VERB方式的路由。
express.Router使得整個控制器變成了一個不依賴於任何外部實例的獨立模塊,更有利於模塊的拆分,同時對於測試也更加友好。
express.Router可以認為是一個微型的只用來處理中間件與控制器的app,它擁有和 app 類似的方法,例如 get、post、all、use 等等。
app.js
app.use(require('./controllers/book'));
controllers/book.js
var router = require('express').Rouer(); // 新建一個 router var A = require('../middlewares/A'); var B = require('../middlewares/B'); var C = require('../middlewares/C'); // 在 router 上裝備控制器與中間件 router.get('/books', A, B, C, function (req, res) { var retA = req.A; // 中間件 A 的輸出結果 var retB = req.B; // 中間件 B 的輸出結果 var retC = req.C; // 中間件 C 的輸出結果 // ... 其余程序邏輯 }); // ... // 返回 router 供 app 使用 module.exports = router;
通過 express.Router,控制器與中間件的代碼緊密的聯系在一起,並且避免了傳遞 app 的潛在風險。同時,一個 router 就是一個完整的功能模塊,不需要任何裝配就可以執行。這一點對於單元測試來說非常簡單。
2.5.3 中間件重用
express.Router 可以認為是一個迷你的 app,它擁有一個獨立的中間件隊列。這個特性可以用來共享一些常用的中間件,例如:
例:parseBook方法是個中間件方法。
var bookRouter = express.Router(); app.use('/books', bookRouter); //路由指定中間件 bookRouter.use(parseBook); //如下三個控制器都會經過 parseBook 中間件。 bookRouter.get('/books/:bookId', viewBook); bookRouter.get('/books/:bookId/edit', editBook); bookRouter.get('/books/:bookId/move', moveBook); app.get('/other_link', otherController); // 不會經過 parseBook 中間件。
例:Restful的Router。
var bookRouter = express.Router(); bookRouter .route('/books/:bookId?') .get(function (req, res) { // ... }) .put(function (req, res) { // ... }) .post(function (req, res) { // ... }) .delete(function (req, res) { // ... })
app.route
假定app是Express的實例對象,Express 4.0為該對象提供了一個route屬性。app.route實際上是express.Router()的縮寫形式,直接掛載到根路徑。
app.route('/login') .get(function(req, res) { res.send('this is the login form'); }) .post(function(req, res) { console.log('processing'); res.send('processing the login form!'); });
2.6 request對象
使用req.query解析http查詢字符串
// GET /search?q=tobi+ferret req.query.q // => "tobi ferret"
// GET /shoes?order=desc&shoe[color]=blue&shoe[type]=converse req.query.order // => "desc" req.query.shoe.color // => "blue" req.query.shoe.type // => "converse"
使用req.params獲取url中的參數
app.get('/blog/:year/:month/:day/:title', function (req, res) { var fileName = './blogs/' +
req.params.year + '-' + req.params.month + '-' + req.params.day + '-' + req.params.title + '.md';
fs.readFile(fileName, 'utf-8', function (err, data) { if (err) { res.send(err); } res.send(data); }); });
獲取請求中參數的通用方法
req.param(name)
// ?name=tobi req.param('name') // => "tobi" // POST name=tobi req.param('name') // => "tobi" // /user/tobi for /user/:name req.param('name') // => "tobi"
request.ip
request.ip屬性用於獲得HTTP請求的IP地址。
request.files
request.files用於獲取上傳的文件。
2.7 response對象
response.redirect方法
response.redirect方法用於網址的重定向。
response.redirect("/hello/anime"); response.redirect("http://www.example.com"); response.redirect(301, "http://www.example.com");
response.sendFile方法
response.sendFile方法用於發送文件。
response.sendFile("/path/to/anime.mp4");
response.render方法
response.render方法用於渲染網頁模板。
app.get("/", function(request, response) { response.render("index", { message: "Hello World" }); //將message變量傳入index模板,渲染成HTML網頁。 });
2.8 使用EJS模板
EJS是一個JavaScript模板庫,用來從JSON數據中生成HTML字符串。EJS的優點是將會帶給你明確、維護性良好的HTML代碼結構。EJS模板以”.ejs”為后綴。
2.8.1 基本應用
數據
{ title: 'Cleaning Supplies', supplies: ['mop', 'broom', 'duster'] }
模板
<h1><%= title %></h1> <ul> <% for(var i=0; i<supplies.length; i++) {%> <li><%= supplies[i] %></li> <% } %> </ul>
用數據渲染模板,WEB的顯示結果:
2.8.2 Include功能
EJS提供了模版包含(include)的功能,可以include不同的頁面部件,組合在一起。
其語法為:<% include foot.ejs %>
2.8.3 其他
EJS實際可以不依賴Express而獨立使用的。甚至在瀏覽器中也有很好的應用。
3 Express所需要的工具組件
Express 4.x 的版本做了很多改進,將很多功能分離出去,以第三方組件的形式為Express提供了更多的功能。同時Express也會有更好的擴展性。
3.1 body-parser
body-parser是Node.js的body解析中間件。用於解析HTTP報文body部分的數據。
body-parser無法處理multipart的body,對於multipart,可能需要使用如下模塊:
· busboy 與 connect-busboy
· multiparty 與 connect-multiparty
· multer
其他body解析器:
· body
· co-body
API示例
var express = require('express') var bodyParser = require('body-parser') var app = express()
// 解析application/x-www-form-urlencoded app.use(bodyParser.urlencoded())
// 解析application/json app.use(bodyParser.json()) // 解析application/vnd.api+json 作為json app.use(bodyParser.json({ type: 'application/vnd.api+json' })) app.use(function (req, res, next) { console.log(req.body) // populated! next() })
bodyParser.json(options)
返回一個解析json的中間件。該解析器可以接受Unicode編碼的請求體,可以作自動解壓縮gzip,或解碼。
選項:
- strict - 僅解析對象和數組。(default: true)
- inflate - 設置是否解壓被壓縮的body (default: true)
- limit - request的body的最大長度。 (default: <100kb>)
- reviver - 傳遞給JSON.parse()
- type - 解析的類型。(default: json)
- verify - 判定body內容的函數。
type參數將直接傳遞給 type-is 庫。該值可以是一個擴展名(如:json),一個mime類型(如:application/json),或者一個帶通配符的mime(如:*/json)
若提供verify 參數,其函數形式為verify(req, res, buf, encoding),buf是Buffer類型,是請求體。encoding 是請求的編碼。解析會被拋出的錯誤而中止。
reviver 參數會被直接傳遞給 JSON.parse方法獲取其第二個參數。可以在MDN 的文檔中找到更多相關資料。
bodyParser.raw(options)
返回的中間件,中間件將body作為Buffer。該解析器可以作自動解壓縮gzip或者解碼。
選項:
- inflate - 同bodyParser.json (default: true)
- limit - 同bodyParser.json (default: <100kb>)
- type - 同bodyParser.json (default: application/octet-stream)
- verify - 同bodyParser.json
type參數將直接傳遞給 type-is 庫。該值可以是一個擴展名(如:bin),一個mime類型(如:application/octet-stream),或者一個帶通配符的mime(如:application/*)
bodyParser.text(options)
返回的中間件,中間件將body作為字符串。該解析器可以作自動解壓縮gzip或者解碼。
選項:
- defaultCharset - 若沒有指定content-type,該值為默認字符集。 (default: utf-8)
- inflate - 同bodyParser.json。 (default: true)
- limit - 同bodyParser.json。 (default: <100kb>)
- type - 同bodyParser.json。 (default: text/plain)
- verify - 同bodyParser.json。
type參數將直接傳遞給 type-is 庫。該值可以是一個擴展名(如:txt),一個mime類型(如:text/plain),或者一個帶通配符的mime(如:text/*)
bodyParser.urlencoded(options)
返回一個擁有解析urlencoded 請求體的中間件。該解析器可以接受Unicode編碼的請求體,該可以作自動解壓縮gzip,或解碼。
選項:
- extended - 使用qs模塊解析擴展的語法。(default: true)
- inflate - 同bodyParser.json。 (default: true)
- limit - 同bodyParser.json。 (default: <100kb>)
- type - 同bodyParser.json。 (default: urlencoded)
- verify - 同bodyParser.json。
extended 參數允許選擇解析urlencoded 數據的庫,當為false時,使用querystring 庫解析,當為true時,使用qs庫解析。“擴展”的語法允許富對象和數組編碼為urlencoded 格式,允許類JSON表達式。更多信息請參考qs庫
type參數將直接傳遞給 type-is 庫。該值可以是一個擴展名(如:urlencoded),一個mime類型(如:application/x-www-form-urlencoded),或者一個帶通配符的mime(如: */x-www-form-urlencoded)
req.body
HTTP中報文體的對象。
例:
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);
3.2 cookie-parser
cookieParser中間件,用於分析和處理req.cookies的cookie數據。
3.3 express-session
express-session是一個為Express提供的 session中間件。
var express = require('express') var session = require('express-session')
var app = express() app.use(session({secret: 'keyboard cat'}))
session(options)
為session設置存儲選項。session ID存儲於cookie,其他數據不會被存儲在cookie中。
- name - cookie的名稱(默認: 'connect.sid')
- store - session的存儲實例。
- secret - session cookie通過secret 加密。
- cookie - session相關cookie的設置項。(默認: { path: '/', httpOnly: true, secure: false, maxAge: null })
- genid - session ID的生成函數。 (默認: 使用uid2庫。)
- rolling - 強制對每個response的cookie設置過期日期。(默認:false)
- resave - 強制session為被保存的。(默認:true)
- proxy - 信任反向代理,當時也secure cookies時候,當該項為true,則"x-forwarded-proto"報文頭將會被使用。當設置為false時,所有的報文頭都會被忽略。若不設置,則使用express的"trust proxy"設置。(默認:undefined)
- saveUninitialized - 強制session以"uninitialized"被保存到倉庫。會話是未初始化的時不能被修改。這對實現登錄會話是非常有用的。(默認:true)
- unset - 通過該選項,刪除req.session或將其設為null后,設置req.session的結局。若為”keep”,則session將會在倉庫中維持着,但無法被修改;若為”destory”則將銷毀倉庫中的session。(默認:'keep')
options.genid
對新session生成session ID。提供一個函數。函數返回一個字符串作為session ID。函數有一個req參數,在生成ID時,可以使用一些值附加到req上。
注意:session ID需要唯一。
例:
app.use(session({ genid: function(req) { return genuuid(); // use UUIDs for session IDs }, secret: 'keyboard cat' }))
Cookie選項
請注意 secure: true 是一個被推薦的選項。然而,這需要https的支持。HTTPS需要安全的Cookie。如果secure 被設置了,若使用HTTP訪問網站程序時,則cookie將不會被設置。如果你的node.js在代理之后,且使用了 secure: true ,被需要設置,則需要在express中設置"trust proxy" 。
var app = express() app.set('trust proxy', 1) // trust first proxy
app.use(session({ secret: 'keyboard cat' , cookie: { secure: true } }))
在生產環境下,使用安全cookie。可以為了開發測試,如下是一個的設置基於NODE_ENV的例子:
var app = express() var sess = { secret: 'keyboard cat' cookie: {} } if (app.get('env') === 'production') { app.set('trust proxy', 1) // trust first proxy sess.cookie.secure = true // serve secure cookies }
app.use(session(sess))
cookie.maxAge默認是null的,即expires沒有配置,所以cookie用於瀏覽器的會話cookie。當時用戶關閉瀏覽器,會話cookie就會被移除。
req.session
存儲或訪問session數據。
示例:
app.use(session({ secret: 'keyboard cat', cookie: { maxAge: 60000 }})) app.use(function(req, res, next) { var sess = req.session if (sess.views) { sess.views++ res.setHeader('Content-Type', 'text/html') res.write('<p>views: ' + sess.views + '</p>') res.write('<p>expires in: ' + (sess.cookie.maxAge / 1000) + 's</p>') res.end() } else { sess.views = 1 res.end('welcome to the session demo. refresh!') } })
session.regenerate()
調用該方法重新生成session,一旦完成一個SID,Session實例將會在req.session中被初始化。
req.session.regenerate(function(err) { // 此處將使用一個新的session。 })
session.destroy()
銷毀session,移除req.session,將會重新生成下一個request。
req.session.destroy(function(err) { // 此處無法使用session })
session.reload()
重新加載session數據。
req.session.reload(function(err) { // session升級 })
session.save()
req.session.save(function(err) { // session被保存 })
session.touch()
更新maxAge屬性。該方法不需要調用,session中間件會自動做這件事情。
req.session.cookie
每個session必須有一個唯一的cookie對象對應。
- req.session.cookie.expires 用於cookie的過期設置。若req.session.cookie.expires設置為false,則使得cookie僅保持用戶代理。
- req.session.cookie.maxAge 為session.cookie的剩余時間,單位為毫秒。
例:設置並查看cookie.maxAge的時間
var hour = 3600000 req.session.cookie.expires = new Date(Date.now() + hour) req.session.cookie.maxAge = hour
30秒之后,req.session.cookie.maxAge返回剩余時間。
req.session.cookie.maxAge // => 30000
Session倉庫的實現
每個session倉庫必須實現如下方法:
- .get(sid, callback)
- .set(sid, session, callback)
- .destroy(sid, callback)
推薦實現也實現如下方法:
- .length(callback)
- .clear(callback)
例:以下是一個redis的實現。
https://github.com/visionmedia/connect-redis
Express官方資料:
https://www.npmjs.org/package/express
https://github.com/visionmedia/expressjs.com
https://github.com/visionmedia/express#quick-start
body-parser相關參考資料:
https://github.com/expressjs/body-parser
https://www.npmjs.org/package/body-parser
cookie-parser相關參考資料:
https://github.com/expressjs/cookie-parser
https://www.npmjs.org/package/cookie-parser
express-session相關參考資料:
https://github.com/expressjs/session
https://www.npmjs.org/package/express-session
關於expressjs更多的組件請在如下地址搜索:
感謝如下博客作者為我們提供的學習資料:
http://javascript.ruanyifeng.com/nodejs/express.html
http://www.cnblogs.com/moonpanda/p/3669735.html
http://blog.segmentfault.com/nark/1190000000488358
http://lostjs.com/2014/04/24/upgrade-express-4/
https://github.com/visionmedia/express/wiki/Migrating-from-3.x-to-4.x
http://www.cnblogs.com/yuanzm/p/3770986.html
http://my.oschina.net/tangdu/blog/282207
http://www.cnblogs.com/ahl5esoft/p/3769781.html
http://www.90it.net/topic/web/node
為了防止無良網站的爬蟲抓取文章,特此標識,轉載請注明文章出處。LaplaceDemon/SJQ。
http://www.cnblogs.com/shijiaqi1066/p/3821150.html