那些年曾谈起的跨域

53次阅读

共计 15180 个字符,预计需要花费 38 分钟才能阅读完成。

对于前端开发来说跨域应该是最不陌生的问题了,无论是开发过程中还是在面试过程中都是一个经常遇到的一个问题,在开发过程中遇到这个问题的话一般都是找后端同学去解决,以至于很多人都忽略了对跨域的认识。为什么会导致跨域?遇到跨域又怎么去解决呢?本文会对这些问题一一的介绍。

JavaScript 中,在不同的域名下面进行数据交互,就会遇到跨域问题,说到跨域首先要从同源说起,浏览器为了提供一种安全的运行环境,各个浏览器厂商协定使用同源策略。

什么同源策略

同源策略:同源策略(Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说 Web 是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。

同源策略是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到 XSSCSRF 等攻击。所谓同源是指 协议 + 域名 + 端口 三者相同,即便两个不同的域名指向同一个 ip 地址,也非同源。

Url 组成部分

了解同源策略以后,同样需要对 url 的组成部分也顺带了解一下吧,只有了解 url 之后当出现跨域的时候才知道哪里出了问题,这样才能和后端、运维开怼,怼天怼地对空气。O(∩_∩)O 哈哈~

从上图中能够清晰的看出 url 中每个部分的含义:

  1. protocol:协议 常用的协议是http
  2. auth:验证 ,因为明文传输用户名和密码,非HTTPS 环境下很不安全,一般用的非常少。
  3. hostname:主机地址,可以是域名,也可以是 IP 地址
  4. port:端口http 协议默认端口是:80 端口,如果不写默认就是:80 端口
  5. pathname:路径 网络资源在服务器中的指定路径
  6. serarch:查询字符串 如果需要从服务器那里查询内容,在这里编辑
  7. hash:哈希 网页中可能会分为不同的片段,如果想访问网页后直接到达指定位置,可以在这部分设置

项目过程过程中经常会用到一些缓存,浏览器为了网页的安全在缓存的时候,由于同源策略的问题对其缓存内容进行了限制,其实想想也是对的,如果不使用同源策略的话,很容易冲掉缓存的东西。

  1. CookieLocalStorageIndexDB 等无法读取。
  2. DOM无法获得。
  3. AJAX请求不能发送。

当然浏览器也没有把所有的东西都限制了,比如图片、互联网资源等还是允许跨域请求的。允许跨域请求都是使用标签,只有三个标签是允许跨域加载资源:

  1. <img src=XXX>
  2. <link href=XXX>
  3. <script src=XXX>

在项目开发过程中时不时的就会遇到下面这样抛出了错误,有的人可能在开发过程中没有遇到过,如果是的话,你可能遇到一个很不错的后端或者运维。

XMLHttpRequest cannot loadhttp://www.******.com/. No 'Access-Control-Allow-Origin' 
header is present on the requested resource. Origin 'null' is therefore not allowed access.

上面的报错就是典型的跨域报错,既然跨域这么常见到底都有哪些情况会导致跨域的问题:

说明 是否允许通信
同一域名下 允许
同一域名下不同文件夹 允许
同一域名,不同端口 不允许
同一域名,不同协议 不允许
域名和域名对应 ip 不允许
主域名相同,子域名不同 不允许
同一域名,不同二级域名 不允许
不同域名 不允许

跨域解决方案

由于浏览器的限制造成了很多的跨域问题,同样也是为了安全,既然出现了跨域就必定要有一些对应的解决方案,总不能遇到跨域之后项目就不做了啊,可能瞬间就凉了。闲话就不多扯了。

JSONP

在遇到跨域的时候经常会提及到的一个词就是JSONP,一直在说JSONP?可是通过什么原理来实现的呢?我觉得应该了解一下到底什么再去了解一下实现固然原理也就懂得咯。

什么是 JSONP

JSONP:JSON的一种“使用模式”,可用于解决主流浏览器的跨域数据访问的问题。由于同源策略,一般来说位于 server1.example.com 的网页无法与不是 server1.example.com 的服务器沟通,而 HTML<script>元素是一个例外。利用 <script> 元素的这个开放策略,网页可以得到从其他来源动态产生的 JSON 资料,而这种使用模式就是所谓的 JSONP。用JSONP 抓到的资料并不是 JSON,而是任意的JavaScript,用JavaScript 直译器执行而不是用 JSON 解析器解析。– 选自百度百科

对于 JSONP 简单的百度了一下,百度给出的解释如上,看完整段话,有一些小的收获,第一 script 标签具有开放策略,可以使用 src 的开放性解决其跨域问题。在这里简单的阐述一下个人观点。JSONP可以分为两个部分来解读,JSONpaddingJSON 固然就不用解释了,只是一种数据格式,paddingcss 中是内填充的意思,其实 JSONP 的原理与内填充有些类似。通过把数据填充 js 文件中然后引入到页面中,并在页面中使用。

有没有注意过百度,其实百度的即时搜索就是使用 JSONP 来实现的,可以尝试一下,在百度中搜索一下,就会在 Network 中看到一个以 sugrec 为开头的请求,这个请求就是使用的 JSONP 的形式,为了大家方便特意截选了一个段连接。

连接:https://www.baidu.com/sugrec?prod=pc&wd=json&pwd=json&cb=query

返回格式:query({
    "q": "json",
    "p": false,
    "g": [{
        "type": "sug",
        "sa": "s_1",
        "q": "json 格式"
    }, {
        "type": "sug",
        "sa": "s_2",
        "q": "jsonp"
    }, {
        "type": "sug",
        "sa": "s_3",
        "q": "json 解析"
    },
    ...]
})

通过对百度的即时搜索的分析就可以简单的看出 JSONP 的实现原理,请求会的 js 文件中包含一个函数,其函数名称就是连接中 cb 的参数最为参数传给后台,后台通过处理并在执行这个与参数对应的函数的,当函数执行的时候将把数据以实参的形式传递给对应的函数,解决跨域问题。为了方便阅读这里只截取了代码片段。

案例:

前端代码:

$('#btn').click(function(){var frame = document.createElement('script');
    frame.src = 'http://localhost:5000/jsonp?name=aaron&age=18&callback=query';
    $('body').append(frame);
});

function query(res){console.log(res.message+res.name+'你已经'+res.age+'岁了');
}

后端代码:

router.get('/jsonp', (req, res) => {let {name,age,callback} = req.query;
    let data = {message:'success',name,age};
    data = JSON.stringify(data);
    res.end(`${callback}(${data})`);
});

通过如上代码就可以简单的实现 JSONP,虽然JSONP 解决了跨域的问题,还是有很多弊端的,比如会在页面中添加一些 script 标签,数据不能双向操作等等。

使用 JSONP 的时候尤其要注意一点,一定要把插入的 script 放到所应用函数的下面。这个和 js 的执行顺序有关系,如果把 script 标签放在上面的话,其方法还没有被读取在 script 标签中就执行了这个方法必定报错的,这点很重要哦。

document.domain

document.domain项目中一般应用的较少,默认情况下 document.domain 存放的是载入文档的服务器的主机名。可以在控制台输出一下,得到的则是 segmentfault.com 这个域名。我在项目中所用到的则是结合 iframe 的时候遇到的跨域,并使用的 domain 解决的。

在使用 document.domain 实现跨域的时候需要注意一下,这两个域名必须属于同一个一级域名! 而且所用的协议,端口都要一致,否则无法利用 document.domain 进行跨域。Javascript出于对安全性的考虑,而禁止两个或者多个不同域的页面进行互相操作。而相同域的页面在相互操作的时候不会有任何问题。

简单的解释一下,例如想要在 www.a.com 中将看到 segmentfault.com 中的内容并将其网页使用 iframe 将其嵌入到其网页中,但是此时浏览器是不允许通过 JavaScript 直接操作 segmentfault.com 的,因为这两个页面属于不同的域,在操作之前浏览器会检测是否符合同源策略,如果符合则允许操作,反之则不行。

若想要同过 document.domain 实现跨域的话,必须使其满足同源策略,这个时候就需要用到 document.domaindocument.domain 都设成相同的域名就可以了。但要注意的是,document.domain的设置是有限制的,我们只能把 document.domain 设置成自身或更高一级的父域,且主域必须相同。

例如:

a.com
news.a.com

news.a.com属于 a.com 的一个子域名,按照上面所说已经满足了上面的规则,如果想要实现跨域操作就需要对接子页面的 document.domain 进行操作。

父页面:

document.domain = 'a.com';
var ifr = document.createElement('iframe');
ifr.src = 'news.a.com/map.html';
ifr.style.display = 'none';
document.body.appendChild(ifr);
ifr.onload = function(){
    var doc = ifr.contentDocument || ifr.contentWindow.document;
    var oUl = doc.getElementById('ul1');
    alert(oUl.innerHTML);
    ifr.onload = null;
};

子页面:

document.domain = 'a.com';
$ajax.get({//  ... 省略})

其实现原理就是通过 iframe 载入一个与你想要通过 ajax 获取数据的目标页面处在相同的域的页面,所以这个 iframe 中的页面是可以正常使用 ajax 去获取你要的数据的,然后就是通过我们刚刚讲得修改 document.domain 的方法,让我们能通过 js 完全控制这个 iframe,这样我们就可以让iframe 去发送 ajax 请求,然后收到的数据我们也可以获得了。

location.hash

若理解了 document.domain 实现跨域原理,那么 location.hash 也就很号理解了,其原理与 document.domain 很相似一样都是动态插入一个 iframe,然后把iframesrc指向服务端地址,而服务端同样都是输出一段 JavaScript 代码,同样都是利用和子窗口之间的通信完成数据传输,同样要针对同源策略做出处理。

既然说到了 hash 到底什么是 hash 这里也就单独的说一下吧,虽然很好理解,但是对于新同学来说可能还不知道 hash 具体是什么?

hash: 一般翻译做散列、杂凑,或音译为哈希,是把任意长度的输入(又叫做预映射 pre-image)通过散列算法变换成固定长度的输出,该输出就是散列值。这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,所以不可能从散列值来确定唯一的输入值。简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。— 节选自百度百科

读完之后感觉自己整个人都不好了,有些似懂非懂的意思,我所理解的哈希是指一个过程,这个过程就是把任意长度的输入,通过哈希算法,变换成固定长度的输出,所输出的称为哈希值。这种变换是一种压缩映射,也即哈希值所占的空间一般来说远小于输入值的空间,不同的输入可能会哈希出相同的输出(概率很小)。

哈希有如下特点:

  1. 如果两个哈希值是不相同的(根据同一函数),那么这两个散列值的原始输入一定是不相同的。
  2. 如果两个哈希值相同,两个输入值很可能 (极大概率) 是相同的,但也可能不同,这种情况称为“哈希碰撞”
  3. 抗篡改能力:对于一个数据块,哪怕只改动其一个比特位,其 hash 值的改动也会非常大。
  4. 它是一种单向函数是“非对称”的,即它是一个从明文到密文的不可逆的映射, 只有加密过程, 没有解密过程。

那么哈希在平时项目开发中有什么用途呢?可以用哈希来做什么事情?对于前端来说用到哈希最多的时候可能就是锚点定位。通过不同的哈希值定位到描点指定的元素位置上。

<a href='#1'>red</a>
<a href='#2'>black</a>
<a href='#3'>yellow</a>
<a href='#4'>pink</a>
<div id='1' style='width:500px;height:200px;background-color:red'> </div>
<div id='2' style='width:500px;height:200px;background-color:black'> </div>
<div id='3' style='width:500px;height:200px;background-color:yellow'> </div>
<div id='4' style='width:500px;height:1200px;background-color:pink'> </div>

关于更多细节的东西不再这里赘述了,如果想要了解更多的话大家可以自行google,再说下去的话可能就跑题了。

简单的介绍了一下哈希与哈希的用处那么又该如何使用哈希来实现跨域呢?其实很简单,如果 index 页面要获取远端服务器的数据,动态插入一个 iframe,将iframe 的 src 属性指向服务端地址。这时 top window 和包裹这个 iframe 的子窗口由于同源策略的原因是不能直接通信的,所以改变子窗口的路径就行了,将数据当做改变后的路径的 hash 值加在路径上,然后就能通信了,将数据加在 index 页面地址的 hash 值上。index页面监听地址的 hash 值变化然后做出判断,处理数据。

父页面:

<iframe src="a.com/index.php/msg#key=1&key1=2"></iframe>

由于哈希值的改变不会改变网页的网址的,所以服务端可以通过获取到哈希来解析 url 中的参数,并把数据返回给前端即可。通过 parent.location.hash 去改变哈希值,然后就可以像 document.domain 一样去获取到子页面的数据了。parent.location.hash该方法是有局限性的,在 IEChrome中是不支持这种操作的。那么整个问题应如何解决呢?

在同域的域名下面添加一个 *.html(* 代表任意名) 文件,然后把通过 iframe*.html引入到父页面中,并把需要请求的接口 iframe 添加到 *.html 中去请求,这样就可以解决了。

http://localhost:6000/a.html

<!DOCTYPE html>
<html>
<head>
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
<title> 无 </title>
</head>
<body>
<script type="text/javascript">
function sendRequest(){var ifr = document.createElement('iframe');
    ifr.style.display = 'none';
    ifr.src = 'http://localhost:7000/b.html#Aaron';
    document.body.appendChild(ifr);
}
function checkHash(){var data = location.hash?location.hash.substring(1):'';
    if(data) location.hash = '';
}
setInterval(checkHash,1000);
window.onload = sendRequest;
</script>
</body>
</html>

http://localhost:7000/b.html

<!DOCTYPE html>
<html>
<head>
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
<title> 无 </title>
</head>
<body>
<script type="text/javascript">
function checkHash(){
    var data = '';
    switch(location.hash){
        case '#Aaron':
              data = 'my Aaron';
              break;
        case '#Angie':
              data = 'my Angie';
              break;
        default : break;
    }
    data && callBack('#'+data);
}
function callBack(hash){var proxy = document.createElement('iframe');
   proxy.style.display = 'none';
   proxy.src = 'http://localhost/c.html'+hash;
   document.body.appendChild(proxy);
}
window.onload = checkHash;
</script>
</body>
</html>

http://localhost:6000/c.html

<!DOCTYPE html>
<html>
<head>
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
<title> 无 </title>
</head>
<body>
<script type="text/javascript">
parent.parent.location.hash = self.location.hash.substring(1);
</script>
</body>
</html>

a.html中有一个隐藏的 iframe, 该iframe 指向异域 http://localhost:7000/b.htmlb.html,且传递 hash 值给 b.html`b.html 获取 hash 值,生成 data 值,然后动态创建 iframe,该iframedata值传给与 a.html 同域的 c.html 因为c.htmla.html` 同域,可以传值固然也就解决了跨域问题。

window.name

window.name这个属性不是一个简单的全局属性只要在一个 window 下,无论 url 怎么变化,只要设置好了 window.name,那么后续就一直都不会改变,同理,在iframe 中,即使 url 在变化,iframe中的 window.name 也是一个固定的值,利用这个,我们就可以实现跨域了。

http://localhost:6000/a.html

<iframe src="http://localhost:7000/b.html" frameborder="1"></iframe>
<script>
var ifr = document.querySelector('iframe')
ifr.style.display = 'none'
var flag = 0;
ifr.onload = function () {if (flag == 1) {ifr.contentWindow.close();
    } else if (flag == 0) {
        flag = 1;
        ifr.contentWindow.location = 'http://localhost:6000/proxy.html';
    }
}
</script>

http://localhost:7000/b.html

var person = {
  name: 'Aaron',
  age: 18
}
window.name = JSON.stringify(person)

http://localhost:6000/proxy.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>proxy</title>
</head>
<body>
<p> 这是 proxy 页面 </p>
</body>
</html>

http://localhost:6000 下有一个 a.html,在http://localhost:7000 下有一个 b.html,在http://localhost:6000/a.html 中创建了一个 iframe 标签并把地址指向了 http://localhost:7000/b.html,在b.html 中的 window.name 赋值保存了一段数据,但是现在还获取不了,因为是跨域的,所以,我们可以把 src 设置为当前域的 http://localhost:6000/proxy.html,虽然域名改变了但是window.name 是没有改变的。这样就可以拿到我们想要的数据了。

postMessage(HTML5)

可能很多不知道 postMessage 整个 API, 在HTML5 中新增了 postMessage 方法允许来自不同源的脚本采用异步方式进行有限的通信,可以实现跨文本档、多窗口、跨域消息传递,postMessage在很多浏览器中都已经得到了良好的支持,所以可放心的使用。该方法可以通过绑定 windowmessage事件来监听发送跨文档消息传输内容。

postMessage()方法接受两个参数

    1. data: 要传递的数据,html5 规范中提到该参数可以是 JavaScript 的任意基本类型或可复制的对象,然而并不是所有浏览器都做到了这点儿,部分浏览器只能处理字符串参数,所以我们在传递参数的时候需要使用 JSON.stringify() 方法对对象参数序列化,在低版本 IE 中引用 json2.js 可以实现类似效果。
    1. origin:字符串参数,指明目标窗口的源,协议 + 主机 + 端口号 +URLURL会被忽略,所以可以不写,这个参数是为了安全考虑,postMessage()方法只会将 message 传递给指定窗口,当然如果愿意也可以建参数设置为 ”*”,这样可以传递给任意窗口,如果要指定和当前窗口同源的话设置为 ”/”。

    http://localhost:6000/a.html

    <!DOCTYPE HTML>
    <html>
    <head>
    <meta charset="utf-8">
    <title> 无 </title>
    </head>
    <body>
    <div>
        <input id="text" type="text" value="My name’s Aaron" />
        <button id="send" > 发送消息 </button>
    </div>
    <iframe id="receiver" src="http://localhost:7000/b.html"></iframe>
    <script>
    window.onload = function() {var receiver = document.getElementById('receiver').contentWindow;
        var btn = document.getElementById('send');
        btn.addEventListener('click', function (e) {e.preventDefault();
            var val = document.getElementById('text').value;
            receiver.postMessage("Hello"+val+"!", "http://localhost:7000");
        });
    }
    </script>
    </body>
    </html>

    http://localhost:7000/b.html

    <!DOCTYPE HTML>
    <html>
    <head>
    <meta charset="utf-8">
    <title> 无 </title>
    </head>
    <body>
    <div id="message">
        Hello World!
    </div>
    <script>
    window.onload = function() {var messageEle = document.getElementById('message');
        window.addEventListener('message', function (e) {if (e.origin !== "http://localhost:6000") {return;}
            messageEle.innerHTML = "从"+ e.origin +"收到消息:" + e.data;
        });
    }
    </script>
    </body>
    </html>

    这样我们就可以接收任何窗口传递来的消息了,为了安全起见,我们利用这时候的 MessageEvent 对象判断了一下消息源,MessageEvent对象,这个对象中包含很多东西。

    1. data:顾名思义,是传递来的message
    2. source:发送消息的窗口对象
    3. origin:发送消息窗口的源(协议 + 主机 + 端口号)

    使用 postMessage 方法比以上方法用起来要轻便,不必有太多的繁琐操作,可以说 postMessage 是对于解决跨域来说是一个比较好的解决方案,不会显得代码特别的臃肿,并且各个浏览器又有良好的支持。

    跨域资源共享(CORS)

    CORS:全称 ” 跨域资源共享 ”(Cross-origin resource sharing)。CORS需要浏览器和服务器同时支持,才可以实现跨域请求,目前几乎所有浏览器都支持 CORSIE 则不能低于 IE10CORS 的整个过程都由浏览器自动完成,前端无需做任何设置,跟平时发送 ajax 请求并无差异。CORS的关键在于服务器,只要服务器实现 CORS 接口,就可以实现跨域通信。

    跨域资源共享 (CORS) 是一种机制,它使用额外的HTTP 头来告诉浏览器让运行在一个 origin (domain) 上的Web 应用被准许访问来自不同源服务器上的指定的资源。当一个资源从与该资源本身所在的服务器不同的域、协议或端口请求一个资源时,资源会发起一个跨域 HTTP 请求。在上面说过 src 是不受同源策略限制的,但是出于安全原因,浏览器限制从脚本内发起的跨源 HTTP 请求。例如,XMLHttpRequestFetchAPI 遵循同源策略。这意味着使用这些 APIWeb应用程序只能从加载应用程序的同一个域请求 HTTP 资源,除非响应报文包含了正确 CORS 响应头。

    所有 CORS 相关的的头都是 Access-Control 为前缀的。下面是每个头的一些细节。

    字段 描述
    Access-Control-Allow-Methods 该字段必需,它的值是逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法。注意,返回的是所有支持的方法,而不单是浏览器请求的那个方法。这是为了避免多次 ” 预检 ” 请求
    Access-Control-Allow-Headers 如果浏览器请求包括 Access-Control-Request-Headers 字段,则 Access-Control-Allow-Headers 字段是必需的。它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段,不限于浏览器在 ” 预检 ” 中请求的字段
    Access-Control-Allow-Credentials 该字段与简单请求时的含义相同。
    Access-Control-Max-Age 该字段可选,用来指定本次预检请求的有效期,单位为秒。上面结果中,有效期是 20 天(1728000 秒),即允许缓存该条回应 1728000 秒(即 20 天),在此期间,不用发出另一条预检请求。
    import express from "express";
    import cors from "cors";
    
    const app = express()
    const
    var corsOptions = {
      origin: 'http://example.com',
      optionsSuccessStatus: 200
    }
     
    app.get('/products/:id', cors(corsOptions), (req, res, next) => {res.json({msg: 'This is CORS-enabled for only example.com.'})
    })
     
    app.listen(80, function () {console.log('启用 corba, 端口:80')
    })

    使用 CORS 简单请求,非常容易,对于前端来说无需做任何配置,与发送普通 ajax 请求无异。唯一需要注意的是,需要携带 cookie 信息时,需要将 withCredentials 设置为 true 即可。CORS的配置,完全在后端设置,配置起来也比较容易,目前对于大部分浏览器兼容性也比较好,现在应用最多的就是 CORS 解决跨域了。

    WebSocket 协议跨域

    WebSocketHTML5 新推出的一个 API,通过WebSocket 可以实现客户端与服务端的即时通信,如聊天室,服务数据同步渲染等等。WebSocket是点对点通信,服务端与客户端可以通过某种标识完成的。WebSocket是不受同源策略限制的所以可以利用这个特性直接与服务端进行点对点通信。

    以下示例没有使用 HTML5WebSocket而是使用的 socket.io 完成类似的功能操作。

    若若的说一句:其实我一直以为 WebSocketAjax一样是受 同源策略 限制的,经过学习才发现不是的。真是学到老活到老(关谷口音)。O(∩_∩)O

    服务端:

    var io = require('socket.io')(1234);
    io.sockets.on('connection', (client) => {client.on('message', function (msg) { // 监听到信息处理
            client.send('服务器收到了信息:' + msg);
        });
        client.on("disconnect", function () { // 断开处理
            console.log("client has disconnected");
        });
    });
    console.log("listen 1234...");

    客户端:

    $(function () {var iosocket = io.connect('http://localhost:1234/');
        var $ul = $("ul");
        var $input = $("input");
        iosocket.on('connect', function () {  // 接通处理
            $ul.append($('<li> 连上啦 </li>'));
            iosocket.on('message', function (message) {  // 收到信息处理
                $ul.append($('<li></li>').text(message));
            });
            iosocket.on('disconnect', function () { // 断开处理
                $ul.append('<li>Disconnected</li>');
            });
        });
    
        $input.keypress(function (event) {if (event.which == 13) { // 回车
                event.preventDefault();
                console.log("send :" + $input.val());
                iosocket.send($input.val());
                $input.val('');
            }
        });
    });

    Websocket既然能支持跨域方法,那就是说,一个开放给公网的 Websocket 服务任何人都能访问,这样的话会使数据变得很不安全,所以可以通过对连接域名进行认证即可。

    服务器反代

    学习路程首先了解了一下什么是反代,在计算机网络中,反向代理是代理服务器的一种。服务器根据客户端的请求,从其关联的一组或多组后端服务器(如 Web 服务器)上获取资源,然后再将这些资源返回给客户端,客户端只会得知反向代理的 IP 地址,而不知道在代理服务器后面的服务器簇的存在。— 节选自百度百科

    反向代理服务器:就 nginxhttp请求转发到另一个或者一些服务器上。从而轻松实现跨域访问。比如服务器中分别部署了 N 个服务器,当客户端发起请求时不用直接请求服务器中 N 个节点上的服务,只需要访问我们的代理服务器就行了,代理服务器根据请求内容分发到不同服务器节点。这仅是一种使用场景,当然还可以做负载均衡等。

    反向代理理解起来不是特别的难,平时生活中最常见的例子,当我们拨打人工客服的时候,并不是直接拨打客服的某一个电话号码,而是拨打总机号码,当我们拨打然后由总机进行处理,然后再分发给不同的客服人员。r 然而当服务人员需要让你挂断电话等待回拨的时候,也不是直接拨打到你的电话,同样是也通过总机之后再转发到你的电话。其实这个总机也就相当于反代服务器。虽然这个例子不太贴切但是多多少少就是这个意思。

    由于不太懂 Nginx 不知道该如何处理这个部分,只是对反向代理做了一个简单的了解,等以后学习了 Nginx 会补上相关代码。

    Nodejs 代理跨域

    使用 Nodejs 进行跨域在我看来,就是使用 Node 服务做了一个中间代理转发,其原理和反向代理差不多,当访问某一个 URL 时需要通过服务器分发到另一个服务器 URL 地址中。这里就不过多的赘述了,直接看代码吧。

    示例代码入下:

    main.js

    import http from "http";
    import httpProxy from "http-proxy";
      
    // 新建一个代理 Proxy Server 对象  
    const proxy = httpProxy.createProxyServer({});  
      
    // 捕获异常  
    proxy.on('error', function (err, req, res) {  
      res.writeHead(500, {'Content-Type': 'text/plain'});  
      res.end('error');  
    });  
        
    // 在每次请求中,调用 proxy.web(req, res config) 方法进行请求分发  
    const server = http.createServer((req, res) => {  
      // 在这里可以自定义你的路由分发  
      let host = req.headers.host, ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;  
      switch(host){  
        case 'www.a.com':   
            proxy.web(req, res, { target: 'http://localhost:3000'});  
            break;  
        case 'www.b.com':  
            proxy.web(req, res, { target: 'http://localhost:4000'});  
            break;
        default:  
            res.writeHead(200, {'Content-Type': 'text/plain'});  
            res.end('Hello Aaron!');  
      }  
    });  
    server.listen(8080);

    如代码所示,当访问 www.a.com 的时候,请求就被转发到了 3000 接口上,访问 www.b.com 时就被转发到了 4000 这个接口上。这样就简单的完成了一个反向代理的工作。

    在使用 vue 开发的时候难免也会遇到跨域问题,或许你根本就没有遇到,可能你们正好处于同一个域里面,还有一种可能就是,后端同学或者运维同学已经处理好有关跨域的相关操作。但是当在开发过程中遇到跨域的时候,什么前端应该有对应的解决办法。vue-cli是基于 Node 服务的,所以我们可以利用这个服务来做代理工作,暂时解决开发中的跨域问题。

    build/webpack.config.js

    module.exports = {
        devServer: {
            historyApiFallback: true,
            proxy: [{
                context: '/login',  //  url 以 /login 为开头时启用代理
                target: 'http://www.a.com:8080',  // 代理跨域目标接口
                changeOrigin: true,
                secure: false,  // 当代理某些 https 服务报错时用
                cookieDomainRewrite: 'www.b.com'  // 可以为 false,表示不修改
            }],
            noInfo: true
        }
    }

    在开发过程中遇到的可以通过这种方式解决,但是到达生产环境时到底使用什么方法还是需要斟酌的,毕竟要使服务数据变得更加的安全才是最好的。

    总结

    以上讲了很多有关跨域的解决方案,有利也有弊,对于我而言可能更加的倾向于后端粑粑或者运维粑粑去解决跨域问题,毕竟前端处理起来毕竟不是很安全,而且后端或者运维处理起来也不是那么的麻烦。

    很感谢大家利用这么长时间来读这篇文章,文章中若有错误请在下方留言,会尽快做出修改。

    以上除 Node 服务以外皆是使用 http-server 进行测试。

    正文完
     0