学会跨域不再低调
- 跨域产生的原因
跨域产生的原因是同源策略的存在。 同源策略是浏览器为了保护网页的安全而设置的一种安全保护手段,在没有明确的授权下,不同的协议、域名、和端口的客户端脚本不能够读写对方的资源。有时为了获取不同网页下的数据会进行一系列地操作,所以产生了跨域。
- 常用方法
- 常用方法
- 通过jsonp跨域(script脚本)
- jquery的ajax跨域(jsonp类型)
- 跨域资源共享(cors,后台)
- nginx代理跨域(php代理)
- node正向代理
- 其它方法
- WebSocket协议跨域
- documenet.domain+iframe跨域
- window.name+iframe跨域
- location.hash+iframe跨域
- postMessage跨域
- 一.jsonP实现跨域
jsonp实现跨域:script标签无同源限制,但仅限于get请求
1.为什么浏览器对script标签不限制同源策略?
首先,我们来了解一下,浏览器是对什么有请求同源限制。 浏览器只对XHR(XML Http Request)请求有同源限制,而对script标签,link标签,img标签没有同源限制。因此这三种标签可以获取到不同域下的数据,而script标签就可以获取到json数据。jsonp就以此为基础,需要向第三方请求时,把含有回调函数的请求放在src标签中。添加到当前网页中,就会立即请求数据。
2.jsonp的原理
当前站点提供一个回调函数来接收数据(函数名可约定,或通过地址参数传递),而第三方网站产生的响应为json数据的包装
(故称之为jsonp,即json padding),形如:callback({“name”:“fegnjie”,“age”:“18”})。一旦开始将包含回调函数参数
的script
标签添加到当前页面,则会立即向第三方网站发起请求,第三方网站响应成功后,当前浏览器会将json对象作为参数传递到callback回调函数中,因此完成了跨域请求。
3.使用流程
- 设置一个回调函数,并将其提前创建
- 创建一个src中包含回调函数的script标签,将标签插入到当前页面。
url语法:
https://需要访问的网址/路径?必要的参数&callback=自定义的回调函数名称
4.开始实验
准备工作做好,找一个远程的cdn,把它放在网页的head标签内。当前的实验需要jquery的支持!使用原生js也没有问题。
<!--client.html-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>client1</title>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js"></script>
</head>
<body>
<h1>我是client1</h1>
<button id="myBtn">向百度发起请求</button>
<script>
$("#myBtn").click(function () {
$.get("https://www.baidu.com/", function (data) {
console.log(data)
}); // 我们最喜欢的百度首页。
});
</script>
</body>
</html>
显然,跨域的存在,让我们的ajax无法得手。难道就这样 ,当然有解决方案。
现在有一个方案,就是使用script脚本的漏洞,它的src是可以放置其它域的链接的,而我们可以动态地创建script标签,并把需要访问的网页连接放在src。这样也许可行。
// 监听点击事件
$("#myBtn").click(function () {
myScript("https://www.baidu.com/"); // 测试一下。
// $.get("https://www.baidu.com/", function (data) { // 暂时不用
// console.log(data)
// });
});
// 创建script标签
function myScript(src){
let newNode = $("script").attr("src",src);
$('body').append(newNode);
}
现在再来点击一下按钮,感觉写的jquery语法不行,太差了,换一下。下面可以避免重复向当前网页添加同一个元素。不过获取数据,也确实是因为把script添加到当前页面,才成功跨域。
// 创建script标签
function myScript(src){
let newNode = $(`<script src="${src}" > <\/script>`);
var hasScript = false;
$('body > script').filter((index,item)=>{ // 只有filter可以过滤遍历
if(item.src === src){
// 而forEach, some, every都不能当作遍历Jquery节点的函数
hasScript = true;
console.log("网页中已经有这个节点了")
}
});
if(!hasScript){ // 不允许出现多个重复的script跳转标签
$('body').append(newNode);
}
}
已经可以向当前页面添加script标签,但好像没什么作用。这是因为要在script的src属性上添加一个回调函数。这个参数名称可自定义,与后台无关,并且一定要提前创建,而不能等待页面加载完成才创建。
url语法:https://需要访问的网址/路径?必要的参数&callback=自定义的回调函数名称
// 设置回调函数, 这个回调函数要提前创建,不能在页面加载完毕后再创建。
// 回调参数名称与url中的参数一致, 这个回调函数名称只与当前页面有关,而后台是没有这个参数的。
function hello(json) {
console.log(json)
}
不过,现在我却不需要ajax,这是为什么呢?其实当前的方法是使用不上异步请求的,当我们将script标签添加到当前页面,已经可以获取 jsonp,(json数据包)。但百度的效果看不出来,因为返回的是一个页面。
现在我要替换一个网站,大家看看就行,就。。。
// 监听事件
$("#myBtn").click(function () {
// 酷*官网的获取json数据的请求头。
/*https://searchrecommend.kugou.com/get/complex?callback=jQuery112402531924339219609
_1636893306420&word=love&_=1636893306422*/
myScript("https://searchrecommend.kugou.com/get/complex?callback=hello&word=love&_=1636893306422");
});
控制台也通过回调函数获取到信息
其实,细心地观察过原url后,酷*官网也是使用了这种方法去跨域请求数据的,也就是殊途同归,
5.实现代码
最后写上代码
<!--jquery版本的jsonp跨域实现-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>client1</title>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js"></script>
</head>
<body>
<h1>我是client1</h1>
<button id="myBtn">向酷*发起请求</button>
<script>
// 设置回调函数, 这个回调函数要提前创建,不能在页面加载完毕后再创建。
// 回调参数名称与url中的参数一致, 这个回调函数名称只与当前页面有关,而后台是没有这个参数的。
function hello(json) {
console.log(json)
}
$(function() {
// 监听事件
$("#myBtn").click(function () {
// 酷*官网的获取json数据的请求头。
/*https://searchrecommend.kugou.com/get/complex?callback=jQuery112402531924339219609_1636893306420&word=love&_=1636893306422*/
myScript("https://searchrecommend.kugou.com/get/complex?callback=hello&word=love&_=1636893306422");
});
// 创建script标签
function myScript(src) {
let newNode = $(`<script src="${src}" > <\/script>`);
var hasScript = false;
$('body > script').filter((index, item) => { // 只有filter可以过渡
if (item.src === src) { // 而forEach, some, every都不能当作遍历Jquery节点的函数
hasScript = true;
console.log("网页中已经有")
}
});
if (!hasScript) { // 不允许出现多个重复的script跳转标签
$('body').append(newNode);
}
}
});
</script>
</body>
</html>
<!--原生js版本的jsonp跨域实现-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>client1</title>
</head>
<body>
<h1>我是client1</h1>
<button id="myBtn">向酷*发起请求</button>
<script>
// 设置回调函数, 这个回调函数要提前创建,不能在页面加载完毕后再创建。
// 回调参数名称与url中的参数一致, 这个回调函数名称只与当前页面有关,而后台是没有这个参数的。
function hello(json) {
console.log(json)
}
window.onload = function(){
// 监听事件
document.querySelector("#myBtn").onclick = function () {
// 酷*官网的获取json数据的请求头。
/*https://searchrecommend.kugou.com/get/complex?callback=jQuery112402531924339219609_1636893306420&word=love&_=1636893306422*/
myScript("https://searchrecommend.kugou.com/get/complex?callback=hello&word=love&_=1636893306422");
};
// 创建script标签
function myScript(src) {
let newNode = document.createElement("script");
newNode.src = src;
document.body.appendChild(newNode);
}
};
</script>
</body>
</html>
相关文章:
- 二.ajax请求实现跨域
1.ajax实现的原理
原生ajax首先不谈兼容性,而同源策略就是因为有它的存在,所以原生的ajax是行不通的。
但jquery已经帮我们封装ajax中,已经有jsonp
类型,它可以帮助我们直接获取数据。
这里的链接已经不需要回调函数,需要把callback回调参数去除
$("#myBtn").click(function () {
$.ajax({
url: 'https://searchrecommend.kugou.com/get/complex?word=love&_=1636893306422',
dataType: "jsonp",
jsonp: "callback",
success: function (data) {
console.log(data)
}
})
})
结果同上面一样,引入一个jquery,确实解决了不少问题。
2.实现代码
<!--jquery版本ajax的jsonp跨域实现-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>jquery版本ajax的jsonp跨域实现</title>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js"></script>
</head>
<body>
<h1>我是jquery版本ajax的jsonp跨域实现</h1>
<button id="myBtn">向酷*发起请求</button>
<script>
$("#myBtn").click(function () {
$.ajax({
url: 'https://searchrecommend.kugou.com/get/complex?word=love&_=1636893306422',
dataType: "jsonp",
jsonp: "callback",
success: function (data) {
console.log(data)
}
})
})
</script>
</body>
</html>
3.原生ajax无法实现
原生Ajax受到浏览器的同源策略的限制,不允许跨域通信。
- 三.cors跨域资源共享
CORS(Cross-Origin Resource Sharing)跨源资源共享:服务器在响应头中告知浏览器受到限制或者接受哪些域的请求。简而言之,就是需要后端开放接口,允许当前的资源和其它域共享,以此跨域获取数据。
因为是需要后端设置相关的信息,Access-Control-Allow-Origin, 可跨资源访问控制的域。
header(“Access-Control-Allow-Origin:*”); // 允许任何来源 header(“Access-Control-Allow-Origin:http://localhost:8888/”); //只允许来自域名http://localhost:8888/的请求
1.准备工具
1.warmpserver集成服务环境
2.Webstorm或者VS code等编译器(也不强制要求)
2.如何使用warmpserver
在www目录下建立如下的文件ajaxdemo/getapi.php
, 然后在getapi.php文件中添加响应的json数据。
<?php
header('Access-Control-Allow-Origin: *'); ## 允许所有的域进行跨资源共享当前数据
echo '{ "username" : "zhangsan", "content":"这是一个测试"}'; # json信息
创建完以上文件后,打开wampserver软件
右下角出现这个图标就说明开启服务器成功。
输入http://localhost/ajaxdemo/getapi.php,php文件 访问成功
http://localhost:80/ajaxdemo/getapi.php
是否感觉不存在跨域?其实回顾上文可知道,http是默认80端口,而端口不同的也会存在跨域问题。
3.访问后端
因为后端设置了跨域资源共享 ,所以当浏览器发送ajax请求的时候,就不会因同源策略的存在而受到限制。
4.实现代码
<!--cors后端跨域资源共享-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>client1</title>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js"></script>
</head>
<body>
<h1>我是cors后端跨域资源共享</h1>
<button id="myBtn">向酷*发起请求</button>
<script>
/*
* 原生ajax的创建过程
* 1.创建XMLHttpRequest对象
* 2.设置请求成功时的回调函数
* 3.打开url链接,开始发送请求.
* (如果是post,中间还要设置请求头setRequestHeader("content-Type","application/x-www-form-urlencoded"))
* 并且内容也是放在send的参数里,而get请求的参数为null)。
* */
window.onload = ()=>{
let xmlhttp = null; // 创建一个全局的xml变量
document.querySelector("#myBtn").onclick = loadXmlDoc;
function loadXmlDoc(){
// 1.创建对象
if(window.XMLHttpRequest){ // IE7 , FireFox, Chrome, Opera, Safari
xmlhttp = new XMLHttpRequest();
}else{ // IE5 IE6
xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
}
// 2.设置请求成功的回调函数
// xmlhttp.onReadyStateChange = responseData;
// 拆分写会导致this指针指向windows,而不是XMLHttpRequest对象,也不要使用箭头函数,因为箭头函数没有自己的this指针。
xmlhttp.onreadystatechange = function(){
if(this.readyState === 4 && this.status === 200){
console.log(this.responseText);
}
};
// 3.打开url链接,开始发送请求, get请求
xmlhttp.open('GET','http://localhost/ajaxdemo/getapi.php',true);
xmlhttp.send(null);
}
};
//使用jquery发起ajax请求
$("#myBtn").click(function () {
$.ajax({
url: 'http://localhost/ajaxdemo/getapi.php',
dataType: "json",
success: function (data) {
console.log(data)
}
})
})
</script>
</body>
</html>
- 四.使用nginx代理请求(php代理)
1.实现原理
因为ajax受到同源策略的影响,所以访问不到数据。但是如果通过nginx访问其它的网页,就不会受到同源限制。因此,nginx相当于中间接口,向目标网页发起请求,同时又返回数据给其它页面。
如果当前网页和php是在同一个域下,就不用跨域访问php文件。所以在不同的域下,还需要在php文件中设置跨域资源共享。header("Access-Control-Allow-Origin:*");
复制一张其它大佬的图
2.nginx访问
后端php文件
<?php
header('Access-Control-Allow-Origin: *'); # 允许访问当前php文件
#$url = 'http://www.baidu.com/';
#$url = 'http://www.weather.com.cn/data/sk/101280101.html'; # 这个网页会返回一个json数据
$url = 'http://searchrecommend.kugou.com/get/complex?word=love&_=1636893306422';
# 需要使用http, 如果是https,则去除s
echo file_get_contents($url); # 向其它域发请请求,获取内容后并返回数据
nginx代理访问成功
前端页面,可以通过ajax或者axios向nginx代理服务器发起请求,而nginx代理服务器就可以向其它网页发起请求。因此,达到了跨域效果。
3.实现代码
<!--cors后端跨域资源共享-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>client1</title>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js"></script>
</head>
<body>
<h1>我是nginx跨域</h1>
<button id="myBtn">向酷*发起请求</button>
<script>
/*
* 原生ajax的创建过程
* 1.创建XMLHttpRequest对象
* 2.设置请求成功时的回调函数
* 3.打开url链接,开始发送请求.
* (如果是post,中间还要设置请求头setRequestHeader("content-Type","application/x-www-form-urlencoded"))
* 并且内容也是放在send的参数里,而get请求的参数为null)。
* */
window.onload = ()=>{
let xmlhttp = null; // 创建一个全局的xml变量
document.querySelector("#myBtn").onclick = loadXmlDoc;
function loadXmlDoc(){
// 1.创建对象
if(window.XMLHttpRequest){ // IE7 , FireFox, Chrome, Opera, Safari
xmlhttp = new XMLHttpRequest();
}else{ // IE5 IE6
xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
}
// 2.设置请求成功的回调函数
// xmlhttp.onReadyStateChange = responseData;
// 拆分写会导致this指针指向windows,而不是XMLHttpRequest对象,也不要使用箭头函数,因为箭头函数没有自己的this指针。
xmlhttp.onreadystatechange = function(){
if(this.readyState === 4 && this.status === 200){
console.log(this.responseText);
}
};
// 3.打开url链接,开始发送请求, get请求
xmlhttp.open('GET','http://localhost/ajaxdemo/getchange.php',true);
xmlhttp.send(null);
}
};
//使用jquery发起ajax请求
// $("#myBtn").click(function () {
// $.ajax({
// url: 'http://localhost/ajaxdemo/getchange.php',
// dataType: "json",
// success: function (data) {
// console.log(data)
// }
// })
// })
</script>
</body>
</html>
其实:nginx也可以配置反向代理, 在服务器配置中设置nginx,
location /api {
proxy_pass http://localhost:8080;
}
不管什么情况,都可以在当前域获取到nginx中的静态资源和接口。有点类似于cors跨域资源共享。
不懂的可以看看其它文章
- 五.使用Node正向代理
1.实现原理
让接口和当前端点同域,就可以实现node代理。但深入发现,这其实与node环境有关。比如说使用vue框架建立项目,总会开启一个开发者服务器,而当前的环境就是node环境,有node环境的支持,在配置页(vue.config.js)配置代理请求。就可以进行代理操作,以达到跨域. 但也只能在开发阶段使用, 项目上线并不会有任何效果.
与nginx代理请求的区别
-
nginx代理请求(php代理),是需要一个单独的服务器,或者同域下的服务器进行代理获取,而nginx是相当于一个中间转换站。
-
node正向代理,是使用当前域下的服务器,直接声明proxy代理,在运行的时候,会根据代理参数去访问目标网页。项目打包后代理无效.
2.实现代码 :
在项目下新建vue.config.js
,添加代理.
**注意:**当前的node正向代理是在开发者模式下, 属于构建工具上的配置. 如果说使用npm run serve
打包的话,当前的正向代理是无效的. 它是webpack的配置. 但对于开发阶段需要实现跨域, 这个方法还是可行的.
module.exports = {
devServer:{
proxy:{
"/api":{
target:'http://localhost:80/',
ws: false, //如果要代理 websockets,配置这个参数
secure: false, // 如果是https接口,需要配置这个参数
changeOrigin: true, //是否跨域
pathRewrite:{
'^/api': '/' // 生写路径
}
},
"/kg":{ // 不要取api2, 因为会被上一个匹配到
target:'https://searchrecommend.kugou.com/',
ws: false,
secure: false,
changeOrigin: true,
pathRewrite:{
'^/kg': '/'
}
}
}
},
lintOnSave:false // 取消lint的检测机制
}
APP.vue
配置
<template>
<h1>我是node正向代理跨域</h1>
<button id="myBtn" @click="sendRequest">向酷*发起请求</button>
</template>
<script>
import axios from 'axios';
export default {
name:'App',
methods:{
sendRequest(){
axios.get("/kg/get/complex?word=love&_=1636893306422").then(res=>{
console.log("我想直接获取");
console.log(res);
})
}
},
mounted(){
axios.get("/api/ajaxdemo/getchange.php").then(res=>{
console.log("我是通过了nginx的代理(php代理)")
console.log(res);
})
}
}
</script>
当然,还有其它的方案, 不同的框架与生产环境, 使用代码也将不同. 以上是常用的方案和代码. 其它的跨域方法可以查看以下的方法.
- 总结
常用的方法也就这几种:
- jsonp实现跨域, 使用script脚本的无同源限制,只能使用get请求。
- jquery的ajax实现跨域 ,是因为它封装了jsonp的请求结果。它是jquery独特的封装结果。
- cors跨域资源共享,后端控制允许接受请求的域
- nginx代理访问(php代理),服务器之间不存在跨域,nginx代理帮助我们访问。同时,nginx可以使用cors跨域资源共享,共享数据。
- 使用Node正向代理, 开发者服务器下,配置proxy代理可以实现跨域
以上是常用的5种方法, 其实也可以归类为4种, 第二种的原理其实就和第一种类似, 实现的方法不一样而已.
到底哪一种方法更好呢? 没有什么具体的标准答案. 只有在不同的情况下, 合理地选择方案, 那将是你最优的选择.