Angular由一个bug说起之十三:Cross Origin

跨域

想要了解跨域,首要要了解源

什么是源,源等于协议加域名加端口号

只有这三个都相同,才是同源,反之则是非同源。

比如下面这四个里,只有第4个是同源

而浏览器给服务器发送请求时,他们的源一样,就是同源请求,反之就是非同源请求,非同源请求又称为跨域。

当你是跨域时,浏览器为了确保资源安全,会对跨域的访问资源做出一些限制,也就是浏览器的同源策略。

这是W3C上对同源策略的说明:https://www.w3.org/Security/wiki/Same_Origin_Policy

浏览器会对跨域做出哪些限制?

例如:源a和源b,它们是非同源的,则浏览器会有如下限制:

1. DOM访问限制:源a的脚本不能读取和操作源b的DOM。

页面1

html 复制代码
1.	<!DOCTYPE html>
2.	<html lang="en">
3.	<head>
4.	    <meta charset="UTF-8">
5.	    <meta name="viewport" content="width=device-width, initial-scale=1.0">
6.	    <title>Document</title>
7.	</head>
8.	<body>
9.	    <h1>我是页面1</h1>
10.	    <button onclick="showDOM()">获取页面2的DOM</button>
11.	    <br>
12.	    <iframe id="framePage" src="./demo.html"></iframe>
13.	    <!-- <iframe id="framePage" src="https://www.baidu.com"></iframe> -->
14.	
15.	    <script type="text/javascript" >
16.	        function showDOM(){
17.	            const framePage = document.getElementById('framePage')
18.	            console.log(framePage.contentWindow.document.body) //同源的可以获取,非同源的无法获取
19.	        }
20.	    </script>
21.	    </body>
22.	</html>

页面2

html 复制代码
1.	<!DOCTYPE html>
2.	<html lang="en">
3.	<head>
4.	    <meta charset="UTF-8">
5.	    <meta name="viewport" content="width=device-width, initial-scale=1.0">
6.	    <title>Document</title>
7.	</head>
8.	<body>
9.	    <h2>我是页面2</h2>
10.	</body>
11.	</html>

页面是这样的

点击获取

换一个不同源的页面

javascript 复制代码
1.	<body>
2.	    <h1>我是页面1</h1>
3.	    <button onclick="showDOM()">获取页面2的DOM</button>
4.	    <br>
5.	    <!-- <iframe id="framePage" src="./demo.html"></iframe> -->
6.	    <iframe id="framePage" src="https://www.bilibili.com"></iframe>
7.	
8.	    <script type="text/javascript">
9.	        function showDOM() {
10.	            const framePage = document.getElementById('framePage')
11.	            console.log(framePage.contentWindow.document.body) //同源的可以获取,非同源的无法获取
12.	        }
13.	    </script>
14.	</body>

获取DOM

获取不到

2. 第二个限制,源a不能访问源b的cookie

html 复制代码
3.	<!DOCTYPE html>
4.	<html lang="en">
5.	
6.	<head>
7.	    <meta charset="UTF-8">
8.	    <meta name="viewport" content="width=device-width, initial-scale=1.0">
9.	    <title>Document</title>
10.	</head>
11.	
12.	<body>
13.	    <h1>我是页面1</h1>
14.	    <button onclick="getCookie()">获取页面2的cookie</button>
15.	    <br>
16.	    <!-- <iframe id="framePage" src="./demo.html"></iframe> -->
17.	    <iframe id="framePage" src="https://www.bilibili.com"></iframe>
18.	
19.	    <script type="text/javascript">
20.	        function getCookie() {
21.	            const framePage = document.getElementById('framePage')
22.	            console.log(framePage.contentWindow.document.cookie) //同源的可以获取,非同源的无法获取
23.	        }
24.	    </script>
25.	</body>
26.	
27.	</html>

还是一样的页面,这次获取的是cookie

也是获取不到的,因为你在获取document这一步就已经失败了

3. ajax响应数据限制:源a可以给源b发请求,但是无法获取源b响应的数据。

这是一个获取头条新闻的页面

代码

html 复制代码
28.	<!DOCTYPE html>
29.	<html lang="en">
30.	<head>
31.	    <meta charset="UTF-8">
32.	    <meta name="viewport" content="width=device-width, initial-scale=1.0">
33.	    <title>Document</title>
34.	</head>
35.	<body>
36.	    <button onclick="getNews()">获取头条新闻</button>
37.	
38.	    <script>
39.	        async function getNews() {
40.	            const result = await fetch('https://www.toutiao.com/hot-event/hot-board/?origin=toutiao_pc');
41.	            const data = await result.json();
42.	            console.log('data');
43.	        }
44.	    </script>
45.	</body>
46.	</html>

页面

获取不到

备注:在上述限制中,浏览器对 Ajax 获取数据的限制是影响最大的一个,且实际开发中经常遇到。

几个注意点

  1. 跨域限制仅存在浏览器端,服务端不存在跨域。
  2. 即使跨域了,ajax请求也可以正常发出,但响应数据不会交给开发者。
  3. link,script,img这些标签发出的请求也可能跨域,只不过浏览器对标签跨域不做严格限制,对开发几乎无影响。

那么要如何解决跨域

想要解决跨域,一般有三种方法

第一种,用CORS解决ajax跨域问题

CORS 概述

CORS 全称:Cross-Origin Resource Sharing(跨域资源共享),是用于控制浏览器校验 跨域请求的一套规范,服务器依照 CORS 规范,添加特定响应头来控制浏览器校验,大致规则如下:

  • 服务器明确表示拒绝跨域 请求,或没有表示 ,则浏览器校验不通过
  • 服务器明确表示允许跨域 请求,则浏览器校验通过

了解了CORS之后,还要了解一下简单请求和复杂请求

CORS 会把请求分为两类,分别是:1.简单请求、2.复杂请求。

简单请求要满足这个三个条件

  1. 请求方法要为 get, head或者post。
  2. 请求头字段要符合CORS安全规范(https://fetch.spec.whatwg.org/#cors-safelisted-request-header), 一般只要不手动修改请求头,都能符合改规范。
  3. 请求头的Content-Type的值只能是这三种:

text/plain

multipart/form-data

application/x-www-form-urlencoded

不满足这三个要求的请求都是复杂请求,而复杂请求会自动发送预检请求

预检请求是一种在实际跨域请求发出之前,由浏览器自动发出的一种请求。主要用于向服务器确认是否允许接下来的跨域请求。基本流程是这样的,浏览器先发起一个options请求,携带这个几个请求头,Origin(发起请求的源),Access-Control-Request-Method(实际请求的HTTP方法),Access-Control-Request-Headers(实际请求中使用的自定义头)。如果通过预检,才会继续发起实际的跨域请求。

这就是预检请求

CORS 解决简单请求跨域

前端

html 复制代码
1.	<!DOCTYPE html>
2.	<html lang="en">
3.	<head>
4.	    <meta charset="UTF-8">
5.	    <meta name="viewport" content="width=device-width, initial-scale=1.0">
6.	    <title>Document</title>
7.	</head>
8.	<body>
9.	    <button onclick="getData()">获取数据</button>
10.	
11.	    <script>
12.	        async function getData() {
13.	            const url = 'http://127.0.0.1:8081/';
14.	            const result = await fetch(url);
15.	            const data = await result.json();
16.	            console.log('data');
17.	        }
18.	    </script>
19.	</body>
20.	</html>

服务端代码(以express框架为例):

javascript 复制代码
1.	const express = require('express');
2.	const app = express();
3.	
4.	app.get('/', (req, res) => {
5.	    res.send([{ name: '张三', age: 19 }])
6.	})
7.	
8.	app.listen(8081, () => {
9.	    console.log('服务器成功启动');
10.	})

这个时候是获取不到数据的

设置下cors

javascript 复制代码
1.	const express = require('express');
2.	const app = express();
3.	
4.	app.get('/', (req, res) => {
5.	    res.setHeader('Access-Control-Allow-Origin','http://127.0.0.1:5500')
6.	    res.send([{ name: '张三', age: 19 }])
7.	})
8.	
9.	app.listen(8081, () => {
10.	    console.log('服务器成功启动');
11.	})

这样就能获取到数据了

整体思路:服务器在给出响应时,通过添加Access-Control-Allow-Origin响应头,来明确表达允许某个源发起跨域请求,随后浏览器在校验时,直接通过。

像这样的设置是允许某个源跨域

javascript 复制代码
1.	// 允许 http://127.0.0.1:5500 这个源发起跨域请求
2.	res.setHeader('Access-Control-Allow-Origin', 'http://127.0.0.1:5500')

而这样则是允许所有源跨域

javascript 复制代码
1.	// 允许所有源发起跨域请求
2.	res.setHeader('Access-Control-Allow-Origin', '*')

CORS 解决复杂请求跨域

第一步: 服务器先通过 浏览器的预检请求, 服务器需要返回如下响应头

Access-Control-Allow-Origin(允许的源)

Access-Control-Allow-Methods(允许的方法)

Access-Control-Allow-Headers(允许的自定义头)

Access-Control-Max-Age(预检请求的结果缓存时间(可选))

第二步: 处理实际的跨域请求(与处理简单请求跨域的方式相同)

服务器代码

javascript 复制代码
1.	const express = require('express');
2.	const app = express();
3.	
4.	app.options('/', (req, res) => {
5.	    res.setHeader('Access-Control-Allow-Origin', 'http://127.0.0.1:5500')
6.	    res.setHeader('Access-Control-Allow-Methods', 'GET')
7.	    res.setHeader('Access-Control-Allow-Headers', 'city')
8.	    res.send()
9.	})
10.	
11.	app.get('/', (req, res) => {
12.	    res.setHeader('Access-Control-Allow-Origin', 'http://127.0.0.1:5500')
13.	    res.send([{ name: '张三', age: 19 }])
14.	})
15.	
16.	app.listen(8081, () => {
17.	    console.log('服务器成功启动');
18.	})

前端代码

html 复制代码
1.	<!DOCTYPE html>
2.	<html lang="en">
3.	<head>
4.	    <meta charset="UTF-8">
5.	    <meta name="viewport" content="width=device-width, initial-scale=1.0">
6.	    <title>Document</title>
7.	</head>
8.	<body>
9.	    <button onclick="getData()">获取数据</button>
10.	
11.	    <script>
12.	        async function getData() {
13.	            const url = 'http://127.0.0.1:8081/';
14.	            const result = await fetch(url, { method: 'GET', headers: { city: 'beijing' } });
15.	            const data = await result.json();
16.	            console.log('data', data);
17.	        }
18.	    </script>
19.	</body>
20.	</html>

数据获取到了

预检请求返回的响应头

借助 cors 库快速完成配置

上述的配置中需要自己配置响应头,比较麻烦,借助cors库,可以更方便完成配置

安装cors

npm i cors

配置cors

javascript 复制代码
1.	const express = require('express');
2.	const cors = require('cors');
3.	const app = express();
4.	
5.	// 使用cors中间件
6.	app.use(cors({
7.	    origin: 'http://127.0.0.1:5500', // 允许的源
8.	    methods: ['GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'OPTIONS'], // 允许的方法
9.	    allowedHeaders: ['city'], // 允许的自定义头
10.	    exposedHeaders: ['abc'], // 要暴露的响应头
11.	    optionsSuccessStatus: 200 // 预检请求成功的状态码
12.	}))
13.	
14.	app.get('/', (req, res) => {
15.	    res.setHeader('abc', '123')
16.	    res.send([{ name: '张三', age: 19 }])
17.	})
18.	
19.	app.listen(8081, () => {
20.	    console.log('服务器成功启动');
21.	})
22.	

成功获取数据

第二种,JSONP 解决跨域问题

JSONP 概述: JSONP 是利用了<script>标签可以跨域加载脚本,且不受严格限制的特性,可以说是程序员智慧的结晶,早期一些浏览器不支持 CORS 的时,可以靠 JSONP 解决跨域(现在基本不用了)。

基本流程:

    • 第一步:客户端创建一个<script>标签,并将其src属性设置为包含跨域请求的 URL,同时准备一个回调函数,这个回调函数用于处理返回的数据。
    • 第二步:服务端接收到请求后,将数据封装在回调函数中并返回。
    • 第三步:客户端的回调函数被调用,数据以参数的形势传入回调函数。

示例代码

前端

html 复制代码
1.	<!DOCTYPE html>
2.	<html lang="en">
3.	<head>
4.	    <meta charset="UTF-8">
5.	    <meta name="viewport" content="width=device-width, initial-scale=1.0">
6.	    <title>Document</title>
7.	</head>
8.	<body>
9.	    <script>
10.	        function callback(data) {
11.	            console.log('data', data)
12.	        }
13.	    </script>
14.	    <script src="http://127.0.0.1:8081/users"></script>
15.	</body>
16.	</html>

后端

javascript 复制代码
1.	const express = require('express');
2.	const app = express();
3.	
4.	app.get('/users', (req, res) => {
5.	    res.send(`callback([{ name: '张三', age: 19 }])`)
6.	})
7.	
8.	app.listen(8081, () => {
9.	    console.log('服务器成功启动');
10.	})

结果

优化下代码

前端

html 复制代码
1.	<!DOCTYPE html>
2.	<html lang="en">
3.	
4.	<head>
5.	    <meta charset="UTF-8">
6.	    <meta name="viewport" content="width=device-width, initial-scale=1.0">
7.	    <title>Document</title>
8.	</head>
9.	
10.	<body>
11.	    <button onclick="getUsers()">获取用户</button>
12.	
13.	    <script>
14.	        function test(data) {
15.	            console.log('data', data)
16.	        }
17.	        function getUsers() {
18.	            // 创建script元素
19.	            const script = document.createElement('script')
20.	            // 指定script的src属性
21.	            script.src = 'http://127.0.0.1:8081/users?callback=test'
22.	            // 将script元素添加到body中触发脚本加载
23.	            document.body.appendChild(script)
24.	            // script标签加载完毕后移除该标签
25.	            script.onload = () => {
26.	                script.remove()
27.	            }
28.	        }
29.	    </script>
30.	</body>
31.	
32.	</html>

后端

javascript 复制代码
1.	const express = require('express');
2.	const app = express();
3.	
4.	const users = [
5.	    { name: '张三', age: 19 },
6.	    { name: '李四', age: 20 }
7.	]
8.	
9.	app.get('/users', (req, res) => {
10.	    const { callback } = req.query
11.	    res.send(`${callback}(${JSON.stringify(users)})`)
12.	})
13.	
14.	app.listen(8081, () => {
15.	    console.log('服务器成功启动');
16.	})

效果

用jQuery封装jsonp

javascript 复制代码
1.	$.getJSON('http://127.0.0.1:8081/teachers?callback=?', (data) => {
2.	            console.log(data)
3.	        })

第三种,配置代理服务器解决跨域

这里需要用到http-proxy-middleware这个包来配置代理服务器

下载http-proxy-middleware

npm i http-proxy-middleware

前端

html 复制代码
1.	<!DOCTYPE html>
2.	<html lang="en">
3.	<head>
4.	    <meta charset="UTF-8">
5.	    <meta name="viewport" content="width=device-width, initial-scale=1.0">
6.	    <title>Document</title>
7.	</head>
8.	<body>
9.	    <button onclick="getNews()">获取头条数据</button>
10.	
11.	    <script>
12.	        async function getNews() {
13.	            const url = 'http://127.0.0.1:8081/api/hot-event/hot-board/?origin=toutiao_pc';
14.	            const result = await fetch(url);
15.	            const data = result.json();
16.	            console.log('data', data);
17.	        }
18.	    </script>
19.	</body>
20.	</html>

后端

javascript 复制代码
1.	const express = require('express');
2.	const app = express();
3.	const { createProxyMiddleware } = require('http-proxy-middleware');
4.	
5.	app.use(express.static('./public'))
6.	
7.	app.use('/api', createProxyMiddleware({ // 拦截所有带有/api的请求
8.	    target:'https://www.toutiao.com', // 转发的目标
9.	    changeOrigin:true, // 允许跨域
10.	    pathRewrite:{
11.	      '^/api':'' // 把路径中的/api去掉
12.	    }
13.	  }))
14.	
15.	app.listen(8081, () => {
16.	    console.log('服务器成功启动');
17.	})
18.	

需要把前端代码当成静态资源部署在服务器上

目录

把服务器启动了,需要去http://127.0.0.1:8081获取页面

点击获取数据

数据成功获取

相关推荐
时光匆匆岁月荏苒,转眼我们已不是当年12 天前
【ANGULAR网站开发】初始环境搭建
angular
langzitianya20 天前
XMLHttpRequest接受chunked编码传输的HTTP Response时有问题
vue·xmlhttprequest·angular·chunked·流式
KenkoTech25 天前
Angular由一个bug说起之十二:网页页面持续占用CPU过高
angular
张某人的胡思乱想1 个月前
angular19-官方教程学习
学习·angular
KenkoTech1 个月前
Angular由一个bug说起之十一:排序之后无法展开 Row
angular
余生H1 个月前
Angular v19 (三):增量水合特性详解 - 什么是水合过程?有哪些应用场景?与 Qwik 相比谁更胜一筹?- 哪个技术好我就学哪个,这就是吸心大法吧
前端·javascript·angular·ssr·前端优化·qwik
crary,记忆2 个月前
Angular中的ngOnchange()的汇总
前端·javascript·学习·angular
Greg_Zhong2 个月前
Angular 和 Vue2.0 对比
前端·angular·vue 2
无敌喜之郎2 个月前
Angular数据绑定详解
前端·javascript·angular·数据绑定