您好, 欢迎来到 !    登录 | 注册 | | 设为首页 | 收藏本站

Ajax 跨域

我们先从这么问题来引入我们本章节的学习 —— 什么是跨域请求?

简单来说,跨域请求就是域下的资源请求另外域下的资源。

同域,指的是,协议名、域名、端口号都一致。 举个例子来说,假如 “http://www.a.com” 下的 JavaScript 脚本发起 Ajax 请求 “http://www.a.com/ajax” ,由于 协议名 http 、域名 www.a.com 和 端口号(认都是 80)三者都是一致的,因此都属于同域,不造成跨域请求。而假如其中任一元素不相同,则造成跨域请求。与此同时,浏览器出于安全考虑,基于同源策略则会做一定的限制:比方说:

本章节不考虑不同域文档之跨域交互,主要讲 Ajax 造成的跨域的。

开始讲解 Ajax 造成的跨域问题如何之前,我们思考一下:

假如我们要从山的一边 A 到山的另一边 B,这座山无疑就是个障碍,那么我们有几种办法?

我想,要么我们就直接穿过去,要么我们就“曲线救国”,绕个道也未尝不可。没错,接下来我们要讲的 Ajax 跨域也是从这两方面来讲,既然跨域有这样那样的一些限制,那我们要么就直面去,要么就耍个机灵,同样能够。

JSONP 是非常经典的跨域的。我们知道,在 HTML 中,一些资源的引用事实上是不会受到跨域限制的,比如 script 。浏览器在解析 HTML 的时候,解析到了 script ,会把相应的资源下载下来。我们可以利用这一点,来实现前后端信息的交互。

假如 HTML 有容器为 container,我们要通过 JSONP 的方式来为 container 插入一条,那么,我们可以这么做:

<div id="container">

</div>
// jsonp

// 定义的回调
window.addContent = function (content) {
    document.getElementById('container').innerHTML = content;
}

/**
* 发送 JSONP 请求的
* cb 为回调的名
*/
function sendJsonPRequest (cb) {
    // 创建 script 
    const body = document.getElementsByTagName('body')[];
    const script = document.createElement('script');
    script.type = 'text/javascript';
    
    // 指定的 url ,callback 参数为回调的名
    script.src = `http://localhost:8082/jsonp/get?callback=${cb}`;
    body.appendChild(script); // 到 body 最后面
}

sendJsonPRequest('addContent') // 执行发送 JSONP 请求

显而易见,前端我们会创建 script ,并且附带定义好的回调的名传给服务端。与此同时,我们需要在服务端进行 JSONP 请求的响应。

router.get("/jsonp/get", function(req, res) {
    const cb = req.query.callback; // 读取请求附带的参数 callback
    const resData = '这是一条服务端返回的';
    res.send(`${cb}(${JSON.stringify(resData)})`); // 返回 callback(resData) 格式的数据
});

从右边控制台可以看出来,我们成功创建了 JSONP 的请求,并且结果正如我们预期的执行了 addContent('这是一条服务端返回的'),界面上展示出插入的。

使用 JSONP 的方式,我们可以通过 script 绕过浏览器的跨域限制,进行前后端数据交互。不过另一方面,这种也很有局限性,我们只能够发送 GET 请求,无法满足更加复杂业务的需求。一般我们也不会推荐直接使用 JSONP 的方式来跨域问题。

接下来讲到的一种是服务端代理的方式。要问为什么采取服务端代理的方式呢?很简单,因为浏览器端 Ajax 请求有跨域的限制,那我们就把请求不同域的操作放在服务端好了,毕竟服务端是没有跨域限制这一说的。

举服务端代理的例子,这里我使用了 Express 的中间件,叫做 express-http-proxy 。当然同学们也可以在同域服务端接收到请求的时候,发起 http 请求访问不同域的服务端来模拟这一代理行为。前端方面我使用了 jQuery 的 Ajax 。

$.ajax({
    url: '/proxy/proxy_get',
    method: 'GET',
    data: {
        a: '123',
        b: '234'
    }
}).done(data => {
    console.log(data)
})

很简单,我们就是向同域的服务器发送了请求。

const proxy = require('express-http-proxy');  // 引入代理中间件

// ... 一些

app.use('/proxy', proxy('http://localhost:8082/')); // ,之后 /proxy 都会代理到 http://localhost:8082/ 上
router.get("/proxy_get", function(req, res) {
    const {a, b} = req.query
    res.send(`参数是:${a}${b}`)
});

这是目标服务器的响应,返回 处理后的字符串。

服务端代理通过服务端和服务端之交互来避免浏览器和不同域的服务端之间直接进行交互,从而避免了跨域的问题。当然这种要求我们有中间服务器的存在。

举了两个绕过跨域限制的,接下来我们要谈谈常规的情况。既然有跨域限制了,我们就来老老实实这个问题。

接下来我要讲的,是 CORS

首先展开一下 CORS 的全称:

Cross-origin resource sharing

意思是跨域资源共享,这是 W3C 标准,从字面意思来看不难理解,它允许浏览器向跨域的资源发送请求,并且获得结果数据。

跨域资源共享标准新增了一组 HTTP 首部的字段,使得我们能够通过这些字段来跨域到我们所需要的资源。而要实现这一,我们需要前后端的配合,只有当后端实现了 CORS ,我们才能够通过浏览器直接访问资源。为此,我们先来看看接下来的几个首部字段:

其中,我们更为重要的当属 Access-Control-Allow-Origin 字段,因为这个字段直接关系到你是否能够跨域访问资源的权限了。通常情况下,为了跨域问题,后端同学会设置 Access-Control-Allow-Origin 指定为我们的请求源的域,而前端基本无感。

关于 CORS ,HTTP 请求上会有一些小小区别,最直观的区别就是会不会触发多一次 OPTIONS 预检测请求。我们把一些不会触发预检测请求的请求,称为简单请求,而相反,会触发预检测的请求则是非简单请求

而关于如何区分简单请求和非简单请求,这里我就不再累赘,有兴趣的同学可以读一下 。在实际的工作过程中,使用到 CORS 来跨域限制是非常常见的,这里我们注意一下简单请求和非简单请求的直观区别即可,并在以后的工作中留意一下,而不至于懵逼于为什么多了一次 OPTIONS 请求。

// 全局设置请求过滤
app.all('*',function (req, res, next) {
    res.header('Access-Control-Allow-Origin', 'http://localhost:8080'); // 设置 Access-Control-Allow-Origin
    res.header('Access-Control-Allow-Headers', 'Content-Type, Content-Length, Authorization, Accept, X-Requested-With');  // 设置 Access-Control-Allow-Headers
    res.header('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS'); // 设置 Access-Control-Allow-Methods
    next()
});


// 简单的路由
router.get("/simple/get", function(req, res) {
    const {a} = req.query
    res.send(`参数值是${a}`)
});

后端的工作就是实现 CORS 。正如上方,我们规定了一系列 HTTP 请求头首部字段,使得 这个域的前端脚本拥有向服务端发起请求并取得资源的权限。

$.ajax({
    url: 'http://localhost:8083/simple/get',
    method: 'GET',
    data : {
        a: 
    }
}).done(data => {
    console.log(data)
})

可见,通过 CORS ,前端成功拿到了不同域的服务端的返回。

CORS 是 W3C 的标准。使用 CORS ,我们可以使用使用常规的方式来前后端跨域访问的问题。并且,大多数的工作其实也是放在了服务端上,对于前端而言,基本上可以说是无感的。

当然, CORS 也是存在着一些弊端。正因为它是 W3C 中比较新的方案,导致了各大浏览器引擎没有对其做严格规格的实现,由此可能产生一些不一致的情况。

跨域远不止 Ajax 跨域,而 Ajax 跨域的也不只有本章中提到的这三种。

说跨域远不止 Ajax 跨域,打个比方,不同域的网页之通信也是属于跨域范畴。但由于本章的是 Ajax 跨域,因此我们不做过多的讨论。有兴趣的同学,可以深入去探究一下。

而 Ajax 跨域的,本章提及 3 种,从两个方面来阐述。对于遇见的问题,的要么就是绕个道走,要么就是穿过去走。无论你使用哪一种,肯定也都有利有弊。而实际的应用中,我们到底要采用何种来 Ajax 跨域问题呢?我的建议是关注业务和场景,这就需要同学们在另外层面去进行深入的思考了。

本着鼓励深入学习深入思考的原则,我希望同学们能够在跨域的问题上,进行深入的研究,总结起来。


联系我
置顶