本文是在学习课程满神yyds后记录的笔记,强烈推荐读者去看此课程。
出于浏览器的同源策略限制,浏览器会拒绝跨域请求。
什么是同源策略?
请求的时候拥有相同的协议、域名、端口,只要有一个不同就属于跨域。
主机(http://www.small5.com) | 是否跨域 | 原因 |
---|---|---|
https://www.small5.com | 是 | 协议不同 http和https |
http://www.small5.com:8001 | 是 | 端口不同 80 和 8001 |
http://www.baidu.com | 是 | 域名不同 small5和百度 |
http://www.small5.com/index.html | 否 | 协议、端口、域名全部相同 |
跨域的四种解决方案:
jsonp解决跨域的原理是:由于script标签的src属性不受同源策略的限制,可以跨域请求到服务器端的资源,并且把资源作为脚本(js)来运行,因此可以解决跨域。
img属性也有src属性,但是它只是把服务器的资源返回,并没有进一步做任何js的动作。
但是jsonp解决跨域存在一个限制:它只能发送get请求。
JSONP解决跨域的基本工作原理:
可以看出使用JSONP解决跨域需要前后端相互配合实现。
下面我们通过代码实现JSONP实现跨域访问:
前端html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>jsonp实现跨域</title>
</head>
<body>
<script>
// jsonp函数实现了跨域访问,,其中包括1.定义script并设置src传递回调函数名 2.前端window中动态定义回调函数
// name表示回调函数名
const jsonp = (name) => {
// 2.前端创建一个script标签,并将跨域请求的url作为script标签的src属性值,
// 在url中,需要包含一个特殊的参数,该参数用来告知服务器回调函数的名称,后端服务器通过query拿到回调函数名
let script = document.createElement('script')
script.src = 'http://localhost:3000/api/jsonp?callback=' + name // 将回调函数名传递给服务器
document.body.appendChild(script)
return new Promise((resolve) => {
// 1.前端动态定义一个回调函数,该函数将处理从服务器返回的数据
window[name] = (data) => { // data是后端调用前端回调函数返回的数据
// 4. 返回服务器返回的数据
resolve(data)
}
console.log(window);
})
}
// 调用jsonp方法
// 这里的回调函数名为callback加时间戳 使用时间戳的原因是避免函数重名
jsonp(`callback${new Date().getTime()}`).then(res => {
// 输出服务器返回的数据
console.log(res);
})
</script>
</body>
</html>
后端使用node,index.js
const express = require('express')
const app = express()
// 发送get请求
app.get('/api/jsonp', (req, res) => {
// 3. 服务器接收到请求后,解析url参数,通过query拿到回调函数名,并发送请求:将要发送的数据作为参数传入回调函数
const { callback } = req.query
// 后端响应请求并返回数据
// 注意这里以模板字符串的形式返回数据,实现调用前端的回调函数,传递了值为hello small5的参数,并执行这个前端回调函数
// 实际上这个参数的内容才是真实响应数据,改变参数内容,前端收到的数据也随之改变
res.send(`${callback}('hello small5')`)
})
app.listen(3000, () => {
console.log('server is running');
})
运行端口为3000的服务器
通过Live Server运行html文件,Live Server会自动开一个5500的端口,这和服务器端口不一致,因此是存在跨域的。
通过上图,可以看到使用JSONP实现跨域请求的请求结果,状态码是200,表示请求成功并响应结果,这表明使用JSONP成功实现了跨域请求。
下面我们来看看请求传递的参数和响应结果以验证:
可以看到前端成功传递参数值为callback+时间戳的回调函数名、参数名为callback的参数,以及响应结果是调用此回调函数并传参。
下面我们再看看前端输出的请求结果:
可以看到前端成功接收响应的数据,下面我们通过输出的window来验证是否有一个名为callback+时间戳的回调函数
可以看到完全符合预期,前端确实存在名为callback+时间戳的回调函数。
以上就是JSONP实现跨域请求的所有内容。
前端使用代理来实现跨域请求需要借助前端构建工具,如webpack、rollup、vite之类构建工具,通过在配置文件中配置代理,实现拦截请求并转发到指定服务器上。
这里我以vite来举例:
使用npm i vite -D
安装vite,并在根目录新建vite.config.js
文件。
vite.config,js
import { defineConfig } from 'vite'
export default defineConfig({
server: {
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true, // changeOrigin表示是否改变请求的源
// rewrite: (path)=>path.replace(/^\/api/, '') // rewrite表示重写请求路径,这里的示例实现了匹配到/api/则替换为空
}
}
}
})
node配置服务器:index.js
const express = require('express')
const app = express()
// 返回json数据的get请求
app.get('/api/json', (req, res) => {
res.json({name: 'small5'})
})
app.listen(3000, () => {
console.log('server is running');
})
html文件发送请求:index.html
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>jsonp解决跨域title>
head>
<body>
<script>
// Fetch 是一个基于 promise 的用于浏览器和 node.js 的 HTTP 客户端,
// 它能够轻松的将请求发送到服务器并得到响应。
// Fetch 使用方式简单,又能充分发挥出 Promise 的特性,是越来越多人使用的发起请求的方式之一。
fetch('/api/json').then(res => res.json()).then(res => {
console.log(res);
})
script>
body>
html>
这里我们就不能使用Live Server运行html文件了,需要配置一下pacage.json文件,使用vite运行html文件。
{
"scripts": {
"dev": "vite"
},
"dependencies": {
"express": "^4.17.1",
"express-session": "^1.17.3",
},
"devDependencies": {
"vite": "^4.4.8"
}
}
现在我们使用npm run dev
命令运行:
可以看到vite自动开了url为http://127.0.0.1:5173
客户端域名,这和node后端定义的http://localhost:3000
是存在跨域的。查看请求结果如下:
可以看到状态码200,表示成功实现了跨域请求,再看看接口响应结果和前端得到的请求结果:
成功得到node后端返回的结果,这表明成功实现前端代理实现跨域请求。
接口设置请求头
// 返回json数据的get请求
app.get('/api/json', (req, res) => {
// res.setHeader('Access-Control-Allow-Origin', '*') 任何请求都能跨域 不安全
res.setHeader('Access-Control-Allow-Origin', 'http://127.0.0.1:5500') // 指定某个请求能跨域
res.json({name: 'small5'})
})
由于本人对于Ubuntu不太了解,因此这里主要讲解一下使用Ubuntu安装nginx并实现跨域请求
在Microsoft Store中安装对应的Ubuntu,我安装的是以下版本。
下载完成后启动Ubuntu,可能会报错解决方式参考:Ubuntu 20.04.4 LTS 报错解决方案。
Ubuntu正常启动后输入apt-get install nginx
安装nginx,如果报没有权限的错误,解决这个问题,需要以root用户身份或具有sudo权限的用户身份运行命令。可以尝试在命令前加sudo
。
安装完成后再次输入命令,验证安装结果,得到如下结果表示安装成功
接着我们打开资源管理器,找到Linux目录,安装的nginx默认是在ect文件夹下,
nginx的默认配置文件是在sites-avaliable文件夹下
下面我们打开默认配置文件找到默认服务配置的server配置一个代理。
注意,使用nginx配置代理不能使用localhost,必须使用Windows主机的ip,打开Ubuntu输入cat /etc/resolv.conf
可以获取到主机ip。
我们可以验证一下使用Windows主机ip能否请求成功接口。
下面我们配置好后端node接口并启动服务器如下图:
在Ubuntu上使用命令curl http://主机IP:服务器端口号/接口地址
发送请求。
想了解curl命令可以查看博客:技术分享 | 使用 cURL 发送请求
可以看到返回正确结果,接口发送成功,这表明使用Windows主机ip作为代理域名是没有问题的。
验证Windows主机ip地址作为代理域名没有问题后,我们再使用Windows主机ip在nginx配置文件中配置代理如下:
这里我遇到一个问题,不管是使用记事本还是vscode打开配置文件,再修改编辑后保存会无法保存,尝试了好几次都保存不了,后面我是通过在Ubuntu的Linux系统中使用vim编辑器打开配置文件,然后修改保存,这其中又遇到一个问题,如果没有加sudo权限的话执行vim 配置文件路径
打开后修改文件保存会报此文件是只读文件,不允许修改,因此要在指令前加sudo,提供sudo权限后选择输入E,进入编辑,修改后保存可以看到配置文件成功被修改了。
关于vim编辑器的基本使用可以查看我的博客git的基本使用中的预备知识那节内容。
接着我们在Ubuntu中输入nginx启动nginx,我这里没有权限,添加了sudo后才启动成功,另外由于配置文件的location拼写错误还报错了一次。如下:
接着在浏览器地址栏输入localhost:80
回车,得到如下页面表示nginx启动成功
下面就不再nginx写代码了,直接在开发者工具内发送请求验证是否实现跨域请求是一样的。
通过查看请求状态码可以验证成功实现了跨域请求
使用nginx代理实现跨域适合项目上线时使用,因此这种方式使用非常普遍。
以上就是使用nginx代理实现跨域请求的全部内容了。