HTTP開發之Connect工具集——中間件
繼學習node.js的TCP API和HTTP API之後,node.js web開發進入了正軌,但這就好像Java的servlet一樣,我們不可能使用最基礎得Servlet對象去寫網站,我們也不能使用最基本的node http API去寫一個完整得網站,我們需要更加強大得工具集,web套件,甚至是web開發框架(諸如Java下的Spring MVC),來提供開發者更人性化得web開發環境。
創建網站的基本任務——爲何要有中間件
- 獨立託管靜態文件,諸如:html,css,js,images
- 處理錯誤和不存在的地址
- 處理不同類型的請求
如果我們上來就直接用上一章的http模塊來做的話,我們需要先實現一個“框架”才能做到一個網站最基本的任務,如果是運用生產環境的話,我們希望有這樣的一個“框架”——connect工具集,最好稱它爲工具集,他離所謂的“框架”依舊有點差距。
Connect是基於http API之上的,也就是基於node http模塊寫出來的,方便web開發者使用,他提供了一些工具方法能夠上一些重複性的工作便於實現,讓開發者更加專注於應用的功能業務。
Connect已經是十分基礎的node web開發工具集,在實際開發中很少看到,以後回介紹稍微高級點的express(對於實際開發來說依然很基礎)
特別注意:《了不起的node.js》一書中的代碼和connect版本過時了,如果使用最新版的connect需要參照如下方法使用!
導入Connect等中間件模塊
Connect模塊並非node.js的原生模塊,需要引入外部模塊
同時還要導入“serve-static”,“content-disposition”提供靜態文件訪問支持
最終完整package.json:
{
"name":"node-connect",
"version":"0.0.1",
"description":"Use connect to create a website",
"dependencies":{
"connect":"latest",
"serve-static":"latest",
"content-disposition":"latest"
}
}
node npm包搜索管理網站:https://www.npmjs.com/
注:除了connect外,還導入了serve-static,這個包原本屬於connect集合,後官方獨立出來了,《了不起的node.js》使用的是1.8.x版本的connect(太老了)還包含着static中間件,這裏使用了3.0.0+的版本,則需要額外導入這個靜態中間件模塊。
這些是官網獨立的中間件(Connect/Express負責管理)
將不同功能的中間件從connect獨立出來是一件好事,方便管理維護!(對於開發者需要注意查看更新)
npm install
引用connect模塊
//引入connect模塊
var connect = require("connect");
//依然需要引入http模塊
var http = require("http")
//依然使用http模塊創建服務器
http.createServer().listen(3000);
connect現在是http中間件,如果使用本文最新的connect版本就不能出現《了不起的node.js》一書中的connect.createServer(),因爲connect已經移除了這個方法!!!
createServer交給了http模塊去做,充分體現了connect是中間件的特性,而不是代替http!
依然使用http.createServer()創建http服務器!!!
使用serve-static中間件託管靜態文件
node本身不像nginx,apache,他不是一個完整的http服務器,而是一個語言解析器。
通過對node.js API的開發,可以構建類似nginx一樣的功能,負責提供用戶靜態頁面,這就需要用到serve-static中間件
首先serve-static中間件原本屬於connect中間件,現在已經獨立出來了,也就說其實和connect關係已經不大了,但我依然將兩者放在一回中解說。
靜態文件指的是:html,css,圖片,js等等
在項目目錄下新建一個views目錄,存放靜態html文件
新建index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<h1>hello world</h1>
</body>
</html>
index.js返回靜態頁面代碼:
//引入http模塊
var http = require("http");
//引入serveStatic模塊
var serveStatic = require('serve-static')
var finalhandler = require('finalhandler');
var serve = serveStatic(__dirname+'/views',{'index':['index.html','index.htm']});
http.createServer(function(req,res){
serve(req,res,finalhandler(req,res));
}).listen(3000);
finalhandler是什麼?
作用是友好處理找不到頁面訪問不到URL的異常和錯誤,如果刪除,用戶訪問非法地址將會導致node拋出異常停止工作。
訪問首頁之外的頁面?
views下創建一個hello.html,直接在瀏覽器地址後面+“/hello.html”即可訪問這個頁面。
訪問圖片文件?
如何提示下載文件?
var contentDisposition = require('content-disposition')
var finalhandler = require('finalhandler')
var http = require('http')
var serveStatic = require('serve-static')
var serve = serveStatic(__dirname+'/files', {
'index': false,
'setHeaders': setHeaders
})
//強制下載
function setHeaders (res, path) {
res.setHeader('Content-Disposition', contentDisposition(path))
}
http.createServer(function onRequest (req, res) {
serve(req, res, finalhandler(req, res))
}).listen(3000);
新建一個files目錄,下載的文件放在裏面即可。
到此,serve-static中間件簡化了開發者託管靜態文件的代碼,我們不必向上一章那樣使用fs+http方式去自己實現輸出流文件給瀏覽器客戶端。
使用connect中間件處理不同的請求
在上一回中,如果要對用戶不同訪問給出對應的返回,我們需要在同一個createServer中不停的寫if…else if….else if…else….if………來判斷,這樣顯得代碼十分冗餘,而且根本無法把所有情況寫清楚,也無法擴展,且根本無法維護。
上一回的部分代碼:
//請求爲圖片
if(req.method == 'GET' && req.url.substr(-4)==".jpg"){
fs.stat(__dirname+req.url,function(err,stat){
if(err || !stat.isFile()){
res.writeHead(404);
res.end("找不到圖片");
return;
}
serve(__dirname+req.url,'application/x-jpg');
});
}else if(req.method=="GET" && req.url=='/'){
//請求爲html文件
serve(__dirname+'/form.html','text/html');
}else{
res.writeHead(404);
res.end("網址丟了");
return;
}
接下來使用connect重寫這些代碼:(代碼是基於static-serve那塊修改過來的)
//下載文件模塊
var contentDisposition = require('content-disposition')
//不需要finalhandler
//var finalhandler = require('finalhandler')
//http模塊創建http服務器必須
var http = require('http')
//靜態文件託管中間件
var serveStatic = require('serve-static')
//connect工具集
var connect = require("connect");
var app = connect();
app.use(function(req,res,next){
//任何請求都會打印!是必然執行的一步
console.error('%s %s',req.method,req.url)
next();
});
//圖片顯示
app.use("/images",function(req,res,next){
serveStatic(__dirname+"/images")(req,res,next);
});
//下載文件
app.use("/files",function(req,res,next){
serveStatic(__dirname+"/files",{'index':false,'setHeaders':setHeaders})(req,res,next);
});
//最終處理,所有next都執行不通後到達此處
app.use(function(req,res,next){
res.writeHead(404);
res.end('404 Not Found')
//沒有next了,這是最終方法,返回404 not found就行了
});
//強制下載函數
function setHeaders (res, path) {
res.setHeader('Content-Disposition', contentDisposition(path))
}
//建立http服務器
http.createServer(app).listen(3000);
注意到我們依然需要http模塊來建立http服務器,然後引入了connect模塊,以及之前的serve-static和content-disposition。
這一段代碼實際上是把上一回分類請求處理和這一回靜態文件託管強強聯手了
如果說serve-static這個中間件解決的是靜態文件託管的話,那麼connect中間件其實是解決了路由,請求定向的控制。這樣我們不再需要通過手寫的if…else來判斷用戶請求了什麼類型的文件。
next()函數
這個函數在connect中扮演了十分重要的角色,作用:將不同種類的請求線性的串聯在一行,每一個app.use()其實類似於原來我們寫的if…else語句,next就是在處理不了的情況下,執行另一個app.use(),這樣只要回掉函數做不了就拋給下一個做,這樣不停的“甩鍋”,最終誰都做不了就有一個最終函數,開發者需要建立這樣的最終函數,裏面沒有next()了,說明到最後一步了,必須返回錯誤給用戶看了。我們這裏最終函數是一個404錯誤反饋。
use()函數
第一個參數可以給定一個請求的目錄,相對於node執行的項目目錄,相當於瀏覽器網址後面第一個“/”後面的參數。