首先欢迎你们关注个人Github博客,也算是对个人一点鼓励,毕竟写东西无法得到变现,能坚持下去也是靠的是本身的热情和你们的鼓励。最近工做上事情比较繁多致使博客一度断更,为了挽救惨淡的关注量并兼顾本身有限的时间,准备近期多推出一些有关前端的基础知识学习,一块儿夯实基础。但愿你们多多关注呀! javascript
跨域问题在Web开发中一直都是一个很是常见的问题,但在平常工做说实话使用频率并无那么多,再加上重使用轻原理致使我对跨域问题理解的一直很是的浅显,借此机会让咱们好好探索一下。前端
跨域问题的起源于众所周知的浏览器同源策略。做为现在Web安全基石的同源策略,早在上个世纪九十年代由网景公司提出,当时仅仅只是针对于Cookie
的访问,即不一样源的网页之间Cookie
不能共享。后来同源策略变得更加严格,安全性也逐步提升,升级为:java
说了这么多同源,那么何为同源。一般状况下,若是一个源拥有相同的协议(protocol)、主机(host)、端口(port),则称其为同源。固然咱们说了一般状况下,也就意味着这个方面有例外存在,IE毫无疑问的在这个方面承担了它固有的职责。IE并无将端口号加入到同源策略的组成部分,所以http://host:81
和http://host:80
是属于同源的。虽然同源的安全限制是必要的,可是同时也带了束缚,假设我确实须要向不一样源的地址发送请求该怎么办呢?那就回到了咱们所要讨论的跨域问题。git
往往提起跨域问题,第一个想到的就是JSONP了。JSONP与JSON虽然看起来很像,但却有本质区别 。JSONP全称是JSON with Padding,并非一种新的数据格式,而是数据格式JSON的一种“使用模式”。浏览器的同源限制对含有src
属性的元素(例如: script
、img
)不起做用,JSONP就是利用了script
的这个特性。github
前面咱们说了JSONP利用的是script
标签,绕过浏览器的同源策略,试想咱们经过一个URL获取JSON数据是可能的。然而JSON数据倒是不可执行的,所以JSONP经过将返回的JSON数据用函数包裹来实现执行的目的,这也就是JSONP中P表示padding(包裹)的含义。由于须要函数包裹数据,因此先后端须要提早约定好函数名,咱们通常会在请求的URL中带上函数名称做为参数。例如请求接口:ajax
http://api.demo.com/data?userid=1&jsonp=parseResponse
复制代码
服务器会将对应的数据填充到函数parseResponse
:express
parseResponse({"Name": "小明", "Id" : 1823, "Rank": 7})
复制代码
咱们实现一个最简单的函数用来建立一个JSONP请求:json
function createJSONPRequest(url, callbackName){
var script = document.createElement("script");
script.src = url +"?callback=handleResponse";
document.body.appendChild(script);
}
function handleResponse(res){
console.log(res);
}
createJSONPRequest("http://localhost:3001/jsonp", "parseResponse")
复制代码
而后咱们用express.js来响应这个JSONP请求:后端
// 服务器端口号:3001
app.get('/jsonp', (req, res) => {
var callbackName = req.query.callback;
var mockData = {
name: "MrErHu"
};
res.send(`${callbackName}(${JSON.stringify(mockData)})`);
})
复制代码
运行时你会发现浏览器会正确打印出跨域访问的数据,说明咱们的跨域请求成功。api
固然咱们并不须要在前端手动去实现该函数,不少第三方库已经封装了JSONP的请求,咱们以JQuery提供的Ajax理由为例,上面的请求用JQuery去实现代码是:
// 客户端端口号:3000
$.ajax({
url: "http://localhost:3001/jsonp",
type: "GET",
dataType: "jsonp",
jsonpCallback: "parseResponse",
success: function (res) {
console.log(res);
}
});
复制代码
咱们能够看到在JQuery中提供的ajax
函数中经过配置dataType
为jsonp
,则能够实现JSONP请求,须要注意的是JQuery只是将其封装在ajax
函数内,两者实质上有本质区别。
了解JSONP的实现原理,咱们知道这玩意确定不会存在浏览器兼容性问题,毕竟我不相信会存在不支持script
标签的浏览器。可是由于正是经过script
标签实现的,JSONP也就只能支持GET
请求,那么若是咱们想实现POST
请求的跨域有什么好的办法吗?
CORS是Cross-origin resource sharing的缩写,即跨域资源共享,属于W3C标准,容许跨域发送XMLHttpRequest
请求,支持多种HTTP Method。与JSONP的原理不一样的是,CORS采用的是先后端HTTP表头协商的方式(我本身起的名字)判断是否容许跨域访问,而且相比与JSONP来讲,CORS须要客户端和服务端共同支持,而且整个过程由浏览器自动完成。对于前端开发者而言,跨域的CORS通讯与普通的Ajax通讯没有差异,整个跨域处理的过程主要集中在服务器端。CORS通讯分红简单请求(simple request)和非简单请求(not-so-simple request)两种模式。
所谓的简单请求是指,请求方法属于: HEAD
、GET
、POST
之一,而且HTTP的头信息不超出如下几种字段:
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type:仅限于三个值:application/x-www-form-urlencoded、multipart/form-data、text/plain
复制代码
若是不知足以上任一条件,均属于非简单请求。对于简单请求,浏览器会直接发送CORS请求。当咱们使用Ajax发送一条跨域请求,浏览器会在HTTP请求头(Request Headers)中增长Origin
属性:
Origin
属性用来代表当前的请求来自于哪一个源(协议、主机、端口)。若是服务端支持CORS跨域,则须要在响应头中返回如下属性:
Access-Control-Allow-Origin:
Access-Control-Allow-Credentials
Access-Control-Expose-Headers
复制代码
Access-Control-Allow-Origin
: 表示跨域的访问的源,其中"*"表示容许来自任何源的请求跨域访问Access-Control-Allow-Credentials
: 表示跨域访问是否容许带有Cookie信息,该值仅能够容许设置为true
。若是服务器不容许请求携带Cookie,则不须要发送该属性。值得注意的是,携带的Cookie信息仅仅只是所跨域的服务器域名设置的Cookie,不能携带其余域名下的Cookie信息,Cookie仍然循序同源策略。而且还须要知足Access-Control-Allow-Origin
指定的域与当前的请求的域彻底一致(不能是"*")以及在XMLHttpRequest显式设置withCredentials
属性为true
,表示浏览器也容许发送Cookie。Access-Control-Expose-Headers
: 该字段可选,表示浏览器能够从该XMLHttpRequest
请求中拿到的响应头信息。默认仅能拿到Cache-Control
、Content-Language
、Content-Type
、Expires
、Last-Modified
、Pragma
这六个属性,若是想取得其余的属性,必须在该属性中指定。咱们用express.js
模拟一下:
// 服务器端口号:3001
app.post('/cors', (req, res) => {
var mockData = {
name: "MrErHu"
};
var origin = req.get("origin");
res.set("Access-Control-Allow-Origin", origin);
res.set("Access-Control-Allow-Credentials", true);
res.send(JSON.stringify(mockData))
});
复制代码
当咱们前端Ajax请求:
// 浏览器端口号:3000
$.ajax({
url: "http://localhost:3001/cors",
type: "POST",
success: function (res) {
console.log(res);
}
});
复制代码
你会发现该条请求已经不会出现跨域问题,能够正确访问到数据。
对于非简单请求,浏览器会预先发送一次预检请求,询问服务器是否容许跨域访问以及是否容许HTTP方法,只有获得确定答复后,浏览器才会发出正式的HTTP请求。好比咱们跨域向服务器发送一次PUT
请求:
// 浏览器端口号:3000
$.ajax({
url: "http://localhost:3001/cors",
type: "PUT",
success: function (res) {
console.log(res);
}
});
复制代码
你会发现浏览器首先会发送OPTIONS请求去预检本次跨域请求。
当预检请求检测了Origin
、Access-Control-Request-Method
和Access-Control-Request-Headers
属性后就会正式发送请求。须要注意的是,不只仅是上述三个属性,预检请求还会返回Access-Control-Max-Age
属性用来表示该条预检请求的有效期,在有效期内,浏览器不会再次发送预检请求,仅会使用该条缓存的预检请求。
咱们用express.js
来模拟本次请求:
// 服务器端口号:3001
app.options('/cors', (req, res) => {
var origin = req.get("origin");
res.set("Access-Control-Allow-Origin", origin);
res.set("Access-Control-Allow-Credentials", true);
res.set("Access-Control-Allow-Methods", "PUT");
res.set("Access-Control-Max-Age", 24 * 60 * 60 * 1000);
res.send();
});
app.put('/cors', (req, res) => {
var mockData = {
name: "MrErHu"
};
var origin = req.get("origin");
res.set("Access-Control-Allow-Origin", origin);
res.set("Access-Control-Allow-Credentials", true);
res.send(JSON.stringify(mockData))
});
复制代码
你就会发现本次PUT
跨域请求成功,正确返回数据,达到了咱们跨域的要求。
与JSONP相比,CORS支持更多的HTTP方法而且整个过程由浏览器自动完成,可是仅仅只兼容IE10及以上的浏览器,若是须要兼容老版本IE浏览器,CORS可能就不适合你了。
咱们这边文章着重讲述了两种跨域的基本原理,但实际上围绕跨域问题还有不少值得学习的地方,好比后端对跨域请求的处理,以及前端跨域带来种种的安全性问题。其实不只仅是JSONP与CORS,Websocket也能实现跨域请求,之后有机会能够学习一下,最后仍是欢迎你们关注个人博客,水平欠佳,但愿体谅,愿一同进步。