JavaScript

跨域的几种方法

字数:7626    阅读时间:39min
阅读量:63222

一,什么是跨域;同源策略又是什么
二,CORS跨域资源共享(官方推荐)
三,JSONP(野路子 有局限 好用)
四,iframe相关跨域(document.domain;window.name;location.hash)
五,HTML5相关跨域(postMessage(); WebSocket)
六,服务器代理跨域(node中间件;nginx反向代理)
七,总结

一,什么是跨域;同源策略又是什么;

个人能理解:为了防止除自身域外的其他域,恶意获取或篡改网站的信息和样式,出于浏览器安全的基础上提出了'同源策略';而同源策略的要求就是地址源的'协议 域名 端口'这些全部相同,如有一个不同就是不同源,称之为跨域;以下是域名是否同源的例子

但是很显然我们工作学习中经常会用到非同源下的资源,这种时候就要求我们必须要进行跨域请求 ,因此也催生出了一系列的跨域方案,这里我们逐一介绍

二,CORS跨域资源共享(官方推荐)

1,什么是CORS:

CORS的全称是跨域资源共享(Cross-Origin Resource Sharing) ,是一种Ajax跨域请求的方案。大家都知道Ajax是浏览器提供的一套可以向服务端发送获取数据实现无刷新、动态获取数据的Api;受同源策略的影响它原本是不能跨域的,后来W3C颁布了一项CORS('跨域资源共享')技术,允许浏览器跨域发送XMLHttpRequest请求;现在通过服务端添加白名单,Ajax请求就可以轻松实现跨域;

2,CORS的使用

CORS方案是通过浏览器端发送跨域的XMLHttpRequest请求,再配合服务端配置来进行跨域的(Ajax+服务端允许);它和同源的Ajax请求代码几乎是一样的,所有的动作都是浏览器和服务端处理的,所以更侧重于服务端的配置;在发送跨域请求的时候,浏览器会将CORS请求分为简单请求和非简单请求;非简单请求比简单请求多一次Option '预检'请求;

满足一下两个条件就是简单请求,不满足就是非简单请求

  1. 请求方式是:HEAD,GET,POST。
  2. THHP请求头信息不超过以下字段:
    • Accept
    • Accept-Language
    • Content-Language
    • Content-Type(只限于三个值 application/x-www-form-urlencoded,multipart/form-data,text/plain)
    • DPR
    • Downlink
    • Save-Data
    • Viewport-Width
    • Viewport-Width

(1).简单请求:浏览器会直接发出一个带有Origin信息头的CORS请求,这个Origin就是本次请求的地址源(协议+域名+端口),然后服务端会检查 'Access-Control-Allow-Origin '是否设置了允许当前源(Origin)访问;

如果服务端不允许当前源的访问,会返回一个正常的HTTP回应,浏览器检查回应头信息没有' Access-Control-Allow-Origin '字段,就知道不允许跨域;如果服务器设置了允许当前源访问,会在HTTP回应中添加以下几个信息头 ,浏览器检查有'Access-Control-Allow-Origin: http://121.196.221.138'字段,就知道是允许此源跨域的

(2).非简单请求:浏览器会先发一个Option请求到服务端,Option'预检请求'的请求头除了Origin请求源之外,还有两个特殊的字段:Access-Control-Request-Method (什么请求方式);Access-Control-Request-Headers (浏览器CORS请求额外发送的头部信息字段);服务器接收到浏览器预检之后检查Origin以及 Access-Control-Request-Method , Access-Control-Request-Headers ,服务端确认允许(此域的该种请求方式及该种请求头)跨域之后再给出浏览器反馈;以下是浏览器设置请求头为Content-Type: application/json 的非简单的POST请求

一旦通过了'预检'请求之后,浏览器每次再发送CORS请求都跟正常的简单请求一样

3,CORS的优缺点

优点:允许所有的跨域类型请求,可载信息量大,安全可靠,与同源Ajax代码相似配置简单
缺点:浏览器要求现代浏览器或要IE10以上

三, JSONP(野路子 有局限 好用)。

1,什么是JSONP,与JSON什么关系

JSONP是“JSON with padding”的简写,可以将其翻译为“被包裹的JSON” 。是一种简单易用、流传广泛的非官方经典跨域方案。JSON与JSONP不单单只是一个"P"的区别;JSON是一种数据格式,JSONP是一种借用JSON数据格式的跨域请求方案。他们既有联系又大不相同

2,JSONP的推导和例子

在这里我们可以模拟一下前辈们是如何发现推导JSONP跨域请求方案的;在使用HTML标签时我们会发现link、img、a、iframe、script等标签的src属性和href属性是不受同源策略影响的,但是我们跨域是要拿到数据并使用的,基于这点考虑发现这些标签中script标签的src是能拿到链接文件的内容并使用的,由此我们发现了JSONP跨域请求的大门

开心的同时我们也发现了一些问题:1,如果请求的数据不是javaScript规定的数据类型就会报错;2,就算是规定的数据类型,我们还是要将其放入到变量中或者其他方式才可以调用(我们知道一个数据直接暴漏在js中时会报错的);那么怎么规避这两个错误呢?

我们知道JSON是一种支持的多种数据类型的数据类型,而他也刚好被javaScript所支持,那么我们可以将服务端的数据写成JSON的格式传给客户端,这样就不用担心数据类型的问题了;/*把服务端数据写成JSON格式*/

数据类型的问题解决了,我们又如何处理返回回来的数据呢,我们最先想到的是把数据赋值给一个全局变量,但是扔给全局变量的话会导致一些问题(全局暴露、命名冲突、数据处理逻辑分散等等),还可以把数据当作参数丢给函数,这样就不存在这些问题了/*让服务端返回一个包裹数据的函数callback(JSON)*/

至此这两个问题都解决了,是不是对JSONP为什么叫:被包裹的JSON有了一定的理解了;返回的数据就是一个被函数包裹起来的JSON;然而事情并没有这么简单就结束了,一个问题的解决往往意味着下个问题将要被发现;我们从服务端返回的函数 callback(JSON) 如果没有在客户端预先定义好也是会报错的;

那么定义函数的时候如何让后端也知道我们定义好的函数名是什么呢?也许你会想直接固定一个函数名,告诉后端不就好了,这样做只有一个JSONP请求还好,如果有很多JSONP你岂不是得一遍遍得告诉后端函数名,这样做怕是会挨揍的emm...;值得高兴的是我们在指定URL的时候可以向服务端发送一个callback值;如此一来我们就可以通过设置script的src的callback值来统一客户端和服务端的函数名称了。栗子如下:

3,JSONP的优缺点
  • 优点:兼容性好,小巧,简单易上手
  • 缺点:
    1. 无法发送POST请求,也就是说JSONP技术只能用于请求,无法上传或修改异域数据
    2. 无法监测JSONP请求是否失败
    3. 可能存在安全隐患 ,如果你所访问的资源不可靠,里边被人安装病毒,你引用过来就废了

四,iframe相关跨域

我们在开发页面的时候也会经常使用iframe标签进行页面嵌套,使用在页面嵌套的过程中会需要到页面之间的通信,不幸的是我们的页面有可能不在同一域下,这样一来页面之间通信就没那么容易了; ifrmae跨域需要父页面和子页面都是自己控制的,如果把iframe随便指向一个其他网站,想通过跨域手段操作它基本上是不可能的。 接下来介绍iframe相关的跨域方案

1,window.domain+iframe

如果两个文件的主域名相同但是子域名不同,我们可以通过设置window.domain的来强制两个文件的基础主域相同,从而让两个文件互相通信
父窗口地址:http://aaa.test.com/test.html
子窗口地址:http://bbb.test.com/iframe.html

2,window.name + iframe

window.name是一个很有意思的属性;它是一个所有浏览器都有的默认值是空字符串(只能是字符串)的全局属性;在你设置了window.name属性后再通过该页面打开同源或者非同源的其他页面后window.name的值是一样的;虽然在同一页面下请求不同地址window.name是一样的,但是想要访问不同源下的window.name属性是不可能的,emm...有点难理解;

我们可以将不同源的页面数据放到window.name值里,再创建一个同源的代理空页面,在父页面通过iframe标签的src引入不同源的页面,然后再通过改变iframe标签的src刷新引入同源的代理空页面,如此一来同源的代理空页面 的window.name和不同源的window.name的值一样了,父页面也能访问同源代理空页面的window.name了,emm...有点绕

3,location.hash + iframe

location.hash就是url'http://121.196.221.138#hhh'中的'#hhh',同一域下改变hash是不会导致页面刷新的(这个上一个window.name全局属性很像);因此我们可以把需要的数据放到hash中进行传递。然鹅,不同域是不能获取对方hash值的。我们可以像上一个window.name跨域方案一样创建一个和原页面同源的中间页面,来进行跨域

a.html和b.html同源(http://localhost:80) 而c.html(http://localhost:88);我们在a.html中引入c.html,再在c.html中引入b.html,然后在c.html中改变的hash值,再在监听hash值的变化,这样a.html就拿到了c.html传过来的hash值
(这里突然想到让c.html直接修改a.html的hash再由a.html监听hash变化岂不是简单粗暴,再一想如果随便把a.html值改变了岂不是会覆盖原先的数据emm...)

五,HTML5相关跨域

1,postMessage()

postMessage是html5 XMLRequest level2中的API,它是为数不多可以跨域的window属性之一;允许来自不同源脚本采用异步的方式进行跨文档、多窗口之间的有限通信

● postmessage()适用于以下场景
  原页面和打开的新页面之间的通信
  多窗口之间的通信
  原页面与嵌套的iframe窗口之间的通信

● 语法:otherWindow.postMessage(message, targetOrigin, [transfer])
  otherWindow : 要给发送信息的目标窗口对象;可以是window.frames的某个成员,也可以是window.open创建的那个窗口。
  message : 发送到其他 窗口的数据 ;
  targetOrigin : 指定接收信息的地址;"*"表示所有地址的域都可接收
  transfer :(可选) 是一串和message 同时传递的 Transferable 对象. 这些对象的所有权将被转移给消息的接收方,而发送一方将不再保有所有权;

● 这里我们还需要了解message事件的几个属性
  event.data:传递过来的字符串或对象;
  event.origin:发送该消息窗口的域名;
  event. source:发送该消息的窗口对象;

举个栗子: http://localhost:80/a.html页面向http://localhost:88/b.html传递“我是a”,然后后者传回"我是b"。

2,WebSocket

WebSocket是什么,与HTTP又有什么关系,可以用来干什么:
  1).WebSocket是HTML5持久化的一个网络通讯协议。2).他和HTTP一样都是基于TCP协议的应用层协议;HTTP协议的通信只能由客户端发起,使用''轮询"的方式不断地向服务端询问是否有信息更新,这样效率低又浪费资源。由此问题我们聪明的攻城狮们提出了WebSocket协议;WebSocket与HTTP有良好的兼容性,在建立连接的时候会借助HTTP,建立好连接之后客户端与服务端之间的通信便与HTTP无关了。 3).用来实现客户端与服务端双向通信; 是服务器推送技术的一种很好实现 ;同时也是一种跨域解决方案 ;  

原生的WebSocket API使用起来比较麻烦,我们这里就不多做介绍。ws库很好地封装了webSocket接口,提供了更简单、灵活的接口(当然你也可以使用socket.io库,它相比ws库更健壮些,但是ws要比socket.io快的多并能跑更多的服务);接下来我们用node开启一个webSocket协议服务,并实现客户端与服务端的双工通信:

  1. 检查node.js 是否安装好 (打开命令行输入node -v查看;没有安装点击下载安装);
  2. 创建一个文件夹来存放我们的项目(eg:WebSocketServer);在此文件夹打开命令行(文件夹地址栏输入cmd回车),输入npm init(初始化当前项目),然后依次安装依赖npm install express --save;npm install ws --save;
  3. 创建客户端及服务端文件(如下)。命令行输入'node 服务端文件夹名'运行服务,打开客户端文件(直接打开,不要使用localhost:8080打开)

六,服务器代理跨域

1.node中间件代理

说到底跨域问题就是浏览器的同源策略引起的,服务端之间是不存在跨域的;那么我们是不是可以在本地专门起一个服务器来接收浏览器的请求信息,再由这个服务器把请求信息传递给我们目标服务器,然后目标服务器再将响应的数据发送给本地新起的那个服务器,新起的服务器再将数据传给本地的客户端;这样通过中间新起的这个本地服务器将请求送给目标服务器再将数据传回客户端就完成了跨域问题

代理服务跨域示意图

接下来我们再来一个栗子来感受一下用nodejs做中间层跨域,其中需要的http模块的知识了,可以参考浅析nodejs的http模块

至此我们大致了解了以node作为中间层进行跨域访问的原理;但是我们为什么要用node做中间层呢?

  • 前端熟悉的语言,学习成本低
  • 都是JS可以前后端复用
  • 相似的特性:事件驱动、非阻塞I/O
  • 执行速度也可接受
  • 有利于一些SEO的解决方案

node做中间层不仅仅只是用来跨域的,还有以下这些用途:

  • 代理:在开发环境下,我们可以利用代理来,解决最常见的跨域问题;在线上环境下,我们可以利用代理,转发请求到多个服务端。
  • 缓存:缓存其实是更靠近前端的需求,用户的动作触发数据的更新,node中间层可以直接处理一部分缓存需求。
  • 限流:node中间层,可以针对接口或者路由做响应的限流。
  • 日志:相比其他服务端语言,node中间层的日志记录,能更方便快捷的定位问题(是在浏览器端还是服务端)。
  • 监控:擅长高并发的请求处理,做监控也是合适的选项。
  • 鉴权:有一个中间层去鉴权,也是一种单一职责的实现。
  • 路由:前端更需要掌握页面路由的权限和逻辑。
  • 服务端渲染:node中间层的解决方案更灵活,比如SSR、模板直出、利用一些JS库做预渲染等等。
2.nginx反向代理

Nginx是一个高性能的HTTP和反向代理服务器,也是一个IMAP/POP3/SMTP 代理服务器 ;具有响应快,占用内存小,并发能力强,可靠性高, 支持7层负载均衡(将请求均匀转发至多台服务器上,通过部署多台相同服务的服务器,以减轻服务器的压力) 等优点;使用Nginx反向代理进行跨域只需要修改Nginx的配置就行,不用修改其他客户端服务端代码,支持所有浏览器不影响服务器性能,是最简单的跨域方式 。 下载Nginx,解压后双击nginx.exe,在nginx目录下运行命令行输入 nginx -v 出现版本号,则安装成功。

Nginx反向代理跟node中间件的跨域原理是一样的,用Nginx开一个代理服务器,设置允许客户端访问,反向代理目标服务器; 举个简单的栗子:

总结

  • CORS支持所有类型跨域,是HTTP请求跨域的根本解决方案,官方推荐,但是要求ie10+
  • JSONP轻巧简单兼容性好,但是只支持GET请求,安全性不高
  • iframe相关,如果主域相同只子域不同,设置document.domain还是比较简单方便的;window.name和 location.hash都需要创建一个空iframe页面来中转信息,可以中postMessage()代替
  • HTML5相关,postMessage()解决的是页面与页面之间的跨域通信WebSocket是一种新协议,能实现客户端与服务端的双工通信,解决的是客户端与服务端跨域; 但他们都是H5特性浏览器要求IE10+
  • 服务器代理跨域主要是因为服务器不受同源策略的限制;适合前后端分离的前端调后端接口;node属于大前端范畴学习成本低,nginx属于后端,但是占内存小高并发,性能好;看需求进行选择

没有最好只有更好,工作学习中还是要根据使用场景,再决定使用那种跨域才好;

野生小园猿
励志做一只遨游在知识海洋里的小白鲨
查看“野生小园猿”的所有文章 →

相关推荐