背景
跨域是由浏览器的同源策略引起的,是指页面请求的url地址,必须与浏览器上的url地址处于同域上(即域名,端口,协议相同)。
这是为了防止某域名下的接口被其他域名下的网页非法调用,是浏览器对JavaScript施加的安全限制。
这个措施的出发点是好的,但是程序页面开发的过程中,却常常给前端开发者带来麻烦。
由于前端开发过程中,静态资源是放在本地电脑上的,访问这些资源通常通过IP方式(127.0.0.1)或者localhosts来访问,与线上服务器所在域名不符,不能顺利调用服务的端口。
解决跨域问题常用的解决方案有这两个:
- JSONP:利用script标签可跨域的特点,在跨域脚本中可以直接回调当前脚本的函数。
- CORS:服务器设置HTTP响应头中Access-Control-Allow-Origin的值,解除跨域限制。
但是这两个跨域方案都存在一个致命的缺陷,严重依赖后端的协助。
开发中遇到的每一个接口都需要提前找后端进行特殊的处理。而且即使后端愿意帮忙,某些接口不是随便就能开放的(譬如已经在上线正式环境的接口)。
无论如何,依赖后端协助的跨域解决方案都会在一定程度上限制前端开发的进度。
那么有没有前端独立就能实现的跨域方案呢?有的,我们可以利用「代理」或「反向代理」技术来实现开发中的跨域问题。
代理与反向代理
代理
代理,也称正向代理,意思是一个位于客户端和目标服务器(target server)之间的服务器,为了从目标服务器取得内容,客户端向代理发送一个请求并指定目标(目标服务器),然后代理向目标服务器转交请求并将获得的内容返回给客户端。
通俗地说:
- 「客户端」可以看作一个黑社会大佬,「目标服务器」可以看作一家饭店,「代理服务器」可以看作小弟。
- 「老大」想吃饭店的酱排骨饭,就让「小弟」去买,「小弟」跑到「饭店」要个酱排骨饭。
- 「饭店」酱排骨饭做好,送到「小弟」手上,「小弟」最后再把酱排骨饭拿给「大佬」。
说白了,小弟就是个跑腿的,代理大佬的需求。
数据流程:
- 数据请求过程:浏览器-》代理服务器-》目标服务器
- 数据返回过程:目标服务器-》代理服务器-》浏览器
应用:
最经典的应用就是科学上网:我是一个国内用户,我访问不了google,但是我能访问一个香港的某个代理服务器。
这个香港的代理服务器可以访问google,于是我先把请求发送到那个代理服务器,告诉他我需要访问google,代理服务器去取内容,最后返回给我。
就好比,大佬被抓起来坐牢了,不能出去买酱排骨,只好拜托小弟去买回来。
反向代理
百度百科的解释如下:
反向代理(Reverse Proxy)方式是指以代理服务器来接受internet上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给internet上请求连接的客户端,此时代理服务器对外就表现为一个反向代理服务器。
数据流程:
- 数据请求过程:浏览器-》【反向代理服务器-》处理数据的服务器】
- 数据返回过程:【处理数据的服务器-》反向代理服务器】-》浏览器
通俗地说:
「浏览器」可以看作食客,「【反向代理服务器-》处理数据的服务器】」这一个整体可以看作饭店,其中「反向代理服务」相当于点单的服务员。「处理数据的服务器」可以理解为是厨师。
- 「食客」向来到「饭店」向「服务员」点菜,但服务员并不会真正去做菜,他是下达命令让「厨师」去做菜。
- 「厨师」把菜做好了给「服务员」,「服务员」再把菜端给「食客」。
在外部看来,「代理服务器」和「处理数据的服务器」是一个整体。就好比,食客只会去饭店吃饭,而不是去找厨师吃饭(即对于浏览器来说,到达反向代理服务器已经完成任务了,后面的操作则由反向代理服务器负责)。
具体饭店怎么操作,对食客是透明的。有可能某个服务员即当服务器也当厨师(即反向代理服务器和处理数据的服务器是同一台PC机)。
补充一下,没有反向代理,就好比没有了服务员,食客直接向厨师要吃的。譬如,你饿了,直接叫妈妈做饭是一样的(少了下订单的步骤)
比较
从用途上来讲:
- 正向代理的典型用途是为在防火墙内的局域网客户端提供访问Internet的途径。正向代理还可以使用缓冲特性减少网络使用率。
- 反向代理的典型用途是为后端的多台服务器提供负载平衡,或为后端较慢的服务器提供缓冲服务。
从安全性来讲:
- 正向代理允许客户端通过它访问任意网站并且隐藏客户端自身,因此你必须采取安全措施以确保仅为经过授权的客户端提供服务。
- 反向代理对外都是透明的,访问者并不知道自己访问的是一个代理。
从使用方来看:
- 正向代理是浏览器端进行配置的,与服务器端无关,甚至可以对服务端隐藏。
- 反向代理是服务器端配置的,对浏览器端是透明的。
利用代理实现跨域
实现原理
对正向代理服务器进行配置,当获取非接口数据时,让代理服务器指向开发者本机的资源。当访问接口时,访问后端接口数据。
相当于大佬让小弟把酱排骨饭里面的饭和酱排骨分开买,饭自己家煮,酱排骨才去饭店买。
程序运行过程
- 浏览器访问页面,假设访问淘宝页面:taobao.com/index.html(假设这个页面中调用了taobao.com/api/getNew获取最新商品的接口)
- taobao.com/index.html请求经过代理服务器,根据配置,index.html页面请求127.0.0.1:3000
- 127.0.0.1:3000返回index.html文件给浏览器。
- 浏览器运行index.html页面,发起taobao.com/api/etNew请求。
- taobao.com/api/getNew请求经过代理服务器,但由于没有对这个接口进行特殊配置,会正常访问淘宝服务器中的接口。
- 淘宝服务器接受到taobao.com/api/getNew请求,检查请求头的hosts字段,发现是taobao.com,没有跨域,将结果返回给代理服务器。
- 代理服务器拿到结果,返回给浏览器,浏览器进行解析显示。
代理配置(以mac下的charles为例)
- 打开charles的映射关系表【charles->tool->Map Remote】。
- 点击add可以添加映射关系。
- 点击 ? 符号,可以进入配置规则介绍页面。
注意:
- 匹配taobao.com/api/ 的配置项要放在匹配taobao.com/ 的配置项前,让匹配API的优先级更高。否则将只匹配到taobao.com/*的配置。
- 如果接口请求中,有涉及到https协议的,需要提前在电脑上安装charles的证书 。
- chrome系浏览器的请求是不经过charles代理的,这时需要设置电脑上的网络设置,设置代理地址为charles。
- 微信开发者工具是不走系统代理的,需要额外设置。【微信开发者工具-》设置-》代理-》指向代理服务器】
利用反向代理实现跨域
反向代理需要用到nginx,其详细介绍请看 http://www.nginx.cn/doc/http://www.nginx.cn/doc/
实现原理
原理大体相同,但是处理的端不同,反向代理实在服务器端进行处理。首先修改hosts文件,将域名指向开发者的电脑本身,
把自己伪装成服务端,再通过nginx对不同的请求进行转发,把静态资源指向开发者本地电脑的资源,将接口指向实际的服务器。
相当于把饭店设置在了黑社会的楼下,去楼下买酱排骨饭的时候,饭店饭自己做,酱排骨则偷偷跑去别的饭店买。
代理配置
- 设置hosts文件,将目标域名指向本机。
- 编辑nginx配置,对不同的资源请求,指向到对应地址。同样的,将静态资源指向本机服务,将接口指向真正的服务器。
程序运行过程
- 浏览器访问页面,假设访问淘宝页面:taobao.com/index.html
- taobao.com域名解析先经过hosts文件配置,发现taobao.com域名指向127.0.0.1,则向本机发起请求。
- nginx接收到taobao.com/index.html请求,根据nginx的配置,将把这个请求转发给127.0.0.1:3000。
- 浏览器拿到index.html文件,发起taobao.com/api/getNew请求
- nginx接收到taobao.com/api/getNew请求请求,根据nginx的配置,将把这个请求转发给真正的淘宝服务器中。
- 淘宝服务器将数据返回给nginx,再返回给浏览器执行。
简单的对比
- 使用charles等正向代理方式比较简单,需要掌握的知识点也比较少。但同时其可配置性较弱,更适合小型项目使用。
- 使用nginx的反向代理则相对复杂一些,需要了解基本的nginx配置。但其可配置性较强,支持URL的正则匹配,设置优先级等,适合复杂的项目使用