Node.js:Express 中间件 CORS 跨域资源共享

Node.js:Express 中间件 & CORS

    • 中间件
      • 全局中间件
      • 局部中间件
      • 分类
      • 错误级中间件
      • 内置中间件
    • CORS
      • 原理
      • 预检请求


中间件

中间件是不直接接收请求,也不直接发送响应,而是在这之间处理一个中间过程的组件。

在这里插入图片描述

当一个请求到来,会经过多个中间件进行处理,后一个中间件拿到前一个中间件的处理结果,进行再处理,处理完毕后发给下一个中间件,以此类推。直到所有任务执行完毕,最后得到一个响应,再发送回给客户端。

express中,所谓的中间件不过就是一个函数,接收参数返回结果。

全局中间件函数定义:

function (req, res, next){next()
}

其接收三个参数,前两个参数与请求的响应函数一致,next是中间件必须有的参数,并且在中间件函数的末尾,必须调用next()方法,这样才会调用下一个中间件函数。


全局中间件

定义好中间件函数后,可以通过app.use将其注册到服务中。

app.use(Middleware)

其中Middleware是一个中间件函数。

这种被直接注册到app.use上的中间件,称为全局生效中间件客户端发起的任何请求,都会触发全局中间件

app.use(function (req, res, next){console.log("Middleware running...")next()
})app.get('/', function (req, res){console.log("get / success")
})app.get('/index.html', function (req, res){console.log("get /index.html success")
})app.listen(80, () => {console.log("create web server success")
})

以上服务,定义了一个匿名的中间件函数,并且注册到app.use中,两个响应函数分别响应//index.html

在浏览器中访问这两个地址,查看控制台:

Middleware running...
get / success
Middleware running...
get /index.html success

两个请求都触发了中间件,并且中间件比路由先执行。

中间件之间又要如何传递参数?在从收到请求到发送响应期间,所有的中间件共享同一个reqres对象

因此上游的中间件可以把属性或方法添加到这两个对象中,然后下游的中间件只需要访问这两个对象就可以拿到参数。

示例:

app.use(function (req, res, next){console.log("Middleware running...")req.sendStr = 'hello world!'next()
})app.get('/', function (req, res){console.log("get / success")res.send(req.sendStr)
})app.get('/index.html', function (req, res){console.log("get /index.html success")res.send(req.sendStr)
})

这个代码,在第一个中间件处,给req添加了一个对象sendStr = 'hello world!',在最后的路由函数中,就可以直接获取req.sendStr并发送出去。

如果要定义多个中间件,只需要多次使用app.use注册即可:

app.use(function (req, res, next){console.log("Middleware 1 running...")next()
})app.use(function (req, res, next){console.log("Middleware 2 running...")next()
})app.get('/', function (req, res){console.log("get / success")
})

多个中间件会以定义的顺序依次执行,访问/的输出结果:

Middleware 1 running...
Middleware 2 running...
get / success

可以看到,先执行了Middleware 1后执行Middleware 2,最后执行路由函数。


局部中间件

如果不使用app.use注册中间件,而是把中间件注册到某个路由上,称为局部中间件,这种中间件只在某个路由触发时执行。

注册局部中间件直接将中间件函数写入到getpost方法中:

app.get('url', Middleware, function(){})
app.post('url', Middleware, function(){})

示例:

const vm1 = function(req, res, next){console.log("Middleware 1 running...")next()
}app.get('/', vm1, function (req, res){console.log("get / success")
})app.get('/index.html', function (req, res){console.log("get /index.html success")
})app.listen(80, () => {console.log("create web server success")
})

以上代码为get /路由绑定了中间件vm1,但是get /index.html没有绑定。

访问get /

Middleware 1 running...
get / success

访问get /index.html

get /index.html success

此时只有get /触发了局部中间件。

如果要定义多个局部中间件,有两种形式:

app.get('url', Middleware1, Middleware2, function(){})
app.post('url', [Middleware1, Middleware2], function(){})

第一种是直接传入多个中间件函数,第二种是把多个中间件函数作为一个数组进行传入。执行顺序从前往后。

一些中间件的注意事项:

  1. 中间件必须在路由之前注册
  2. 所有中间件必须调用next()方法
  3. next()方法后面不要再写其他逻辑,作为整个函数的结尾

分类

Express官方将中间件的用法,分为了五大类:

  1. 应用级中间件
  2. 路由级中间件
  3. 错误级中间件
  4. Express内置中间件
  5. 第三方中间件

应用级中间件

只要中间件被绑定到app上,就是应用级中间件,先前讲解的两个全局和局部中间件,都属于应用级中间件。

路由级中间件

如果中间件被绑定到express.Router对象上,那么就是路由级中间件。

示例:

const app = express()
const router = express.Router()// 路由级中间件
router.use(function (req, res, next){next()
})app.use('/', router)

在博客 [Node.js:Express 服务 & 路由] 讲解路由模块化时,讲解过这个对象,如果想把路由进行模块化,就在一个新的模块中专门绑定路由到这个Router对象上,然后再把这个对象共享给外部。


错误级中间件

错误级中间件专门用于捕获整个项目发送的异常错误,防止项目崩溃。

函数格式:

function (err, req, res, next){next()
}

在基本的中间件函数上,第一个参数增加一个err参数,用于捕获全局的异常。

示例:

const express = require('express')
const app = express()app.get('/', function (req, res){throw new Error(' / create a error!') // 抛出异常res.send('success')
})// 注册错误级中间件
app.use(function (err, req, res, next){res.send('something happen: ' + err.message)
})app.listen(80, () => {console.log("create web server success")
})

以上代码,在访问get /时,会抛出一个异常,如果不处理项目就崩溃了。

随后为该服务注册了一个错误级中间件,在中间件内部err就是异常对象,直接把异常信息发送回给客户端。

注意:只有错误级别的中间件才可以在路由之后注册,其余的中间件都必须在路由前注册

输出结果:

在这里插入图片描述

可以看到,此处得到的结果是错误信息,说明错误被处理了。


内置中间件

Express内置了三个中间件,这些中间件可以快速完成某些功能:

  • express.static:托管静态资源
  • express.json:解析json格式的请求数据
  • express.urlencoded:解析URL-encoded格式的请求数据

其中第一个中间件已经在之前详细讲解过了,接下来看看后两个中间件的功能:

启动如下服务:

const express = require('express')
const app = express()app.post('/user', function (req, res){console.log(req.body)
})app.listen(80, () => {console.log("create web server success")
})

其中post /user路由,会把收到的请求的请求体输出到控制台。

使用postman发送一个POST请求,请求内容为一个json字符串:

{"name": "张三","age": 18
}

控制台输出结果:

undefined

奇怪了,明明发送了一个json字符串,为什么请求体得到的是一个undefined

如果不配置解析数据的中间件,那么req.body = undefined

而这个解析数据的中间件,就是express.json或者express.urlencoding

express.json

想要解析刚才的json格式数据,只需要将express.json注册到服务上即可:

const express = require('express')
const app = express()app.use(express.json()) // 注册处理数据的中间件app.post('/user', function (req, res){console.log(req.body)
})app.listen(80, () => {console.log("create web server success")
})

再次发送相同的请求,控制台输出结果就是正确的字符串了。

express.urlencoded

postman发送以下数据:

在这里插入图片描述

以键值对的形式发送数据,如果依然使用express.json进行解析,虽然req.body不是undefined了,但是由于检测不到json字符串,最后会得到一个空对象。

这种键值对形式的数据,就需要express.urlencoded中间件了:

const express = require('express')
const app = express()app.use(express.urlencoded({ extended: false }))app.post('/user', function (req, res){console.log(req.body)
})app.listen(80, () => {console.log("create web server success")
})

使用urlencoded时,要传入一个对象,属性值固定为extended: false

发起同样的请求,输出结果:

在这里插入图片描述

最后发送的数据,就被转化为了一个对象。


CORS

现有以下服务:

const express = require('express')
const app = express()app.get('/user', function (req, res){res.send(req.query)
})app.post('/user', express.urlencoded({ extended: false }),function (req, res){res.send(req.body)})app.listen(80, () => {console.log("create web server success")
})

这个服务接收一个get /usr或者post /usr请求,并把请求参数发送回给客户端。但是这样无法解决跨域问题。

test.html中编写以下代码:

<button id="btnGET">GET</button>
<button id="btnPOST">POST</button><script>// 1. 测试GET接口$('#btnGET').on('click', function () {$.ajax({type: 'GET',url: 'http://127.0.0.1/user',data: { name: '张三', age: 20 },success: function (res) {console.log(res)},})})// 2. 测试POST接口$('#btnPOST').on('click', function () {$.ajax({type: 'POST',url: 'http://127.0.0.1/user',data: { name: '张三', age: 20 },success: function (res) {console.log(res)},})})
</script>

通过点击按钮,分别发送get /userpost /user

输出结果:

在这里插入图片描述

此时两个请求都失败了,因为打开文件采用的是file协议,而请求的urlhttp协议,两者协议不同,构成跨域。

第三方封装了一个Express中间件,提供了非常方便的跨域解决方案CORS

  1. 安装中间件:
npm i -g cors
  1. 导入cors中间件并注册到服务上:
const express = require('express')
const app = express()// 导入core中间件
const cors = require('cors')
app.use(cors()) // 注册app.get('/user', function (req, res){res.send(req.query)
})app.post('/user', express.urlencoded({ extended: false }), function (req, res){res.send(req.body)
})app.listen(80, () => {console.log("create web server success")
})

再次访问:

在这里插入图片描述

跨域问题瞬间就解决了。


原理

CORS全称跨域资源共享,是一种基于 HTTP 头的机制,该机制通过允许服务器标示除了它自己以外的其他源,使得浏览器允许这些源访问加载自己的资源。

在正常跨域访问时,服务器会正常收到来自浏览器的请求,并发出响应:

在这里插入图片描述

但是当浏览器检测到响应跨域,依照同源策略,那么就会拦截这个响应,导致客户端接收不到这个响应。

在这里插入图片描述

引入cors中间件后,cors会修改HTTP响应头,解除浏览器的跨域访问限制

Access-Control-Allow-Origin 响应头

Access-Control-Allow-Origin指定了允许访问该资源的外域URL,只有符合要求的地址,才允许请求当前服务器。

res.setHeader('Access-Control-Allow-Origin', 'https://example')

以上代码,可以指定只有https://example可以访问当前服务器。如果不希望限制任何客户端对服务的访问,那么第二个参数填入通配符*

res.setHeader('Access-Control-Allow-Origin', '*')

Access-Control-Allow-Headers 响应头

Access-Control-Allow-Headers指定了允许访问该资源的请求头,默认情况下包含以下九种请求头:

  1. Accept
  2. Accept-Language
  3. Content-Language
  4. DPR
  5. Downlink
  6. Save-Data
  7. Viewport-Width
  8. Width
  9. Content-Type

如果请求头不在这九种类型中,就会请求失败。

如果希望服务端能够接收其他类型的请求,就需要通过Access-Control-Allow-Headers属性。

res.setHeader('Access-Control-Allow-Headers', 'Content-Type, X-Custom-Header')

在设置头部时,第二个参数填入允许被请求的头部,以逗号分隔,这些头部就可以被申请了。

Access-Control-Allow-Methods 响应头

默认情况下,CORS只允许客户端发起GETPOSTHEAD请求。如果客户端希望使用其它的请求类型,比如PUTDELETE,就需要使用Access-Control-Allow-Methods

res.setHeader('Access-Control-Allow-Methods ', 'PUT, DELETE')

同样的,将允许访问的方法填写到第二个参数中,以逗号分隔多个方法。

如果允许所有方法的访问,那么第二个参数指定为通配符"*"

那么以上三个响应头有什么用?

当浏览器接收到响应时,如果该响应跨域了,就会去检测上述响应头部,查看自己的请求是否符合要求,如果符合要求,那么允许客户端接收该响应

cors这个包,就是修改了以上内容,使得客户端可以跨域访问服务端资源。


预检请求

在使用CORS发起请求时,分为简单请求和预检请求。

如果满足以下条件,则为简单请求:

  1. 请求方式为GETPOSTHEAD之一
  2. HTTP头部信息不超过之前的九个字段
  3. 该请求是XMLHttpRequest对象,且没有使用setRequestHeader()方法注册自定义头部

当一个CORS请求不符合简单请求的条件时,那么该请求就是预检请求。

注意:只有使用CORS发起请求时,才分为简单请求和预检请求,如果没有使用CORS,或者请求是同源的,那么不属于以上分类。

CORS中,浏览器要依据响应报文的头部字段,判断自己的请求是否合法,如果不合法那么就会触发同源策略,不允许客户端接收这个响应。

如果HTTP的请求比较复杂,而这个响应由不符合条件,不被服务器接收,那么这个数据传输就是无效的,浪费了网络资源。

为此,对于较为复杂的请求,浏览器会先发送一个OPTION预检请求,这个请求不携带任何内容。当服务器响应之后,读取响应头部中的字段,查看自己是否可以请求对应的资源,如果可以请求,那么再发送真正要请求的报文。

示例:

html页面中增加一个delete按钮,发送DELETE请求:

$('#btnDelete').on('click', function () {$.ajax({type: 'DELETE',url: 'http://127.0.0.1/user',success: function (res) {console.log(res)},})
})

在服务端配置接收DELETE请求的路由:

const express = require('express')
const app = express()const cors = require('cors')
app.use(cors())app.delete('/user', express.urlencoded({ extended: false }), function (req, res){res.send(req.body)
})app.listen(80, () => {console.log("create web server success")
})

此处别忘了要绑定app.use(cors()),否则接收不到这个请求

点击按钮发送请求,后台监控网络:

在这里插入图片描述

可以看到,总共发送了两个请求,第一个请求的大小是0 B,这是预检请求,不携带任何数据,第二个请求才是真正的请求内容。

查看预检请求:

在这里插入图片描述

这个请求的类型是OPTION,请求收到的响应中,包含两个重要字段:

access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE
access-control-allow-origin: *

这代表服务器允许接受DELETE请求类型,并且允许*所有源发来的请求。

浏览器检测到自己符合条件,于是发送第二个数据请求。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/2971.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

【VScode】中文版ChatGPT编程工具-CodeMoss!教程+示例+快捷键

文章目录 1. 多模型选择2. 编辑快捷键3. 历史记录收藏 CodeMoss使用教程1. 安装CodeMoss插件2. 配置AI模型3. 使用快捷键4. 进行代码优化与解释5. 收藏历史记录 总结与展望 在当今快速发展的编程世界中&#xff0c;开发者们面临着越来越多的挑战。如何提高编程效率&#xff0c;…

LabVIEW适合开发的软件

LabVIEW作为一种图形化编程环境&#xff0c;主要用于测试、测量和控制系统的开发。以下是LabVIEW在不同应用场景中的适用性和优势。 一、测试与测量系统 LabVIEW在测试与测量系统中的应用广泛&#xff0c;是工程测试领域的主流工具之一。利用其强大的数据采集与处理功能&…

命令模式(Command)

1 意图 将一个请求封装为一个对象&#xff0c;从而使得可以用不同的请求对客户进行参数化;对请求排队或记录请求日志&#xff0c;以及支持可撤销的操作。 2 结构 Command 声明执行操作的接口。ConcreteCommand 将一个接收者对象绑定于一个动作;调用接收者相应的操作&#xff0c…

Linux 线程概念

一. 线程的基本概念 线程是进程内的一个执行单元&#xff0c;它是调度和执行的基本单位。 1.1 Linux中的线程 在Linux系统中&#xff0c;在CPU眼中&#xff0c;看到的PCB都要比传统的进程更加轻量化。 1.2 线程的优点 创建一个新线程的代价要比创建一个新进程小得多。与进…

Qt聊天室项目

目录 项目要求 项目背景 技术分析 架构设计 服务器架构 模块划分 模块之间的交互 客户端架构 模块划分 模块之间交互 项目展示 项目实现 服务器 ui server.pro dialog.h dialog.cpp 客户端 ui cient.pro dialog.h dialog.cpp 打包步骤不做演示 视频演示 项目…

node.js模块化分析

什么是Node.js模块化 Node.js中的模块化‌是指将一个大文件拆分成独立且相互依赖的多个小模块。每个JS文件被视为一个独立的模块&#xff0c;模块之间是互相不可见的。如果一个模块需要使用另一个模块&#xff0c;则需要使用指定的语法来引入该模块&#xff0c;并且只能使用模块…

sql练习专场(一) (1-5)

这是总结的一些sql题目&#xff0c;共25道题&#xff0c;每个博客会写5道题 第一题 这道题需要找出连续活跃3天以上的用户&#xff0c;其中每个用户每天可以连续登录多次。 create table sql1_1(uid string,dt string );insert into sql1_1 values(A,2023-10-01),(A,2023…

自动化立体仓库:详细设计方案

导语 大家好&#xff0c;我是社长&#xff0c;老K。专注分享智能制造和智能仓储物流等内容。 这份文件是关于自动化立体仓库设计方案的详细介绍&#xff0c;包括了自动化立体仓库的重要性、特点、设计程序、机械部分设计方案、系统硬件设计、系统软件设计以及系统调试等关键部分…

windows运行ffmpeg的脚本报错:av_ts2str、av_ts2timestr、av_err2str => E0029 C4576

问题描述 我目前的环境是&#xff1a; 编辑器&#xff1a; Microsoft Visual Studio Community 2022 (64 位) 运行的脚本是ffmpeg自带的remux样例&#xff0c;只不过我想用c语言执行这个样例。在执行的过程中报错如下图&#xff1a; C4576 后跟初始值设定项列表的带圆括…

CentOS 文件系统扩容与缩容

一、 概述 理解Linux文件系统的管理&#xff0c;需要了解以下的一张图&#xff1a; 一般使用LVM (Logical Volume Manager) 管理磁盘存储&#xff0c;该工具允许用户更灵活地分配和管理存储空间。主要有以下几个概念&#xff1a; PV&#xff08;Physical Volume&#xff0c;物…

Linux系统使用第三方邮件客户端发送邮件

文章目录 安装第三方邮件客户端&#xff08;s-nail&#xff09;S-nail的简单介绍重要的特性差异 配置邮件服务配置文件 (以QQ邮箱为例)获取QQ邮箱授权码获取QQ服务器证书使用 OpenSSL 获取 QQ 邮箱服务器的证书安装OpenSSL连接到 QQ 邮箱的 SMTP 服务器并下载证书保存证书验证证…

家常菜点餐|基于java和小程序的家庭大厨家常菜点餐系统设计与实现(源码+数据库+文档)

家常菜点餐系统 目录 基于java和小程序的家庭大厨家常菜系统设计与实现 一、前言 二、系统设计 三、系统功能设计 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 博主介绍&#xff1a;✌️大厂码农|毕设布道师&am…

利士策分享,青年暴富难守,因何在?

利士策分享&#xff0c;青年暴富难守&#xff0c;因何在? 在人生的长河中&#xff0c;有些人似乎被命运特别眷顾&#xff0c;在年轻之时便轻易地获得了财富。 然而&#xff0c;令人遗憾的是&#xff0c;这些早年得志、财富易得的人&#xff0c;往往难以长久地守住这份来之不…

Echarts环形图引线设置

直接上图吧 直接上代码吧 let labelArr [直接访问, 邮件营销, 联盟广告, 视频广告, 搜索引擎]; let valueArr [{ value: 335, name: 直接访问 },{ value: 310, name: 邮件营销 },{ value: 234, name: 联盟广告 },{ value: 135, name: 视频广告 },{ value: 154, name: 搜索引…

Java8->Java19的初步探索

导读 最近网上开始了大量的关于Java19的讨论&#xff0c;我也想着用了Java8这么久该接受一点新的东西了&#xff0c;于是便开始研究了起来 Java 19 Java19是一个免费版本。下面是JDK19的支持图 image.png &#xff08;来源&#xff1a; https://www.bilibili.com/video/BV1V84…

软件设计师-上午题-15 计算机网络(5分)

计算机网络题号一般为66-70题&#xff0c;分值一般为5分。 目录 1 网络设备 1.1 真题 2 协议簇 2.1 真题 3 TCP和UDP 3.1 真题 4 SMTP和POP3 4.1 真题 5 ARP 5.1 真题 6 DHCP 6.1 真题 7 URL 7.1 真题 8 浏览器 8.1 真题 9 IP地址和子网掩码 9.1 真题 10 I…

nodejs批量修改word文档目录样式

工作中遇到一个需求:写个nodejs脚本,对word文档(1000+个)的目录页面进行美化。实现过程遇到不少麻烦,在此分享下。 整体思路 众所周知,Docx格式的Word文档其实是个以xml文件为主的zip压缩包,所以,页面美化整体思路是:先将文档后缀名改为zip并解压到本地,然后将关键的…

MathType在Word中的安装与配置记录

一、记录过程 1.MathType安装包下载 可直接下载本人已经安装过的安装包&#xff0c;亲测可以使用&#xff0c;下载链接如下&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1g-iOgKIqzSNz0E5rEUryug 提取码&#xff1a;1kb3 2.安装后配置 word中会出现mathtype的选项…

无人机之中继通信技术篇

一、定义与原理 无人机中继通信技术是指通过无人机搭载中继设备&#xff0c;将信号从一个地点传输到另一个地点&#xff0c;从而延长通信距离并保持较好的通信质量。其原理类似于传统的中继通信&#xff0c;即在两个终端站之间设置若干中继站&#xff0c;中继站将前站送来的信号…

轴流风机和后倾式风机的安装要求

后向离心风机风压大&#xff0c;风量足&#xff0c;安装方便。因为不需要蜗壳&#xff0c;所以风道往往需要自行设计&#xff0c;而风道的合理与否&#xff0c;大大影响了后向离心风机的效率。那么后向离心风机的安装技巧有哪些&#xff1f;怎样达到风机的最佳使用效果呢&#…