前端跨域的解决方案

2018年3月1号更,之前的文章是17年5月写的,通过近一年的工作,对前端跨域有了更深刻的认识。

首先,什么是跨域?

只有协议 域名端口 三个都相同才算作同源,其他的都属于跨域请求。
比如:

http://www.baidu.com:8080
http://www.baidu.com:1000  // 端口不同
https://www.baidu.com:8080 // 协议不同
http://www.qq.com          // 域名不同

通常浏览器会给出这样的错误提示:
这里写图片描述

如何解决跨域?

通常的解决方案是:

  • jsonp
  • CORS
  • nginx代理
  • domain
  • postMessage

下面逐个介绍这些方案:

Jsonp

特点:仅支持 GET 请求,需要后台也做处理。

jsonp(json with padding)顾名思义就是把data进行一层包装,利用script可以外链的特点来实现跨域请求。

在js中,link,a,script可以使用外链,不受同源策略限制。

jsonp用原生js实现起来,比较冗余。因为你得创建元素,插入元素。
所以通常的方法是使用jquery。

function originJsonp (url, opts, fn) {
    let prefix = opts.prefix || '__jp'
    let id = opts.name || (prefix + '0')
    let param = opts.param || 'callback'
    let target = document.getElementsByTagName('script')[0] || document.head
    let script
    let timer

    // 清除script 及 定时器
    function clear () {
        if (script.parentNode) {
            script.parentNode.removeChild(script)
        }
        window[id] = {}
        timer && clearTimeout(timer)
    }

    window[id] = function (data) {
        clear()
        fn && fn(data)
    }

    // 不用判断 > -1   因为~-1 === 0
    url += (~url.indexOf('?') ? '&' : '?') + param + '=' + encodeURIComponent(id);
    url = url.replace('?&', '?');

    script = document.createElement('script')
    script.src = url
    target.parentNode.insertBefore(script, target)
}

// 把回调函数promise化
function jsonp (url, opts) {
    return new Promise((resolve, reject) => {
        originJsonp(url, opts, (data) => {
            resolve(data)
        })
    })
}

通过创建script标签,然后在window上定义一个callback函数,当脚本请求回来之后,会形如 callback({username: 'SpawN'}) ,所以就会调用你在window上定义的callback函数,从而获取数据。

从上面的例子中可以看出,jsonpajax 没有联系,但是jQuery中为了实现方法的内聚性,把jsonp 也封装在了 ajax

ajax是XHR对象发起request。
jsonp是添加script标签

$.ajax({
    type: 'get',  // jquery只支持是get跨域哦~
    url: '你请求的地址的url',
    data: {
        这里存入你的数据
    },
    dataType: 'jsonp',   // 希望服务器返回的数据类型
    jsonp: callback,     // 默认是callback,这个是在request是带着的
    jsonpCallback: 'callback' // 默认是jquery自动生成的,很长,可以自己命名一个,比较容易管理,这个是从服务器返回过来的用来包裹json的。 callback({"username":"jack"})
})

使用 jsonp 的时候通常会遇到的问题:

 //报错提示 Unexpected token :

我之前面试的时候,让请求一个接口,当时对于跨域问题的研究还非常浅,就直接用jqueryjsonp 去请求,结果报错。最后才知道是因为接口不支持jsonp。

其实上面介绍 jsonp 用法的时候已经知道,后台返回来的是由函数包裹的json,那么自然是需要后台去进行处理支持。

那么后台如何支持jsonp呢?

就是这个jsonp: callback 这个属性。如果是callback,那么后台若是php代码的话,就需要在echo的外围包个callback(名字需要和jsonp设置的一致,但是基本上都是callback)

<?php 
    $arr = array ('username'=>'jack','age'=>21,'gender'=>'male'); 
    echo $_GET['callback']."(".json_encode($arr).")";
?>

CORS

CORS (Cross origin resource shared)是HTML5出的规范,这个是需要在服务器端设置

Access-Control-Allow-Origin: http://xxx.xxx.com

后面意思是允许该源对我服务器发起的请求。其他的一概不响应。
也可以粗暴的设置为*,允许任何源对我服务器的请求。

浏览器会将CORS请求分为两类: 简单请求 和 非简单请求。
在进行接口访问的时候,浏览器会发送一个预请求来检测是否可以正常访问,当服务器正确响应之后,浏览器才会继续发送正确的请求。

当你的项目中是以 cookie 来做用户身份判断的时候,那你就不能把
Access-Control-Allow-Origin 设置为 * ,只能够明确指定域名

Nginx

Nginx是一个http和反向代理服务器。能实现跨域,就是利用了其反向代理的特性。所谓反向代理就是以代理服务器来实现跨域的访问。

如何使用呢?

在Nginx中找到conf/nginx.conf中:

server {
        listen       80;                //默认的端口号
        server_name  localhost;         // 默认的域名

        #charset koi8-r;

        #access_log  logs/host.access.log  main;

        location / {                          // 默认的配置
            root   html;
            index  index.html index.htm;
        }

        location /login {                    // 为了实现跨域的配置
            rewrite .* /mall/member/login.htm break; 
            proxy_pass  http://shangchengcmbs.fangshangqu.com;  
        }
}

/ login 是一个别名。 我们可以在HTML中请求的时候在url里只写/login就ok了
rewrite: 是对从proxy_pass过来的请求的拦截,并重写,其中/mall/member/login.htm 是添加到proxy_pass后面的。 另外break是对匹配第一个之后就停止匹配。

proxy_pass: 是把请求代理到的主机地址。

这些配置好了之后,打开cmd 输入
start nginx 就可以开启nginx服务器
nginx.exe -s quit 关闭服务器
nginx.exe -s reload 重启服务器
nginx -v 查看nginx版本号

最后打开localhost 就可以了~

常用

不管是 webpack 还是 gulp ,我们都会使用 http-proxy-middleware 中间件来处理跨域问题。包括我们熟知的 vue-clicreate-react-app 都是如此。

举个例子:
当我们的接口是:

https://m.mock.com/api/getPageData?id=2
https://m.mock.com/wechat/getOAuth

这种场景的配置如下:

proxy: {
  '/apis': {
    target: 'https://m.mock.com',
    changeOrigin: true,
    pathRewrite: {
      '^/apis': ''
    }
  }
}

axios.defaults.baseURL = '/apis'

因为我们注意到接口里面并没有一个标志字符串,所以我们就自己创造一个 /apis 来进行跨域标志,然后再把其置为空字符串。

这个写法是所有 /apis 的请求都会被代理到 https://m.mock.com 下,而 changeOrigin: true 是更改 referer,在跨域配置中是个必须项,然后再把所有 /apis 替换为 ''

而如果是这种场景:

https://m.mock.com/api/getPageData?id=2
https://m.mock.com/api/wechat/getOAuth

那我们可以看到,我们的所有接口都是 /api 作为前缀的,这也是符合接口规范的。

那这样配置就ok了

proxy: {
  '/api': {
    target: 'https://m.mock.com',
    changeOrigin: true
  }
}

axios.defaults.baseURL = '/api'

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值