前言
互联网辉煌发展二十年后的今天,行业变得越来越卷,搞了多年前端的我们也不得不学习后端开发。刚用nodeJS环境搭建了一个后端接口服务,用ajax访问就出现了下面问题。
大概意思是使用XMLHttpRequest技术从http://127.0.0.1:14949 访问http://localhost:3000资源时被cors策略阻止,请求的资源上没有"访问控制允许源"标头。
跨域问题
按照程序员解决问题的思路,快速复制报错信息粘贴百度搜索,很快有了答案,原来是遇到了跨域问题。要理解跨域问题先来理清几个概念。
同源策略
同源策略是浏览器的一个安全功能,不同源的网页脚本在没有明确授权的情况下,不能读写对方资源。所谓同源是指"协议+域名+端口"三者相同。
在图A中A服务器有三个资源分别是index.html、test.json、util.js,index.html访问test.json资源数据:
访问中协议、域名和端口号都相同属于同源访问不会违背浏览器安全限制。
图B中从A服务器index.html访问B服务器test.json资源:
域名(ip)不属于同源访问,违背浏览器安全限制。
什么是跨域
使用AJAX技术(XMLHttpRequest 对象),从一个源去请求另一个源资源时,违反浏览器同源策略限制,引起的安全问题,称为跨域。
备注:localhost和127.0.0.1虽然都指向本机,但也属于跨域
出现以上情况都会引起跨域,前端使用ajax技术访问后端资源时就会出现如下错误:
代码演示
- 后端代码:
// 商品列表 let productList = [ { number: 1001, name: 'javascript高级编程', url: 'https://img2.baidu.com/it/u=1527170010,3082273564&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500', price: 88.98, num: 0, state: false, }, { number: 1002, name: 'css高级编程', url: 'https://img2.baidu.com/it/u=1209311252,2459534360&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500', price: 58.58, num: 0, state: false, }, { number: 1003, name: 'html高级编程', url: 'https://img0.baidu.com/it/u=2950970616,2356748823&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=496', price: 48.58, num: 0, state: false, }, ] let http = require('http') // 引入内置http模块 // 创建一个web服务 let server = http.createServer(function (request, response) { // response.setHeader('Access-Control-Allow-Origin', '*')// 跨域问题 response.writeHead(200, { 'content-type': 'text/html;charset=utf-8' })// 解决乱码 productList = JSON.stringify(productList) // 将数组转字符串 response.write(productList) // 向响应对象写入数据helloworld response.end() // 结束写入,发送数据给请求客户端 }) // 启动web服务 server.listen(3000, () => console.log('web服务启动成功,监听3000端口...'))
- 前端代码:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>跨域问题</title> </head> <body> <button class="product-list">获取数据</button> <!-- 显示后端数据 --> <p></p> <script> const btn = document.querySelector('.product-list') const pEle = document.querySelector('p') btn.addEventListener('click', function () { getData() }) /** *异步获取后端商品列表数据 */ function getData() { // 1. 创建XMLHttpRequest let xhr = new XMLHttpRequest() // 2. 建立连接 xhr.open('get', 'http://localhost:3000') // 3. 发送请求 xhr.send() // 4. 接收响应数据 xhr.onreadystatechange = function () { // 4.1 是否响应完成 if (xhr.readyState === 4) { // 4.2 是否成功响应 if (xhr.status === 200) { let data = xhr.responseText // 响应内容 pEle.innerHTML = data } else { alert('网络出错 ' + xhr.status) } } } } </script> </body> </html>
- 跨域解决方案
跨域解决方案不少,这里讨论三个常用方法:
1. jsonp技术
2. cors跨域资源共享
3. 代理服务器
JSONP技术
首先,不知道大家有没有注意,不管是我们的script标签的src还是img标签的src,或者说link标签的href他们没有被同源策略所限制,比如我们有可能使用一个网络上的图片,就可以请求得到,<img src="https://ss.bd.com/aa.jpg/>
jsonp就是使用同源策略这一“漏洞”,实现的跨域请求
其基本原理是利用HTML的<script>标签天生可以跨域这一特点,用其加载另一个域的JSON数据。
加载完成后会自动运行一个回调函数通知调用者。此过程需要另一个域的服务端支持。
该协议的一个要点就是允许用户传递一个callback参数给服务端,然后服务端返回数据时会将这个callback参数作为函数名来包裹住JSON数据,这样客户端就可以随意定制自己的函数来自动处理返回数据了。
- jsonp示例
服务端代码:
// 商品列表 let productList = [ { number: 1001, name: 'javascript高级编程', url: 'https://img2.baidu.com/it/u=1527170010,3082273564&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500', price: 88.98, num: 0, state: false, }, { number: 1002, name: 'css高级编程', url: 'https://img2.baidu.com/it/u=1209311252,2459534360&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500', price: 58.58, num: 0, state: false, }, { number: 1003, name: 'html高级编程', url: 'https://img0.baidu.com/it/u=2950970616,2356748823&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=496', price: 48.58, num: 0, state: false, }, ] let http = require('http') // 引入内置http模块 // 创建一个web服务 let server = http.createServer(function (request, response) { response.writeHead(200, { 'content-type': 'text/html;charset=utf-8' })// 解决乱码 productList = JSON.stringify(productList) // 将数组转字符串 response.write(`callback(${productList})`) // 向响应对象写入数据helloworld response.end() // 结束写入,发送数据给请求客户端 }) // 启动web服务 server.listen(3000, () => console.log('web服务启动成功,监听3000端口...'))
- 客户端代码:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>jsonp技术实现</title> <!-- jsonp技术实现 1. jsonp技术利用script标签天生可以跨域特点解决跨域问题 代码动态创建script标签,将请求url地址作为script标签src属性值 2. jsonp需要服务端支持 把真正的数据封装到一个函数中一起返回 {code:1,info:'helloworld'} callback({code:1,info:'helloworld'}) 缺点: 只支持get请求 --> <!-- <script src="http://localhost:3000/"></script> --> </head> <body> <script> sendProductList('http://localhost:3000') function sendProductList(url){ let scriptEle = document.createElement('script') // <script> scriptEle.setAttribute('src',url) document.body.appendChild(scriptEle) } function callback(res){ let resObj = JSON.parse(res) console.log(res) console.log(resObj) } </script> </body> </html>
- jsonp弊端 : JSONP仅仅用于get请求
跨域资源共享CORS
CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource )。
它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用sharing的限制。
这个操作只需服务端修改代码,如果你作为前端现在还没有遇到过跨域问题,很可能是后端使用cors解决了跨域。
// 创建一个web服务 let server = http.createServer(function (request, response) { response.setHeader('Access-Control-Allow-Origin', '*')// 跨域问题 response.writeHead(200, { 'content-type': 'text/html;charset=utf-8' })// 解决乱码 productList = JSON.stringify(productList) // 将数组转字符串 response.write(productList) // 向响应对象写入数据helloworld response.end() // 结束写入,发送数据给请求客户端 }) // 启动web服务 server.listen(3000, () => console.log('web服务启动成功,监听3000端口...'))
核心代码为:
response.setHeader('Access-Control-Allow-Origin', '*')// 跨域问题
当然在实际开发中设置的代码会更复杂,更完善一些,这里仅作测试,目标理解跨域。
代理服务器
跨域问题引起的核心原因是违反浏览器的安全策略引发安全问题。如果能让访问的资源不直接通过浏览器访问而是通过服务器代发是不是可以解决这个问题呢?
代理服务器技术同样可以帮我们解决跨域问题,这也是现在前端接触最多的跨域解决方案。
代码演示:
vue3配置 vue.config.js
nginx服务器配置
server { listen 3000; server_name localhost; # proxy the PHP scripts to Apache listening on 127.0.0.1:80 # location /prod-api/ { proxy_pass http://43.136.34.132:8082/; } }
小结
本文介绍了什么是同源策略,跨域问题,跨域错误,解决跨域的三个方案,相信通过本节学习你对跨域会有一个更清晰的认识。