探讨跨域请求资源的几种方式(总结)


Posted in Javascript onDecember 02, 2016

跨域请求资源的几种方式,具体如下:

1.什么是跨域

2.JSONP

3.proxy代理

4.cors

5.xdr

由于浏览器同源策略,凡是发送请求url的协议、域名、端口三者之间任意一与当前页面地址不同即为跨域。具体可以查看下表

探讨跨域请求资源的几种方式(总结)

JSONP

这种方式主要是通过动态插入一个script标签。浏览器对script的资源引用没有同源限制,同时资源加载到页面后会立即执行(没有阻塞的情况下)。

<script>
   var _script = document.createElement('script');
   _script.type = "text/javascript";
   _script.src = "http://localhost:8888/jsonp?callback=f";
   document.head.appendChild(_script);
  </script>

实际项目中JSONP通常用来获取json格式数据,这时前后端通常约定一个参数callback,该参数的值,就是处理返回数据的函数名称。

<!doctype html>
<html>
 <head>
  <meta charset="utf-8">
  <meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no">
  <title>jsonp_test</title>

  <script>
   var f = function(data){
    alert(data.name);
   }
   /*var xhr = new XMLHttpRequest();
   xhr.onload = function(){
    alert(xhr.responseText);
   };
   xhr.open('POST', 'http://localhost:8888/cors', true);
   xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
   xhr.send("f=json");*/
  </script>
  
  <script>
   var _script = document.createElement('script');
   _script.type = "text/javascript";
   _script.src = "http://localhost:8888/jsonp?callback=f";
   document.head.appendChild(_script);
  </script>
 </head>
 var query = _url.query;
    console.log(query);
    var params = qs.parse(query);
    console.log(params);
    var f = "";
  
    f = params.callback;
  
    res.writeHead(200, {"Content-Type": "text/javascript"});
    res.write(f + "({name:'hello world'})");
    res.end();

探讨跨域请求资源的几种方式(总结)

缺点:

1、这种方式无法发送post请求(这里)

2、另外要确定jsonp的请求是否失败并不容易,大多数框架的实现都是结合超时时间来判定。

Proxy代理

这种方式首先将请求发送给后台服务器,通过服务器来发送请求,然后将请求的结果传递给前端。

<!doctype html>
<html>
 <head>
  <meta charset="utf-8">
  <meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no">
  <title>proxy_test</title>

  <script>
   var f = function(data){
    alert(data.name);
   }
   var xhr = new XMLHttpRequest();
   xhr.onload = function(){
    alert(xhr.responseText);
   };
   xhr.open('POST', 'http://localhost:8888/proxy?http://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer', true);
   xhr.send("f=json");
  </script>
 </head>
 
 <body>
 </body>
</html>
var proxyUrl = "";
   if (req.url.indexOf('?') > -1) {
     proxyUrl = req.url.substr(req.url.indexOf('?') + 1);
     console.log(proxyUrl);
   }
   if (req.method === 'GET') {
     request.get(proxyUrl).pipe(res);
   } else if (req.method === 'POST') {
     var post = '';   //定义了一个post变量,用于暂存请求体的信息

    req.on('data', function(chunk){  //通过req的data事件监听函数,每当接受到请求体的数据,就累加到post变量中
      post += chunk;
    });
  
    req.on('end', function(){  //在end事件触发后,通过querystring.parse将post解析为真正的POST请求格式,然后向客户端返回。
      post = qs.parse(post);
      request({
           method: 'POST',
           url: proxyUrl,
           form: post
         }).pipe(res);
    });
   }

探讨跨域请求资源的几种方式(总结)

需要注意的是如果你代理的是https协议的请求,那么你的proxy首先需要信任该证书(尤其是自定义证书)或者忽略证书检查,否则你的请求无法成功。12306就提供了一个鲜活的例子。

探讨跨域请求资源的几种方式(总结)

探讨跨域请求资源的几种方式(总结)

还需要注意一点,对于同一请求浏览器通常会从缓存中读取数据,我们有时候不想从缓存中读取,所以会加一个preventCache参数,这个时候请求url变成:url?preventCache=12345567....;这本身没有什么问题,问题出在当使用某些前端框架(比如jquery)发送proxy代理请求时,请求url为proxy?url,同时设置preventCache:true,框架不能正确处理这个参数,结果发出去的请求变成proxy?url&preventCache=123456(正长应为proxy?url?preventCache=12356);后端截取后发送的请求为url&preventCache=123456,根本没有这个地址,所以你得不到正确结果。

CORS

这是现代浏览器支持跨域资源请求的一种方式。

探讨跨域请求资源的几种方式(总结)

当你使用XMLHttpRequest发送请求时,浏览器发现该请求不符合同源策略,会给该请求加一个请求头:Origin,后台进行一系列处理,如果确定接受请求则在返回结果中加入一个响应头:Access-Control-Allow-Origin;浏览器判断该相应头中是否包含Origin的值,如果有则浏览器会处理响应,我们就可以拿到响应数据,如果不包含浏览器直接驳回,这时我们无法拿到响应数据。

<!doctype html>
<html>
 <head>
  <meta charset="utf-8">
  <meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no">
  <title>jsonp_test</title>

  <script>
   /*var f = function(data){
    alert(data.name);
   }*/
   var xhr = new XMLHttpRequest();
   xhr.onload = function(){
    alert(xhr.responseText);
   };
   xhr.open('POST', 'http://localhost:8888/cors', true);
   xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
   xhr.send("f=json");
  </script>
  
  <script>
   /* var _script = document.createElement('script');
   _script.type = "text/javascript";
   _script.src = "http://localhost:8888/jsonp?callback=f";
   document.head.appendChild(_script);*/
  </script>
 </head>
 
 <body>
 </body>
</html>

前端cors

if (req.headers.origin) {

      res.writeHead(200, {
        "Content-Type": "text/html; charset=UTF-8",
        "Access-Control-Allow-Origin":'http://localhost'/*,
        'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
        'Access-Control-Allow-Headers': 'X-Requested-With, Content-Type'*/
      });
      res.write('cors');
      res.end();
    }

探讨跨域请求资源的几种方式(总结)

如果我们把Access-Control-Allow-Origin去掉,浏览器会驳回响应,我们也就拿不到数据。

探讨跨域请求资源的几种方式(总结)

需要注意的一点是Preflighted Request的透明服务器验证机制支持开发人员使用自定义的头部、GET或POST之外的方法,以及不同类型的主题内容。总结如如:

1、非GET 、POST请求

2、POST请求的content-type不是常规的三个:application/x- www-form-urlencoded(使用 HTTP 的 POST 方法提交的表单)、multipart/form-data(同上,但主要用于表单提交时伴随文件上传的场合)、text/plain(纯文本)

3、POST请求的payload为text/html

4、设置自定义头部

OPTIONS请求头部中会包含以下头部:Origin、Access-Control-Request-Method、Access-Control-Request-Headers,发送这个请求后,服务器可以设置如下头部与浏览器沟通来判断是否允许这个请求。

Access-Control-Allow-Origin、Access-Control-Allow-Method、Access-Control-Allow-Headers

var xhr = new XMLHttpRequest();
   xhr.onload = function(){
    alert(xhr.responseText);
   };
   xhr.open('POST', 'http://localhost:8888/cors', true);
   xhr.setRequestHeader("Content-Type", "text/html");
   xhr.send("f=json");
if (req.headers.origin) {

      res.writeHead(200, {
        "Content-Type": "text/html; charset=UTF-8",
        "Access-Control-Allow-Origin":'http://localhost',
        'Access-Control-Allow-Methods': 'GET,POST,OPTIONS',
        'Access-Control-Allow-Headers': 'X-Requested-With, Content-Type'/**/
      });
      res.write('cors');
      res.end();
    }

探讨跨域请求资源的几种方式(总结)

如果你在调试状态,你会发现后台代码执行了两遍,说明发送了两次请求。注意一下我们的onload代码只执行了一次,所以说OPTIONS请求对程序来说是透明的,他的请求结果会被缓存起来。

如果我们修改一下后台代码,把Content-Type去掉,你会发现OPTIONS请求失败。

探讨跨域请求资源的几种方式(总结)

探讨跨域请求资源的几种方式(总结)

通过setRequestHeader('X-Request-With', null)可以避免浏览器发送OPTIONS请求。

根据我的测试,当使用cors发送跨域请求时失败时,后台是接收到了这次请求,后台可能也执行了数据查询操作,只是响应头部不合符要求,浏览器阻断了这次请求。

XDR

这是IE8、IE9提供的一种跨域解决方案,功能较弱只支持get跟post请求,而且对于协议不同的跨域是无能为力的,比如在http协议下发送https请求。看一下微软自己的例子就行

<!DOCTYPE html>

<html>
<body>
 <h2>XDomainRequest</h2>
 <input type="text" id="tbURL" value="http://www.contoso.com/xdr.txt" style="width: 300px"><br>
 <input type="text" id="tbTO" value="10000"><br>
 <input type="button" onclick="mytest()" value="Get">   
  <input type="button" onclick="stopdata()" value="Stop">   
  <input type="button" onclick="readdata()" value="Read">
 <br>
 <div id="dResponse"></div>
 <script>
  var xdr;
  function readdata()
  {
   var dRes = document.getElementById('dResponse');
   dRes.innerText = xdr.responseText;
   alert("Content-type: " + xdr.contentType);
   alert("Length: " + xdr.responseText.length);
  }
  
  function err()
  {
   alert("XDR onerror");
  }

  function timeo()
  {
   alert("XDR ontimeout");
  }

  function loadd()
  {
   alert("XDR onload");
   alert("Got: " + xdr.responseText);
  }

  function progres()
  {
   alert("XDR onprogress");
   alert("Got: " + xdr.responseText);
  }

  function stopdata()
  {
   xdr.abort();
  }

  function mytest()
  {
   var url = document.getElementById('tbURL');
   var timeout = document.getElementById('tbTO');
   if (window.XDomainRequest)
   {
    xdr = new XDomainRequest();
    if (xdr)
    {
     xdr.onerror = err;
     xdr.ontimeout = timeo;
     xdr.onprogress = progres;
     xdr.onload = loadd;
     xdr.timeout = tbTO.value;
     xdr.open("get", tbURL.value);
     xdr.send();
    }
    else
    {
     alert("Failed to create");
    }
   }
   else
   {
    alert("XDR doesn't exist");
   }
  }
 </script>
</body>
</html>

以上就是我在实际项目中遇到的跨域请求资源的情况,有一种跨域需要特别注意就是在https协议下发送https请求,除了使用proxy代理外其他方法都无解,会被浏览器直接block掉。如果哪位道友知道解决方法,麻烦你告诉我一声。

最后附上完整的测试demo

iss中:

<!doctype html>
<html>
 <head>
  <meta charset="utf-8">
  <meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no">
  <title>jsonp_test</title>

  <script>
   /*var f = function(data){
    alert(data.name);
   }*/
   var xhr = new XMLHttpRequest();
   xhr.onload = function(){
    alert(xhr.responseText);
   };
   xhr.open('POST', 'http://localhost:8888/cors', true);
   xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
   xhr.setRequestHeader("aaaa","b");
   xhr.send("f=json");
  </script>
  
  <script>
   /* var _script = document.createElement('script');
   _script.type = "text/javascript";
   _script.src = "http://localhost:8888/jsonp?callback=f";
   document.head.appendChild(_script);*/
  </script>
 </head>
 
 <body>
 </body>
</html>

node-html

<!doctype html>
<html>
 <head>
  <meta charset="utf-8">
  <meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no">
  <title>proxy_test</title>

  <script>
   var f = function(data){
    alert(data.name);
   }
   var xhr = new XMLHttpRequest();
   xhr.onload = function(){
    alert(xhr.responseText);
   };
   xhr.open('POST', 'http://localhost:8888/proxy?https://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer', true);
   xhr.send("f=json");
  </script>
 </head>
 
 <body>
 </body>
</html>

 

node-server

var http = require('http');
var url = require('url');
var fs = require('fs');
var qs = require('querystring');
var request = require('request');

http.createServer(function(req, res){
  var _url = url.parse(req.url);
  if (_url.pathname === '/jsonp') {
    var query = _url.query;
    console.log(query);
    var params = qs.parse(query);
    console.log(params);
    var f = "";
  
    f = params.callback;
  
    res.writeHead(200, {"Content-Type": "text/javascript"});
    res.write(f + "({name:'hello world'})");
    res.end();
  } else if (_url.pathname === '/proxy') {
   var proxyUrl = "";
   if (req.url.indexOf('?') > -1) {
     proxyUrl = req.url.substr(req.url.indexOf('?') + 1);
     console.log(proxyUrl);
   }
   if (req.method === 'GET') {
     request.get(proxyUrl).pipe(res);
   } else if (req.method === 'POST') {
     var post = '';   //定义了一个post变量,用于暂存请求体的信息

    req.on('data', function(chunk){  //通过req的data事件监听函数,每当接受到请求体的数据,就累加到post变量中
      post += chunk;
    });
  
    req.on('end', function(){  //在end事件触发后,通过querystring.parse将post解析为真正的POST请求格式,然后向客户端返回。
      post = qs.parse(post);
      request({
           method: 'POST',
           url: proxyUrl,
           form: post
         }).pipe(res);
    });
   }
  } else if (_url.pathname === '/index') {
    fs.readFile('./index.html', function(err, data) {
     res.writeHead(200, {"Content-Type": "text/html; charset=UTF-8"});
      res.write(data);
      res.end();
    });
  } else if (_url.pathname === '/cors') {
    if (req.headers.origin) {

      res.writeHead(200, {
        "Content-Type": "text/html; charset=UTF-8",
        "Access-Control-Allow-Origin":'http://localhost',
        'Access-Control-Allow-Methods': 'GET,POST,OPTIONS',
        'Access-Control-Allow-Headers': 'X-Requested-With, Content-Type,aaaa'/**/
      });
      res.write('cors');
      res.end();
    }
  }
  
}).listen(8888);

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
用js计算页面执行时间的函数
Dec 07 Javascript
JavaScript中逗号运算符介绍及使用示例
Mar 13 Javascript
KnockoutJS 3.X API 第四章之数据控制流with绑定
Oct 10 Javascript
JavaScript中的ajax功能的概念和示例详解
Oct 17 Javascript
简述vue中的config配置
Jan 23 Javascript
webpack配置打包后图片路径出错的解决
Apr 26 Javascript
JavaScript实现点击出现图片并统计点击次数功能示例
Jul 23 Javascript
使用JavaScript破解web
Sep 28 Javascript
vue使用codemirror的两种用法
Aug 27 Javascript
关于layui 实现点击按钮添加一行(方法渲染创建的table)
Sep 29 Javascript
关于React Native 无法链接模拟器的问题
Jun 21 Javascript
react 路由Link配置详解
Nov 11 Javascript
jQuery实现倒计时(倒计时年月日可自己输入)
Dec 02 #Javascript
JavaScript 计算笛卡尔积实例详解
Dec 02 #Javascript
jQuery联动日历的实例解析
Dec 02 #Javascript
利用jQuery插件imgAreaSelect实现获得选择域的图像信息
Dec 02 #Javascript
利用jQuery插件imgAreaSelect实现图片上传裁剪(同步显示图像位置信息)
Dec 02 #Javascript
有关suggest快速删除后仍然出现下拉列表的bug问题
Dec 02 #Javascript
Vue.js路由组件vue-router使用方法详解
Dec 02 #Javascript
You might like
php中将汉字转换成拼音的函数代码
2012/09/08 PHP
ThinkPHP调试模式与日志记录概述
2014/08/22 PHP
PHP的Yii框架中View视图的使用进阶
2016/03/29 PHP
laravel 解决多库下的DB::transaction()事务失效问题
2019/10/21 PHP
Javascript - HTML的request类
2006/07/15 Javascript
JavaScript加密解密7种方法总结分析
2007/10/07 Javascript
Mootools 1.2教程 类(一)
2009/09/15 Javascript
JavaScript 对Cookie 操作的封装小结
2009/12/31 Javascript
javascript下判断一个元素是否存在的代码
2010/03/05 Javascript
jquery选择器简述
2015/08/31 Javascript
Javascript计算二维数组重复值示例代码
2016/12/18 Javascript
Javascript 对cookie操作详解及实例
2016/12/29 Javascript
轻松理解JavaScript之AJAX
2017/03/15 Javascript
打造通用的匀速运动框架(实例讲解)
2017/10/17 Javascript
vue项目中axios使用详解
2018/02/07 Javascript
浅谈react-router@4.0 使用方法和源码分析
2019/06/04 Javascript
node实现爬虫的几种简易方式
2019/08/22 Javascript
vue在线动态切换主题色方案
2020/03/26 Javascript
JavaScript 引用类型实例详解【数组、对象、严格模式等】
2020/05/13 Javascript
vue中的.$mount('#app')手动挂载操作
2020/09/02 Javascript
Vue 按照创建时间和当前时间显示操作(刚刚,几小时前,几天前)
2020/09/10 Javascript
[46:10]2014 DOTA2国际邀请赛中国区预选赛 CnB VS HGT
2014/05/21 DOTA
python通过线程实现定时器timer的方法
2015/03/16 Python
python实现的jpg格式图片修复代码
2015/04/21 Python
Python模块WSGI使用详解
2018/02/02 Python
详解Python中的分组函数groupby和itertools)
2018/07/11 Python
python time.sleep()是睡眠线程还是进程
2019/07/09 Python
Python configparser模块操作代码实例
2020/06/08 Python
html5新特性与用法大全
2018/09/13 HTML / CSS
Mixbook加拿大:照片书,照片卡,剪贴簿,年历和日历
2017/02/21 全球购物
2014国培学习感言
2014/03/05 职场文书
新年主持词
2014/03/27 职场文书
我们的节日元宵活动方案
2014/08/23 职场文书
暑期政治学习心得体会
2014/09/02 职场文书
公司委托书格式范本
2014/09/16 职场文书
Python中 range | np.arange | np.linspace三者的区别
2022/03/22 Python