一、什么是express?
Express 是基于 Node.js 平台,快速、开放、极简的 Web 开发框架它提供一系列强大的特性,帮助你创建各种 Web 和移动设备应用。
二、EXPRESS特点
- Web 应用程序 Express 是一个保持最小规模的灵活的 Node.js Web 应用程序开发框架,为 Web 和移动应用程序提供一组强大的功能。
- API 使用您所选择的各种 HTTP 实用工具和中间件,快速方便地创建强大的 API。
- 性能 Express 提供精简的基本 Web 应用程序功能,而不会隐藏您了解和青睐的 Node.js 功能。
三、快速入门
安装EXPRESS
首先假设您已经安装了node, 新建一个项目目录
mkdir myExpress ,
cd myExpress
npm init
接下来在 myapp 目录下安装 Express 并将其保存到依赖列表中
npm install express --save
以上命令会将 Express 框架安装在当前目录的 node_modules 目录中, node_modules 目录下会自动创建 express 目录。以下几个重要的模块是需要与 express 框架一起安装的:
-
body-parser body-parser是一个HTTP请求体解析中间件,用于处理 JSON, Raw, Text 和 URL 编码的数据。 bodyParser.json()--解析JSON格式 ---bodyParser.raw()--解析二进制格式 ---bodyParser.text()--解析文本格式 ---bodyParser.urlencoded()-- 创建 application/x-www-form-urlencoded 解析,formData格式的解析
-
cookie-parser 这就是一个解析Cookie的工具。通过req.cookies可以取到传过来的cookie,并把它们转成对象。
-
multer node.js 中间件,用于处理 enctype='multipart/form-data'(设置表单的MIME编码)的表单数据。
npm install body-parser cookie-parser multer
3.1 第一个例子Hello world
新建index.js配置文件,代码如下 创建一个Express应用。express()是一个express模块导出的入口函数
const express = require('express')
const app = express()
app.get('/', (req, res) => res.send('Hello World!'))
app.listen(3003, () => console.log('Example app listening on port 3003!'))
3.2 请求和响应
Express 应用使用回调函数的参数: request 和 response 对象来处理请求和响应的数据。
app.get('/', function (req, res) { // --})
request 和 response 对象的具体介绍:
3.2.1 Request 对象
request 对象表示 HTTP 请求,包含了请求查询字符串,参数,内容,HTTP 头部等属性。常见属性有:
req.app:当callback为外部文件时,用req.app访问express的实例
req.baseUrl:获取路由当前安装的URL路径
req.body / req.cookies:获得「请求主体」/ Cookies
req.fresh / req.stale:判断请求是否还「新鲜」
req.hostname / req.ip:获取主机名和IP地址
req.originalUrl:获取原始请求URL
req.params:获取路由的parameters
req.path:获取请求路径
req.protocol:获取协议类型
req.query:获取URL的查询参数串
req.route:获取当前匹配的路由
req.subdomains:获取子域名
req.accepts():检查可接受的请求的文档类型
req.acceptsCharsets / req.acceptsEncodings / req.acceptsLanguages:返回指定字符集的第一个可接受字符编码
req.get():获取指定的HTTP请求头
req.is():判断请求头Content-Type的MIME类型
3.2.2 Response 对象
response 对象表示 HTTP 响应,即在接收到请求时向客户端发送的 HTTP 响应数据。常见属性有:
res.app:同req.app一样
res.append():追加指定HTTP头
res.set()在res.append()后将重置之前设置的头
res.cookie(name,value [,option]):设置Cookie
opition: domain / expires / httpOnly / maxAge / path / secure / signed
res.clearCookie():清除Cookie
res.download():传送指定路径的文件
res.get():返回指定的HTTP头
res.json():传送JSON响应
res.jsonp():传送JSONP响应
res.location():只设置响应的Location HTTP头,不设置状态码或者close response
res.redirect():设置响应的Location HTTP头,并且设置状态码302
res.render(view,[locals],callback):渲染一个view,同时向callback传递渲染后的字符串,如果在渲染过程中有错误发生next(err)将会被自动调用。callback将会被传入一个可能发生的错误以及渲染后的页面,这样就不会自动输出了。
res.send():传送HTTP响应
res.sendFile(path [,options] [,fn]):传送指定路径的文件-会自动根据文件extension设定Content-Type
res.set():设置HTTP头,传入object可以一次设置多个头
res.status():设置HTTP状态码
res.type():设置Content-Type的MIME类型
四、编写用于Express应用程序的中间件
中间件函数是能够访问请求对象(req)、响应对象(res)和应用程序请求-响应周期中的next函数的函数。next函数是Express路由器中的一个函数,当调用该函数时,它将执行当前中间件之后的中间件。
中间件功能可以执行以下任务:
- 执行任何代码。
- 更改请求和响应对象。
- 结束请求-响应循环。
- 调用堆栈中的下一个中间件。
如果当前中间件函数没有结束请求-响应周期,则必须调用next()将控制权传递给下一个中间件函数。否则,请求将挂起。
从版本4开始。x, Express不再依赖于Connect。以前包含在Express中的中间件功能现在位于单独的模块中
第三方中间件
4.1 cookie-parser
- cookie设置:使用Express的内置方法res.cookie。
- cookie解析:使用cookie-parser中间件。
var express = require('express');
var cookieParser = require('cookie-parser');
var app = express();
const cors = require('cors');
// app.use(cookieParser()); //不签名
// 初始化中间件,传入的第一个参数为singed secret
app.use(cookieParser('123456')); //签名
//使用cors跨域中间件
// app.use(cors());
app.use(proxy)
app.use(function (req, res, next) {
console.log(req.cookies.username);
console.log(req.signedCookies.username);
// 传入第三个参数 {signed: true},表示要对cookie进行摘要计算
res.cookie('username', 'laney',{ maxAge: 1000*60*60*60,signed: true});
next();
});
app.get('/info', (req, res) =>{
res.send('Hello World!');
console.log(req.cookies.username); // 第二次访问,输出laney
});
app.listen(3000);
服务器可能需要拿到 Cookie,这时需要服务器显式指定Access-Control-Allow-Credentials字段, 告诉浏览器可以发送 Cookie
同时,开发者必须在 AJAX 请求中打开withCredentials属性。
res.setHeader('Access-Control-Allow-Credentials', 'true');
true 允许携带cookie
false 不携带
需要注意的是,如果服务器要求浏览器发送 Cookie,Access-Control-Allow-Origin就不能设为星号,必须指定明确的、与请求网页一致的域名。同时,Cookie 依然遵循同源政策,只有用服务器域名设置的 Cookie 才会上传,其他域名的 Cookie 并不会上传,且(跨域)原网页代码中的document.cookie也无法读取服务器域名下的 Cookie。
function proxy(req, res,next){
//跨域处理
// 注意: http://192.168.1.29:8080为静态资源访问的地址
res.setHeader('Access-Control-Allow-Origin', 'http://192.168.1.29:8080');
// res.setHeader('Access-Control-Allow-Origin', '*'); //允许任何源
//允许任何源 ,如果服务器要求浏览器发送 Cookie,这是不能设置为*
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE'); //允许任何方法
// res.setHeader('Access-Control-Allow-Methods', '*');
res.setHeader('Access-Control-Allow-Headers', 'X-Token,Content-Type')
// res.setHeader('Access-Control-Allow-Headers', '*'); //允许任何类型
res.setHeader('Access-Control-Allow-Credentials', 'true');
next(); //next 方法就是一个递归调用
}
是否使用了axios?
axios默认是不允许设置cookie的,所以如果用了,需要你单独启动一下全局设置 axios.defaults.withCredentials = true;//让ajax携带cookie 在入口的页面设置即可全局使用。
axios.defaults.withCredentials = true; //true 允许携带cookie, false 不携带
node后台启动时是否设置了允许携带cookie? res.header('Access-Control-Allow-Credentials', 'true');
作用:方便操作客户端中的cookie值。
- 安装cookie-parser第三方cookie操作模块
yarn add cookie-parser
- 引入
const cookieParser = require('cookie-parser');
app.use(cookieParser('123456')); //使用cookie中间件,传入签名123456进行加密
- 设置cookie,需要设置signed签名
res.cookies('key','value',option)
其中option要求是要json格式:有以下选项
- domain: 域名。设置子域名(二级域名)是否可以访问cookie。 例:domain:'.主域名' name=value:键值对,可以设置要保存的 Key/Value,注意这里的 name 不能和其他属性项的名字一样
- expires: 过期时间(秒),在设置的某个时间点后该 Cookie 就会失效,如 expires=Wednesday, 09-Nov-99 23:12:40 GMT
- maxAge: 最大失效时间(毫秒),设置在多少后失效
- secure: 当 secure 值为 true 时, cookie 在 HTTP 中是无效,在 HTTPS 中才有效
- path: 表示 cookie 影响到的路由,如 path=/。如果路径不能匹配时,浏览器则不发送这个 Cookie
- httpOnly:默认为false,建议设置为true, 客户端将无法通过document.cookie读取到 COOKIE 信息,可防止 XSS 攻击产生
- signed: 表示是否签名(加密) cookie, 设为 true 会对这个 cookie 签名,这样就需要用res.signedCookies 访问它,前提需要设置上面中间件app.use传参 。 未签名则用 res.cookies 访问
- 被篡改的签名 cookie 会被服务器拒绝,并且 cookie值会重置为它的原始值
res.cookie('cart', { items: [1,2,3] }, { maxAge: 10000*4,signed:true,httpOnly:true });
res.cookie('username', "laney", { maxAge: 10000*2,signed:true});
- 获取cookie
//签名的cookie:
console.log(req.signedCookies);
console.log(req.signedCookies.username);
//没签名的cookie
console.log(req.cookies.username);
4.2 cookie签名的作用
主要是出于安全考虑,防止cookie被篡改,增强安全性
基于前面的例子展开。假设网站通过nick这个cookie来区分当前登录的用户是谁。在前面例子中,登录用户的cookie中,username对应的值如下:(decode后的)
s:laney.uVofnk6k+9mHQpdPlQeOfjM8B5oa6mppny9d+mG9rD0
此时,有人试图修改这个cookie值,来达到伪造身份的目的。比如修改成xiaoming:
s:xiaoming.uVofnk6k+9mHQpdPlQeOfjM8B5oa6mppny9d+mG9rD0
当网站收到请求,对签名cookie进行解析,发现签名验证不通过。由此可判断,cookie是伪造的。
仅通过username这个cookie的值来判断登录的是哪个用户,这是一个非常糟糕的设计。虽然在秘钥未知的情况下,很难伪造签名cookie。但用户名相同的情况下,签名也是相同的。这种情况下,其实是很容易伪造的。
另外,开源组件的算法是公开的,因此秘钥的安全性就成了关键,要确保秘钥不泄露。
五、路由
路由是指确定应用程序如何响应到特定端点的客户机请求,该端点是URI(或路径)和特定的HTTP请求方法(GET、POST等)。 每个路由可以有一个或多个处理程序函数,这些函数在匹配路由时执行。
app.METHOD(PATH, HANDLER)
说明: app是express的一个实例。 METHOD是HTTP请求方法(小写),比如post,get,put等。 PATH是服务器上的路径。 HANDLER是在匹配路由时执行的函数。
app.get('/ab?cd', function (req, res) {
res.send('ab?cd')
})
app.get('/users/:userId/books/:bookId', function (req, res) {
res.send(req.params)
})
5.1 路由处理程序 -- Route handlers
您可以提供多个回调函数,它们的行为类似于中间件来处理请求。唯一的例外是,这些回调可能调用next('route')来绕过剩余的路由回调。您可以使用此机制对路由施加前置条件,然后如果没有理由继续当前路由,则将控制权传递给后续路由。
app.get('/example/b', function (req, res, next) {
console.log('the response will be sent by the next function ...')
next()
}, function (req, res) {
res.send('Hello from B!')
})
5.2 响应头方法 -- Response methods
下表中响应对象(res)上的方法可以向客户机发送响应,并终止请求-响应周期。如果没有从路由处理程序调用这些方法,客户机请求将挂起。
| 方法 | 描述 |
|---|---|
| res.download() | 提示要下载的文件。 |
| res.end() | 结束响应过程。 |
| res.json() | 发送一个JSON响应 |
| res.jsonp() | 发送一个支持JSONP的JSON响应。 |
| res.redirect() | 重定向请求 |
| res.render() | 呈现视图模板 |
| res.send() | 发送各种类型的响应 |
| res.sendFile() | 以流的形式发送文件 |
| res.sendStatus() | 设置响应状态代码,并将其字符串表示形式作为响应体发送 |
5.3 app.route()
您可以使用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')
})
5.4 可挂载路由处理程序 -- express.Router
使用express来创建模块化的可挂载路由处理程序。路由器实例是一个完整的中间件和路由系统;因此,它经常被称为“迷你应用程序”。 下面的示例创建一个路由器作为模块,在其中加载一个中间件函数,定义一些路由,并将路由器模块挂载在主应用程序中的路径上。 在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('/home', function (req, res) {
res.send('Birds home page')
})
// 关于我们
router.get('/about', function (req, res) {
res.send('About birds')
})
module.exports = router;
然后在app中加载路由器模块
var birds = require('./birds')
// ...
app.use('/birds', birds)
该应用程序现在将能够处理/birds/home和/birds/about的请求,以及调用特定于路由的timeLog中间件功能。
六、利用 Express 托管静态文件
Express 提供了内置的中间件 express.static 来设置静态文件如:图片, CSS, JavaScript 等。 你可以使用 express.static 中间件来设置静态文件路径。例如,如果你将图片, CSS, JavaScript 文件放在 public 目录下,你可以这么写:
app.use(express.static('public'));
现在,你就可以访问 public 目录中的所有文件了:
http://localhost:3000/images/kitten.jpg
http://localhost:3000/css/style.css
Express 在静态目录查找文件,因此,存放静态文件的目录名不会出现在 URL 中。 如果要使用多个静态资源目录,请多次调用 express.static 中间件函数:
app.use(express.static('public'))
app.use(express.static('files'))
为express提供的文件创建虚拟路径前缀(其中路径实际上并不存在于文件系统中)。静态函数,为静态目录指定一个挂载路径,如下所示:
app.use('/static', express.static('public'))
现在,你就可以通过带有 /static 前缀地址来访问 public 目录中的文件了。
http://localhost:3000/static/images/kitten.jpg
http://localhost:3000/static/css/style.css
如果您从另一个目录运行express应用程序,使用您想要服务的目录的使用path模块更更安全 app.use('/static', express.static(path.join(__dirname, 'public')))
七、Express 应用程序生成器
通过应用生成器工具 express-generator 可以快速创建一个应用的骨架。 express-generator 包含了 express 命令行工具。通过如下命令即可局部安装:
安装:yarn add express-generator
当然你也可以选择全局安装, 但是配置相对麻烦点
注意:如果选择全局安装,express 4.0以上的 版本需要同时全局安装express和express-generator,需要这2个安装后还是提示以下错误,就需要配置环境变量
win10安装express后仍报错:'express' 不是内部或外部命令,也不是可运行的程序 或批处理文件
修改完,一定记得重启控制台服务
环境变量的配置参考:https://blog.csdn.net/m0_37750720/article/details/82937463
当使用npm安装一些全局的软件包时,不知道安装到了什么位置,可以使用命令 npm root -g 进行查询,通常默认会保存在以下位置:
-h 参数可以列出所有可用的命令行参数: npx express -h 使用express命令创建项目
npx express --view=ejs myapp
npx express --view=ejs --css=less myapp //支持less的预加载写法
命令创建了一个名称为 myapp 的 Express 应用。此应用将在当前目录下的 myapp 目录中创建,并且设置为使用 ejs 模板引擎(view engine)
然后安装所有依赖包:
cd myapp
npm install
node bin/www
如果要实时监听配置文件可以使用 nodemon bin/www,前提时候需要全局安装 nodemon
然后在浏览器中打开 http://localhost:3000/ 网址就可以看到这个应用了。
通过生成器创建的应用一般都有如下目录结构:
├── app.js //入口文件(主文件) 总路由 (其他的路由 要由它来分配)
├── bin //启动目录 里面包含了一个启动文件 www 默认监听端口是 3000 (不用)
│ └── www
├── package.json //包描述文件 最重要的是 依赖的模板列表 dependencies
//依赖列表里面的所有模板 可以通过 cnpm i 一次性全部安装
├── public //所有的前端静态资源 html css image js
│ ├── images
│ ├── javascripts
│ └── stylesheets
│ └── style.css
├── routes
放的是 路由 文件 (默认有两个)
路由主要定义 url 和 资源 的映射关系 ( 一一对应关系 )
主要用来接收前端发送的请求 响应数据给前端
│ ├── index.js
│ └── users.js
└── views //主要放置 ejs 后端模板文件
├── error.pug
├── index.pug
└── layout.pug
node_modules: //所有安装的依赖模块 都在这个文件夹里面
通过 Express 应用生成器创建应用只是众多方法中的一种。你可以不使用它,也可以修改它让它符合你的需求。
通过pm2启动项目,安装:
npm install pm2 -g
pm2 start app.js 注意:pm2是在后台启动的,还可以操作其他命令
7.1 使用Express的模板引擎
模板引擎允许您在应用程序中使用静态模板文件。在运行时,模板引擎用实际值替换模板文件中的变量,并将模板转换为发送给客户机的HTML文件。这种方法使设计HTML页面变得更容易。
使用Express的一些流行模板引擎是Pug、Mustache和EJS。Express应用程序生成器使用Jade作为默认值,但它还支持其他一些功能。
npm install ejs--save app.set('views', '.views'); //指定视图目录 app.set('view engine', ejs) // 注册模板引擎
八、仿写一个简版Express
1. 体验express
const express = require('./common/myexpress.js');
const app = express();
app.get('/',(req,res) => {
res.end('Hello world')
})
app.get('/users',(req,res) => {
res.end(JSON.stringify({name:'abcooooo'}))
})
app.get('/list',(req,res) => {
res.end(JSON.stringify({name:'list'}))
})
app.listen(3200 , () => {
console.log('Example listen at 3200')
})
2. 实现myexpress
const http = require('http');
const url = require('url');
let routers = [];
class Application {
get(path,hander){
routers.push({
path,
method:'get',
hander
});
}
listen(){
const server = http.createServer(function(req,res){
const {pathname} = url.parse(req.url,true);
const {method,headers} = req;
console.log(method);
//在路由表routers通过pathname,找到回调,然后执行
var tet = routers.find(v=>{
return v.path == pathname && req.method.toLowerCase()==v.method
})
tet && tet.hander(req,res);
})
//在Application原型上添加listen方法匹配路径, 执行对应的hander
server.listen(...arguments)
// server.listen(3200,() => {
// console.log('Example listen at 3200')
// })
}
}
module.exports = function(){
return new Application();
}