express框架


前面的話

  Express是一個簡潔、靈活的 node.js Web 應用開發框架, 它提供一系列強大的特性,幫助開發者創建各種 Web 和移動設備應用。本文將詳細介紹express框架

 

概述

  官網對Express的描述,它是一個基於 Node.js 平台,快速、開放、極簡的 web 開發框架。優點是易上手、高性能、擴展性強

  1、易上手:nodejs最初就是為了開發高性能web服務器而被設計出來的,然而相對底層的API會讓不少新手望而卻步。express對web開發相關的模塊進行了適度的封裝,屏蔽了大量復雜繁瑣的技術細節,讓開發者只需要專注於業務邏輯的開發,極大的降低了入門和學習的成本

  2、高性能:Express僅在web應用相關的nodejs模塊上進行了適度的封裝和擴展,較大程度避免了過度封裝導致的性能損耗

  3、擴展性強:基於中間件的開發模式,使得express應用的擴展、模塊拆分非常簡單,既靈活,擴展性又強

【安裝】

  安裝express前,首先安裝nodejs,接下來為應用創建一個目錄,然后進入此目錄並將其作為當前工作目錄

$ mkdir myapp
$ cd myapp

  通過 npm init 命令為應用創建一個 package.json 文件

$ npm init

  此命令要求輸入幾個參數,例如應用的名稱和版本。 直接按“回車”鍵接受默認設置即可,下面這個除外:

entry point: (index.js)

  鍵入 app.js 或者所希望的名稱,這是當前應用的入口文件。如果希望采用默認的 index.js 文件名,只需按“回車”鍵即可

  接下來安裝 Express 並將其保存到依賴列表中:

$ npm install express --save

  如果只是臨時安裝 Express,不想將它添加到依賴列表中,只需略去 --save 參數即可:

$ npm install express

 

入門實例

  在項目根目錄下,新建一個啟動文件,假定叫做index.js,新建一個public文件夾,並在public目錄下,新建index.html

var express = require('express');
var app = express();
app.use(express.static(__dirname + '/public'));
app.listen(8080);

  運行index.js后,訪問http://localhost:8080,它會在瀏覽器中打開public目錄的index.html文件

  當然,也可以在index.js之中,生成動態網頁

// index.js
var express = require('express');
var app = express();
app.get('/', function (req, res) {
  res.send('Hello world!');
});
app.listen(3000);

  運行index.js文件后,會在本機的3000端口啟動一個網站,網頁顯示Hello World

  啟動腳本index.js的app.get方法,用於指定不同的訪問路徑所對應的回調函數,這叫做“路由”(routing)。上面代碼只指定了根目錄的回調函數,因此只有一個路由記錄。實際應用中,可能有多個路由記錄

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

app.get('/', function (req, res) {
  res.send('Hello world!');
});
app.get('/customer', function(req, res){
  res.send('customer page');
});
app.get('/admin', function(req, res){
  res.send('admin page');
});

app.listen(3000);

  這時,最好就把路由放到一個單獨的文件中,比如新建一個routes子目錄

// routes/index.js
module.exports = function (app) {
  app.get('/', function (req, res) {
    res.send('Hello world');
  });
  app.get('/customer', function(req, res){
    res.send('customer page');
  });
  app.get('/admin', function(req, res){
    res.send('admin page');
  });
};

  然后,原來的index.js就變成下面這樣

// index.js
var express = require('express');
var app = express();
var routes = require('./routes')(app);
app.listen(3000);

 

生成器

  通過應用生成器工具 express 可以快速創建一個應用的骨架

  [注意]一定要使用全局模式安裝express-generator,否則無法使用express命令

$ npm install express-generator -g

  -h 選項可以列出所有可用的命令行選項:

$ express -h
  Usage: express [options] [dir]
  Options:
    -h, --help          output usage information
    -V, --version       output the version number
    -e, --ejs           add ejs engine support (defaults to jade)
        --hbs           add handlebars engine support
    -H, --hogan         add hogan.js engine support
    -c, --css <engine>  add stylesheet <engine> support (less|stylus|compass|sass) (defaults to plain css)
        --git           add .gitignore
    -f, --force         force on non-empty directory

  例如,下面的示例就是在當前工作目錄下創建一個命名為 myapp 的應用

$ express myapp

   create : myapp
   create : myapp/package.json
   create : myapp/app.js
   create : myapp/public
   create : myapp/public/javascripts
   create : myapp/public/images
   create : myapp/routes
   create : myapp/routes/index.js
   create : myapp/routes/users.js
   create : myapp/public/stylesheets
   create : myapp/public/stylesheets/style.css
   create : myapp/views
   create : myapp/views/index.jade
   create : myapp/views/layout.jade
   create : myapp/views/error.jade
   create : myapp/bin
   create : myapp/bin/www

  然后安裝所有依賴包:

$ cd myapp 
$ npm instal

  啟動這個應用(MacOS 或 Linux 平台):

$ DEBUG=myapp npm start

  Windows 平台使用如下命令:

> set DEBUG=myapp & npm start

  然后在瀏覽器中打開 http://localhost:3000/ 網址就可以看到這個應用了。i

  通過 Express 應用生成器創建的應用一般都有如下目錄結構:

.
├── app.js
├── bin
│   └── www
├── package.json
├── public
│   ├── images
│   ├── javascripts
│   └── stylesheets
│       └── style.css
├── routes
│   ├── index.js
│   └── users.js
└── views
    ├── error.jade
    ├── index.jade
    └── layout.jade

7 directories, 9 files

 

HTTP模塊

  Express框架建立在node.js內置的http模塊上。http模塊生成服務器的原始代碼如下

var http = require("http");

var app = http.createServer(function(request, response) {
  response.writeHead(200, {"Content-Type": "text/plain"});
  response.end("Hello world!");
});

app.listen(3000, "localhost");

  上面代碼的關鍵是http模塊的createServer方法,表示生成一個HTTP服務器實例。該方法接受一個回調函數,該回調函數的參數,分別為代表HTTP請求和HTTP回應的request對象和response對象。

  Express框架的核心是對http模塊的再包裝。上面的代碼用Express改寫如下

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

app.get('/', function (req, res) {
  res.send('Hello world!');
});

app.listen(3000);

  比較兩段代碼,可以看到它們非常接近。原來是用http.createServer方法新建一個app實例,現在則是用Express的構造方法,生成一個Epress實例。兩者的回調函數都是相同的。Express框架等於在http模塊之上,加了一個中間層

 

中間件

【概述】

  Express 是一個自身功能極簡,完全是由路由和中間件構成一個的 web 開發框架:從本質上來說,一個 Express 應用就是在調用各種中間件

  簡單說,中間件(middleware)就是處理HTTP請求的函數。它最大的特點就是,一個中間件處理完,再傳遞給下一個中間件。App實例在運行過程中,會調用一系列的中間件

  每個中間件可以從App實例,接收三個參數,依次為request對象(代表HTTP請求)、response對象(代表HTTP回應),next回調函數(代表下一個中間件)。每個中間件都可以對HTTP請求(request對象)進行加工,並且決定是否調用next方法,將request對象再傳給下一個中間件

  中間件的功能包括:1、執行任何代碼;2、修改請求和響應對象;3、終結請求-響應循環;4、調用堆棧中的下一個中間件

  如果當前中間件沒有終結請求-響應循環,則必須調用 next() 方法將控制權交給下一個中間件,否則請求就會掛起

  一個不進行任何操作、只傳遞request對象的中間件,就是下面這樣

function uselessMiddleware(req, res, next) {
  next();
}

  上面代碼的next就是下一個中間件。如果它帶有參數,則代表拋出一個錯誤,參數為錯誤文本

function uselessMiddleware(req, res, next) {
  next('出錯了!');
}

  拋出錯誤以后,后面的中間件將不再執行,直到發現一個錯誤處理函數為止

【分類】

  Express 應用可使用如下幾種中間件:1、應用級中間件;2、路由級中間件;3、錯誤處理中間件;4、內置中間件;5、第三方中間件

  1、應用級中間件綁定到 app 對象 使用 app.use() 和 app.METHOD(),其中, METHOD 是需要處理的 HTTP 請求的方法,例如 GET, PUT, POST 等等,全部小寫

  2、路由級中間件綁定的對象為 express.Router()

  3、錯誤處理中間件和其他中間件定義類似,只是要使用 4 個參數,而不是 3 個,其簽名如下: (err, req, res, next)

app.use(function(err, req, res, next) {
  console.error(err.stack);
  res.status(500).send('Something broke!');
});

  4、express.static 是 Express 唯一內置的中間件。它基於 serve-static,負責在 Express 應用中提托管靜態資源

  5、通過使用第三方中間件從而為 Express 應用增加更多功能。安裝所需功能的 node 模塊,並在應用中加載,可以在應用級加載,也可以在路由級加載。下面的例子安裝並加載了一個解析 cookie 的中間件: cookie-parser

$ npm install cookie-parser
var express = require('express');
var app = express();
var cookieParser = require('cookie-parser');

// 加載用於解析 cookie 的中間件
app.use(cookieParser());

【use方法】

  use是express注冊中間件的方法,它返回一個函數。下面是一個連續調用兩個中間件的例子

var express = require("express");
var http = require("http");

var app = express();

app.use(function(request, response, next) {
  console.log("In comes a " + request.method + " to " + request.url);
  next();
});

app.use(function(request, response) {
  response.writeHead(200, { "Content-Type": "text/plain" });
  response.end("Hello world!\n");
});

http.createServer(app).listen(1337);

  上面代碼使用app.use方法,注冊了兩個中間件。收到HTTP請求后,先調用第一個中間件,在控制台輸出一行信息,然后通過next方法,將執行權傳給第二個中間件,輸出HTTP回應。由於第二個中間件沒有調用next方法,所以request對象就不再向后傳遞了。

  use方法內部可以對訪問路徑進行判斷,據此實現簡單的路由,根據不同的請求網址,返回不同的網頁內容

var express = require("express");
var http = require("http");

var app = express();

app.use(function(request, response, next) {
  if (request.url == "/") {
    response.writeHead(200, { "Content-Type": "text/plain" });
    response.end("Welcome to the homepage!\n");
  } else {
    next();
  }
});

app.use(function(request, response, next) {
  if (request.url == "/about") {
    response.writeHead(200, { "Content-Type": "text/plain" });
  } else {
    next();
  }
});

app.use(function(request, response) {
  response.writeHead(404, { "Content-Type": "text/plain" });
  response.end("404 error!\n");
});

http.createServer(app).listen(1337);

  上面代碼通過request.url屬性,判斷請求的網址,從而返回不同的內容。注意,app.use方法一共登記了三個中間件,只要請求路徑匹配,就不會將執行權交給下一個中間件。因此,最后一個中間件會返回404錯誤,即前面的中間件都沒匹配請求路徑,找不到所要請求的資源。

  除了在回調函數內部判斷請求的網址,use方法也允許將請求網址寫在第一個參數。這代表,只有請求路徑匹配這個參數,后面的中間件才會生效。無疑,這樣寫更加清晰和方便

app.use('/path', someMiddleware);

  上面代碼表示,只對根目錄的請求,調用某個中間件。

  因此,上面的代碼可以寫成下面的樣子

var express = require("express");
var http = require("http");

var app = express();

app.use("/home", function(request, response, next) {
  response.writeHead(200, { "Content-Type": "text/plain" });
  response.end("Welcome to the homepage!\n");
});

app.use("/about", function(request, response, next) {
  response.writeHead(200, { "Content-Type": "text/plain" });
  response.end("Welcome to the about page!\n");
});

app.use(function(request, response) {
  response.writeHead(404, { "Content-Type": "text/plain" });
  response.end("404 error!\n");
});

http.createServer(app).listen(1337);

 

托管靜態資源

  上面介紹了, express.static 是 Express 唯一內置的中間件,負責在 Express 應用中提托管靜態資源,例如圖片、CSS、JavaScript 文件等

express.static(root, [options])

  參數 root 指提供靜態資源的根目錄,可選的 options 參數擁有如下屬性

屬性          類型      缺省值     描述
dotfiles      String   “ignore”   是否對外輸出文件名以點開頭的文件。可選值為allow、deny和ignore
etag          Boolean   true     是否啟用 etag 生成
extensions    Array    []       設置文件擴展名備份選項
index          Mixed    “index.html” 發送目錄索引文件,設置為 false 禁用目錄索引。
lastModified    Boolean  true  設置Last-Modified頭為文件在操作系統上的最后修改日期。可選值為truefalse
maxAge          Number   0       以毫秒或者其字符串格式設置 Cache-Control 頭的 max-age 屬性。
redirect        Boolean  true     當路徑為目錄時,重定向至 “/”。
setHeaders      Function          設置 HTTP 頭以提供文件的函數。
var options = {
  etag: false,
  extensions: ['htm', 'html'],
  index: false,
  maxAge: '1d',
  redirect: false,
  setHeaders: function (res, path, stat) {
    res.set('x-timestamp', Date.now());
  }
}
app.use(express.static('public', options));

  一般地,如果不需要特殊的設置,將靜態資源文件所在的目錄作為參數傳遞給 express.static 中間件就可以提供靜態資源文件的訪問了。例如,假設在 public 目錄放置了圖片、CSS 和 JavaScript 文件

app.use(express.static('public'));

  現在,public 目錄下面的文件就可以訪問了

http://localhost:3000/images/kitten.jpg
http://localhost:3000/css/style.css
http://localhost:3000/js/app.js
http://localhost:3000/images/bg.png
http://localhost:3000/hello.html

  如果靜態資源存放在多個目錄下面,可以多次調用 express.static 中間件:

app.use(express.static('public'));
app.use(express.static('files'));

  訪問靜態資源文件時,express.static 中間件會根據目錄添加的順序查找所需的文件。

  如果希望所有通過 express.static 訪問的文件都存放在一個“虛擬(virtual)”目錄(即目錄根本不存在)下面,可以通過為靜態資源目錄指定一個掛載路徑的方式來實現,如下所示:

app.use('/static', express.static('public'));

  現在,可以通過帶有 “/static” 前綴的地址來訪問 public 目錄下面的文件了

http://localhost:3000/static/images/kitten.jpg
http://localhost:3000/static/css/style.css
http://localhost:3000/static/js/app.js
http://localhost:3000/static/images/bg.png
http://localhost:3000/static/hello.html

 

常用中間件

【cookie-parser()】

  用於解析cookie的中間件,添加中間后,req具備cookies屬性。通過req.cookies.xxx可以訪問cookie的值

$ npm install cookie-parser
var cookieParser = require('cookie-parser')
app.use(cookieParser(secret, options))

  secret 是可選參數,用於對cookie進行簽名 ,通過它可以判斷出客戶是否修改了cookie,這是處於安全考慮,這個參數是任意字符串

  options 可選參數,是一個json對象,可選項包括path、expires、maxAge、domain、secure、httpOnly

var express      = require('express')
var cookieParser = require('cookie-parser')
 
var app = express()
app.use(cookieParser())
 
app.get('/', function(req, res) {
  console.log('Cookies: ', req.cookies)
})
 
app.listen(8080)

【express-session】

  session運行在服務器端,當客戶端第一次訪問服務器時,可以將客戶的登錄信息保存。 當客戶訪問其他頁面時,可以判斷客戶的登錄狀態,做出提示,相當於登錄攔截。session可以和Redis或者數據庫等結合做持久化操作,當服務器掛掉時也不會導致某些客戶信息(購物車)丟失。

  當瀏覽器訪問服務器並發送第一次請求時,服務器端會創建一個session對象,生成一個類似於key,value的鍵值對, 然后將key(cookie)返回到瀏覽器(客戶)端,瀏覽器下次再訪問時,攜帶key(cookie),找到對應的session(value)。客戶的信息都保存在session中

$ npm install express-session
var express = require('express')
var session = require('express-session')
var app = express()
app.use(session(options))

  options 常用選項如下:

  name - 默認'connect.sid',可自定義

  store - session 儲存器實例

  secret - 用於對cookie進行簽名 ,通過它可以判斷出客戶是否修改了cookie,這是處於安全考慮,這個參數是任意字符串

  cookie - 對session cookie的設置 。默認值 { path: '/', httpOnly: true, secure: false, maxAge: null }

  genid -  是個函數,調用它來生成一個新的會話ID。 (默認:使用UID2庫)

  rolling -  強制對每個響應的Cookie,重置到期日期。 (默認:false)

  resave - 每一次都重新保存,即使沒修改過(默認:true)

  proxy - ture/false,是否支持trust proxy,,需要設置 app.enable('trust proxy');一般來說,無需設置

  常用方法如下:

  Session.destroy() :刪除session,當檢測到客戶端關閉時調用

  Session.reload() :當session有修改時,刷新session

  Session.regenerate() :將已有session初始化

  Session.save() :保存session

var express = require('express');
var cookieParser = require('cookie-parser');
var session = require('express-session');
 
app.use(cookieParser('sessiontest'));
app.use(session({
 secret: 'sessiontest',//與cookieParser中的一致
 resave: true,
 saveUninitialized:true
}));
//修改router/index.js,第一次請求時保存一條用戶信息。
router.get('/', function(req, res, next) {
 var user={
  name:"Chen-xy",
  age:"22",
  address:"bj"
 }
 req.session.user=user;
 res.render('index', {
  title: 'the test for nodejs session' ,
  name:'sessiontest'
 });
});
//修改router/users.js,判斷用戶是否登陸。
router.get('/', function(req, res, next) {
 if(req.session.user){
  var user=req.session.user;
  var name=user.name;
  res.send('你好'+name+',歡迎來到我的家園。');
 }else{
  res.send('你還沒有登錄,先登錄下再試試!');
 }
});

【serve-favicon】

  設置網站的 favicon圖標

$ npm install serve-favicon
var express = require('express')
var favicon = require('serve-favicon')
var path = require('path')
 
var app = express()
app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')))
 
// Add your routes here, etc. 
 
app.listen(3000)

【body-parser】

  bodyParser用於解析客戶端請求的body中的內容,內部使用JSON編碼處理,url編碼處理以及對於文件的上傳處理

$ npm install body-parser
var bodyParser = require('body-parser')

  1、底層中間件用法:這將攔截和解析所有的請求;也即這種用法是全局的。

var express = require('express')
var bodyParser = require('body-parser')
  
var app = express()
  
// parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: false }))
  
// parse application/json
app.use(bodyParser.json())
  
app.use(function (req, res) {
 res.setHeader('Content-Type', 'text/plain')
 res.write('you posted:\n')
 res.end(JSON.stringify(req.body, null, 2))
})

  use方法調用body-parser實例;且use方法沒有設置路由路徑;這樣的body-parser實例就會對該app所有的請求進行攔截和解析

  2、特定路由下的中間件用法:這種用法是針對特定路由下的特定請求的,只有請求該路由時,中間件才會攔截和解析該請求;也即這種用法是局部的;也是最常用的一個方式

var express = require('express')
var bodyParser = require('body-parser')
  
var app = express()
  
// create application/json parser
var jsonParser = bodyParser.json()
  
// create application/x-www-form-urlencoded parser
var urlencodedParser = bodyParser.urlencoded({ extended: false })
  
// POST /login gets urlencoded bodies
app.post('/login', urlencodedParser, function (req, res) {
 if (!req.body) return res.sendStatus(400)
 res.send('welcome, ' + req.body.username)
})
  
// POST /api/users gets JSON bodies
app.post('/api/users', jsonParser, function (req, res) {
 if (!req.body) return res.sendStatus(400)
 // create user in req.body
})

  express的post(或者get)方法調用body-parser實例;且該方法有設置路由路徑;這樣的body-parser實例就會對該post(或者get)的請求進行攔截和解析

  3、設置Content-Type 屬性;用於修改和設定中間件解析的body內容類型

// parse various different custom JSON types as JSON
app.use(bodyParser.json({ type: 'application/*+json' });
 
// parse some custom thing into a Buffer
app.use(bodyParser.raw({ type: 'application/vnd.custom-type' }));
 
// parse an HTML body into a string
app.use(bodyParser.text({ type: 'text/html' }));

【morgan】

  Mogran是一個node.js關於http請求的express默認的日志中間件

npm install  morgan

  在basic.js中添加如下代碼

var express = require('express');
var app = express();
var morgan = require('morgan');

app.use(morgan('short'));
app.use(function(req, res, next){
    res.send('ok');
});

app.listen(3000);

  node basic.js運行程序,並在瀏覽器里訪問 http://127.0.0.1:3000 ,打印日志如下

::1 - GET / HTTP/1.1 200 2 - 3.157 ms
::1 - GET / HTTP/1.1 304 - - 0.784 ms

  morgan支持stream配置項,可以通過它來實現將日志落地的效果,代碼如下:

var express = require('express');
var app = express();
var morgan = require('morgan');
var fs = require('fs');
var path = require('path');

var accessLogStream = fs.createWriteStream(path.join(__dirname, 'access.log'), {flags: 'a'});

app.use(morgan('short', {stream: accessLogStream}));
app.use(function(req, res, next){
    res.send('ok');
});

app.listen(3000);

  morgan的API非常少,使用頻率最高的就是morgan(),作用是返回一個express日志中間件

morgan(format, options)

  參數說明如下:

  format:可選,morgan與定義了幾種日志格式,每種格式都有對應的名稱,比如combined、short等,默認是default

  options:可選,配置項,包含stream(常用)、skip、immediate

  stream:日志的輸出流配置,默認是process.stdout

  skip:是否跳過日志記錄

  immediate:布爾值,默認是false。當為true時,一收到請求,就記錄日志;如果為false,則在請求返回后,再記錄日志

 

路由

【路由方法】

  針對不同的請求,Express提供了use方法的一些別名,這些別名是和 HTTP 請求對應的路由方法: getpostputheaddeleteoptionstracecopylockmkcolmovepurgepropfindproppatchunlockreportmkactivitycheckoutmergem-searchnotifysubscribeunsubscribepatchsearch 和 connect

  app.all() 是一個特殊的路由方法,沒有任何 HTTP 方法與其對應,它的作用是對於一個路徑上的所有請求加載中間件

  有些路由方法名不是合規的 JavaScript 變量名,此時使用括號記法,比如 app['m-search']('/', function ...

var express = require("express");
var http = require("http");
var app = express();

app.all("*", function(request, response, next) {
  response.writeHead(200, { "Content-Type": "text/plain" });
  next();
});

app.get("/", function(request, response) {
  response.end("Welcome to the homepage!");
});

app.get("/about", function(request, response) {
  response.end("Welcome to the about page!");
});

app.get("*", function(request, response) {
  response.end("404!");
});

http.createServer(app).listen(1337);

  上面代碼的all方法表示,所有請求都必須通過該中間件,參數中的“*”表示對所有路徑有效。get方法則是只有GET動詞的HTTP請求通過該中間件,它的第一個參數是請求的路徑。由於get方法的回調函數沒有調用next方法,所以只要有一個中間件被調用了,后面的中間件就不會再被調用了

【路由路徑】

  路由方法的第一個參數,都是請求的路徑,稱為路由路徑,它可以是字符串、字符串模式或者正則表達式

  1、字符串匹配

// 匹配 /about 路徑的請求
app.get('/about', function (req, res) {
  res.send('about');
});

  2、字符串模式匹配

// 匹配 acd 和 abcd
app.get('/ab?cd', function(req, res) {
  res.send('ab?cd');
});

  3、正則表達式匹配

// 匹配任何路徑中含有 a 的路徑:
app.get(/a/, function(req, res) {
  res.send('/a/');
});

【路由句柄】

  可以為請求處理提供多個回調函數,其行為類似中間件。唯一的區別是這些回調函數可能調用 next('route') 方法而略過其他路由回調函數。可以利用該機制為路由定義前提條件,如果在現有路徑上繼續執行沒有意義,則可將控制權交給剩下的路徑

  路由句柄有多種形式,可以是一個函數、一個函數數組,或者是兩者混合

  1、使用一個回調函數處理路由

app.get('/example/a', function (req, res) {
  res.send('Hello from A!');
});

  2、使用多個回調函數處理路由

app.get('/example/b', function (req, res, next) {
  console.log('response will be sent by the next function ...');
  next();
}, function (req, res) {
  res.send('Hello from B!');
});

  3、使用回調函數數組處理路由

var cb0 = function (req, res, next) {
  console.log('CB0');
  next();
}
var cb1 = function (req, res, next) {
  console.log('CB1');
  next();
}
var cb2 = function (req, res) {
  res.send('Hello from C!');
}
app.get('/example/c', [cb0, cb1, cb2]);

  4、混合使用函數和函數數組處理路由

var cb0 = function (req, res, next) {
  console.log('CB0');
  next();
}
var cb1 = function (req, res, next) {
  console.log('CB1');
  next();
}
app.get('/example/d', [cb0, cb1], function (req, res, next) {
  console.log('response will be sent by the next function ...');
  next();
}, function (req, res) {
  res.send('Hello from D!');
});

【鏈式路由句柄】

  可使用 app.route() 創建路由路徑的鏈式路由句柄。由於路徑在一個地方指定,這樣做有助於創建模塊化的路由,而且減少了代碼冗余和拼寫錯誤

app.route('/book')
  .get(function(req, res) {
    res.send('Get a random book');
  })
  .post(function(req, res) {
    res.send('Add a book');
  })
  .put(function(req, res) {
    res.send('Update the book');
  });

 

路由器實例

  從Express 4.0開始,路由器功能成了一個單獨的組件Express.Router。它好像小型的express應用程序一樣,有自己的use、get、param和route方法

  可使用 express.Router 類創建模塊化、可掛載的路由句柄。Router 實例是一個完整的中間件和路由系統,因此常稱其為一個 “mini-app”

【基本用法】

  首先,Express.Router是一個構造函數,調用后返回一個路由器實例。然后,使用該實例的HTTP動詞方法,為不同的訪問路徑,指定回調函數;最后,掛載到某個路徑

var express = require('express');
var router = express.Router();
router.get('/', function(req, res) {
  res.send('首頁');
});
router.get('/about', function(req, res) {
  res.send('關於');
});
app.use('/', router);

  上面代碼先定義了兩個訪問路徑,然后將它們掛載到根目錄。如果最后一行改為app.use(‘/app’, router),則相當於為/app/app/about這兩個路徑,指定了回調函數。

  這種路由器可以自由掛載的做法,為程序帶來了更大的靈活性,既可以定義多個路由器實例,也可以為將同一個路由器實例掛載到多個路徑

【router.route方法】

  router實例對象的route方法,可以接受訪問路徑作為參數

var express = require('express');
var router = express.Router();
router.route('/api')
    .post(function(req, res) {
        // ...
    })
    .get(function(req, res) {
        Bear.find(function(err, bears) {
            if (err) res.send(err);
            res.json(bears);
        });
    });
app.use('/', router);

【router中間件】

  use方法為router對象指定中間件,在數據正式發給用戶之前,對數據進行處理。下面是一個中間件的例子

router.use(function(req, res, next) {
    console.log(req.method, req.url);
    next();    
});

  上面代碼中,回調函數的next參數,表示接受其他中間件的調用。函數體中的next(),表示將數據傳遞給下一個中間件

  [注意]中間件放置順序很重要,等同於執行順序。而且,中間件必須放在HTTP動詞方法之前,否則不會執行

【對路徑參數的處理】

  router對象的param方法用於路徑參數的處理

router.param('name', function(req, res, next, name) {
    // 對name進行驗證或其他處理……
    console.log(name);
    req.name = name;
    next();    
});
router.get('/hello/:name', function(req, res) {
    res.send('hello ' + req.name + '!');
});

  上面代碼中,get方法為訪問路徑指定了name參數,param方法則是對name參數進行處理

  [注意]param方法必須放在HTTP動詞方法之前

【實例】

  下面的實例程序創建了一個路由模塊,並加載了一個中間件,定義了一些路由,並且將它們掛載至應用路徑上

  在 app 目錄下創建名為 birds.js 的文件,內容如下:

var express = require('express');
var router = express.Router();

// 該路由使用的中間件
router.use(function timeLog(req, res, next) {
  console.log('Time: ', Date.now());
  next();
});
// 定義網站主頁的路由
router.get('/', function(req, res) {
  res.send('Birds home page');
});
// 定義 about 頁面的路由
router.get('/about', function(req, res) {
  res.send('About birds');
});

module.exports = router;

  然后在應用中加載路由模塊:

var birds = require('./birds');
...
app.use('/birds', birds);

  應用即可處理發自 /birds 和 /birds/about 的請求,並且調用為該路由指定的 timeLog 中間件

 

響應方法

  response對象包含以下9個方法,response對象的方法向客戶端返回響應,終結請求響應的循環。如果在路由句柄中一個方法也不調用,來自客戶端的請求會一直掛起

方法           描述
res.download()    提示下載文件。
res.end()        終結響應處理流程。
res.json()       發送一個 JSON 格式的響應。
res.jsonp()      發送一個支持 JSONP 的 JSON 格式的響應。
res.redirect()    重定向請求。
res.render()     渲染視圖模板。
res.send()       發送各種類型的響應。
res.sendFile()    以八位字節流的形式發送文件。
res.sendStatus()  設置響應狀態代碼,並將其以字符串形式作為響應體的一部分發送。

  1、response.download方法

//下載路徑為'/report-12345.pdf'的文件
res.download('/report-12345.pdf');

//下載路徑為'/report-12345.pdf'的文件,並將文件命名為 'report.pdf'
res.download('/report-12345.pdf', 'report.pdf');

//下載路徑為'/report-12345.pdf'的文件,將文件命名為 'report.pdf',並且回調
res.download('/report-12345.pdf', 'report.pdf', function(err){
  if (err) {
  } else {
  }
});

  2、response.end方法

//終結響應處理流程
res.end();
//設置響應碼為404,並終結響應處理流程
res.status(404).end();

  3、response.json方法

res.json(null)
res.json({ user: 'tobi' })
res.status(500).json({ error: 'message' })

  4、response.jsonp方法

res.jsonp(null)
res.jsonp({ user: 'tobi' })
res.status(500).jsonp({ error: 'message' })

  5、response.redirect方法

res.redirect('/foo/bar');
res.redirect('http://example.com');
res.redirect(301, 'http://example.com');
res.redirect('../login');

  6、response.render方法

res.render('index');
res.render('index', function(err, html) {
  res.send(html);
});
res.render('user', { name: 'Tobi' }, function(err, html) {
  // ...
});

  7、response.send方法

res.send(new Buffer('whoop'));
res.send({ some: 'json' });
res.send('<p>some html</p>');
res.status(404).send('Sorry, we cannot find that!');
res.status(500).send({ error: 'something blew up' });

  8、response.sendFile方法

response.sendFile("/path/to/anime.mp4");

  9、response.sendStatus方法

res.sendStatus(200); // 'OK'
res.sendStatus(403); // 'Forbidden'
res.sendStatus(404); // 'Not Found'
res.sendStatus(500); // 'Internal Server Error'

 

請求方法

【req.params】

// GET /user/tj
req.params.name
// => "tj"

// GET /file/javascripts/jquery.js
req.params[0]
// => "javascripts/jquery.js"

【req.query】

// 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.body】

// POST user[name]=tobi&user[email]=tobi@learnboost.com
req.body.user.name
// => "tobi"

req.body.user.email
// => "tobi@learnboost.com"

// POST { "name": "tobi" }
req.body.name
// => "tobi"

【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"

【req.cookies】

// Cookie: name=tj
req.cookies.name
// => "tj"

【req.ip】

req.ip
// => "127.0.0.1"

req.path

// example.com/users?sort=desc
req.path
// => "/users"

【req.host】

// Host: "example.com:3000"
req.host
// => "example.com"

 

app方法

【set方法】

  set方法用於指定變量的值

app.set("views", __dirname + "/views");
app.set("view engine", "jade");

  上面代碼使用set方法,為系統變量“views”和“view engine”指定值

【get方法】

  除了作為use()方法的別名用法外,get方法還用於獲取變量的值,與set方法相對應

app.get('title');
// => undefined

app.set('title', 'My Site');
app.get('title');
// => "My Site"

【app.enable(name)】

  將設置項 name 的值設為 true 

app.enable('trust proxy');
app.get('trust proxy');
// => true

【app.disable(name)】

  將設置項 name 的值設為 false 

app.disable('trust proxy');
app.get('trust proxy');
// => false

【app.enabled(name)】

  檢查設置項 name 是否已啟用

app.enabled('trust proxy');
// => false

app.enable('trust proxy');
app.enabled('trust proxy');
// => true

【app.disabled(name)】

  檢查設置項 name 是否已禁用

app.disabled('trust proxy');
// => true

app.enable('trust proxy');
app.disabled('trust proxy');
// => false

【app.engine(ext, callback)】

  注冊模板引擎的 callback 用來處理 ext 擴展名的文件

  默認情況下, 根據文件擴展名 require() 加載相應的模板引擎。 比如想渲染一個 “foo.jade” 文件,Express 會在內部執行下面的代碼,然后會緩存 require() ,這樣就可以提高后面操作的性能

app.engine('jade', require('jade').__express);

  那些沒有提供 .__express 的或者想渲染一個文件的擴展名與模板引擎默認的不一致的時候,也可以用這個方法。比如想用EJS模板引擎來處理 “.html” 后綴的文件:

app.engine('html', require('ejs').renderFile);

  這個例子中 EJS 提供了一個 .renderFile() 方法和 Express 預期的格式: (path, options, callback) 一致, 因此可以在內部給這個方法取一個別名 ejs.__express ,這樣就可以使用 “.ejs” 擴展而不需要做任何改動

  有些模板引擎沒有遵循這種轉換, 這里有一個小項目 consolidate.js專門把所有的node流行的模板引擎進行了包裝,這樣它們在 Express 內部看起來就一樣了。

var engines = require('consolidate');
app.engine('haml', engines.haml);
app.engine('html', engines.hogan);

【app.locals】

  應用程序本地變量會附加給所有的在這個應用程序內渲染的模板。這是一個非常有用的模板函數,就像應用程序級數據一樣

app.locals.title = 'My App';
app.locals.strftime = require('strftime');

  app.locals 對象是一個 JavaScript Function,執行的時候它會把屬性合並到它自身,提供了一種簡單展示已有對象作為本地變量的方法。

app.locals({
  title: 'My App',
  phone: '1-250-858-9990',
  email: 'me@myapp.com'
});

app.locals.title
// => 'My App'

app.locals.email
// => 'me@myapp.com'

  app.locals 對象最終會是一個 Javascript 函數對象,不可以使用 Functions 和 Objects 內置的屬性,比如 name、apply、bind、call、arguments、length、constructor。

app.locals({name: 'My App'});

app.locals.name
// => 返回 'app.locals' 而不是 'My App' (app.locals 是一個函數 !)
// => 如果 name 變量用在一個模板里,則返回一個 ReferenceError 

  默認情況下Express只有一個應用程序級本地變量,它是 settings

app.set('title', 'My App');
// 在 view 里使用 settings.title

【app.render(view, [options], callback)】

  渲染 view , 回調函數 callback 用來處理返回的渲染后的字符串。這個是 res.render() 的應用程序級版本,它們的行為是一樣的。

app.render('email', function(err, html){
    // ...
});

app.render('email', { name: 'Tobi' }, function(err, html){
    // ...
});

【app.listen()】

  在給定的主機和端口上監聽請求,這個和 node 文檔中的 http.Server#listen() 是一致的。

var express = require('express');
var app = express();
app.listen(3000);

  express() 返回的 app 實際上是一個 JavaScript Function,它被設計為傳給 node 的 http servers 作為處理請求的回調函數。因為 app 不是從 HTTP 或者 HTTPS 繼承來的,它只是一個簡單的回調函數,可以以同一份代碼同時處理 HTTP 和 HTTPS 版本的服務。

var express = require('express');
var https = require('https');
var http = require('http');
var app = express();

http.createServer(app).listen(80);
https.createServer(options, app).listen(443);

  app.listen() 方法只是一個快捷方法,如果想使用 HTTPS ,或者同時提供 HTTP 和 HTTPS ,可以使用上面的代碼。

app.listen = function(){
  var server = http.createServer(this);
  return server.listen.apply(server, arguments);
};

 

HTTPS

  使用Express搭建HTTPS加密服務器很簡單

var fs = require('fs');
var options = {
  key: fs.readFileSync('E:/ssl/myserver.key'),
  cert: fs.readFileSync('E:/ssl/myserver.crt'),
  passphrase: '1234'
};

var https = require('https');
var express = require('express');
var app = express();

app.get('/', function(req, res){
  res.send('Hello World Expressjs');
});

var server = https.createServer(options, app);
server.listen(8084);
console.log('Server is running on port 8084');

 

模板引擎

  需要在應用中進行如下設置才能讓 Express 渲染模板文件:

  views, 放模板文件的目錄,比如: app.set('views', './views')

  view engine, 模板引擎,比如: app.set('view engine', 'jade')

  然后安裝相應的模板引擎 npm 軟件包

$ npm install jade --save

  一旦 view engine 設置成功,就不需要顯式指定引擎,或者在應用中加載模板引擎模塊,Express 已經在內部加載,如下所示

app.set('view engine', 'jade');

  在 views 目錄下生成名為 index.jade 的 Jade 模板文件,內容如下:

html
  head
    title!= title
  body
    h1!= message

  然后創建一個路由渲染 index.jade 文件。如果沒有設置 view engine,需要指明視圖文件的后綴,否則就會遺漏它

app.get('/', function (req, res) {
  res.render('index', { title: 'Hey', message: 'Hello there!'});
});

  此時向主頁發送請求,“index.jade” 會被渲染為 HTML

 

數據庫

  為 Express 應用添加連接數據庫的能力,只需要加載相應數據庫的 Node.js 驅動即可。這里簡要介紹如何為 Express 應用添加和使用一些常用的數據庫 Node 模塊

【mysql】

$ npm install mysql
var mysql      = require('mysql');
var connection = mysql.createConnection({
  host     : 'localhost',
  user     : 'dbuser',
  password : 's3kreee7'
});

connection.connect();

connection.query('SELECT 1 + 1 AS solution', function(err, rows, fields) {
  if (err) throw err;
  console.log('The solution is: ', rows[0].solution);
});

connection.end();

【MongoDB】

$ npm install mongoskin
var db = require('mongoskin').db('localhost:27017/animals');

db.collection('mamals').find().toArray(function(err, result) {
  if (err) throw err;
  console.log(result);
});

 

上傳文件

  首先,在網頁插入上傳文件的表單

<form action="/pictures/upload" method="POST" enctype="multipart/form-data">
  Select an image to upload:
  <input type="file" name="image">
  <input type="submit" value="Upload Image">
</form>

  然后,服務器腳本建立指向/upload目錄的路由。這時可以安裝multer模塊,它提供了上傳文件的許多功能

var express = require('express');
var router = express.Router();
var multer = require('multer');
var uploading = multer({
  dest: __dirname + '../public/uploads/',
  // 設定限制,每次最多上傳1個文件,文件大小不超過1MB
  limits: {fileSize: 1000000, files:1},
})
router.post('/upload', uploading, function(req, res) {})
module.exports = router

  上面代碼是上傳文件到本地目錄。下面是上傳到Amazon S3的例子。

  首先,在S3上面新增CORS配置文件

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
  <CORSRule>
    <AllowedOrigin>*</AllowedOrigin>
    <AllowedMethod>GET</AllowedMethod>
    <AllowedMethod>POST</AllowedMethod>
    <AllowedMethod>PUT</AllowedMethod>
    <AllowedHeader>*</AllowedHeader>
  </CORSRule>
</CORSConfiguration>

  上面的配置允許任意電腦向你的bucket發送HTTP請求。

  然后,安裝aws-sdk

$ npm install aws-sdk --save

  下面是服務器腳本

var express = require('express');
var router = express.Router();
var aws = require('aws-sdk');
router.get('/', function(req, res) {
  res.render('index')
})
var AWS_ACCESS_KEY = 'your_AWS_access_key'
var AWS_SECRET_KEY = 'your_AWS_secret_key'
var S3_BUCKET = 'images_upload'

router.get('/sign', function(req, res) {
  aws.config.update({accessKeyId: AWS_ACCESS_KEY, secretAccessKey: AWS_SECRET_KEY});
  var s3 = new aws.S3()
  var options = {
    Bucket: S3_BUCKET,
    Key: req.query.file_name,
    Expires: 60,
    ContentType: req.query.file_type,
    ACL: 'public-read'
  }
  s3.getSignedUrl('putObject', options, function(err, data){
    if(err) return res.send('Error with S3')
    res.json({
      signed_request: data,
      url: 'https://s3.amazonaws.com/' + S3_BUCKET + '/' + req.query.file_name
    })
  })
})
module.exports = router

  上面代碼中,用戶訪問/sign路徑,正確登錄后,會收到一個JSON對象,里面是S3返回的數據和一個暫時用來接收上傳文件的URL,有效期只有60秒。

  瀏覽器代碼如下

// HTML代碼為
// <br>Please select an image
// <input type="file" id="image">
// <br>
// <img id="preview">

document.getElementById("image").onchange = function() {
  var file = document.getElementById("image").files[0]
  if (!file) return
  sign_request(file, function(response) {
    upload(file, response.signed_request, response.url, function() {
      document.getElementById("preview").src = response.url
    })
  })
}
function sign_request(file, done) {
  var xhr = new XMLHttpRequest()
  xhr.open("GET", "/sign?file_name=" + file.name + "&file_type=" + file.type)
  xhr.onreadystatechange = function() {
    if(xhr.readyState === 4 && xhr.status === 200) {
      var response = JSON.parse(xhr.responseText)
      done(response)
    }
  }
  xhr.send()
}
function upload(file, signed_request, url, done) {
  var xhr = new XMLHttpRequest()
  xhr.open("PUT", signed_request)
  xhr.setRequestHeader('x-amz-acl', 'public-read')
  xhr.onload = function() {
    if (xhr.status === 200) {
      done()
    }
  }
  xhr.send(file)
}

  上面代碼首先監聽file控件的change事件,一旦有變化,就先向服務器要求一個臨時的上傳URL,然后向該URL上傳文件

 

開發實例

【靜態網頁模板】

  在項目目錄之中,建立一個子目錄views,用於存放網頁模板。

  假定這個項目有三個路徑:根路徑(/)、自我介紹(/about)和文章(/article)。那么,app.js可以這樣寫:

var express = require('express');
var app = express();
 
app.get('/', function(req, res) {
   res.sendfile('./views/index.html');
});
 
app.get('/about', function(req, res) {
   res.sendfile('./views/about.html');
});
 
app.get('/article', function(req, res) {
   res.sendfile('./views/article.html');
});
 
app.listen(3000);

  上面代碼表示,三個路徑分別對應views目錄中的三個模板:index.html、about.html和article.html。另外,向服務器發送信息的方法,從send變成了sendfile,后者專門用於發送文件。

  假定index.html的內容如下:

<html>
<head>
   <title>首頁</title>
</head>
<body>
<h1>Express Demo</h1>
<header>
<p>
   <a href="/">首頁</a> - <a href="/about">自我介紹</a> - <a href="/article">文章</a>
</p>
</header>
</body>
</html>

  about.html內容如下

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
about
</body>
</html>

  article.html內容如下

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    article
</body>
</html>

  運行app.js后,訪問http://localhost:3000/結果如下

  

 【動態網頁模板】

  下面來制作一個動態網頁網站,以使用ejs引擎為例

  npm install ejs

  將view engine修改為ejs,並將模板的后綴修改為.html

var express = require('express');
var app = express();
var ejs = require('ejs');

// 指定模板文件的后綴名為html
app.set('view engine', 'html');
//運行ejs引擎讀取html文件
app.engine('.html', ejs.__express);

app.get('/', function (req, res){
    res.render('index');
});

app.get('/about', function(req, res) {
    res.render('about');
});

app.get('/article', function(req, res) {
    res.render('article');
});

  接下來,新建數據腳本。渲染是指將數據代入模板的過程。實際運用中,數據都是保存在數據庫之中的,這里為了簡化問題,假定數據保存在一個腳本文件中

  在項目目錄中,新建一個文件blog.js,用於存放數據。blog.js的寫法符合CommonJS規范,使得它可以被require語句加載

// blog.js文件
var entries = [
    {"id":1, "title":"第一篇", "body":"正文", "published":"7/2/2017"},
    {"id":2, "title":"第二篇", "body":"正文", "published":"7/3/2017"},
    {"id":3, "title":"第三篇", "body":"正文", "published":"7/4/2017"},
    {"id":4, "title":"第四篇", "body":"正文", "published":"7/5/2017"},
    {"id":5, "title":"第五篇", "body":"正文", "published":"7/10/2017"},
    {"id":6, "title":"第六篇", "body":"正文", "published":"7/12/2017"}
];
exports.getBlogEntries = function (){
   return entries;
}
exports.getBlogEntry = function (id){
   for(var i=0; i < entries.length; i++){
      if(entries[i].id == id) return entries[i];
   }
}

  新建header.html和footer.html

<!-- views/header.html文件 -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title><%=title %></title>
</head>
<body>
    
<!-- views/footer.html文件 -->
   <footer>
      <p>
         <a href="/">首頁</a>
         <a href="/about">自我介紹</a>
      </p>
   </footer>      
</body>
</html>

  接着,新建模板文件index.html

<!-- views/index.html文件 -->
<% include header.html %>
<h1>文章列表</h1>
<% for(var i=0; i < entries.length; i++){  %>
    <p>
        <a href="/article/<%=entries[i].id %>"><%=entries[i].title %></a>
        <br>
          <span>時間: <%=entries[i].published %></span>        
    </p>
<% } %>  
<% include footer.html %>

  新建模板文件about.html

<!-- views/about.html文件 -->
<% include header.html %>
<h1><%=title %> </h1>
<p>正文</p>
<% include footer.html %>

  新建模板文件article.html

<!-- views/article.html文件 -->
<% include header.html %>
<h1><%=blog.title %></h1>
<p>時間: <%=blog.published %></p>
<p><%=blog.body %></p>
<% include footer.html %>

  最后,改寫app.js文件

var express = require('express');
var app = express();
var ejs = require('ejs');

// 加載數據模塊
var blogEngine = require('./blog');
 
app.set('view engine', 'html');
app.engine('html', ejs.__express);

app.get('/', function(req, res) {
   res.render('index',{title:"最近文章", entries:blogEngine.getBlogEntries()});
});
 
app.get('/about', function(req, res) {
   res.render('about', {title:"自我介紹"});
});
 
app.get('/article/:id', function(req, res) {
   var entry = blogEngine.getBlogEntry(req.params.id);
   res.render('article',{title:entry.title, blog:entry});
});
 
app.listen(3000);

  上面代碼中的render方法,現在加入了第二個參數,表示模板變量綁定的數據。

  現在重啟node服務器,然后訪問http://127.0.0.1:3000來查看結果

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM