首页 > 编程语言 >Node.js 的 Express

Node.js 的 Express

时间:2024-08-22 11:17:39浏览次数:10  
标签:Node req 请求 res app Express 中间件 js express

express

Express基于Node.js平台,快速、开放、极简的Web开发框架

express 的作用和Node.js内置的 http模块类似,都是用来创建web服务器的

本质就是一个 npm 上的第三方包,提供了快速创建 Web 服务器的便捷方法

  1. 不使用 Express 能否创建 Web 服务器?
    能,使用 Node.js 提供的原生 http 模块即可
  2. 有了 http 内置模块,为什么还有用 Express?
    http 内置模块用起来很复杂,开发效率低;
  3. Express 是基于内置的 http 模块进一步封装出来的,能够极大的提高开发效率
    http 内置模块与 Express 是什么关系?
    类似于浏览器中 Web API 和 jQuery 的关系。后者是基于前者进一步封装出来的
//在项目中导入
// npm i express
// 导入epxress
const express = require('express')
// 创建web服务器
const app = express()

const port = 3000
// 启动服务器
app.listen(port, () => {
    console.log('Server running on port 3000')
})

处理getpost请求

// 监听 get 请求
/**
 * @param {string} url - 请求数据的地址
 * @param {function} call - 执行的处理函数
 */
app.get('/url', (req, res) => {
    //实际的请求地址为 监听地址/url
    //req 是 请求体
    //res 是 响应体
})
// 监听 post 请求
/**
 * @param {string} url - 请求数据的地址
 * @param {function} call - 执行的处理函数
 */
app.post('/url', (req, res) => {
    // ...
})

express 中有丰富的方法来处理 req(请求对象),res(响应对象)

req & res

请求对象的方法:

  • req.accepts(types): 检查客户端是否接受指定的响应内容类型。
  • req.acceptsCharsets(charsets): 检查客户端是否接受指定的字符集。
  • req.acceptsEncodings(encodings): 检查客户端是否接受指定的响应编码。
  • req.acceptsLanguages(langs): 检查客户端是否接受指定的语言。
  • req.is(type): 判断请求头中的Content-Type是否符合指定类型。
  • req.get(field): 获取指定的请求头字段的值。
  • req.header(field): 同req.get,获取请求头字段的值。
  • req.params: 包含路由参数的对象。
  • req.query: 包含查询字符串参数的对象。
  • req.body: 包含解析后的请求体(通常用于POST请求)。
  • req.cookies: 包含解析后的Cookie值的对象。
  • req.signedCookies: 同req.cookies,但是使用签名Cookie进行验证。
  • req.session: 如果使用会话中间件,这个属性包含会话数据。
  • req.file: 如果使用multer中间件,这个属性包含上传的文件信息。
  • req.files: 同req.file,但在多个文件上传时包含所有文件。

响应对象的方法:

  • res.append(field, value): 设置HTTP响应头的字段。
  • res.attachment(filename): 设置Content-Disposition响应头,指示浏览器这是一个附件。
  • res.cookie(name, value, options): 设置Cookie。
  • res.clearCookie(name, options): 清除Cookie。
  • res.download(path, filename): 指示浏览器下载文件。
  • res.end([data], [encoding]): 结束响应过程。
  • res.format(types): 根据请求头设置的内容类型返回不同格式的响应。
  • res.get(field): 获取响应头字段的值。
  • res.json([status], [body]): 发送JSON响应。
  • res.jsonp([status], [body]): 发送JSONP响应。
  • res.location(path): 设置响应头Location字段。
  • res.redirect([status], [url]): 重定向请求。
  • res.render(view, [locals], [callback]): 渲染视图模板。
  • res.send([body]): 发送响应正文。
  • res.sendFile(path, [options], [callback]): 发送文件作为响应。
  • res.set(field, value): 设置响应头字段。
  • res.status(code): 设置HTTP响应状态码。
  • res.type(type): 设置Content-Type响应头字段。
  • res.vary(field): 指定使用缓存的变量。

当客户端使用 url?key1=value1&key2=value2的形式传递参数时:

//默认情况下,req.query 是一个空对象
// 获取对应键的值
req.query.key

动态匹配URL参数

app.get('/user/:id/:username', (req, res) => {
  // req.params 是动态匹配到的 URL 参数,默认也是一个空对象
  console.log(req.params)
  res.send(req.params)
})

若:请求的url/user/20240821/xisoil
则:req.params 打印结果如下 { id: '20213128', user: 'xisoil' } ,res.send(req.params) 发送的数据如下

{
    "id": "20213128",
    "user": "xisoil"
}

托管静态资源

express 提供了一个非常好用的函数,叫做 express.static(),通过它,我们可以非常方便地创建一个静态资源服务器,例如,通过如下代码就可以将 public 目录下的图片、CSS 文件、JavaScript 文件对外开放访问了

app.use(express.static('public'))

现在,你就可以访问通过url直接访问public 目录中的所有文件了

托管多个静态资源时

express.static() 函数会根据目录的添加顺序查找所需的文件

app.use(express.static('public'))
app.use(express.static('files'))

此时,先加载public中的文件,再加载files中的文件

挂在路径前缀

如果你想按照下面例子这样来访问,从而避免不同文件夹中同名的文件困扰

像下面一样,添加一个'/' 即可

app.use('/public', express.static('public'))

路由

广义上来讲,路由就是映射关系

在 Express 中,路由指的是客户端的请求与服务器处理函数之间的映射关系

Express 中的路由分 3 部分组成,分别是请求的类型、请求的 URL 地址、处理函数,格式如下

app.method(path, handler())	// method 具体为 get post 等

路由的匹配过程

  • 每当一个请求到达服务器之后,需要先经过路由的匹配,只有匹配成功之后,才会调用对应的处理函数。
  • 在匹配时,会按照路由的顺序进行匹配,如果请求类型和请求的 URL 同时匹配成功,则 Express 会将这次请求,转交给对应的 function 函数进行处理。
    • 按照定义的先后顺序进行匹配
    • 请求类型和请求的URL同时匹配成功,才会调用对应的处理函数

Express 中使用路由最简单的方式,就是把路由挂载到 app 上

const express = require('express')

const app = express()

// 挂载路由
app.get('/', (req, res) => {res.send('Get Request.')})
app.post('/', (req, res) => {res.send('Post Request.')})

app.listen(3000, () => {console.log('express server running at http://127.0.0.1')})

模块化路由

为了方便对路由进行模块化的管理,Express 不建议将路由直接挂载到 app 上,而是推荐将路由抽离为单独的模块。将路由抽离为单独模块的步骤如下:

  • 创建路由模块对应的 .js 文件
  • 调用 express.Router() 函数创建路由对象
  • 向路由对象上挂载具体的路由
  • 使用 module.exports 向外共享路由对象
  • 使用 app.use() 函数注册路由模块
// 路由模块 router.js

const express = require('express')	// 1. 导入 express
const router = express.Router()			// 2. 创建路由对象

// 3. 挂载具体的路由
router.get('/user/list', (req, res) => {res.send('Get user list.')})
router.post('/user/add', (req, res) => {res.send('Add new user.')})

// 4. 向外导出路由对象
module.exports = router
const express = require('express')
const router = require('./router')	// 1. 导入路由模块

const app = express()

// 注意: app.use() 函数的作用,就是来注册全局中间件
// app.use('/files', express.static('./files'))
app.use('/api', router)	// 2. 注册路由模块,若想使用静态资源一样可以加统一的访问前缀

app.listen(80, () => {console.log('http://127.0.0.1')})

中间件

特指业务流程的中间处理环节

Express 中间件的调用流程

当一个请求到达 Express 的服务器之后,可以连续调用多个中间件,从而对这次请求进行预处理

格式

  • Express 的中间件,本质上就是一个 function 处理函数,Express 中间件的格式如下
  • 注意:中间件函数的形参列表中,必须包含 next 参数,而路由处理函数中只包含 req 和 res
  • next 函数是实现多个中间件连续调用的关键,它表示把流转关系转交给下一个中间件或路由
const express = require('express')
const app = express()

// 定义一个最简单的中间件函数
//mw指向的就是一个中间件函数
const mw = function (req, res, next) {
    console.log('这是最简单的中间件函数')
    // 把流转关系,转交给下一个中间件或路由
    next()
}

// 将 mw 注册为全局生效的中间件
app.use(mw)

app.get('/', (req, res) => {
    console.log('调用了 / 这个路由')
    res.send('Home page.')
})
app.get('/user', (req, res) => {
    console.log('调用了 /user 这个路由')
    res.send('User page.')
})

app.listen(3000, () => {
    console.log('http://127.0.0.1')
})

中间件会插入在路由处理之前

箭头函数的简化写法

app.use((req, res, next) => {
  console.log('这是最简单的中间件函数')
  next()
})
  • 多个中间件之间,共享同一份 reqres。基于这样的特性,我们可以在上游的中间件中,统一为 reqres 对象添加自定义的属性或方法,供下游的中间件或路由进行使用
  • 定义多个全局中间件,按照顺序先后调用
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(3000, () => {console.log('http://127.0.0.1')})

局部生效的中间件
不使用 app.use() 定义的中间件叫做局部生效的中间件,只在添加了中间件的路由中生效

app.get('/', mw1, nw2, (req, res) => {res.send('Home page.')})
app.get('/user', [mw1, mw2],(req, res) => {res.send('User page.')})
  1. 一定要在路由之前注册中间件,如果直接匹配到路由就会直接响应了。
  2. 客户端发送过来的请求,可以连续调用多个中间件进行处理
  3. 执行完中间件的业务代码之后,不要忘记调用 next() 函数
  4. 为了防止代码逻辑混乱,调用 next() 函数后不要再写额外的代码
  5. 连续调用多个中间件时,多个中间件之间,共享req 和 res 对象

Express 官方把常见的中间件用法,分成了 5 大类

  • 应用级别的中间件
    通过 app.use()app.get()app.post() ,绑定到 app 实例上的中间件,叫做应用级别的中间件

  • 路由级别的中间件
    绑定到 express.Router() 实例上的中间件,叫做路由级别的中间件。它的用法和应用级别中间件没有任何区别。只不过,应用级别中间件是绑定到 app 实例上,路由级别中间件绑定到 router 实例上,写法如下:

    const express = require('express')
    const router = express.Router()
    
    // 路由级别的中间件
    router.use((req, res, next)=>{
      console.log('Time:' + Date.now())
    	next()
    })
    
    module.experts = router
    
  • 错误级别的中间件
    错误级别中间件的作用:专门用来捕获整个项目中发生的异常错误,从而防止项目异常崩溃的问题。格式:错误级别中间件的 function 处理函数中,必须有 4 个形参,形参顺序从前到后,分别是 (err, req, res, next)错误级别的中间件,必须注册在所有路由之后

  • Express 内置的中间件

    自 Express 4.16.0 版本开始,Express 内置了 3 个常用的中间件,极大提高了 Express 项目的开发效率和体验

    1. express.static() 快速托管静态资源的内置中间件,例如: HTML 文件、图片、CSS 样式等(无兼容性,任何版本都能用)
    2. express.json() 解析 JSON 格式的请求体数据(有兼容性,仅在 4.16.0+ 版本中可用)
    3. express.urlencoded(option) 解析 URL-encoded 格式的请求体数据(有兼容性,仅在 4.16.0+ 版本中可用)
  • 第三方的中间件
    非 Express 官方内置的,由第三方开发出来的中间件。项目中,可以按需下载并配置第三方中间件,从而提高项目的开发效率

手动模拟一个类似于 express.urlencoded 这样的中间件,来解析 POST 提交到服务器的表单数据,实现步骤:

  1. 定义中间件
  2. 监听 req 的 data 事件
    来获取客户端发送到服务器的数据。如果数据量比较大,无法一次性发送完毕,则客户端会把数据切割后,分批发送到服务器。所以 data 事件可能会触发多次,每一次触发 data 事件时,获取到数据只是完整数据的一部分,需要手动对接收到的数据进行拼接。
  3. 监听 req 的 end 事件
    当请求体数据接收完毕之后,会自动触发 req 的 end 事件,可以在 req 的 end 事件中,拿到并处理完整的请求体数据
    Node.js 内置了一个 querystring 模块,专门用来处理查询字符串。通过这个模块提供的 parse() 函数,可以轻松把查询字符串,解析成对象的格式
  4. 使用 querystring模块解析请求体数据
  5. 将解析出来的数据对象挂载为 req.body
  6. 将自定义中间件封装为模块
// custom-body-parser.js

const qs = require('querystring')	// 导入 Node.js 内置的 querystring 模块

const bodyParser = (req, res, next) => {
  // 定义中间件具体的业务逻辑
  // 1. 定义一个 str 字符串,专门用来存储客户端发送过来的请求体数据
  let str = ''
  // 2. 监听 req 的 data 事件,str 中存放的是完整的请求体数据
  req.on('data', (chunk) => {
    str += chunk
  })
  // 3. 监听 req 的 end 事件(请求体发送完毕后自动触发)
  req.on('end', () => {
    req.body = qs.parse(str)	// 把字符串格式的请求体数据,解析成对象格式,不解析的话是 name=zs&gender=%6Eksskk
      //将解析出来的数据挂载在req.body上,供下游中间件访问
    next()
  })
}

module.exports = bodyParser
const express = require('express')
const app = express()

// 导入自己封装的中间件模块,将自定义的中间件函数,注册为全局可用的中间件
app.use(require('./custom-body-parser'))

app.post('/user', (req, res) => {
  res.send(req.body)
})

// 调用 app.listen 方法,指定端口号并启动web服务器
app.listen(3000, function () {
  console.log('Express server running at http://127.0.0.1')
})

练习:创建一个基本的服务器

const express = require('express') // 导入 express 模块
const app = express() // 创建 app 服务器
const port = 3000 // 指定端口
const router = require('./apiRouter.js') // 导入路由模块

app.use(express.urlencoded({ extended: false })) // 配置解析表单数据的中间件

app.use('/api', router) // 全局注册路由
// 请求路径 /api/get
// 请求路径 /api/post

app.listen(port, () => {
    console.log('Express app listening on port: ' + port)
})

创建路由模块和get、post接口

const express = require('express') // 导入 express 模块
const router = express.Router()

router.get('/get', (req, res) => {
    const query = req.query
    console.log(query)
    res.send({
        status: 200,
        msg: 'get success',
        data: query
    })
})

router.post('/post', (req, res) => {
    const body = req.body // 解析表单的body需要全局配置express.urlencoded中间件
    console.log(body)
    res.send({
        status: 200,
        msg: 'post success',
        data: body
    })
})

module.exports = router

当你从从一个网址向这个服务器发送请求时,将会遇到跨域问题

跨域问题

浏览器从一个域名的网页去请求另一个域名的资源时,域名、端口、协议任一不同,都是跨域

解决接口跨域问题的方案主要有两种

  • CORS(主流解决方案,推荐)
  • JSONP(有缺陷:只支持 GET 请求)

使用 CORS 中间件解决跨域问题
CORS(Cross-Origin Resource Sharing,跨域资源共享)Express 的一个第三方中间件,由一系列 HTTP 响应头组成,这些 HTTP 响应头决定浏览器是否阻止前端 JS 代码跨域获取资源。可以很方便地解决跨域问题。使用步骤分为如下 3 步

  1. 运行 npm install cors 安装中间件
  2. 使用 const cors = require(‘cors’) 导入中间件
  3. 在路由之前调用 app.use(cors()) 配置中间件

CORS 在服务器端进行配置,客户端浏览器无须做任何额外的配置,即可请求开启了 CORS 的接口。
CORS 在浏览器中有兼容性,只有支持 XMLHttpRequest Level2 的浏览器,才能正常访问开启了 CORS 的服务端接口(如:IE10+、Chrome4+、FireFox3.5+)

响应头

  1. Access-Control-Allow-Origin
    Origin指定了允许访问该资源的外域 URL,可以控制哪些网页可以请求该服务器,而*表示所有网页均可

    res.setHeader('Access-Control-Allow-Origin', url)
    
  2. Access-Control-Allow-Headers
    默认情况下,CORS 仅支持客户端向服务器发送如下的 9 个请求头

    Accept
    Accept-Language
    Content-Language
    DPR
    Downlink
    Save-Data
    Viewport-Width
    Width
    Content-Type (值仅限于 text/plain、multipart/form-data、application/x-www-form-urlencoded)

    如果客户端向服务器发送了额外的请求头信息,则需要在服务器端,通过 Access-Control-Allow-Headers 对额外的请求头进行声明,否则这次请求会失败!

    res.setHeader('Access-Control-Allow-Headers', 'Content-Type, X-custom-Header')
    
  3. Access-Control-Allow-Methods
    默认情况下,CORS 仅支持客户端发起 GET、POST、HEAD 请求。
    如果客户端希望通过 PUT、DELETE 等方式请求服务器的资源,则需要在服务器端,通过 Access-Control-Alow-Methods来指明实际请求所允许使用的 HTTP 方法

    res.setHeader('Access-Control-Allow-Methods', 'POST, GET, PUT, DELETE')
    
    res.setHeader('Access-Control-Allow-Methods', '*')	// 支持所有请求
    

客户端在请求 CORS 接口时,根据请求方式和请求头的不同,可以将 CORS 的请求分为两大类

简单请求

同时满足以下两大条件的请求,就属于简单请求
请求方式:GET、POST、HEAD 三者之一
HTTP 头部信息不超过以下几种字段:无自定义头部字段、Accept、Accept-Language、Content-Language、DPR、Downlink、Save-Data、Viewport-Width、Width 、Content-Type(只有三个值application/x-www-form-urlencoded、multipart/form-data、text/plain)
预检请求

只要符合以下任何一个条件的请求,都需要进行预检请求

  • ⅰ请求方式为 GET、POST、HEAD 之外的请求 Method 类型
  • ⅱ请求头中包含自定义头部字段
  • ⅲ向服务器发送了 application/json 格式的数据

在浏览器与服务器正式通信之前,浏览器会先发送 OPTION 请求进行预检,以获知服务器是否允许该实际请求,这一次的 OPTION 请求称为预检请求。服务器成功响应预检请求后,才会发送真正的请求,并且携带真实数据。

  • 简单请求的特点:客户端与服务器之间只会发生一次请求
  • 预检请求的特点:客户端与服务器之间会发生两次请求,OPTION 预检请求成功之后,才会发起真正的请求

JSONP 接口(有缺陷只支持GET)
jsonp解决跨域问题的本质就是利用script标签的src属性来进行,由于script标签的src属性不受跨域的现在,所以我们可以将我们要请求的服务器的地址及相关参数作为src的值来进行传输,然后再由后台进行配合就可以实现跨域

  1. 获取客户端发送过来的回调函数的名字
  2. 得到要通过 JSONP 形式发送给客户端的数据
  3. 根据前两步得到的数据,拼接出一个函数调用的字符串
  4. 把上一步拼接得到的字符串,响应给客户端的

nodemon

在编写调试 Node.js 项目的时候,如果修改了项目的代码,需要频繁的手动重新启动服务,使用 nodemon https://www.npmjs.com/package/nodemon 工具,它能够监听项目文件的变动,当代码被修改后,nodemon 会自动重启项目,极大方便了开发和调试。

npm i -g nodemon

现在,我们可以将 node 命令替换为 nodemon 命令,使用 nodemon app.js 来启动项目。

nodemon app.js

标签:Node,req,请求,res,app,Express,中间件,js,express
From: https://www.cnblogs.com/xxaxf/p/18373419

相关文章

  • (附源码)NodeJS农产品在线交易平台-计算机毕设 01124
     NodeJS农产品在线交易平台目 录摘 要1绪论1.1研究背景1.2研究意义1.3论文结构与章节安排2 系统分析2.1可行性分析2.2系统流程分析2.2.1数据新增流程2.2.2 数据删除流程2.3 系统功能分析2.3.1功能性分析2.3.2非功能性分析2.4 ......
  • 使用 JsonSchema 校验 JSON数据
    有时候JSON数据格式需要校验是否合法,我们可以使用JsonSchema来校验数据是否合法。引入pom.xmlhttps://json-schema.org/<dependency><groupId>com.networknt</groupId><artifactId>json-schema-validator</artifactId><versio......
  • 「对比评测」标准WPF DataGrid与DevExpress WPF GridControl有何不同?(一)
    DevExpressWPF拥有120+个控件和库,将帮助您交付满足甚至超出企业需求的高性能业务应用程序。通过DevExpressWPF能创建有着强大互动功能的XAML基础应用程序,这些应用程序专注于当代客户的需求和构建未来新一代支持触摸的解决方案。无论是Office办公软件的衍伸产品,还是以数据为中心......
  • 使用 Vue I18n 进行 Vue.js 应用的国际化
    随着互联网的全球化发展,开发多语言支持的应用变得越来越重要。Vue.js作为一个流行的前端框架,通过vue-i18n插件,能够非常方便地实现应用的国际化(i18n)。本文将介绍如何在Vue.js应用中使用vue-i18n进行国际化设置。什么是国际化(i18n)?国际化(Internationalization)通常简写......
  • js实现点击图片放大效果
    实现点击图片后弹出一个遮罩层来放大显示图片的功能,可以使用简单的HTML和JavaScript来完成:HTML结构-包含图片和一个用于弹出的遮罩层。CSS样式-设置遮罩层和放大的图片样式。JavaScript逻辑-处理点击事件和遮罩层的显示与隐藏。HTML<!DOCTYPEhtml><htmllang="en......
  • 【精品毕设推荐】基于SSM+jsp的人事管理信息系统设计与实现
    点击下载原文及代码,可辅助在本地配置运行摘 要现代经济快节奏发展以及不断完善升级的信息化技术,让传统数据信息的管理升级为软件存储,归纳,集中处理数据信息的管理方式。本人事管理信息系统就是在这样的大环境下诞生,其可以帮助管理者在短时间内处理完毕庞大的数据信息,使用这......
  • 【精品毕设推荐】基于SSM+jsp的社区管理与服务系统设计与实现
    点击下载原文及代码,可辅助在本地配置运行摘要本社区管理与服务系统设计目标是实现社区信息化管理,提高效率,为广大社区用户提供更优质的服务。本文重点阐述了社区管理与服务系统的开发过程,以实际运用为开发背景,基于B/S结构,运用了Java技术和MYSQL数据库进行开发设计,充分保证......
  • 【精品毕设推荐】基于SSM+jsp的人才招聘网站系统设计与实现
    点击下载原文及代码,可辅助在本地配置运行摘要 随着科技的发展,人才招聘的方式也发生着改变。本基于ssm的人才招聘网站正是采用计算机技术和网络设计的新型系统,可以有效的把招聘信息与网络相结合,为用户提供工作帮助和管理需求。本系统采用mysql数据库存储数据,兼容性更强,可跨......
  • 【JS逆向】探索文章链接地址AES加密后如何再次替换变形
    一、加密后的数据变形二、加密函数的定义defencrypt_message(message):cipher=AES.new(key,AES.MODE_ECB)padded_message=pad(message.encode(),AES.block_size,style='pkcs7')cliphertext=cipher.encrypt(padded_message)returncliphert......
  • JS 读写剪贴板
    document.execCommandAPI复制操作复制时,先选中文本,然后调用document.execCommand('copy'):constinputElement=document.querySelector("#input");inputElement.select();document.execCommand("copy");注意:复制操作最好由用户触发,如果脚本自主执行,可能某些浏览器会报......