对于前端开发来说跨域应该是最不陌生的问题了,无论是开发过程中还是在面试过程中都是一个经常遇到的一个问题,在开发过程中遇到这个问题的话一般都是找后端同学去解决,以至于很多人都忽略了对跨域的认识。为什么会导致跨域?遇到跨域又怎么去解决呢?本文会对这些问题一一的介绍。
在JavaScript
中,在不同的域名下面进行数据交互,就会遇到跨域问题,说到跨域首先要从同源说起,浏览器为了提供一种安全的运行环境,各个浏览器厂商协定使用同源策略。
什么同源策略
同源策略:同源策略(Same origin policy
)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。
同源策略是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS
、CSRF
等攻击。所谓同源是指协议+域名+端口
三者相同,即便两个不同的域名指向同一个ip
地址,也非同源。
Url组成部分
了解同源策略以后,同样需要对url
的组成部分也顺带了解一下吧,只有了解url
之后当出现跨域的时候才知道哪里出了问题,这样才能和后端、运维开怼,怼天怼地对空气。O(∩_∩)O哈哈~
从上图中能够清晰的看出url
中每个部分的含义:
- protocol:
协议
常用的协议是http
- auth:
验证
,因为明文传输用户名和密码,非HTTPS
环境下很不安全,一般用的非常少。 - hostname:
主机地址
,可以是域名,也可以是IP地址 - port:
端口
http协议默认端口是:80端口,如果不写默认就是:80端口 - pathname:
路径
网络资源在服务器中的指定路径 - serarch:
查询字符串
如果需要从服务器那里查询内容,在这里编辑 - hash:
哈希
网页中可能会分为不同的片段,如果想访问网页后直接到达指定位置,可以在这部分设置
项目过程过程中经常会用到一些缓存,浏览器为了网页的安全在缓存的时候,由于同源策略的问题对其缓存内容进行了限制,其实想想也是对的,如果不使用同源策略的话,很容易冲掉缓存的东西。
Cookie
、LocalStorage
和IndexDB
等无法读取。DOM
无法获得。AJAX
请求不能发送。
当然浏览器也没有把所有的东西都限制了,比如图片、互联网资源等还是允许跨域请求的。允许跨域请求都是使用标签,只有三个标签是允许跨域加载资源:
<img src=XXX>
<link href=XXX>
<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
可以分为两个部分来解读,JSON
与padding
,JSON
固然就不用解释了,只是一种数据格式,padding
在css
中是内填充的意思,其实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.domain
,document.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
,然后把iframe
的src
指向服务端地址,而服务端同样都是输出一段JavaScript
代码,同样都是利用和子窗口之间的通信完成数据传输,同样要针对同源策略做出处理。
既然说到了hash
到底什么是hash
这里也就单独的说一下吧,虽然很好理解,但是对于新同学来说可能还不知道hash
具体是什么?
hash:一般翻译做散列、杂凑,或音译为哈希,是把任意长度的输入(又叫做预映射pre-image)通过散列算法变换成固定长度的输出,该输出就是散列值。这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,所以不可能从散列值来确定唯一的输入值。简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。 -- 节选自百度百科
读完之后感觉自己整个人都不好了,有些似懂非懂的意思,我所理解的哈希是指一个过程,这个过程就是把任意长度的输入,通过哈希算法,变换成固定长度的输出,所输出的称为哈希值。这种变换是一种压缩映射,也即哈希值所占的空间一般来说远小于输入值的空间,不同的输入可能会哈希出相同的输出(概率很小)。
哈希有如下特点:
- 如果两个哈希值是不相同的(根据同一函数),那么这两个散列值的原始输入一定是不相同的。
- 如果两个哈希值相同,两个输入值很可能(极大概率)是相同的,但也可能不同,这种情况称为“哈希碰撞”
- 抗篡改能力:对于一个数据块,哪怕只改动其一个比特位,其hash值的改动也会非常大。
- 它是一种单向函数是“非对称”的,即它是一个从明文到密文的不可逆的映射,只有加密过程,没有解密过程。
那么哈希在平时项目开发中有什么用途呢?可以用哈希来做什么事情?对于前端来说用到哈希最多的时候可能就是锚点定位。通过不同的哈希值定位到描点指定的元素位置上。
<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="http://localhost:7000/b.html#key=1&key1=2"></iframe>
由于哈希值的改变不会改变网页的网址的,所以服务端可以通过获取到哈希来解析url
中的参数,并把数据返回给前端即可。通过parent.location.hash
去改变哈希值,然后就可以像document.domain
一样去获取到子页面的数据了。parent.location.hash
该方法是有局限性的,在IE
和Chrome
中是不支持这种操作的。那么整个问题应如何解决呢?
在同域的域名下面添加一个*.html
(*代表任意名)文件,然后把通过iframe
把*.html
引入到父页面中,并把需要请求的接口iframe
添加到*.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>
<!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>
<!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.html
的b.html
,且传递hash
值给b.html``b.html
获取hash
值,生成data
值,然后动态创建iframe
,该iframe
将data
值传给与a.html
同域的c.html
因为c.html
与a.html
同域,可以传值固然也就解决了跨域问题。
window.name
window.name
这个属性不是一个简单的全局属性只要在一个window
下,无论url
怎么变化,只要设置好了window.name
,那么后续就一直都不会改变,同理,在iframe
中,即使url在变化,iframe
中的window.name
也是一个固定的值,利用这个,我们就可以实现跨域了。
<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>
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
在很多浏览器中都已经得到了良好的支持,所以可放心的使用。该方法可以通过绑定window
的message
事件来监听发送跨文档消息传输内容。
postMessage()
方法接受两个参数
-
data:要传递的数据,html5规范中提到该参数可以是
JavaScript
的任意基本类型或可复制的对象,然而并不是所有浏览器都做到了这点儿,部分浏览器只能处理字符串参数,所以我们在传递参数的时候需要使用JSON.stringify()
方法对对象参数序列化,在低版本IE
中引用json2.js
可以实现类似效果。 -
origin:字符串参数,指明目标窗口的源,
协议+主机+端口号+URL
,URL
会被忽略,所以可以不写,这个参数是为了安全考虑,postMessage()
方法只会将message
传递给指定窗口,当然如果愿意也可以建参数设置为"*",这样可以传递给任意窗口,如果要指定和当前窗口同源的话设置为"/"。
<!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>
<!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
对象,这个对象中包含很多东西。
- data:顾名思义,是传递来的
message
- source:发送消息的窗口对象
- origin:发送消息窗口的源(协议+主机+端口号)
使用postMessage
方法比以上方法用起来要轻便,不必有太多的繁琐操作,可以说postMessage
是对于解决跨域来说是一个比较好的解决方案,不会显得代码特别的臃肿,并且各个浏览器又有良好的支持。
跨域资源共享(CORS)
CORS:全称"跨域资源共享"(Cross-origin resource sharing
)。CORS
需要浏览器和服务器同时支持,才可以实现跨域请求,目前几乎所有浏览器都支持CORS
,IE
则不能低于IE10
。CORS
的整个过程都由浏览器自动完成,前端无需做任何设置,跟平时发送ajax
请求并无差异。CORS
的关键在于服务器,只要服务器实现CORS
接口,就可以实现跨域通信。
跨域资源共享(CORS
) 是一种机制,它使用额外的HTTP
头来告诉浏览器让运行在一个origin (domain)
上的Web
应用被准许访问来自不同源服务器上的指定的资源。当一个资源从与该资源本身所在的服务器不同的域、协议或端口请求一个资源时,资源会发起一个跨域HTTP
请求。在上面说过src
是不受同源策略限制的,但是出于安全原因,浏览器限制从脚本内发起的跨源HTTP
请求。例如,XMLHttpRequest
和FetchAPI
遵循同源策略。这意味着使用这些API
的Web
应用程序只能从加载应用程序的同一个域请求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 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
解决跨域了。
在开发过程中由于各个公司的差异选用的接口风格也是不同的,很多公司会采用RESTful风格去编写接口,难免就会有些骚操作,在跨域请求时自定义的请求头是不允许这样操作的,因为浏览器根据Response Headers
判断请求是否允许。
跨域时默认允许的方法
- GET
- HEAD
- POST
因为浏览器希望在网页进行跨域请求操作的时候是保证服务端的安全的,不允许任何随便进行跨域,不允许随便的方法进行跨域,以防数据被恶意篡改。所以提供这些限制之后,就可以进行一些非常有利的判断。针对如上问题需要服务端进行特殊处理才行
const http = request("http");
http.createServer(function(request,response){
response.writeHead(200,{
// 设置允许跨域的访问地址
'Access-Control-Allow-Origin':'*',
// 设置允许访问的自定义请求头
'Access-Control-Allow-Headers':'X-Test-Cors',
// 设置允许跨域的methods
'Access-Control-Allow-Methods':'POST,Put,Delete',
// 允许以上三个方式进行跨域的最长时间,1000秒内不需要发送预请求验证了
'Access-Control-Max-Age':'1000'
})
response.end('123')
}).listen(3000)
这样设置,这个请求就成功了,但是可以观测到多了一个请求,这个多出来的请求就是预请求,告诉浏览器,这个自定义请求是允许的,然后再正式的发送这个请求,通过验证之后就请求成功了,浏览器为什么要做这些限制,是因为保证安全,不允许随便一个人都可以进行跨域。
WebSocket协议跨域
WebSocket
是HTML5
新推出的一个API
,通过WebSocket
可以实现客户端与服务端的即时通信,如聊天室,服务数据同步渲染等等。WebSocket
是点对点通信,服务端与客户端可以通过某种标识完成的。WebSocket
是不受同源策略限制的所以可以利用这个特性直接与服务端进行点对点通信。
以下示例没有使用HTML5
的WebSocket
而是使用的socket.io
完成类似的功能操作。
若若的说一句:其实我一直以为WebSocket
与Ajax
一样是受同源策略
限制的,经过学习才发现不是的。真是学到老活到老(关谷口音)。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
地址,而不知道在代理服务器后面的服务器簇的存在。 -- 节选自百度百科
反向代理服务器:就nginx
把http
请求转发到另一个或者一些服务器上。从而轻松实现跨域访问。比如服务器中分别部署了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
}
}
在开发过程中遇到的可以通过这种方式解决,但是到达生产环境时到底使用什么方法还是需要斟酌的,毕竟要使服务数据变得更加的安全才是最好的。
总结
以上讲了很多有关跨域的解决方案,有利也有弊,对于我而言可能更加的倾向于后端粑粑或者运维粑粑去解决跨域问题,毕竟前端处理起来毕竟不是很安全,而且后端或者运维处理起来也不是那么的麻烦。
很感谢大家利用这么长时间来读这篇文章,文章中若有错误请在下方留言,会尽快做出修改。