express框架

前面的話

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

 

概述

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

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

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

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

【安裝】mysql

  安裝express前,首先安裝nodejs,接下來爲應用建立一個目錄,而後進入此目錄並將其做爲當前工做目錄jquery

$ mkdir myapp
$ cd myapp

  經過 npm init 命令爲應用建立一個 package.json 文件git

$ npm init

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

entry point: (index.js)

  鍵入 app.js 或者所但願的名稱,這是當前應用的入口文件。若是但願採用默認的 index.js 文件名,只需按「回車」鍵便可web

  接下來安裝 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對象再傳給下一個中間件

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

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

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

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

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

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

  拋出錯誤之後,後面的中間件將再也不執行,直到發現一個錯誤處理函數爲止

【分類】

  Express 應用可以使用以下幾種中間件:一、應用級中間件;二、路由級中間件;三、錯誤處理中間件;四、內置中間件;五、第三方中間件

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

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

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

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

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

  五、經過使用第三方中間件從而爲 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')

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

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全部的請求進行攔截和解析

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

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)的請求進行攔截和解析

  三、設置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方法,因此只要有一箇中間件被調用了,後面的中間件就不會再被調用了

【路由路徑】

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

  一、字符串匹配

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

  二、字符串模式匹配

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

  三、正則表達式匹配

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

【路由句柄】

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

  路由句柄有多種形式,能夠是一個函數、一個函數數組,或者是二者混合

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

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

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

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!');
});

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

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]);

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

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()  設置響應狀態代碼,並將其以字符串形式做爲響應體的一部分發送。

  一、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 {
  }
});

  二、response.end方法

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

  三、response.json方法

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

  四、response.jsonp方法

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

  五、response.redirect方法

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

  六、response.render方法

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

  七、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' });

  八、response.sendFile方法

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

  九、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來查看結果

相關文章
相關標籤/搜索