【综-网】Http浅析【2】——ajax跨域问题

视频参考:ajax跨域完全讲解

本文精华版:【综合】ajax跨域问题

什么是跨域问题

简单来讲,当前台调用后台,如果接口不是一个域时,就会产生跨域问题。

由于目前前后端分离的技术架构,前台项目和后台项目都是独立开发进行的。当联调测试时,前台页面必然会大量调用后台的接口,此时如果接口不是同一个域,就会发生跨域问题。

测试环境搭建

【后台环境搭建】

使用node+express搭建一个后台系统
(1)新建一个文件夹NodeBack,输入 npm init 初始化项目,创建index.js作为整个后台的入口文件

(2)安装express:npm install express --save

(3)安装body-parse:npm install body-parse--save,用于解析post请求的参数

(4)编写入口文件 index.js

var express = require('express');

/**
 * 解析post来的参数
 app.use(bodyParser.json()); // for parsing application/json
 app.use(bodyParser.urlencoded({ extended: true })); // for parsing application/x-www-form-urlencoded
 * */

var bodyParser = require('body-parser');

var app = express();

app.use(bodyParser.urlencoded({extended: true})); // for parsing application/x-www-form-urlencoded

app.get('/test', function (req, res) {
  var name = req.query.name
  var age = req.query.age
  res.jsonp({
    data:'name is ' + name + "| age is " + age
  });
});

app.post('/login', function (req, res) {
  var name = req.body.name
  var password = req.body.password
  if (password === "123") {
    res.jsonp({
      "name": name,
      "password": password
    });
  } else {
    res.send("error!");
  }
});

var server = app.listen(3005, function () {
  var host = server.address().address;
  var port = server.address().port;

  console.log('Example app listening at http://%s:%s', host, port);
});
GET请求
POST 请求

【前台请求页面】


简单html版

简单起见,就使用Jquery来发送前台请求

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>

  <!--导入jquery-->
  <script src="http://code.jquery.com/jquery-latest.js"></script>
</head>
<body>
<a href="#" onclick="getReq()">发送get请求</a>

<script>
  function getReq(){
    $.getJSON("http://localhost:3005/test?name=hugo&&age=18")
      .then((result)=>{
        console.log(result)
    })
  }
</script>
</body>
</html>

使用firefox,打开此网址,按F12,调出控制台,点击发出get请求


但是在控制台发现报错提示,这就是跨域问题

ajax跨域问题分析

发生跨域问题有三个原因:
【1】浏览器限制:很多同学认为跨域问题的是服务器后台不允许前台跨域访问,但一些情况下并不是这样,还有可能是浏览器多管闲事,处于安全考虑限制了跨域访问。

如何验证这种想法呢,我们可以在get请求中增加一句log

app.get('/test', function (req, res) {
  var name = req.query.name
  var age = req.query.age
  res.jsonp({
    data:'name is ' + name + "| age is " + age
  });
  console.log("receive get request")
});

再次发送请求:



发现响应都是没有任何问题的

【2】跨域,地址中,协议、域名、端口任意一个不一样,浏览器就认为是跨域

【3】XHR(XMLHttpRequest)请求。请求的type为XHR,非json、img等其他类型。
当上述三种情况同时满足时,跨域问题就产生了,

解决思路

【1】解决浏览器限制:通过指定浏览器的启动参数,解除限制,但是这种情况需要用户手动操作,所以交互不友好,所以不推荐使用

【2】转变请求的类型。上面说到,只有请求类型是xhr时,将xhr的类型转化为jsonp的格式

【3】跨域。
被调用(服务器)方支持跨域;
调用方做跨域支持(隐藏跨域):通过代理,从浏览器发送出的请求都是同源请求

全面解决跨域问题

【禁止浏览器检查跨域】

不做介绍,因为实用价值不大。

【jsonp解决跨域】

json for pending :利用js请求资源标签时,请求可以跨域,来解决跨域请求,它是一种变通的解决方案。

function jsonpReq(){
    var result
    $.ajax({
      url:"http://localhost:3005/test?name=hugo&&age=18",
      dataType:'jsonp',
      success:function(json){
        result=json
        console.log(result)
      }
    })
  }

在使用jsonp的时候,后台需要做改动(java后台),不然服务器返回的内容,浏览器会当做js代码来解析,会报语法错误。经测试,node编写的后台无需添加额外的内容

我们对jsonp的原理进行分析,解释为什么java后台需要进行额外设置:


首先,看一下jsonp发送的报文类型是script,在之前分析跨域问题产生的条件之一就是请求报文的类型是xhr,那么jsonp的思路就是改变报文的请求类型,变为script。

第二,普通的ajax请求返回的json对象,而jsonp返回的是js脚本


第三,jsonp发送的url后面接了一串额外的callback字符

i

那么从这里我们就能简单分析jsonp的原理是:jsonp发送的url请求自动添加了一个callback参数,当后台发现callback参数时,就认为是一个jsonp请求,响应头在返回数据时,数据格式就由json变成script,而script的内容就是一个函数调用。

那么java后台的改动就能分析得出如下:


所以,如果我们把url请求中的callback改成callback2,那么后台就不会认为前台请求是一个jsonp请求。即:前台调用的“callback”和后台的“callback”实际上是一个接口上的协议,需要保持一致

很明显,Jsonp是基于Jquery的一种解决方案,但是在react、vue这类本身的是要替代Jquery操作dom结构的框架,效果可想而知。

接下来,再来谈谈jsonp的利弊:

1、服务器端需要改动。当服务器是第三方提供时,这种做法就会出现局限性。
2、只支持GET请求,通过分析原理,我们知道jsonp是通过Jquery动态创建一个script来创建一个请求的,所以请求类型就被局限在了GET。
3、 发送的不是XHR请求,即XHR的诸多特性都无法使用,例如异步、事件机制都无法使用。

综上可见,我们在解决跨域问题时,最好方法还是解决请求是跨域的,而不是改变请求是XHR还是JSONP

下面就继续介绍本文的重点:【让服务器支持跨域】、【前台请求隐藏跨域】

【从系统架构分析跨域】

最常见的javaee 架构


请求流程:
请求从浏览器发出,首先到apache或nginx(静态服务器) ,然后判断客户端请求是静态请求还是动态请求。

简单讲,跟客户端数据有关系的就是动态数据(用户信息,消息列表等),而图片、js、css、html等就是静态请求。

如果是静态请求,静态服务器直接处理,然后把数据返回给客户端。如果是动态请求,则转发给后台的应用>服务器,处理完毕后,应用服务器把数据返回给静态服务,随后静态服务器发送给客户端。

中间的http服务器,一般有两个作用:
(1)处理静态请求
(2)转发动态请求和负载均衡。

那么在看跨域请求时的两种思路:

第一:直接从浏览器里发出请求到被调用方的http服务器。 这时需要在http服务器上做响应的修改,这些修改都是基于http请求关于跨域部分的协议。就是在返回头里增加一些字段,告诉浏览器被调用方允许客户端跨域访问。这样浏览器就不会报跨域的问题。


第二:隐藏跨域。请求不直接从浏览器发出,而是从中间http服务器转出。通过服务器的转发,浏览器不会发现所有的请求都是同一个域。即调用方会发现所有的请求都是从本地的http服务器发出(其实并不是,请求都是经过客户端http服务器的转发)

跨域解决方向-被调用方解决-Filter及spring解决方案

这是基于支持跨域的解决思路,是基于http协议关于跨域方面的一些规定,在响应头里增加指定的字段,告诉浏览器服务端支持跨域调用。 此种情况下,浏览器直接发送请求到服务方。

在据图解释此方案前,我们理清几个问题:

浏览器如何判断是否跨域?

答:通过对比普通请求和跨域请求浏览器里的请求头里的内容,可以发现跨域请求中增加了一个origin字段,字段值为当前域名的信息。也就是说,当浏览器发现请求是跨域时,就会在请求头添加origin的字段。当浏览器接收到响应头后,就会检查里面是否有允许跨域的字段,如果没有,就会报错。

浏览器是先执行?还是先判断?

答:浏览器发送跨域请求给后台,后台日志打印正常,浏览器响应状态码为200,但却报跨域问题,说明浏览器是先执行请求操作,然后在判断是否跨域。那么,是不是所有的请求都是先执行后判断呢?

并不是的,这就引出了简单请求和非简单请求的概念。

简单请求和非简单请求

每次浏览器再发送请求时,会做一次判断,如果是简单请求,那么就是先执行再判断。如果是非简单请求,那么浏览器会先发送一个预检头,检查通过后,浏览器才会发送真正的请求

跨域解决——服务器端实现【Spring 编写的后台】

基于注解,可以使用

@CrossOrigin

那么被注解类下的所有请求方法都是可以支持跨域的

跨域解决——服务器端实现【Spring boot 编写的后台】

这里我们需要了解http协议关于跨域方面所有的要求,针对不同的场景返回不同的头。只有知道了所有的响应头,我们才能在后面的http服务器上配置响应头。

首先需要注册一个filterRegistrationBean ,然后让所有的请求都经过这filter

接下来我们在CrosFilter里的doFilter方法中添加服务器响应头

对于非简单请求,我们在按照上述方法添加响应头后,可能会出现如下的错误:


意思是我们的返回头缺少“access-control-allow-headers”是缺少的。

所以我们需要在后台服务器中再增加一行代码:

res.addHeader("Access-Control-Allow-Headers","Content-type");

在上面我们分析了什么是简单请求和非简单请求,那么在非简单请求的情况下,浏览器会发送两次请求:


这样会影响请求的效率。http协议中增添了一个请求头,对预检头缓存。

res.addHeader("Access-Control-Max-Age","3600");

告诉浏览器缓存预检头,时间为3600s。那么当再次发送跨域请求时,就只会请求一次。

问题:*代表所有的Url请求,那么是不是在任何情况下都没有问题呢?

带自定义头的跨域

当我们直接在跨域请求中添加自定义请求头时(假设自定义请求头为x-header1和x-header2),可能会报如下错误:


这里的解决办法是将自定义的请求头添加到Access-Control_Allow-Headers:

res.addHeader("Access-Control_Allow-Headers","Content-type,x-header1,x-header2");

当然我们也可以动态地设置headers


跨域解决——服务器端实现【Node.js 编写的后台】

跨域解决方向-被调用方解决-Http服务器解决方案

【Nginx配置】

Nginx配置文件:


【apache配置】

相当于把之前在nginx上的配置,再在apache上配置一下,思路也是和nginx一样的。由于apache配置比较复杂,详细配置请参考其他文章。

跨域解决方向-调用方解决

这是基于隐藏跨域的解决思路。此种情况下,请求不会直接从浏览器发送到被调用方,而是从调用方的http服务器转发过去的(被调用方http服务器或者应用服务器接收)。使用了反向代理技术,在浏览器上是看不到任何跨域请求。

这里就和上述apache和nginx操作类型,需要修改他们的配置文件(反向代理的配置)。本文只做简单的配置解释,详细内容请小伙伴们自行百度。

Nginx反向代理

首先我们修改一下host,其效果是当你在浏览器访问 www.a.comwww.b.com时,url地址会映射到127.0.0.1上

接下来配置Nginx配置


然后客户端在进行网络请求时,需要变成nginx代理之后的地址,也就是

/ajaxserver

可以发现,此时调用的url就是本域的地址(浏览器看到的是相对地址),自然就不存在了跨域问题。

apache反向代理配置

思路:增加一个虚拟主机,然后让此虚拟主机,把我们的跨域请求作反向代理

总结

至此,本文简单分析了一下跨域问题产生的原因和常见的解决方法,如果有什么问题欢迎留言交流讨论~

笔者个人订阅号~欢迎小伙伴们关注


微信公众号-感谢关注

若有疑问可以QQ联系笔者,虽然不一定100%解决你的问题,但是可以交流探讨一波:2276604211

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容