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请求类型,并且允许*所有源发来的请求。

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


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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

OPENAI官方prompt文档解析

官方文档地址:https://platform.openai.com/docs/guides/gpt-best-practices 文档中文版来源:OpenAI 官方提示工程指南 [译] | 宝玉的分享 (baoyu.io) 1.写清楚说明 如果prompt给的范围十分模糊或是过于宽泛,那么GPT就会开始猜测您想要的内容,从而导致生成的结果偏离预期. …

ubuntu安装与配置Nginx(1)

在 Ubuntu 上安装和配置 Nginx 是相对简单的。以下是一个逐步指南&#xff1a; 1. 更新系统包 首先&#xff0c;确保你的系统是最新的。打开终端并运行&#xff1a; sudo apt update sudo apt upgrade2. 安装 Nginx 使用以下命令安装 Nginx&#xff1a; sudo apt install …

NVR设备ONVIF接入平台EasyCVR视频分析设备平台视频质量诊断技术与能力

视频诊断技术是一种智能化的视频故障分析与预警系统&#xff0c;NVR设备ONVIF接入平台EasyCVR通过对前端设备传回的码流进行解码以及图像质量评估&#xff0c;对视频图像中存在的质量问题进行智能分析、判断和预警。这项技术在安防监控领域尤为重要&#xff0c;因为它能够确保监…

【机器学习】20. RNN - Recurrent Neural Networks 和 LSTM

1. RNN定义 用于顺序数据 文本数据是序列数据的一个例子 句子是单词的序列——一个单词接另一个单词 每个句子可能有不同数量的单词&#xff08;长度可变&#xff09; 每个句子之间可能有长距离的依赖关系 rnn可以记住序列中较早的相关信息 RNN在每个时间点取序列中的1个…

ElementUI el-form表单多层数组的校验

问题描述 提示&#xff1a;这里描述项目中遇到的问题&#xff1a; ElementUI el-form表单多层数组的校验 页面效果&#xff1a; 数据结构&#xff1a; addform: {code: ,type: ,value: ,state: 1,remark: ,fieldList: [{fieldCode: ,resolverEntities: [{resolverType: , re…

Java SpringBoot调用大模型AI构建AI应用

本文是一个用springboot 结合spring mvc 和spring ai alibaba 调用国产大模型通义千问的具体例子&#xff0c;按照这个做能够快速的搞定Java应用的调用。 然后就可以把这类应用泛化到所有的涉及到非结构化数据结构化的场景中。 Spring AI&#xff1a;简化Java中大模型调用的框…

利用frp进行SSH端口转发(内网穿透同理)

题记 公司内网有一台设备&#xff0c;可以根据微步情报来对恶意服务器进行封禁。很不幸我的vps因为开着cs被标记为恶意了&#xff0c;导致我在公司网络连不上我的vps&#xff0c;每次连还要挂代理。于是我打算将我vps的22端口转发到我们公司的vps的10022端口上。本篇文章来自11…

Python基于TensorFlow实现双向循环神经网络GRU加注意力机制分类模型(BiGRU-Attention分类算法)项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后关注获取。 1.项目背景 随着深度学习技术的发展&#xff0c;循环神经网络&#xff08;RNN&#xff09;及其变种如门控循环…

CSS、Less、Scss

CSS、Less和SCSS都是用于描述网页外观的样式表语言&#xff0c;但它们各自具有不同的特点和功能。以下是对这三者的详细阐述及区别对比&#xff1a; 详细阐述 CSS&#xff08;Cascading Style Sheets&#xff09; 定义&#xff1a;CSS是一种用来表现HTML或XML等文件样式的计算机…

parted 磁盘分区

目录 磁盘格式磁盘分区文件系统挂载使用扩展 - parted、fdisk、gdisk 区别 磁盘格式 parted /dev/vdcmklabel gpt # 设置磁盘格式为GPT p # 打印磁盘信息此时磁盘格式设置完成&#xff01; 磁盘分区 开始分区&#xff1a; mkpart data_mysql # 分区名&…

OpenCV视觉分析之目标跟踪(9)计算扩展相关系数computeECC()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 计算两幅图像之间的增强相关系数值 78 Enhanced Correlation Coefficient (ECC)&#xff1a;增强相关系数是一种用于图像配准的技术&#xff0c…

ESP32-C3 入门笔记03:VScode + flash_download_tool 下载烧录程序(ESP-IDF + PlatformIO)

ESP32-C3 支持多种烧录方式&#xff0c;主要包括以下几种&#xff1a; VS Code 串口烧录&#xff1a;使用 VS Code 配合 PlatformIO 或 ESP-IDF 插件进行串口烧录。串口连接通常使用 UART 接口&#xff0c;通过 USB 转串口芯片与电脑连接。步骤大致如下&#xff1a; 配置 VS Co…

Java使用apache.commons.io框架下的FileUtils类实现文件的写入、读取、复制、删除

Apache Commons IO 是 Apache 开源基金组织提供的一组有关IO&#xff08;Input/Output&#xff09;操作的小框架&#xff0c;它是 Apache Commons 项目的一部分&#xff0c;专注于提供简单易用的 API&#xff0c;用于处理输入和输出操作。Apache Commons IO 是一个功能强大的 J…

Mac 电脑 使用sudo创建项目后,给了读写权限,仍报权限问题

问题&#xff1a;sudo创建的项目&#xff0c;都已经改成读写权限了&#xff0c;但是修改项目中的内容还是报没权限。 原因&#xff1a;当你使用 sudo 创建项目时。这是因为 sudo 会以 root 用户的身份创建文件和目录&#xff0c;这些文件和目录默认属于 root 用户&#xff0c;…

3. keil + vscode 进行stm32协同开发

1. 为什么使用vscode 主要还是界面友好&#xff0c;使用习惯问题&#xff0c;vscode 从前端&#xff0c;js, c/c, qt, 仓颉&#xff0c;rust都有很好插件的支持&#xff0c;并且有romote&#xff0c; wsl 等很多插件可以提高效率&#xff0c; 唯一的问题就是要使用插件进行环境…

Spring MVC 完整生命周期和异常处理流程图

先要明白 // 1. 用户发来请求: localhost:8080/user/1// 2. 处理器映射器(HandlerMapping)的工作 // 它会找到对应的Controller和方法 GetMapping("/user/{id}") public User getUser(PathVariable Long id) {return userService.getById(id); }// 3. 处理器适配…

Hadoop生态圈框架部署(四)- Hadoop完全分布式部署

文章目录 前言一、Hadoop完全分布式部署&#xff08;手动部署&#xff09;1. 下载hadoop2. 上传安装包2. 解压hadoop安装包3. 配置hadoop配置文件3.1 虚拟机hadoop1修改hadoop配置文件3.1.1 修改 hadoop-env.sh 配置文件3.3.2 修改 core-site.xml 配置文件3.3.3 修改 hdfs-site…

【智能算法应用】天鹰优化算法求解二维路径规划问题

摘要 路径规划问题在机器人和无人机导航中起着关键作用。本文提出了一种基于天鹰优化算法的二维路径规划方法。天鹰优化算法&#xff08;Eagle Strategy Optimization, ESO&#xff09;通过模拟天鹰的捕猎行为&#xff0c;寻找最优路径。实验结果显示&#xff0c;该算法能够有…

数据结构之二叉树——堆 详解(含代码实现)

1.堆 如果有一个关键码的集合 K { &#xff0c; &#xff0c; &#xff0c; … &#xff0c;}&#xff0c;把它的所有元素按完全二叉树的顺序存储方式存储 在一个一维数组中&#xff0c;则称为小堆( 或大堆 ) 。将根节点最大的堆叫做最大堆或大根堆&#xff0c;根节点最小的…

【机器学习】25. 聚类-DBSCAN(density base)

聚类-DBSCAN-density base 1. 介绍2. 实现案例计算 3. K-dist4. 变化密度5. 优缺点 1. 介绍 DBSCAN – Density-Based Spatial Clustering of Applications with Noise 与K-Means查找圆形簇相比&#xff0c;DBSCAN可以查找任意形状和复杂形状的簇&#xff0c;如S形、椭圆、半圆…