Express 完整学习

The Truth is single one

作者 Haojen Ma 日期 2016-05-29
Express 完整学习

Express 是一个简洁而灵活的 node.js Web应用框架, 提供一系列强大特性帮助你创建各种Web应用.

express

安装

express 作为一个依赖,安装到项目中。你可以在项目根目录中直接使用

npm install express --save // --save 是保存到`packge.json dependencies`依赖里

或者直接在package.json dependencies下写入“express”:"*",然后在根目录下运行

npm install

安装完成后,你可以通过npm ls察看express的依赖。

创建一个express应用

创建一个express应用。

var express = require('express'); //加载exress
var app = exress(); //实例化一个express应用

// 在express上侦听get请求
app.get('/',function(request, response){
	response.send('hi, this express application!')
});

//监听服务器
app.listen(3000);

上述代码的作用是:建立一个express应用,当服务器侦测到get方法的/根目录请求时,会使用res.send方法返回给浏览器我们指定好的字符串,app.get()就是一个路由(routing) 而实际应用中,我们可以指定多个路由请求。

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

app.get('/'.function(){}) // 主页请求
app.get('user',function(){}) // user 请求
app.get('admin',function(){}) // admin 请求

app.listen(3000);

实际上,当我们挂在更多的路由时,我们会将其编写专门的路由文件,然后将它导入主项目,上面的路由我们可以写成下面这样

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

//将项目导出
module.exports = function(app){
	app.get('/'.function(){}) // 主页请求
	app.get('user',function(){}) // user 请求
	app.get('admin',function(){}) // admin 请求
}

然后我们再在主项目文件中调用它

//...省略部分代码
var routers = require('./routers') //路由目录
routers(app) //传入express 实例

app.lissten(3000)

路由

路由是指如何定义应用的端点URL以及如何响应处理客户端的请求 路由是由一个 url and http 请求方法和若干个句柄组成,它的结构如下:app.METHOD(path, [callback...], callback) app 是express对象的一个实例,METHOD是一个HTTP请求方法,path代表了服务器的路径,callback是当路由匹配时要执行的函数。

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

app.get('/',function(req, res){
	res.send('hi')
})

路由方法

路由方法源于http请求方法, 和express实例相关,实例如下:

app.get('/',function(req, res){
	res.send('send message')
})

app.post('/',function(req, res){
	res.send('is a POST send message')
})

app.all()

一个特殊的路由方法,没有任何http方法与其对应,它的作用是对于一个路径上的所有请求加载中间件,在下面的例子中,来自 /user 的请求,不管使用什么http请求方法,里面的句柄都会得到执行

app.all('/user',function(req, res, next){
	console.log('accessing the secret section...')
	next()
})

路由路径

路由路径和请求方法一起定义了请求的端点, 它可以是字符串、字符串模式或正则表达式

app.get('/',fn) //匹配根路径的get请求

app.get('/about',fn) //匹配 /about 路径的请求

app.get('/random.text',fn) //匹配random.text文件的路径请求

字符串模式匹配

app.get('/ab?cd',fn) //匹配 acd or abcd

app.get('/ab+cd') //匹配 abcd abbcd abbbcd ..

app.get('/ab*cd') //匹配 abcd abxcd ab123cd ..

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

字符 ?、+、* 和 () 是正则表达式的子集,- 和 . 在基于字符串的路径中按照字面值解释

路由句柄

可以为请求提供多个回调函数,路由句柄可以有多个型式:一个回调函数,回调函数数组,回调函数和函数数组混合。

var c0 = function(req,res,next){
	//do something...
	next()
}

var c2 = function(req,res,next){
	//do something...
	next()
}

app.get('/',function(req,res,next){
	// do something..
	next() //调用函数数组
},[c0,c1])

响应方法

下表中响应对象(res)的方法向客户端返回响应,终结请求响应的循环。如果在路由句柄中一个http方法都没调用,来自客户端的请求会一直挂起。

方法 描述
res.download() 提示下载文件
res.end() 结束响应
res.json() 发送一个JSON格式的响应
res.jsonp() 发送一个支持 JSONP 的 JSON 格式的响应
res.redircet() 重定向
res.send() 发送各种类型的响应
res.render() 渲染模板
res.sendFile 以八位字节流型式发送文件
res.sendStatus 发送浏览器状态码

app.route()

创建路由路径的链式路由句柄。由于路径在一个地方指定,这样做有助于创建模块化的路由,而且减少了代码冗余和拼写错误。

app.route('/user') //指定路径
	.get(function(req,res){})
	.post(function(req,res){})
	.put(function(req,res){})

express.Router()

可使用 express.Router 类创建模块化、可挂载的路由句柄。Router 实例是一个完整的中间件和路由系统,因此常称其为一个 “mini-app”。

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

router.use(function(req,res,next){
	//do something
	next()
})

router.get('/',function(req,res,next){})
router.get('/about',function(req,res,next){})

moudle.exports = router

在 index.js 入口文件里导入 router 文件

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

var router = require('./router')
// 使用
app.use('/admin',router)

中间件

中间件就是处理HTTP请求的函数,其特点是一个中间件处理完,再传递给下一个中间件。APP在运行的过程中,会调用一系列的中间件。 *中间件(Middleware)*是一个函数,它可以访问请求对象(request object),和响应对象(response object),一般按照约定,我们在调用时会依次简写成 reqres,另一个回调参数命名为next,用来将http对象传递给下一个中间件。

function demoMiddleware(req,res,next){
	next() //将request请求对象传递给下一个中间件
	next('some value') //如果next中间件里带有参数,则代表抛出一个错误,且后续的中间件不会运行,直到该错误有被函数处理为之。
}

中间件的功能包括:

  • 执行任何代码
  • 修改请求和响应对象
  • 终结请求-响应循环
  • 调用堆栈中的下一个中间件

需要注意的是,如果当前中间件没有终结请求-响应循环,则必须调用next()方法将控制权交给下一个中间件,否则请求就会一直挂起。

Express 中间件的种类

  • 应用级中间件
  • 路由级中间件
  • 错误处理中间件
  • 内置中间件
  • 第三方中间件

应用级中间件

应用级中间件绑定在 app 对象,使用app.use()和app.METHOD(),METHOD 是需要处理的HTTP 请求的方法,例如:GET, PUT, POST 等。例如:

var app = express();

// 没有挂载路径的中间件, 应用的**每个**请求都会执行该中间件。
app.use(function(req, res, next){
	console.log('应用的每个请求都会执行该中间件')
	next() //next方法将中间件完整的传递给下一个中间件。
})

// 挂载至 /user/:id 的中间件,任何指向 /user/:id 的请求都会执行它
app.use('/user/:id',function(req, res, next){
	console.log('request type:', req.method)
	next()
})

// 一个路由和句柄函数,处理指向 /user/:id 的 GET 请求
app.get('/user/:id', function(req, res, next){
	res.send('USER')
})

装载一组中间件:

app.use('/user/:id',function(req, res, next){
	//do something
	next() //调用下一个中间件
},function(req, res, next){
	res.send('user info'); 
})

给同一个请求路径定义多个路由:

app.get('/user/:id',function(req, res, next){
	//do something
	next()
})
	
app.get('user/:id',function(req,res,next){
	res.end('do something')
})

但是注意,下面这个例子的第二个路由永远不会被调用

app.get('/user/:id',function(req, res, next){
	//do something
	next()
},function(req,res,next){
	res.send('responsed End!')
})

// 下面的代码永远不会被调用,因为上述GET请求的第二个中间件已经结束了请求-响应循环。
app.get('user/:id',function(req,res,next){
	res.end('do something')
})

在中间件栈中跳过剩余中间件的方法:在 next() 方法里传入 'route'字符串参数。next('route')只对使用 app.VERB() or router.VERB()加载的中间件有效。

如果在 next()里传入其它参数(除了「router」字符串),当前next后面的其它非错误处理路由/中间件函数不会被调用,next里的参数也会被当成错误信息抛出。如果需要对错误进行处理,需要创建新的错误处理函数路由。

app.get('/user/:id',function(req, res, next){
	//do something
	next('route')
},function(req, res){
	//这里永远不会调用
})
	
//第一个路径请求执行后,会执行这个请求
app.get('user/:id',function(req,res,next){
	res.end('do something')
})

路由级中间件

路由级中间件和应用级中间件一样,只是它绑定的对象为 express.Router()

var router = express.Router()

路由级使用 router.use() 或 router.VERB() 加载。

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

var router = express.Router()

router.get('/foo',function(req,res,next){}) //..

挂载到应用

app.use(router)
//OR
app.use('/user',router) //输出路径为 /user/foo

错误处理中间件

错误处理中间件和其它中间件定义类似,只是需要四个参数,而不是三个。其参数如下:(err,req,res,next)

错误处理中间件必须有上述四个参数,即便不需要也得声明它,否则中间件会被识别成一个常规中间件,不能处理错误。

app.use(function(err,req,res,next){
	//do something..
	res.status(500).send('something broke') //返回500状态码和一个错误信息
})

express 内置了一个错误处理句柄,它可以捕获应用中可能出现的任意错误。这个缺省的错误处理中间件将被添加到中间件堆栈的底部。如果你向next()传递了一个error,而你并没有在错误处理句柄中处理这个error,express内置的默认错误处理句柄就是最后兜底的。最后错误将被连同堆栈追踪信息一同反馈开发模式下的客户端。

设置环境变量 NODE_ENV 为 production 就可以让应用运行在生产环境模式下

内置中间件

从 4.x 版本开始, Express 已经不再依赖 Connect 了。除了 express.static ,Express 以前内置的中间件现在已经全部单独作为模块安装使用了。详情请参阅 中间件列表

express.static(root,[options])

express.static is express onl one inside middleware. 它基于 serve-static,负责在 express 应用中提供托管静态资源。 可选的 options 参数如下:

  • dotfiles
  • etag
  • extensions
  • index
  • lastModified
  • maxAge
  • redirect
  • setHeaders

第三方中间件

通过使用第三方中间件为express应用增加更多功能。

使用方式:

npm install cookie-parser --save //安装一个express插件 ,该插件用来解析cookie

//..省略引入代码
var cookieParser = require('cookie-parser');

app.use(cookieParser()) //加载

总结

我理解的中间件

中间件分出路由级中间件的好处就是可以更好的将路由整合成一个单独的模块,便于管理。

Express 模板引擎

设置模板引擎

  • views 放模板文件的目录,app.set('views','./views')
  • view engine 设置模板引擎, app.set('view engine','jade') //jade or ejs

安装

npm install jade --save

引用

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

使用

app.get('/',function(req, res, next){
// 需要注意的是 如果没有设置 view engine ,需要指定 render path 模板的后缀名,用来确定使用的模板引擎
	res.render('index',{
		//模板变量
	}) 
})

HTTP 请求对象

request 对象代表http请求,及请求中的查询字符串、请求体、http头等。request对象继承自node.js的incomingMessage对象,该对象中的属性,事件,方法,在request对象中都可以使用。我们可以在路由处理器(回调函数)中访问这个对象。

属性

req.app

req.app 是对express application实例的引用,通过这个属性,我们可以访问app对象中的设置、属性、中间件等。

如果我们创建了一个模块,如果我们仅在exports导出了中间件函数,且在主文件中require()引用了它,那么我们可以在express的req.app实例中访问这个文件。

//index.js
app.get('/somepath',require('./test.js'))

//test.js
module.exports = function(req,res){
	res.send('send something..')
}

req.baseUrl

路由实例被挂载后的URL路径。

req.body

表示请求体的一个key-value数据对象。默认值是undeifined,其在body-parser和multer等body解析后可访问。

意味着如果你需要解析req.body里面的数据,你需要先使用中间件将其解析后才能使用。

//..省略部分代码
var bodyParser = require('body-parser')

var multer = require('multer') // 尚未用过

// use then
app.use(bodyParser.json()) //解析JSON数据

req.cookies

当使用cookie-parser中间件解析cookie后,req.cookies属性是一个表示cookie的key-value对象。没有使用cookie时,其值为{}

req.fresh

新鲜度判断,待补充

req.hostname

请求的主机名

req.ip

请求的主机IP

req.ips

返回一个IP数组

req.method

返回请求调用的方法: get、post、put、delete 等等..

req.originaUrl

原始url

req.params

这是一个表示路径参数的对象。如,我们使用/user/:name路径时,那么req.params.name表示路径中的:name属性。该对象默认值为{}。

app.get('/user/:someUser',function(req,res,next){
	console.log(req.parmas.someUser) 
})

req.path

表示请求中的路径部分

// www.haojen.com/admin?password=abcd
app.get('/login',function(req,res,next){
	console.log(req.path) // “admin”
})

req.protocol

表示http请求的协议类型

req.query

这个属性表示URL查询字符串('?'之后的部分)的key-value对象。如果请求中不包括查询字符串,这个属性的值为{}。

// 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.route

显示当前匹配的路径路由信息

req.secure

表示是否使用TLS连接的一个布尔值。

req.signedCookies

当使用cookie解析中间件时,该属性表示请求时使用的cookie签名,签名的作用是防止cookie被篡改。

req.stale

表示请求是"stale"(旧请求)的,与req.fresh属性相对。

req.subdomains

一个表示请求中的子域名的数组。

// Host: "tobi.ferrets.itbilu.com"
req.subdomains
// => ["ferrets", "tobi"]

req.xhr

一个表示客户是否使用Ajax请求的布尔值,如果X-Requested-With请求头为XMLHttpRequest时,则为true。

方法

检测内容类型:req.accepts()

req.accepts(types) //types 是类型

检测是否是可访问的内容类型,基于AcceptHTTP请求头检测。如果匹配失败,则返回false,这时应用应该响应406(不可接受的请求类型)。

字符集检测:req.acceptsCharsets()

req.acceptsCharsets(charset [, ...])

返回一个可接受的字符集。该方法会根据Accept-CharsetHTTP头检测,如果指定的字符集不可用,则返回false。

编码方式检测:req.acceptsEncodings()

req.acceptsEncodings(encoding [, ...])

返回一个可接受的编码方式。该方法会根据Accept-EncodingHTTP头检测,如果指定的字符集不可用,则返回false。

语言检测:req.acceptsLanguages()

req.acceptsLanguages(lang [, ...])

返回一个可接受的语言。该方法会根据Accept-LanguageHTTP头检测,如果指定的语言不可用,则返回false。

获取HTTP请求头值:req.get()

req.get(field)

返回指定HTTP请求头的字段值(不区分大小写),推荐使用Referrer替换Referer字段。

MIME类型检查:req.is()

req.is(type)

检查Content-TypeHTTP头中是否是指定类型的typeMIME类型。

// 当 Content-Type: text/html; charset=utf-8
req.is('html');
req.is('text/html');
req.is('text/*');
// => true

//  当 Content-Type 是 application/json
req.is('json');
req.is('application/json');
req.is('application/*');
// => true

req.is('html');
// => false

req.param()

req.param(name[,defaultValue])

获取 req.params,req.body or req.query 的参数值,其获取顺序为:

  • req.params

  • req.body

  • req.query

    //?name=haojen req.param('nname')

    //POST name = haojen req.param('name')

    // /user/tobi for /user/:name req.param('name')

总结

request对象的属性和方法主要的用途是用来获取请求的状态、类型,和解析传递过来的信息。

Response对象

response对象代表http响应信息,响应信息在express应用收到http请求后发送给客户端。

属性

res.app

通过这个属性我们可以访问app对象中的设置、属性、中间件等。 res.app 与 request对象中的 req.app 属性完全一样。

res.headersSent

一个表示http头信息是否已发送的布尔值

app.get('/',function(req,res,next){
	console.log(res.headersSent) //false
	res.send('OK');
	console.log(res.headersSent) //true
})

res.locals

一个针对当前的包涵在响应信息的局部变量,这个变量是提供给要渲染的视图使用的。这个属性与app.locals完全相同。 res.locals 属性很适合在在顶级请求中暴露一些公用信息,如:验证用户的信息、用户设置等。

方法

响应http头添加信息:res.append( )

res.append(field,[value])

向指定http字头段 field 中添加value。如果不存在,会创建字段。

res.append('link')
res.append('Set-Cookie','foo=bar;path=/;HttpOnly')

设置附件响应头:res.attachment()

res.attachment([filename])

设置Cookie: res.cookie( )

res.cookie(name,value[,options])

设置cookie名为「name」的值为「value」。值可以是一个字符串,或是转换为json型式的对象。option是一个可选对象,对象中可以有以下值:

domain:{String}-Cookie的有效域名。默认为应用的域名 encode:{Function}-用户设置Cookie编码方式的函数(同步)。默认为encodeURIComponent expires:{Date}-Cookie超时时间(GTM)。默认为会话Cookie httpOnly:{Boolean}-是否只能通过服务器访问Cookie maxAge:{String}-Cookie最大存在时间,expires的方便写法 secure:{Boolean}-是否仅在HTTPS下有效 signed:{Boolean}-Cookie是否签名 res.cookie()本质上是按照RFC 6265标准设置Set-Cookie头,了解更多请参考Http Cookie机制及Cookie的实现原理。

res.cookie('name', 'tobi', { domain: '.example.com', path: '/admin', secure: true });
res.cookie('rememberme', '1', { expires: new Date(Date.now() + 900000), httpOnly: true });

清除Cookie:res.clearCookie( )

清除指定名称的cookie。可选值options 与res.cookie的一样。

res.clearCookie('name',{path:'/admin'});

下载: res.download( )

结束响应: res.end( )

发送数据后结束响应。不添加任何参数时,会在不响应任何数据的情况下结束响应。

res.end()
res.status(404).end()

格式化响应:res.format( )

获取响应头:res.get( )

获取指定的http响应头,匹配时不区分大小写

res.get('content-type') // text.plain

响应JSON格式:res.json()

发送一个JSON格式的响应。

响应JSONP格式:res.jsonp( )

设置link头:res.links()

设置location头: res.location( )

URL重定向: res.redirect( )

渲染视图 res.render( )

res.render(view [,locals][,callback])

渲染视图并将渲染后的html发送到客户端。可选参数:

  • locals:{Object} 视图使用的本地变量对象

  • callback: {function} 视图渲染完后的回调函数,格式为:fn(err,html)

    res.render('index') //发送渲染后的视图

    res.render('index',function(err,html){ res.send(html) // 如果制订了回调函数,则渲染后的html需要显式的发送 })

    res.render('index',{name:'haojen'},function(err,html){ //.. })

发送响应文件:res.send( )

res.send([body])

发送http响应,里面的[body]参数可以使一个buffer、字符串、对象或者数组(会转换成JSON格式响应)。这个方法自动做了很多简单有效的工作,如:自动添加content-length响应头,并自动添加http缓存新鲜度支持。

发送文件:sendFile( )

res.sendFile(path [,options][,fn])

它会发送 path中指定的文件,并自动使用文件扩展名设置content-type响应头,除非在options选项中指定它。path必须是一个绝对路径。

options对象可选参数如下:

  • maxAge:设置缓存Cache-Control头的max-age属性。默认0
  • root:相对文件名的根目录
  • lastModified:设置Last-Modified头(文件最后修改时间)。默认为Enabled,Express4.9.0+
  • headers:文件相关的头信息
  • dotfiles:是否支持'.'文件,可选值有:“allow”、“deny”、“ignore”,默认:“ignore”

设置响应状态:res.sendStatus()

设置statusCode HTTP状态码

res.sendStatus(200); // equivalent to res.status(200).send('OK')
res.sendStatus(403); // 等于 res.status(403).send('Forbidden')
res.sendStatus(404); // 等于 res.status(404).send('Not Found')
res.sendStatus(500); // 等于 res.status(500).send('Internal Server Error')

如果是未知的HTTP状态码,则发送的状态码与状态描述一样:

res.sendStatus(2000); // 等于 res.status(2000).send('2000')

设置http响应头:res.set( )

res.set(filed [,opthons])

可以一次性设置多个,可以传入一个设置对象:

res.set('content-type':'text/plain');
or
res.set({
	'content-type':'text/plain',
	'content-length':'123',
	'etag':'12334'
})

这个方法是 res.header(field [,value])的别名。

设置状态码 :res.status()

res.status(code)

设置:

res.status(403).end();
res.status(400).send('bad req')
res.status(200).send('sucees')

设置content-type:res.type( )

res.type(type)

使用mime.lookup()返回的type值设置Content-Type 响应头。

res.type('.html');              // => 'text/html'
res.type('html');               // => 'text/html'
res.type('json');               // => 'application/json'
res.type('application/json');   // => 'application/json'
res.type('png');                // => image/png:

设置vary:res.vary( )

res.vary(field)

将Vary字段添加到响应头,如果指定的字段不存在时。

res.vary('User-Agent').render('docs');

小结

response 对象的用途是:按照需求,将不同类型的数据发送给客户端浏览器,并设置cookie和状态码信息。

express学习总结

epxress是基于node.js的框架,基于底层进行了封装和添加更多的常用方法,使用框架的好处便是让我们更专注与业务层面的实现,而不用于去重复的造轮子。但这不应该是我们不去学习底层的理由,相反的,我们应该去了解原生的nodejs,这样做显而易见的好处便是:调试和查找bug,和实现nodejs框架所没有的功能。