Express
什么是Express
- 官方给出的概念:Express是基于Node.js平台,快速,开放,极简的Web开发框架
通俗的理解:Express的作用和Node.js内置的http模块类似,是专门用来创建的Web服务器的
- Express的本质:就是一个npm上的第三方包,提供了快速创建Web服务器的便捷方法
-
express 官方网址:Express - 基于 Node.js 平台的 web 应用开发框架 - Express中文文档 | Express中文网
进一步理解Express
- 思考:不使用Express能否创建Web服务器?
能,使用Node.js提供的原生http模块即可
- 有了http模块,为什么还要用Express?
http模块用起来很复杂,开发效率低,Express是基于内置的http模块进一步封装出来的,能够极大的提高开发效率
- http内置模块与Express是什么关系?
类似于Web API和jQuery的关系(),后者是前者进一步封装出来得到的
Express能做什么
对于前端程序员来说,最常见的两种服务器,分别是:
- Web网站服务器:专门对外提供Web网页资源的服务器
- API接口服务器:专门对外提供API接口的服务器
使用Express,我们可以方便,快捷的创建Web网站的服务器或API接口的服务器
创建基本的Web服务器
先下载express包 npm i express
监听GET请求
通过 app.get()方法,可以监听用户端的GET请求,具体语法格式如下
监听POST请求
通过app.post()方法,可以监听客户端的POST请求,具体的语法格式如下
把内容响应给客户端
通过res.send()方法,可以把处理好的内容,发送给客户端
Express的基本使用
获取URL中携带的参数
通过req.query对象,可以访问到客户端通过查询字符串的形式,发送到服务器的参数:
获取URL中的动态参数
通过req.params对象,可以访问到URL中,通过:匹配到的动态参数:
// 1. 导入 express
const express = require('express')
// 2. 创建 web 服务器
const app = express()
// 4. 监听客户端的 GET 和 POST 请求,并向客户端响应具体的内容
app.get('/user', (req, res) => {
// 调用 express 提供的 res.send() 方法,向客户端响应一个 JSON 对象
res.send({ name: 'zs', age: 20, gender: '男' })
})
app.post('/user', (req, res) => {
// 调用 express 提供的 res.send() 方法,向客户端响应一个 文本字符串
res.send('请求成功')
})
app.get('/', (req, res) => {
// 通过 req.query 可以获取到客户端发送过来的 查询参数
// 注意:默认情况下,req.query 是一个空对象
console.log(req.query)
res.send(req.query)
})
// 注意:这里的 :id 是一个动态的参数
app.get('/user/:ids/:username', (req, res) => {
// req.params 是动态匹配到的 URL 参数,默认也是一个空对象
console.log(req.params)
res.send(req.params)
})
// 3. 启动 web 服务器
app.listen(80, () => {
console.log('express server running at http://127.0.0.1')
})
托管静态资源
express.static()
express提供了一个非常好用的参数,叫做express.static(),通过它,我们可以非常方便的创建一个静态资源管理器,例如:通过如下代码就可以将public目录下的图片,CSS文件,JavaScript文件对外开放访问了
app.use(express.static('public'))
现在就可以访问public目录中所有的文件了
注意:Express在指定的静态目录中查找文件,并对外提供资源的访问路径,因此,存放静态文件的目录不会出现在URL中
静态资源中间件注意事项
- index.html文件为默认打开的资源
- 如果静态资源与路由规则同时匹配,谁先匹配谁就响应
- 路由响应动态资源,静态资源中间件响应静态资源
托管多个静态资源目录
如果要托管多个静态资源目录,请多次调用express.static()函数
访问静态资源文件时,express.static()函数会根据目录的添加顺序查找所需的文件
挂载路径前缀
如果希望在托管的静态资源访问路径之前,挂载路径前缀,则可以使用如下的方式:
app.use('/public',express.static('public'))
const express = require('express')
const app = express()
// 在这里,调用 express.static() 方法,快速的对外提供静态资源
app.use('/files', express.static('./files'))
app.use(express.static('./clock'))
app.listen(80, () => {
console.log('express server running at http://127.0.0.1')
})
nodemon
为什么要使用nodemon
在编写调试Node.js项目的时候,如果修改了项目的代码,则需要频繁的手动close掉,然后重新启动,非常繁琐,我们可以使用nodemon这个工具,它能够监听项目文件的变动,当代码被修改后,nodemon会自动帮我们重启项目,极大方便了开发和调试
Express 路由
什么是路由
广义上来讲,路由就是映射关系
例如:人工客服中的每个按键都对应不同的服务,在这里,路由就是按键与服务之间的映射关系
Express中的路由
在Express中,路由指的是客户端的请求与服务器处理函数之间的映射关系
Express中的路由三个部分组成,分别是请求的类型,请求的url地址,处理函数,格式如下:
app.METHOD(PATH,HANDLER)
//path即为请求的URL地址
//handler为处理函数
Express中的路由的例子
路由的匹配过程
每当一个请求到达服务器之后,需要先经过路由的匹配,只有匹配成功之后,才会调用对应的处理函数。
在匹配时,会按照路由的顺序进行匹配,如果请求类型和请求的URL同时匹配成功,则Express会将这次请求,转交给对应的function函数进行处理
路由匹配的注意点:
- 按照定义的先后顺序进行匹配
- 请求类型和请求的URL同时匹配成功,才会调用对应的处理函数
路由的使用
最简单的用法
在Express中使用路由最简单的方式,就是把路由挂载到app上,示例代码如下:
const express = require('express')
// 创建web服务器,命名为app
const app = express()
// 挂载路由
app.get('/', (req, res) => {
console.log(req.method)
console.log(req.url)
console.log(req.httpVersion)
console.log(req.headers)
//express操作
console.log(req.path)
console.log(req.query)
//获取ip
console.log(req.ip)\
//获取请求头
console.log(req.get('host'))
res.send('hello world.')
})
app.post('/', (req, res) => {
res.send('Post Request.')
})
// 启动web服务器
app.listen(80, () => {
console.log('http://127.0.0.1')
})
模块化路由
为了方便对路由进行模块化的管理,Express不建议将路由直接挂载到app上,而是推荐将路由抽离为单独的模块,将路由抽离为单独模块的步骤如下:
- 创建路由模块对应的.js文件
- 调用express.Router()函数创建路由对象
- 向路由对象上挂载具体的路由
- 使用module.exports向外共享路由对象
- 使用app.use()函数注册路由模块
为路由模块添加前缀
类似于托管静态资源时,为静态资源统一挂载访问前缀一样,路由模块添加前缀的方式也非常简单
app.use('/api', router)
03router.js:
// 这是路由模块
// 1. 导入 express
const express = require('express')
// 2. 创建路由对象
const router = express.Router()
// 3. 挂载获取用户列表的路由
router.get('/user/list', (req, res) => {
//原生响应
//res.statusCode-404
//res.statusMessage='love'
//res.setHeader('xxx','yyy')
//res.write('hello express')
//express响应
//res.send('Get user list.')
//res.status(500),set('abc','def').send('这都是OK')
//其他响应
res.redirect('http://atguigu.com')//重定向
res.download('./package.json')//下载响应
res.json()//响应JSON
res.sendFile(__dirname+'/home.html')//响应文件内容
})
// 挂载添加用户的路由
router.post('/user/add', (req, res) => {
res.send('Add new user.')
})
// 4. 向外导出路由对象
module.exports = router
// 导入express
const express = require('express')
// 创建路由对象
const app = express()
// app.use('/files', express.static('./files'))
// 1. 导入路由模块
const router = require('./03.router')
// 2. 注册路由模块 添加统一的访问前缀 /api
app.use('/api', router)
// 注意: app.use() 函数的作用,就是来注册全局中间件
app.listen(80, () => {
console.log('http://127.0.0.1')
})
在Node.js的HTTP模块或Express框架中,res.end()
和res.send()
方法都是用于响应客户端请求,但它们之间存在一些关键差异:
-
原生与框架方法:
res.end()
是Node.js原生HTTP模块中的方法,它用于结束响应过程,可以发送一个可选的数据体作为响应的结束。如果在调用res.end()
之前没有写入任何响应头,它会自动添加一个Content-Length
头。res.send()
是Express框架提供的一个便捷方法,它不仅可以发送数据,还会自动设置Content-Type头部和HTTP状态码(默认为200 OK)。它内部会调用res.write()
和res.end()
来完成响应的发送。
-
数据类型与自动处理:
res.end()
主要接受字符串或Buffer作为参数,适合于发送简单的文本或二进制数据。它不会自动设置Content-Type
,除非之前已手动设置。res.send()
则更加灵活,它可以发送各种数据类型(包括字符串、对象、数组等),并且会根据数据类型自动设置Content-Type
。例如,发送JSON数据时,它会设置Content-Type
为application/json
。
-
状态码处理:
- 使用
res.end()
时,需要手动设置HTTP状态码,如res.statusCode = 404; res.end('Not Found');
。 res.send()
可以自动处理状态码,也可以显式传递,例如通过res.status(404).send('Not Found')
。在Express中,更推荐使用链式调用来设置状态码和发送响应。
- 使用
-
使用场景:
- 当只需要发送简单的响应体结束响应时,可以使用
res.end()
。 - 当需要更复杂的响应处理,包括自动设置头部、状态码以及处理不同数据类型时,应该使用
res.send()
。
- 当只需要发送简单的响应体结束响应时,可以使用
总的来说,res.send()
在Express应用中提供了更高级的功能和更简便的API,而res.end()
则是更底层、更基础的结束响应的方法。在编写Express应用时,通常优先考虑使用res.send()
,除非有特殊需求。
中间件
Express中间件地调用流程
当一个请求到达Express都服务器之后,可以连续调用多个中间件,从而对这次请求进行预处理
Express中间件的格式
Express的中间件,本质上就是一个function处理函数,Express中间件的格式如下:
注意:中间件的形参列表中,必须包含next函数,而路由处理函数中只包含req和res
next函数的作用
next函数是实现多个中间件连续调用的关键,他表示把流转关系转交给下一个中间件或路由
Express中间件初体验
定义中间件函数
可以通过如下的方式,定义一个最简单的中间件函数:
全局生效的中间件
客户端发起的任何请求,到达服务器之后,都会触发的中间件,叫做全局生效的中间件
通过调用app.use(中间件函数),即可定义一个全局生效的中间件,示例代码如下:
定义全局中间件的简化形式
const express = require('express')
const app = express()
// 定义一个最简单的中间件函数
// const mw = function (req, res, next) {
// console.log('这是最简单的中间件函数')
// 把流转关系,转交给下一个中间件或路由
// next()
// }
// 将 mw 注册为全局生效的中间件
// app.use(mw)
// 这是定义全局中间件的简化形式
app.use((req, res, next) => {
console.log('这是最简单的中间件函数')
next()
})
app.get('/', (req, res) => {
console.log('调用了 / 这个路由')
res.send('Home page.')
})
app.get('/user', (req, res) => {
console.log('调用了 /user 这个路由')
res.send('User page.')
})
app.listen(80, () => {
console.log('http://127.0.0.1')
})
const express=require('express')
const fs=require('fs')
const path=require('path')
const app=express()
function recordMiddleware(req,res,next){
// 获取url ip
let {url,ip}=req
// console.log(url,ip)
fs.appendFileSync(path.resolve(__dirname+'/access.log'),`${url},${ip}\r\n`)
next()//请求之后先执行全局中间件函数,之后再执行请求之后的后续函数
}
app.use(recordMiddleware)
app.get('/home',(req,res)=>{
res.send('前台首页')
})
app.get('/admin',(req,res)=>{
res.send('后台首页')
})
app.all('*',(req,res)=>{
res.send('<h1>404 NotFound</h1>')
})
app.listen(3000,()=>{
console.log('服务已经启动,端口3000正在监听中')
})
中间件的作用
多个中间件之间,共享同一份req和res,基于这样的特性,我们可以在上游的中间件中,统一为req和res对象添加自定义的属性或方法,供下游的中间件或路由使用
const express = require('express')
const app = express()
// 这是定义全局中间件的简化形式
app.use((req, res, next) => {
// 获取到请求到达服务器的时间
const time = Date.now()
// 为 req 对象,挂载自定义属性,从而把时间共享给后面的所有路由
req.startTime = time
next()
})
app.get('/', (req, res) => {
res.send('Home page.' + req.startTime)
})
app.get('/user', (req, res) => {
res.send('User page.' + req.startTime)
})
app.listen(80, () => {
console.log('http://127.0.0.1')
})
定义多个全局中间件
可以使用app.use()连续定义多个全局中间件,客户端请求到达服务器之后,会按照中间件定义的先后顺序依次执行调用
const express = require('express')
const app = express()
// 定义第一个全局中间件
app.use((req, res, next) => {
console.log('调用了第1个全局中间件')
next()
})
// 定义第二个全局中间件
app.use((req, res, next) => {
console.log('调用了第2个全局中间件')
next()
})
// 定义一个路由
app.get('/user', (req, res) => {
res.send('User page.')
})
app.listen(80, () => {
console.log('http://127.0.0.1')
})
局部生效的中间件
不使用app.use()定义的中间件,叫做局部生效的中间件,示例代码如下:
定义多个局部中间件
可以在路由中,通过如下两种等价的方式,使用多个局部中间件:
// 导入 express 模块
const express = require('express')
// 创建 express 的服务器实例
const app = express()
// 1. 定义中间件函数
const mw1 = (req, res, next) => {
console.log('调用了第一个局部生效的中间件')
next()
}
const mw2 = (req, res, next) => {
console.log('调用了第二个局部生效的中间件')
next()
}
// 2. 创建路由
app.get('/', [mw1, mw2], (req, res) => {
res.send('Home page.')
})
app.get('/user', (req, res) => {
res.send('User page.')
})
// 调用 app.listen 方法,指定端口号并启动web服务器
app.listen(80, function () {
console.log('Express server running at http://127.0.0.1')
})
了解中间件的5个注意事项
- 一定要在路由之前注册中间件
- 客户端发送过来的请求,可以连续调用多个中间件进行处理
- 执行完中间件的业务代码之后,不要忘记调用next()函数
- 为了防止代码逻辑混乱,调用next()函数后不要再写额外的代码
- 连续调用多个中间件时,多个中间件之间,共享req和res对象
中间件的分类
为了方便理解和记忆中间件的作用,Express官方把常见的中间件用法,分成了5大类,分别是
-
应用级别的中间件
通过app.use()或app.get()或app.post(),绑定到app实例上的中间件,叫做应用级别的中间件,代码示例如下:
-
路由级别的中间件
绑定到express.Router()实例上的中间件,叫做路由级别的中间件,它的用法和应用级别的中间件没有任何区别,只不过,应用级别的中间件是绑定到app实例上,路由级别的中间件绑定到router实例上,代码示例如下:
-
错误级别的中间件
错误级别中间件的作用:专门用来捕获整个项目中发生的异常错误,从而防止项目异常崩溃的问题。
格式:错误级别中间件的function处理函数中,必须有4个形参,形参顺序从前到后,分别是(err,req,res,next)
// 导入 express 模块
const express = require('express')
// 创建 express 的服务器实例
const app = express()
// 1. 定义路由
app.get('/', (req, res) => {
// 1.1 人为的制造错误
throw new Error('服务器内部发生了错误!')
res.send('Home page.')
})
// 2. 定义错误级别的中间件,捕获整个项目的异常错误,从而防止程序的崩溃
app.use((err, req, res, next) => {
console.log('发生了错误!' + err.message)
res.send('Error:' + err.message)
})
// 调用 app.listen 方法,指定端口号并启动web服务器
app.listen(80, function () {
console.log('Express server running at http://127.0.0.1')
})
注意:错误级别的中间件应该放在所有路由之后 ,去捕获所有的错误
-
Express内置的中间件
自Express4.16.0版本开始,Express内置了3个常用的中间件,极大的提高了Express项目的开发效率和体验
- express.static快速托管静态资源的内置文件,例如:HTML文件,图片,CSS样式等(无兼容性)
- express.json解析JSON格式的请求体数据(有兼容性,仅在4.16.0+版本可使用)
- express.urlencoded解析 URL-encoded格式的请求体数据(有兼容性,仅在4.16.0+版本可使用)
// 导入 express 模块
const express = require('express')
// 创建 express 的服务器实例
const app = express()
// 注意:除了错误级别的中间件,其他的中间件,必须在路由之前进行配置
// 通过 express.json() 这个中间件,解析表单中的 JSON 格式的数据
app.use(express.json())
// 通过 express.urlencoded() 这个中间件,来解析 表单中的 url-encoded 格式的数据
app.use(express.urlencoded({ extended: false }))
app.post('/user', (req, res) => {
// 在服务器,可以使用 req.body 这个属性,来接收客户端发送过来的请求体数据
// 默认情况下,如果不配置解析表单数据的中间件,则 req.body 默认等于 undefined
console.log(req.body)
res.send('ok')
})
app.post('/book', (req, res) => {
// 在服务器端,可以通过 req,body 来获取 JSON 格式的表单数据和 url-encoded 格式的数据
console.log(req.body)
res.send('ok')
})
// 调用 app.listen 方法,指定端口号并启动web服务器
app.listen(80, function () {
console.log('Express server running at http://127.0.0.1')
})
-
第三方的中间件
非Express官方内置的,而是由第三方开发出来的中间件,叫做第三方中间件,在项目中,大家可以按需下载并配置第三方中间件,从而提高项目的开发效率
例如:在Express@4.16.0之前的版本中,经常使用body-parser这个第三方中间件,来解析请求体数据,使用步骤如下:
- 运行npm install body-parser安装中间件
- 使用require导入中间件
- 调用app.use()注册并使用中间件
// 导入 express 模块
const express = require('express')
// 创建 express 的服务器实例
const app = express()
// 1. 导入解析表单数据的中间件 body-parser
const parser = require('body-parser')
// 2. 使用 app.use() 注册中间件
app.use(parser.urlencoded({ extended: false }))
// app.use(express.urlencoded({ extended: false }))
app.post('/user', (req, res) => {
// 如果没有配置任何解析表单数据的中间件,则 req.body 默认等于 undefined
console.log(req.body)
res.send('ok')
})
// 调用 app.listen 方法,指定端口号并启动web服务器
app.listen(80, function () {
console.log('Express server running at http://127.0.0.1')
})
注意:Express内置的express.urlencoded中间件,就是基于body-parser这个第三方中间件进一步封装出来的
body-parser
使用路由中间件,并不是所有的请求都要处理返回数据,使用前先下载body-parser
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h2>用户登录</h2>
<hr>
<form action="http://127.0.0.1:3000/login" method="post">
用户名:<input type="text" name="username"><br>
密码:<input type="password" name="password"><br>
<button>登录</button>
</form>
</body>
</html>
const express=require('express')
const bodyParser=require('body-parser')
const app=express()
// 解析JSON格式的请求体的中间件
const jsonParser=bodyParser.json()
// 解析querystring格式请求体的中间件
const urlencodeParser=bodyParser.urlencoded({extended:false})
app.get('/login',(req,res)=>{
// res.send('表单页面')
res.sendFile(__dirname+'/form.html')
})
// 当urlencodeParser执行完毕之后,会给req身上添加body的的属性
app.post('/login',urlencodeParser,(req,res)=>{
// 获取请求体数据
res.send('获取用户的数据')
console.log(req.body)
})
app.listen(3000,()=>{
console.log('3000端口监听中')
})
防盗链
通过确认主机名来判断图片是否可被外界网站使用
app.use((req,res,next)=>{
// 检测请求头中的referer是否为127.0.0.1
let referer=req.get('referer')
if(referer){
// 实例化
let url=new URL(referer)
// 获取hostname(请求头中的主机名)
let hostname=url.hostname
console.log(hostname)
if(hostname!=='127.0.0.1'){
// 响应404
res.status(404).send('<h1>404 Not Found</h1>')
return
}
next()
}
})
自定义中间件
需求描述与实现步骤
自己手动模拟一个类似于express.urlencoded这样的中间件,来解析POST提交到服务器的表单数据
实现步骤:
- 定义中间件
使用app.use()来定义全局生效的中间件,代码如下:
app.use(function(req,res,next){
//中间件的业务逻辑
})
- 监听req的data事件
在中间件中,需要监听req对象的data事件,来获取客户端发送到服务器的数据
如果数据量比较大,无法一次性发送完毕,则客户端会把数据切割后,分批发送到服务器,所以data事件可能会触发多次,每一次触发data事件时,获取到数据只是完整数据的一部分,需要手动对接收的数据进行拼接
- 监听req的end事件
当请求体数据接收完毕之后,会自动触发req的end事件
因此,我们可以在req的end事件中,拿到并处理完整的请求体数据,示例代码如下:
- 使用querystring模块解析请求体数据
Node.js内置了一个querystring模块,专门用来处理查询字符串,通过这个模块提供的parse()函数,可以轻松的把查询字符串,解析成对象的格式,示例代码如下:
- 将解析出来的数据对象挂载为req.body
上游的中间件和下游的中间件及路由之间,共享一份req和res,因此,我们可以将解析出来的数据,挂载为req的自定义属性,命名为req.body,
供下游使用,示例代码如下:
// 导入 express 模块
const express = require('express')
// 创建 express 的服务器实例
const app = express()
// 导入 Node.js 内置的 querystring 模块
const qs = require('querystring')
// 这是解析表单数据的中间件
app.use((req, res, next) => {
// 定义中间件具体的业务逻辑
// 1. 定义一个 str 字符串,专门用来存储客户端发送过来的请求体数据
let str = ''
// 2. 监听 req 的 data 事件
req.on('data', (chunk) => {
str += chunk
})
// 3. 监听 req 的 end 事件
req.on('end', () => {
// 在 str 中存放的是完整的请求体数据
// console.log(str)
// TODO: 把字符串格式的请求体数据,解析成对象格式
const body = qs.parse(str)
req.body = body
next()
})
})
app.post('/user', (req, res) => {
res.send(req.body)
})
// 调用 app.listen 方法,指定端口号并启动web服务器
app.listen(80, function () {
console.log('Express server running at http://127.0.0.1')
})
将自定义中间件封装为模块
为了优化代码结构,我们可以把自定义的中间件函数,封装为独立的模块,示例代码如下:
custom-body-parser.js:
// 导入 Node.js 内置的 querystring 模块
const qs = require('querystring')
const bodyParser = (req, res, next) => {
// 定义中间件具体的业务逻辑
// 1. 定义一个 str 字符串,专门用来存储客户端发送过来的请求体数据
let str = ''
// 2. 监听 req 的 data 事件
req.on('data', (chunk) => {
str += chunk
})
// 3. 监听 req 的 end 事件
req.on('end', () => {
// 在 str 中存放的是完整的请求体数据
// console.log(str)
// TODO: 把字符串格式的请求体数据,解析成对象格式
const body = qs.parse(str)
req.body = body
next()
})
}
module.exports = bodyParser
// 导入 express 模块
const express = require('express')
// 创建 express 的服务器实例
const app = express()
// 1. 导入自己封装的中间件模块
const customBodyParser = require('./14.custom-body-parser')
// 2. 将自定义的中间件函数,注册为全局可用的中间件
app.use(customBodyParser)
app.post('/user', (req, res) => {
res.send(req.body)
})
// 调用 app.listen 方法,指定端口号并启动web服务器
app.listen(80, function () {
console.log('Express server running at http://127.0.0.1')
})
EJS模板引擎
模板引擎是分离用户界面和业务数据的一种技术(分离HTML和JS)
EJS是一个高效的JavaScript的模板引擎
官网:EJS -- Embedded JavaScript templates
中文站:EJS -- 嵌入式 JavaScript 模板引擎 | EJS 中文文档
EJS初体验
// EJS初体验
// 1.安装 ejs
// 2.导入EJS
const ejs=require('ejs')
const fs=require('fs')
// 字符串
let china='中国'
// let str=`我爱你中国 ${china}`
// 使用ejs渲染
// let str='我爱你 <%= china %>'
let weather='我今天天气不错'
let str=fs.readFileSync('./ejs.html').toString()
let result=ejs.render(str,{china:china,weather})//render函数(渲染)把尖括号里面的东西变为china对象中china属性的值
console.log(result)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h2>我爱你 <%= china %></h2>
<p><%= weather %></p>
</body>
</html>
ejs列表渲染
const ejs=require('ejs')
const xiyou=['唐僧','孙悟空','猪八戒','沙僧']
// // 原生js
// let str='<ul>'
// xiyou.forEach(item=>{
// str+=`<li>${item}</li>`
// })
// str+='</ul>'
// console.log(str)
// EJS实现
const fs=require('fs')
let str=fs.readFileSync('./ejs.html').toString()
let result=ejs.render(str,{xiyou:xiyou})
console.log(result)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<ul>
<% xiyou.forEach(item=>{ %>
<li><%= item %></li>
<% }) %>
</ul>
</body>
EJS条件渲染
// 条件渲染
/**
* 通过isLogin决定最终的输出内容
* true 输出 <span>欢迎回来</span>
* false 输出 <button>登录</button> <button>注册</button>
*/
在express中使用ejs
// 在express中使用ejs
const express=require('express')
const path=require('path')
const app=express()
// 设置模板引擎
app.set('view engine','ejs')
// 设置模板文件存放位置 模板文件:具有模板语法内容的文件
app.set('views',path.resolve(__dirname,'./views'))
app.get('/home',(req,res)=>{
// render响应
// res.render('模板的文件名','数据')
// 声明变量
let title='尚硅谷 - 让天下没有难学的技术'
res.render('home',{title})
})
app.listen(3000,()=>{
console.log('服务已经启动,端口3000正在监听中')
})
注意要提前创建文件夹views,并且里面有名为home的子文件夹,文件类型为ejs
express - generator
npm install -g express-generator
express -e 文件名 安装generator,在该文件夹下打开终端 npm i,下载依赖包
文件上传报文以及处理文件上传请求
在上述generator文件夹中找到routes文件夹下的index.js,在里面写路由模块
在views中建立portrait.ejs文件
有关formidable的使用,参见121_express框架_处理文件上传_哔哩哔哩_bilibili
lowdb的使用参见125_案例实践_04_lowdb的介绍与演示_哔哩哔哩_bilibili
使用Express写接口
创建基本的服务器
创建API路由模块
编写GET接口
编写POST接口
注意:如果要获取URL-encoded格式的请求体数据,必须配置中间件 app.use(express.urlencoded({extended:false}))
16.apiRouter.js
const express = require('express')
const router = express.Router()
// 在这里挂载对应的路由
router.get('/get', (req, res) => {
// 通过 req.query 获取客户端通过查询字符串,发送到服务器的数据
const query = req.query
// 调用 res.send() 方法,向客户端响应处理的结果
res.send({
status: 0, // 0 表示处理成功,1 表示处理失败
msg: 'GET 请求成功!', // 状态的描述
data: query, // 需要响应给客户端的数据
})
})
// 定义 POST 接口
router.post('/post', (req, res) => {
// 通过 req.body 获取请求体中包含的 url-encoded 格式的数据
const body = req.body
// 调用 res.send() 方法,向客户端响应结果
res.send({
status: 0,
msg: 'POST 请求成功!',
data: body,
})
})
// 定义 DELETE 接口
router.delete('/delete', (req, res) => {
res.send({
status: 0,
msg: 'DELETE请求成功',
})
})
module.exports = router
// 导入 express
const express = require('express')
// 创建服务器实例
const app = express()
// 配置解析表单数据的中间件
app.use(express.urlencoded({ extended: false }))
// 必须在配置 cors 中间件之前,配置 JSONP 的接口
app.get('/api/jsonp', (req, res) => {
// TODO: 定义 JSONP 接口具体的实现过程
// 1. 得到函数的名称
const funcName = req.query.callback
// 2. 定义要发送到客户端的数据对象
const data = { name: 'zs', age: 22 }
// 3. 拼接出一个函数的调用
const scriptStr = `${funcName}(${JSON.stringify(data)})`
// 4. 把拼接的字符串,响应给客户端
res.send(scriptStr)
})
// 导入路由模块
const router = require('./16.apiRouter')
// 把路由模块,注册到 app 上
app.use('/api', router)
// 启动服务器
app.listen(80, () => {
console.log('express server running at http://127.0.0.1')
})
接口的跨域问题
刚才的GET和POST接口,存在一个很严重的问题:不支持跨域请求(网页为file协议,接口为https协议)
解决接口跨域问题的方案主要有两种:
- CORS(主流的解决方案:推荐使用)
- JSONP(有缺陷的解决方案,只支持GET请求)
COTS跨域资源共享
使用cors中间件解决跨域问题
cors是Express的一个第三方中间件,通过安装和配置cors中间件,可以很方便的解决跨域问题
使用步骤分为如下三步:
- 运行 npm install cors 安装中间件
- 使用const cors=require('cors')导入中间件
- 在路由之前调用app.use(cors())配置中间件
// 一定要在路由之前,配置 cors 这个中间件,从而解决接口跨域的问题
const cors = require('cors')
app.use(cors())
// 导入 express
const express = require('express')
// 创建服务器实例
const app = express()
// 配置解析表单数据的中间件
app.use(express.urlencoded({ extended: false }))
// 必须在配置 cors 中间件之前,配置 JSONP 的接口
app.get('/api/jsonp', (req, res) => {
// TODO: 定义 JSONP 接口具体的实现过程
// 1. 得到函数的名称
const funcName = req.query.callback
// 2. 定义要发送到客户端的数据对象
const data = { name: 'zs', age: 22 }
// 3. 拼接出一个函数的调用
const scriptStr = `${funcName}(${JSON.stringify(data)})`
// 4. 把拼接的字符串,响应给客户端
res.send(scriptStr)
})
// 一定要在路由之前,配置 cors 这个中间件,从而解决接口跨域的问题
const cors = require('cors')
app.use(cors())
// 导入路由模块
const router = require('./16.apiRouter')
// 把路由模块,注册到 app 上
app.use('/api', router)
// 启动服务器
app.listen(80, () => {
console.log('express server running at http://127.0.0.1')
})
什么是CORS
CORS(Cross-Origin Resource Sharing,跨域资源共享)由一系列HTTP响应头组成,这些HTTP响应头决定浏览器是否阻止JS代码跨域获取资源
浏览器的同源安全策略默认会阻止网页”跨域“获取资源,但如果接口服务器配置了CORS相关的HTTP响应头,就可以解除浏览器端的跨域访问限制
CORS跨域资源共享
CORS的注意事项
- CORS主要在服务端进行配置,客户端浏览器无需做任何额外的配置,即可请求开启了CORS的接口
- CORS在浏览器中有兼容性,只有支持XMLHttpRequest Level2的浏览器,才能正常的访问开启了CORS的服务端接口(例如:IE10+,Chrome4+,FireFox3.5+)
CORS响应头部 - Access-Control-Allow-Origin
响应头部中可以携带一个Access-Control-Allow-Origin字段,其语法如下:
Access-Control-Allow-Origin:<origin>|*
其中,origin参数的值指定了允许访问该资源的外域URL
例如:下面的字段值将只允许来自http://itcast.cn的请求
res.setHeader('Access-Control-Allow-Origin','http://itcast.cn')
如果指定了Access-Control-Allow-Origin字段的值为通配符*,表示允许来自任何域的请求,示例代码如下:
res.setHeader('Access-Control-Allow-Origin','*')
CORS响应头部-Access-Control-Allow-Headers
默认情况下,CORS仅支持客户端向服务器发送如下的9个请求头:
如果客户端向服务器发送了额外的请求头信息,则需要在服务器端,通过Access-Control-Allow-Headers对额外的请求头进行声明,否则这次请求会失败!
CORS响应头部- Access-Control-Allow-Methods
默认情况下,CORS仅支持客户端发起GET,POST,HEAD请求
如果客户端希望通过PUT,DELETE等方式请求服务器的资源,则需要在服务器端,通过Access-Control-Allow-Methods来指明实际请求所允许使用的HTTP方法
CORS请求的分类
客户端在请求CORS接口时,根据请求方式和请求头的不同,可以将CORS的请求分为两大类,分别是:
- 简单请求
同时满足两大条件的请求,就属于简单请求:
- 请求方式:GET,POST,HEAD三者之一
- HTTP头部信息不超过以下几种字段:无定义头部字段
- 预检请求
只要符合以下任何一个条件的请求,都需要进行预检请求:
- 请求方式为GET,POST,HEAD之外的请求Method类型
- 请求头中包含自定义头部字段
- 向服务器发送了application/json格式的数据
在浏览器与服务器正式通信之前,浏览器会先发送OPTION请求进行预检,以获知服务器是否允许该实际请求,所以这一次的OPTION请求称为"预检请求",服务器成功响应预检请求后,才会发送真正的请求,并且携带真实数据
简单请求和预检请求的区别:
简单请求的特点:客户端与服务器之间只会发生一次请求
预检请求的特点:客户端与服务器之间会发生两次请求,OPTION预检请求成功之后,才会发起真正的请求
JSONP接口
JSONP概念与特点
概念:浏览器1通过<script>标签的src属性,请求服务器上的数据,同时,服务器返回一个函数的调用,这种请求数据的方式叫做JSONP
特点:
- JSONP不属于真正的Ajax请求,因为他没有使用XMLHttpRequest这个对象
- JSONP仅支持GET请求,不支持POST,PUT,DELETE等请求
如果项目中已经配置了CORS跨域资源共享,为了防止冲突,必须在配置CORS中间件之前声明JSONP的接口,否则JSONP接口会被处理成开启了CORS的接口,示例代码如下:
实现JSONP接口的步骤
- 获取客户端发送过来的回调函数的名字
- 得到要通过JSONP形式发送给客户端的数据
- 根据前两步得到的数据,拼接出一个函数调用的字符串
- 把上一步拼接得到的字符串,响应给客户端的<script>标签进行解析执行
// 必须在配置 cors 中间件之前,配置 JSONP 的接口
app.get('/api/jsonp', (req, res) => {
// TODO: 定义 JSONP 接口具体的实现过程
// 1. 得到函数的名称
const funcName = req.query.callback//查询字符串中的callback属性的值
// 2. 定义要发送到客户端的数据对象
const data = { name: 'zs', age: 22 }
// 3. 拼接出一个函数的调用
const scriptStr = `${funcName}(${JSON.stringify(data)})`
// 4. 把拼接的字符串,响应给客户端
res.send(scriptStr)
})
在网页中使用jQuery发起JSONP请求
调用$.ajax()函数,提供JSONP的配置选项,从而发起JSONP请求,示例代码如下: