Express 4.x Node.js的Web框架


為了防止無良網站的爬蟲抓取文章,特此標識,轉載請注明文章出處。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

wps_clip_image-23406

 

 

 

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         強迫接受非空目錄

wps_clip_image-23155

 

 

1.2.1 創建hello-express的工程

使用命令:express hello-express ,默認創建基於jade的項目。

wps_clip_image-32696

 

 

使用-e參數創建基於ejs的項目。使用命令:express -e hello-express

wps_clip_image-14881

 

 

在當前目錄下會創建新的目錄。

wps_clip_image-6317

 

目錄內容如下所示:

wps_clip_image-28261

 

進入到工程目錄

wps_clip_image-2331

 

 

1.2.2 安裝express及依賴

使用命令:npm install

wps_clip_image-21901

 

該過程會自動下載網絡上的依賴包,請耐心等待命令結束。創建包后,可查看目下的package.json文件:

wps_clip_image-2537

 

 

1.2.3 啟動工程

使用命令:npm start

wps_clip_image-4466

注意:該實例無法以 node app.js 為啟動方式,而是用指令 npm start 作為啟動。原因如下所述。

 

npm執行的時候會讀取當前目錄的package.json文件,這個也就是我上面那個bug出現的原因

執行npm start其實是執行package.json中的script對應的對象中的start屬性所對應的命令行。

wps_clip_image-30700

 

 

也就是執行bin/www文件,該文件無后綴名。使用文本工具打開,可以看到其中的腳本。npm start命令,就是執行的該腳本程序。

wps_clip_image-19564

 

 

express默認端口為3000。瀏覽器訪問 http://127.0.0.1:3000/ 。顯示頁面Welcome to Express,即安裝成功。

wps_clip_image-31123

 

 

 

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的顯示結果:

wps_clip_image-27354

 

 

 

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,可能需要使用如下模塊:

· busboyconnect-busboy

· multipartyconnect-multiparty

· formidable

· 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官方資料:

http://expressjs.com/

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更多的組件請在如下地址搜索:

https://github.com/expressjs

https://www.npmjs.org/

 

感謝如下博客作者為我們提供的學習資料:

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

 


免責聲明!

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



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