Ajax——同步/异步;跨域/跨域解决方法

推荐阅读:前端常见跨域解决方案 写的很全面很详细 https://segmentfault.com/a/1190000011145364

一、同步和异步的区别?
  • 同步:浏览器向服务器请求数据,服务器比较忙,浏览器一直等着(页面白屏),直到服务器返回数据,浏览器才能显示页面。
  • 异步:浏览器向服务器请求数据,服务器比较忙,浏览器可以自如的干原来的事情(显示页面),服务器返回数据的时候通知浏览器一声,浏览器把返回的数据再渲染到页面,局部更新。
二、跨域问题

根据应用场景:简单的跨域请求jsonp即可,复杂的cors,窗口之间JS跨域postMessage,开发环境下接口跨域用nginx反向代理node中间件比较方便。

  • 理解跨域的概念:协议、域名、端口都相同才同域,否则都是跨域。
  • 常见跨域场景:
    URL                                 说明                     是否允许通信
    http://www.baidu.com/a.js
    http://www.baidu.com/b.js         同一域名,不同文件或路径           允许
    http://www.baidu.com/lab/c.js
    
    http://www.baidu.com:8000/a.js
    http://www.baidu.com/b.js         同一域名,不同端口                不允许
    
    http://www.baidu.com/a.js
    https://www.baidu.com/b.js        同一域名,不同协议                不允许
    
    http://www.baidu.com/a.js
    http://192.168.1.102/b.js          域名和域名对应相同ip              不允许
    
    http://www.baidu.com/a.js
    http://x.baidu.com/b.js           主域相同,子域不同                不允许
    http://baidu.com/c.js
    
    http://www.baidu1.com/a.js
    http://www.baidu2.com/b.js        不同域名                        不允许
    
  • 跨域解决方法:
    • 1、通过jsonp跨域 ▲
    • 2、document.domain + iframe跨域
    • 3、window.name + iframe跨域
    • 4、location.hash +iframe跨域
    • 5、window.postMessage跨域
    • 6、跨域资源共享(CORS)
    • 还有nginx代理跨域、nodejs中间件代理跨域、WebSocket协议跨域

一、JSONP 缺点:只能实现get一种请求

通常为减轻web服务器的负载,把js css img等静态资源分离到另一个独立域名的服务器上,在html页面再通过相应的标签从不同域名下加载静态资源,而被浏览器允许。基于此原理,我们可以通过动态创建script标签,再请求一个带参数网址实现跨域通信。

1.)原生实现

<script>
var script = document.createElement('script');
script.type = 'text/javascript';

// 传参一个回调函数名给后端,方便后端返回时执行这个在前端定义的回调函数
script.src = 'http://www.domain2.com:8080/login?user=admin&callback=handleCallback';
document.head.appendChild(script);

// 回调执行函数
function handleCallback(res){
console.log(JSON.stringify(res));
}
</script>

<!----服务端返回如下---->
handleCallback({"status": true, "user": "admin"})

2.)jquery ajax

$.ajax({
  url: 'http://www.domain2.com:8080/login',
  type: 'get',
  dataType: 'jsonp',  // 请求方式为jsonp
  jsonpCallback: "handleCallback",    //自定义回调函数
  data: {}
});

3.)vue.js

this.$http.jsop('http://www.domain2.com:8080/login', {
  parms: {},
  jsonp: 'handleCallback'
}).then((res)=>{
  console.log(res);
})

后端node.js代码示例:

var querystring = require('querystring');
var http = require('http');
var server = http.createServer();

server.on('request', function(req, res){
var params = qs.parse(req.url.split('?')[1]);
var fn = params.callback;

// jsonp返回设置
res.writeHead(200, {'Content-type': 'text/javascript'});
res.write(fn + '(' + JSON.stringify(params) + ')');

res.end();
});

server.listen('8080');
console.log('Server is running at port 8080...');

二、document.domain +iframe跨域。仅限主域相同,子域不同的跨域应用场景

原理:两个页面都通过js强制设置document.domain为基础主域,就实现了同域。(设置是有限制的:只能把document.domain设置成自身或更高一级的父域。且主域必须相同)

<!---父窗口(http://www.doamin.com/a.html)--->
<iframe id="iframe" src="http://child.domain.com/b.html"></iframe>
<script>
  document.domain = 'domain.com';
var user = 'admin';
</script>

<!---子窗口(http://child.domain.com/b.html)--->
<script>
  document.domain = 'domain.com';
//获取父窗口中的变量
alert('从父级获取js数据-->' + window.parent.user)
</script>

三、window.name + iframe跨域

原理:window.name特征:name值在不同的页面(甚至不同域名)加载后依旧存在,并且可以支持非常长的 name 值(2MB),其值为字符串形式

① a.html

// a页面 http://www.domain1.com/a.html
var proxy = function(url, callback){
var state = 0;
var iframe = document.createElement('iframe');

iframe.src = url // 加载跨域页面

// onload 时间会触发两次,第一次加载跨域页,并留存数据于window.name
iframe.onload = function(){
if(stae === 1){
// 第二次onload(同域proxy页)成功后,读取同域window.name中数据
callback(iframe.contentWindow.name);
destoryFrame();
}else if(state === 0){
// 第一次onload(跨域页)成功后,切换到同域代理页面
iframe.contentWindow.location = 'http://www/domain1.com/proxy.html';
state = 1;
}
};

document.body.appendChild(iframe);

// 获取数据以后销毁这个iframe,释放内存;为了保证安全(不被其它域frame js访问)
function destoryFrame(){
iframe.contentWindow.document.write('');
iframe.contentWindow.close();
document.body.removeChild(iframe);
}
};

// 请求跨域b.html的数据
proxy('http://www.domain2.com/b.html', function(data){
alert(data);
});

② proxy.html(http://www.domain1.proxy.html

中间代理页面,与a.html同域,内容为空即可

③ b.html

<script>
  window.name = 'this is domain2 data!!'
</script>

总结:通过iframesrc属性有外域转向本地域,跨域数据即由iframewindow.name从外域传递到本地域。这个巧妙地绕过了浏览器的跨域访问限制,但同时它又是安全操作。

四、location.hash +iframe跨域

原理:A域要和B跨域相互通信,通过中间也C来实现。三个页面,不同域之间利用iframelocation.hash传值,相同域之间直接js访问来通信。

示例:a.html —> b.html —> c.html,a、b不同域只能通过hash值单向通信,b、c也是如此,但c、a同域,所以c可以通过parent.parent访问a页面所有对象。

// a.html (http://www.js001.com/a.html)
<iframe id="iframe" src="http://www.js002.com/b.html" style="display:none"></iframe>
<script>
  var iframe = document.getElementById('iframe');

// 向b.html传值
setTimeout(function(){
iframe.src = iframe.src + '#user=admin';
}, 1000)

// 开放给同域c.html的回调方法
function onCallback(res){
alert('data from c.html...' + res);
}
</script>

// b.html (http://www.js002.com/b.html)
<iframe id="iframe" src="http://www.js001.com/c.html" style="display:noe"></iframe>
<script>
  var iframe = document.getElementById('iframe');

// 监听a.html传来的hash值,在传给c.html
window.onhashchange = function(){
iframe.src = iframe.src + location.hash;
}
</script>

// c.html (http://www.js001.com/c.html)
<script>
  window.onhashchange = function(){   // 监听b.html传来的hash值
// 在通过操作同域a.html的js回调,将结果传回
window.parent.parent.onCallback('' + location.hash.replace('#user=', ''));
}
</script>

/*
window.parent 返回当前窗口的父窗口对象,若没有父窗口,则为parent属性自身的引用;若当前窗口是<iframe>,<object>或者<frame>,则它的父窗口是嵌入它的那个窗口。
Location.replace()方法以给定的URL来替换当前的资源
*/

五、window.postMessage跨域

window.postMessage(message,targetOrigin)方法是html5新引进的特性。是为数不多可以跨域操作的widow属性之一,可用于解决以下方面的题:

a. 页面和其打开的新窗口的数据传递

b. 多窗口之间消息传递

c. 页面与嵌套的iframe消息传递

d. 上面三个场景的跨域数据传递

方法:两个参数:

message:html5规范支持任意基本类型或可复制的对象,但部分浏览器只支持字符串,所以传参时最好用JSON.stringify()序列化。

targetOrigin: 协议+主机+端口号,也可以设置为"*",表示可以传递给任意窗口,如果要指定和当前窗口同源的话设置为"/"。

// 页面A a.html  http://www.domain1.com/a.html
<iframe id="iframe" src="http://www.domain2.com/b.html" style="dispaly:none"></iframe>
<script>
  var iframe = docunment.getElementById('iframe');
iframe.onload = function(){
var data = { name:'123456' };
};

// 接收domain2返回数据
window.addEventListener('message', function(e){
alert('data from domain2....' + e.data);
}, false);
</script>

// 页面B b.html  http://www.domain2.com/b.html
<script>
  // 接收domain1的数据
window.addEventListener('message', function(e){
alert('data from domain1...' + e.data);

var darta = JSON.parse(e.data);
if(data){
data.number = 16;

//处理后再发回domain1
window.parent.postMessage(JSON.stringify(data), 'http://www.doamin1.com')
}
}, false);
</script>

六、跨域资源共享(CORS)

普通跨域请求:只服务端设置Access-Control-Allow-Origin即可,前端无需设置;若要带cookie请求:前后端都需要设置。

注意:由于同源策略的限制,所读取的cookie为跨域请求接口所在与的cookie,而非当前页。若想实现当前页cookie的写入,可参考:七、Nginx反向代理中设置proxy_cookie_domain和八、NodeJs中间件代理中cookieDomainRewrite参数的设置。

所有浏览器都支持该功能(IE8+:IE8/9需要使用XDomainRequest对象来支持CORS)。

  • 1、前端设置

    • 1.)原生ajax

      // 前端设置是否带cookie
      xhr.withCredentials = true;
      

      示例代码:

      var xhr = new XMLHttpRequest();       // IE8/9需用window.XDomainRequest兼容
      
      // 前端设置时候带cookie
      xhr.withCredentials = true;
      
      xhr.open('post', 'http://www.domain2.com:8080/login', true);
      xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
      xhr.send('user=admin');
      
      xhr.onreadystatechange = function(){
          if(xh.readyState == 4 && xhr.status == 200){
              alert(xhr.responseText);
          }
      };
      
    • 2.)jQuery ajax

      $.ajax({
          ...
         xhrFields: {
             withCredentials: true    // 前端设置是否带cookie
         },
         crossDomain: true,   // 会让请求头中包含跨域的额外信息,但不会含cookie
          ...
      });
      
    • 3.)vue框架

      axios设置:

      axios.defaults.withCredentials = true
      

      vue-resource设置:

      Vue.http.options.credentials = true
      
  • 2、服务端设置

    若后端设置成功,前端浏览器控制台则不会出现跨域报错信息,反之,说明没成功。

    • 1.) Java 后台

      /*
       * 导入包:import javax.servlet.http.HttpServletResponse;
       * 接口参数中定义:HttpServletResponse response
       */
      
      // 允许跨域访问的域名:若有端口需写全(协议+域名+端口),若没有端口末尾不用加'/'
      response.setHeader("Access-Control-Allow-Origin", "http://www.domain1.com"); 
      
      // 允许前端带认证cookie:启用此项后,上面的域名不能为'*',必须指定具体的域名,否则浏览器会提示
      response.setHeader("Access-Control-Allow-Credentials", "true"); 
      
      // 提示OPTIONS预检时,后端需要设置的两个常用自定义头
      response.setHeader("Access-Control-Allow-Headers", "Content-Type,X-Requested-With");
      
    • 2.)Nodejs后台

      var http = require('http');
      var server = http.createServer();
      var qs = require('querystring');
      
      server.on('request', function(req, res) {
          var postData = '';
      
          // 数据块接收中
          req.addListener('data', function(chunk) {
              postData += chunk;
          });
      
          // 数据接收完毕
          req.addListener('end', function() {
              postData = qs.parse(postData);
      
              // 跨域后台设置
              res.writeHead(200, {
                  'Access-Control-Allow-Credentials': 'true',     // 后端允许发送Cookie
                  'Access-Control-Allow-Origin': 'http://www.domain1.com',    // 允许访问的域(协议+域名+端口)
                  /* 
                   * 此处设置的cookie还是domain2的而非domain1,因为后端也不能跨域写cookie(nginx反向代理可以实现),
                   * 但只要domain2中写入一次cookie认证,后面的跨域接口都能从domain2中获取cookie,从而实现所有的接口都能跨域访问
                   */
                  'Set-Cookie': 'l=a123456;Path=/;Domain=www.domain2.com;HttpOnly'  // HttpOnly的作用是让js无法读取cookie
              });
      
              res.write(JSON.stringify(postData));
              res.end();
          });
      });
      
      server.listen('8080');
      console.log('Server is running at port 8080...');
      

七、Nginx代理

① Nginx配置解决iconfont跨域

浏览器跨域访问js、css等常规静态资源被同源策略许可,但iconfont字体文件(eot|otf|ttf|woff|svg)例外,此时可在nginx的静态资源服务器中加入以下配置。

location / {
  add_header Access-Control-Allow-Origin *;
}

② Nginx反向代理接口跨域

跨域原理:同源策略是浏览器的安全策略,不是HTTP协议的一部分。服务端调用HTTP接口只是使用HTTP协议,不会执行JS脚本,不需要同源策略,也就不存在跨域问题。

实现:通过nginx配置一个代理服务器(域名与domain1相同,端口不同)做跳板机,反向代理访问domain2接口,并且可以顺便修改cookie中domain信息,方便当前域cookie写入,实现跨域登录。

nginx 具体配置:

#proxy服务器
server {
    listen       81;
    server_name  www.domain1.com;

    location / {
        proxy_pass   http://www.domain2.com:8080;  #反向代理
        proxy_cookie_domain www.domain2.com www.domain1.com; #修改cookie里域名
        index  index.html index.htm;

        # 当用webpack-dev-server等中间件代理接口访问nignx时,此时无浏览器参与,故没有同源限制,下面的跨域配置可不启用
        add_header Access-Control-Allow-Origin http://www.domain1.com;  #当前端只跨域不带cookie时,可为*
        add_header Access-Control-Allow-Credentials true;
    }
}

​ 1.) 前端代码示例:

var xhr = new XMLHttpRequest();

// 前端开关:浏览器是否读写cookie
xhr.withCredentials = true;

// 访问nginx中的代理服务器
xhr.open('get', 'http://www.domain1.com:81/?user=admin', true);
xhr.send();

​ 2.) Nodejs后台示例:

var http = require('http');
var server = http.createServer();
var qs = require('querystring');

server.on('request', function(req, res) {
    var params = qs.parse(req.url.substring(2));

    // 向前台写cookie
    res.writeHead(200, {
        'Set-Cookie': 'l=a123456;Path=/;Domain=www.domain2.com;HttpOnly'   // HttpOnly:脚本无法读取
    });

    res.write(JSON.stringify(params));
    res.end();
});

server.listen('8080');
console.log('Server is running at port 8080...');

八、Nodejs中间件代理跨域

node中间件实现跨域代理,原理大致与nginx相同,都是通过启一个代理服务器,实现数据的转发,也可以通过设置cookieDomainRewrite参数修改响应头中cookie中域名,实现当前域的cookie写入,方便接口登录认证。

  • 1、非vue框架的跨域(2次跨域)

    利用node + express + http-proxy-middleware搭建一个proxy服务器。

    —1)前端代码示例

    —2)中间件服务器

    —3)Nodejs后台 (同六:nginx)

  • 2、 vue框架的跨域(1次跨域)

    利用node + webpack + webpack-dev-server代理接口跨域。在开发环境下,由于vue渲染服务和接口代理服务都是webpack-dev-server同一个,所以页面与代理接口之间不再跨域,无须设置headers跨域信息了。

    webpack.config.js部分配置:

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

九、WebSocket协议跨域

原生WebSocket API使用起来不太方便,我们使用Socket.io,它很好地封装了webSocket接口,提供了更简单、灵活的接口,也对不支持webSocket的浏览器提供了向下兼容。

1.) 前端代码:

<div>user input:<input type="text"></div>
<script src="https://cdn.bootcss.com/socket.io/2.2.0/socket.io.js"></script>
<script>
var socket = io('http://www.domain2.com:8080');

// 连接成功处理
socket.on('connect', function() {
    // 监听服务端消息
    socket.on('message', function(msg) {
        console.log('data from server: ---> ' + msg); 
    });

    // 监听服务端关闭
    socket.on('disconnect', function() { 
        console.log('Server socket has closed.'); 
    });
});

document.getElementsByTagName('input')[0].onblur = function() {
    socket.send(this.value);
};
</script>

2.) Nodejs socket后台:

var http = require('http');
var socket= require('socket.io');

// 启动http服务
var server = http.creareServer(function(req, res){
  res.writeHead(200, {
      'Content-type': 'text/html'
  });
  res.end();
});

server.listen('8080');
console.log('Server is running ....')

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

推荐阅读更多精彩内容

  • 什么是跨域 跨域,是指浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对JavaScript实...
    HeroXin阅读 824评论 0 4
  • 什么是跨域 跨域,是指浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对JavaScript实...
    Yaoxue9阅读 1,277评论 0 6
  • 什么是跨域 跨域,是指浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对JavaScript实...
    他方l阅读 1,056评论 0 2
  • 一 、JSONP 利用script标签的src属性天然可以跨域的特点,在跨域脚本中可以直接回调当前脚本的函数 va...
    海子小夜曲阅读 295评论 0 0
  • Django我自己的学习笔记,查看文集 一个基于Python的Web开发框架。 同时也是全国使用最多的Python...
    stiller阅读 2,140评论 0 0