js学习笔记:跨域

2016-11-26 08:48:59来源:CSDN作者:crystal6918人点击

第七城市

CORS 跨域资源共享

通过XHR实现Ajax通信的一个主要限制,来源于跨域安全策略。默认情况下,XHR对象只能访问与包含它的页面位于同一域中的资源,就是使用 XMLHttpRequest 对象发起 HTTP 请求就必须遵守同源策略。这种安全策略可以预防某些恶意行为。但是,实现合理的跨域请求也是十分重要的。

CORS(Cross-Origin Resource Sharing,跨域资源共享),是W3C的一个工作草案,定义了在必须访问跨域资源时,浏览器与服务器应该如何沟通。它的思想是,使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功还是应该失败。

跨源资源共享标准通过新增一系列 HTTP 头,让服务器能声明哪些来源可以通过浏览器访问该服务器上的资源。
另外,对那些会对服务器数据造成破坏性影响的 HTTP 请求方法(特别是 GET 以外的 HTTP 方法,或者搭配某些MIME类型的POST请求),标准强烈要求浏览器必须先以 OPTIONS 请求方式发送一个预请求(preflight request),从而获知服务器端对跨源请求所支持 HTTP 方法。
在确认服务器允许该跨源请求的情况下,以实际的 HTTP 请求方法发送那个真正的请求。服务器端也可以通知客户端,是不是需要随同请求一起发送信用信息(包括 Cookies 和 HTTP 认证相关数据)。

接下来具体看一下这三种CORS场景:

简单请求

所谓的简单请求是指:

  • 只使用 GET, HEAD 或者 POST 请求方法。如果使用 POST 向服务器端传送数据,则数据类型(Content-Type)只能是 application/x-www-form-urlencoded, multipart/form-data 或 text/plain中的一种。
  • 不会使用自定义请求头

比如一个简单的GET或POST请求,它没有自定义的头部,主题内容是text/plain。在发送该请求时,需要给它附加一个额外的Origin头部,其中包含请求页面的源信息(协议、域名和端口),一边服务器根据这个头部信息来决定是否给予响应。

Origin:http://www.nczonline.net

如果服务器认为这个请求可以接受,就在Access-Control-Allow-Origin头部中回发相同的源信息。

Access-Control-Allow-Origin:http://www.nczonline.net

如果没有这个头部,或者这个头部的源信息不匹配,浏览器就会驳回请求。

如上,通过使用 Origin 和 Access-Control-Allow-Origin 就可以完成最简单的跨站请求。不过 Access-Control-Allow-Origin 需要为 * 或者包含由 Origin 指明的站点。

浏览器对CORS的实现

除了IE,其他浏览器都通过XMLHttpRequest对象实现了对CORS的原生支持。
在尝试打开不同来源的资源时,无需编写额外的代码就可以出发这一行为。
如果要请求位于另一个域中的资源,使用标准的XHR对象并在open()方法中传入绝对URL即可:

var xhr = new XMLHttpRequest();xhr.open("get","http://www.somewhere-else.com/page","true");xhr.send();

跨域XHR对象也可以访问status和statusText属性,并且也支持同步请求。

但跨域XHR有一些限制,为了安全这些限制是必须的:

  • 不能使用setRequestHeader()设置自定义头部
  • 不能发送和接受cookie
  • 调用getAllResponseHeaders()方法总会返回空字符串

由于无论同源请求还是跨源请求都使用相同的接口,因此对于本地资源最好使用相对URL,访问远程资源时再使用绝对URL。这样做能消除歧义,避免出现限制访问头部或本地cookie等问题。

预请求

不同于上面讨论的简单请求,“预请求”要求必须先发送一个 OPTIONS 请求给目的站点,来查明这个跨站请求对于目的站点是不是安全可接受的。这样做,是因为跨站请求可能会对目的站点的数据造成破坏。
当请求具备以下条件,就会被当成预请求处理:

  • 请求以 GET, HEAD 或者 POST 以外的方法发起请求。或者,使用 POST,但请求数据为 application/x-www-form-urlencoded, multipart/form-data 或者 text/plain 以外的数据类型
    比如说,用 POST 发送数据类型为 application/xml 或者 text/xml 的 XML 数据的请求。
  • 使用自定义请求头(比如添加诸如 X-PINGOTHER)

这种请求发送下列头部:

Origin: http://foo.example  与简单请求相同Access-Control-Request-Method: POST   //请求自身使用的方法Access-Control-Request-Headers: X-PINGOTHER  //(可选)自定义的头部信息,多个头部以逗号分隔。

发送这个请求后,服务器可以决定是否允许这种类型的请求。
服务器通过在响应中发送如下头部与浏览器进行沟通:

Access-Control-Allow-Origin: http://foo.exampleAccess-Control-Allow-Methods: POST, GET, OPTIONS  //允许的方法,多个方法以逗号分隔Access-Control-Allow-Headers: X-PINGOTHER  //允许的头部,多个头部以逗号分隔Access-Control-Max-Age: 1728000  //应该将这个请求缓存多长时间

响应头Access-Control-Max-Age告诉浏览器,本次“预请求”的响应结果有效时间是多久。在上面的例子里,1728000秒代表着20天内,浏览器在处理针对该服务器的跨站请求,都可以无需再发送“预请求”,只需根据本次结果进行判断处理。

带凭据的请求

默认情况下,跨域请求不提供凭据(cookie、HTTP认证及客户端SSL证明)。通过将withCredentials属性设置为true,可以指定某个请求应该发送凭据。
如果服务器接受带凭据的请求,会用下面的HTTP头部来响应:

Access-Control-Allow-Credentials:true

如果发送的是带凭据的请求,但服务器的响应中没有包含这个头部,那么浏览器就不会把响应交给js,而且会调用onerror()事件处理程序。
另外,服务器还可以在预请求的响应中发送这个头部,表示允许源发送带凭据的请求。

在CORS出现之前,要实现跨域Ajax通信颇费一些周折,开发人员都会利用DOM中能够执行跨域请求的功能,在不依赖XHR对象的情况下也能发送某种请求。虽然CORS已经广泛应用,但这些技术由于不需要修改服务器代码,现在也经常应用。

图像Ping

第一种跨域请求技术是使用< img>标签。
我们知道一个网页可以从任何网页中加载图像,不用担心跨域不跨域,这也是在线广告跟踪浏览量的主要方式。
可以动态创建图像,使用它们的onload和onerror事件处理程序来确定是否接受到了响应。

  • 动态创建图像应常用于图像Ping。图像Ping是与服务器进行简单、单向的跨域通信的一种方式。
  • 请求的数据是通过查询字符串形式发送的,而响应可以是任何内容,但通常是像素图或者204响应。
  • 通过图像Ping,浏览器得不到任何具体的数据,但通过侦听load和error事件,它能知道响应是什么时候收到的。
var img = new Image();img.onload =img.onerror = function(){    alert("done")};img.src = "http://www.example.com/test?name=Nicholas";

这里创建了一个Image实例,然后将onload和onerror事件处理程序指定为同一个函数。这样无论是什么响应,只要请求完成,就能得到通知。
请求从设置src属性那一刻开始,而这个例子在请求中发送了一个name参数。

图像Ping最常用于跟踪用户点击页面或动态广告曝光次数。

但图像Ping有两个主要的缺点:一是只能发送GET请求,二是无法访问服务器的响应文本。
因此,图像Ping只能用于浏览器与服务器间的单向通信。

JSONP

JSONP是JSON with padding的简写,表示填充式JSON或参数式JSON,是应用JSON的一种新方法。
JSONP看起来与JSON差不多,只不过是被包含在函数调用中的JSON:

callback({"name":"nicholas"});

JSONP由两部分组成:回调函数和数据。
回调函数是当响应到来时应该在页面中调用的函数。回调函数的名字一般是在请求中指定的。
数据就是传入回调函数中的JSON数据。
下面是一个典型的JSONP请求:

http://freegeoip.net/json/?callback=handleResponse

通过查询字符串来指定JSONP服务的回调函数是很常见的,就像上面的URL所示,这里指定的回调函数的名字叫handleResponse()。

JSONP是通过动态script元素来使用的,使用时可以为src属性指定一个跨域URL。
这里的script元素与img元素类似,都有能力不受限制地从其他域加载资源。
因为JSONP是有效的JavaScript代码,所以在请求完成后,即在JSONP响应加载到页面中以后就会立即执行:

function handleResponse(response){    alert(response);}var script = document.createElement("script");script src = "http://freegeoip.net/json/?callback=handleResponse";document.body.insertBefore(script,document.body.firstChild);

JSONP之所以很流行,主要原因是它非常简单易用。与图像Ping相比,它的优点是能够直接访问响应文本,支持在浏览器与服务器间双向通信。

但是JSON也有两点不足:

  • JSONP是从其他域中加载代码执行。如果其他域不安全,很可能会在响应中夹带一些恶意代码,而此时除了完全放弃JSONP调用之外,没有办法追究。因此在使用不是自己运维的web服务时,一定要保证它安全可靠。
  • 要确定JSONP请求是否失败并不容易。虽然h5给script元素新增了一个onerror事件处理程序,但目前还没有得到任何浏览器支持。

Comet

Ajax是一种从页面向服务器请求数据的技术,而Comet则是一种服务器向页面推送数据的技术。Comet能够让信息近乎实时地被推送到页面上,非常适合处理体育比赛的分数和股票报价。
有两种实现Comet的方式:长轮询和流。
传统的短轮询即浏览器定时向服务器发送请求,看看有没有更新的数据。

这里写图片描述

Comet的长轮询把短轮询颠倒了一下。页面发起一个到服务器的请求,然后服务器一直保持连接打开,直到有数据可发送。发送完数据之后,浏览器关闭连接,随机又发起一个到服务器的新请求。这一过程在页面打开期间一直持续不断:

这里写图片描述

无论是短轮询还是长轮询,浏览器都要在接收数据之前先发起对服务器的连接。
两者最大的区别在于服务器何时发送数据:短轮询是服务器立即发送响应,无论数据是否有效;而长轮询是等待发送响应。
轮询的优势是所有浏览器都支持,因为使用XHR对象和setTimeout就能实现。

第二种流行的Comet实现是HTTP流。流不同于上述两种轮询,因为它在页面的整个生命周期内只使用一个HTTP连接
具体来说,就是浏览器向服务器发送一个请求,而服务器保持连接打开,然后周期性地向浏览器发送数据。

所有服务器端语言都支持打印到输出缓存然后刷新(将输出缓存中的内容一次性全部发送到客户端)的功能,这也正是实现HTTP流的关键所在。

在大多数浏览器中,通过监听readystatechange事件及检测readyState的值是否为3,就可能利用XHR对象实现HTTP流。
在这些浏览器中,随着不断从服务器接收数据,readyState的值会周期性地变成3.当readyState的值变为3时,responseText属性中就会保存接收到的所有数据,此时,就需要比较此前接收到的数据,决定从什么位置开始取得最新的数据。
使用XHR对象实现HTTP流的典型代码如下:

function createStreamingClient(url,progress,finished){    var xhr = new XMLHttpRequest(),     recieved = 0;    xhr.open("get",url,true);    xhr.onreadystatechange = function(){        var result;        if(xhr.readyState == 3){            //只取得最新数据并调整计数器            result = xhr.responseText.substring(recieved);            recieved += result.length;;            //调用progress回调函数            progress(result);        }else if(xhr.readyState == 4){            finished(xhr.responseText);        }    };    xhr.send(null);    return xhr;}var client = createStreamingClient("streaming.php",function(data){    alert("recieved"+data);},function(data){    alert("done");})

只要readystatechange事件发生,而且readyState值为3,就对responseText进行分割以取得最新数据。这里的recieved用于记录已经处理了多少字符,每次readyState值为3时都递增。

实际上管理Comet的连接是很容易出错的。

第七城市

最新文章

123

最新摄影

微信扫一扫

第七城市微信公众平台