跨域HTTP请求是指当前文档访问其他源提供的资源。两个页面具有相同的协议,域名和端口,则两个页面属于相同的源,不同子域名之间也属于不同的源。这是浏览器端的同源策略的约束,同源策略则是浏览器隔离潜在恶意文件的安全机制。
目前常用的跨域解决方案有EmbedPing、JSONP、CORS。
一些知识点
-
跨域限制是浏览器的一种行为
当我们在浏览器的一个页面中尝试进行一次跨域操作,比如我们在 localhost 的一个页面通过 ajax 访问 127.0.0.1 的一个路由,127.0.0.1已经将数据返回给浏览器,浏览器禁止了获取响应数据行为。我们可以看一张图:
-
跨域请求的几种方式
跨域写
浏览器始终支持通过跨域写操作。例如通过表单提交方式向其他源提交数据,点击链接重定向到其他源。跨域嵌入
浏览器始终支持跨域资源的嵌入。例如通过 img、script、video 等元素的 src 属性嵌入跨域资源。跨域读
跨域的解决方案
EmbedPing
这是我自己瞎取的一个名字,大致是利用浏览器支持标签嵌入跨域属性实现的一种方案。img、script、link[rel='stylesheet']会在页面渲染过程中请求其src/href设置的资源地址,我们可以在访问的路由中做处理。例如使用img的src属性:
<script>
var img = new Image()
img.src = 'http://localhost:4000/check'
</script>
因为无法获取到服务端返回的数据,EmbedPing是浏览器向服务器单向请求的过程。
我们可以使用 EmbedPing 做一些不严格统计。
JSONP
JSONP也利用了 script 标签的 src 属性,浏览器会执行加载成功后的js。JSONP的局限是只能发送一个GET请求。
应用示例
看一个简单示例,我们希望在当前域的页面上打印服务器的一个状态,我们使用JSONP可以这样实现,前可以看一段简单代码(服务端使用koa构建的):
当前域属于 http://localhost:3000
,我们在页面上这请求
<body>
<script>
var script = document.createElement('script')
script.src = 'http://localhost:4000/jsonp/run'
script.async = true
document.body.appendChild(script)
</script>
</body>
在 http://localhost:4000/jsonp/run
中,我们返回了一段打印状态的script
var valForServerB = 'a'
router.get('/jsonp/run', (ctx) => {
let script = `
alert('我来自服务器b,我的值是${valForServerB}')
`
ctx.body = script
})
这样,我们可以在当前页面上显示 valForServerB
的值了。
几种类型
根据 JSONP 返回的代码片段不同,混入自己的情感,我将 JSONP 分为三种类型(这种带着情感的划分,需要各位大牛的指正):
返回执行
返回代码即执行代码,直接在页面上产生效果返回定义
返回代码是函数的定义,具体调用时机交给当前页面决定返回调用
函数的执行过程是在页面上所定义的,JSONP的返回只是函数的调用,当然包含传给函数的参数。
为了保持当前页面的的绝对控制权,返回调用应该是应用最广的
错误处理
JSONP使用过程中,会有两种常见错误:
请求失败或服务器返回失败
捕获此类错误,我们是借用 script 的 onerror 处理服务器返回内容错误
针对这类错误,我们会在当前页面借用定时器去处理。在返回调用时,我们可以在页面上的函数定义中埋下时间因子;在返回定义时,我们可以设置时间点去检测期望调用的方法是否存在。当然,此方法会因为网络等因素变得不可靠。
CORS(跨域资源共享)
CORS是当前页面与其他域执行的一种双方约束,需要浏览器应用和服务器程序之间的协调。
我们在服务器端可以通过设置CORS响应头部:
Access-Control-Allow-Origin: <origin> | *
允许访问的源Access-Control-Allow-Methods
允许访问的方法Access-Control-Allow-Headers
运行添加的请求头部Access-Control-Expose-Headers
允许客户端可以访问的头部Access-Control-Max-Age
预请求的缓存周期Access-Control-Allow-Credentials
是否运行请求加入用户凭证信息
相应的,在请求头部中,我们可以加入:
Origin
告知服务器请求的源Access-Control-Request-Method
告知服务器使用请求的的方法Access-Control-Request-Headers
告知服务器,请求中携带的头部
结语
跨域请求好,安全第一位。在我们跨域请求时,我们要确保我们请求的服务器返回的数据是可信任的。