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.)原生实现




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设置成自身或更高一级的父域。且主域必须相同)








三、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


总结:通过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)



// b.html (http://www.js002.com/b.html)



// c.html (http://www.js001.com/c.html)


/*
window.parent 返回当前窗口的父窗口对象,若没有父窗口,则为parent属性自身的引用;若当前窗口是


// 页面B b.html  http://www.domain2.com/b.html

六、跨域资源共享(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.) 前端代码:

user input:

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..');
  });
})

你可能感兴趣的:(Ajax——同步/异步;跨域/跨域解决方法)