前言
什么是跨域: 浏览器为了安全性,设置同源策略导致的, 或者说是一种浏览器的限制同源策略: 是一种约定,WEB 应用只能请求同一个源的资源什么时候会跨域: 协议名、域名、端口号 不同
本文将从原理, 到最简代码实现, 演示解决跨域的方法和流程,纸上得来终觉浅 绝知此事要躬行, 只有自己手敲实现过, 才能对其原理理解更加深刻。
一、JSONP
1、原理
JSONP是利用 html 中的script标签没有跨域访问限制的来实现的 工作原理:
- 客户端通过script标签向服务器发送请求, 同时定义好回调函数接收
<script src="http://example.com/api?callback=handleResponse"></script>
- 服务器接收到请求后,会把数据进行填充,并且放回回调函数中,例如
handleResponse({data:'response'})
- 客户端在页面中定义相应的回调函数
handleResponse
, 服务器返回的脚本会被浏览器执行, 从而触发回调函数,处理服务器返回的数据(把数据作为参数传给客户端定义的函数并执行), 这样就实现了跨域请求和数据获取
局限: JSONP 的限制是需要通过
<script>
标签加载外部脚本, 但是它有一些局限性JSONP 只支持 GET 请求, 无法通过 POST 或 其他类型请求 JSONP 只能接收 纯文本数据,无法处理 JSON 对象 或 XML 数据
2、实站(最简化代码)
接下来就通过最简代码的方式, 实现JSONP
的原理
客户端 index.html
<!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> </head> <body> JSONP </body> <script> // 像页面中添加一个script标签, 通过script标签进行访问, 返回一个可执行的 函数 function sendJsonp() { const callbackName = `jsonpFn_${Date.now()}`; // 为了防止缓存 加一个时间戳 // 发送请求前先将函数名放到window上, 赋值一个函数, 当后端收到请求后,会返回一个该函数执行的脚本, 并将函数的参数传入到该函数中 window[callbackName] = function (data) { console.log(data, ".datadatadatadata"); }; // 将该函数拼成一个url地址请求到后端 const url = `http://localhost:4000/jsonp?q=1&w=2&jsonp=${callbackName}`; const script = document.createElement("script"); script.src = url; // 将脚本标签添加到head中,发送请求 document.head.appendChild(script); } sendJsonp(); </script> </html>
服务端代码
const express = require("express"); const app = express(); app.get("/jsonp", (req, res) => { // 查询参数 const { jsonp, q, w } = req.query; const result = { g: Array.from({ length: 10 }, (_, i) => ({ q: `${w}${i + 1}` })), }; res.send(`${jsonp}(${JSON.stringify(result)})`); }); app.listen(4000, () => { console.log("sever 4000"); });
解释: 从上面简化的代码中,可以看出, 客户端就是通过构建 script 标签, 通过src 的访问将函数名发送给服务端, 并且构建一个函数等待执行, 服务端收到后,拿着函数名在拼装成一个函数调用的字符串, 当客户端发送 script 请求时,会将结果进行执行, 因为函数挂载到了window上, 所以会执行我们之前构造好的函数, 并将data 这个参数传递进去,我们就拿到了结果
二、CORS
1、概念
CORS 跨域资源共享:是一种浏览器机制,允许跨域请求共享资源,在Web开发中,当一个网页向另一个域名下的资源发起请求时,如果请求的目标域和当前域不同,就会设计到跨域问题, 默认情况下, 浏览器会限制这种跨域请求,以保护用户安全
2、工作流程 (可略)
CORS 跨域资源共享通过在服务端设置相应的响应头来解决跨域问题, 它使用一些特定的HTTP 头部和预检请求已实现跨域通信
- 1、发出跨域请求: 在浏览器中的网页向其他域名下的资源发起跨域请求时, 浏览器会首先发送一个跨域请求
- 2、发送预检请求(可选):对于某些类型的跨域请求,例如带有一些自定义的HTTP头部或特殊方法(PUT、DELETE)的请求,浏览器会发送一个OPTIONS请求,称为预检请求,用于向服务器验证跨域请求是否被允许
- 3、服务器设置响应头: 服务器收到跨域请求后,可以设置响应头来告诉浏览器允许请求的源(Origin)、允许的 HTTP 方法、允许的自定义头部信息
- Access-Control-Allow-Origin 指定允许访问域名,可以单个 通配符 列表
- Access-Control-Allow-Methids 指定允许的 HTTP 方法
- Access-Control-Allow-Headers 指定允许的自定义头
- access-Control-Allow-Credentials 是否允许发送身份凭证 允许时 Origin不能为*
- 4、处理跨域响应,如果服务器设置了响应头,浏览器会根据指定的规则处理
3、实战 ( 最简化代码 )
客户端代码
我们通过 livesever 先给页面起一个静态服务, 域名为 http://127.0.0.1:5501/HTTP/xxx/indexMini.html
indexMini.html 页面
<!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>Document</title> </head> <body> <script> async function getUsers() { const response = await fetch("http://localhost:3000/users", { method: "POST", }); const data = await response.json(); console.log(data); } getUsers(); </script> </body> </html>
服务端代码
const express = require("express"); const app = express(); app.use((req, res, next) => { next(); }); app.post("/users", (req, res) => { res.json([{ id: 1, name: "zhangsan11" }]); }); app.listen(3000, () => { console.log("sever 3000"); });
解释: 上面用静态服务起一个客户端 端口 5501, 然后通过express 起一个后端服务 端口3000, 客户端刷新页面会发起一个/users的请求, 一定会发生跨域问题, 'no-cors' to fetch the resource with CORS disabled.
显示 no-cors
获取资源失败, 其实后端是收到了请求,也将数据返回去了, 但是接口的响应表头中没有设置允许的 源 Orign, 所以浏览器对于这种跨域又没有设置 Orign 的情况, 就会报错 阻止客户端收到返回值
1、 跨域问题的解决
app.use((req, res, next) => { res.header("Access-Control-Allow-Origin", "*"); next(); });
实际上只需要后端增加一行代码, 就可以解决跨域问题了, 因为 *
代表所有源都允许, 如果希望指定的源或者给几个指定源添加白名单的形式,可以这样写
const whiteList = ["http://127.0.0.1:5501"]; if (whiteList.includes(req.headers.origin)) { res.header("Access-Control-Allow-Origin", req.headers.origin); }
这样就只能针对特定的源允许跨域请求了
2、针对复杂请求的相关处理与解决
上面我们解决完跨域问题了, 但是还存在几个问题, 我们的请求是post的,还没有添加header, 一旦修改请求方法,或者header增加一些字段,就发现有问题了,所以下面开始处理这些复杂请求 先简单区分什么是复杂请求什么是简单请求: 区分他们有两个指标, 一个是请求方法: HEAD、GET、POST是简单请求, header中常见的 Content-Type 是 application/x-www-form-urlencoded
和 multipart/form-data
和一些 Accept
、 Accept-Language
、Content-Language
这些都是简单请求, 简单请求就很简单,不需要在俄额外进行服务端的处理设置了, 可以尽情使用
除此之外,其他的请求方法 和 其他的请求头。都是复杂请求, 复杂请求会额外发送一个 OPTION 请求来询问服务端是否支持, 那么怎么才能支持呢? 就是要分别处理 这里格外需要注意, header 中的 Content-Type: "application/json
是复杂请求, 比如 POST
的时候 我们经常需要这种类型, 那么在跨域的时候就需要特殊处理了,否则会报错 Request header field content-type is not allowed
这种解决只需要在代码中增加一行
res.header("Access-Control-Allow-Headers", "Content-Type");
如果想增加自定义的头 比如请求时候 再增加一个 custom: 3000
headers: { "Content-Type": "application/json", custom: "3000", },
这样也只需要后端在允许的Headers中加一下
js
复制代码
res.header("Access-Control-Allow-Headers", "Content-Type, custom");