标准CORS抹平跨域Ajax凸现了Web app多域分布式结构

跨域问题和任务应该是当下前端开者常规任务之一。跨域的全称是跨域Ajax请求(Cross-Domain Ajax Requests),它与「一般Ajax请求」有什么联系呢?按「技术与任务关系」的指引,如果跨域是新的Ajax任务,那跨域请求引入什么新任务内容,又有怎样的新技术呢?

我发现,由于缺乏一种全面的抽象的指导(我指的是一种理论),很多「有关跨域编程的文章」都流于表面和解释混乱,例如本文将略译的《Using CORS for Cross-Domain Ajax Requests》,标题“使用CORS技术实现跨域请求”,CORS是不是一种技术,是谁的技术,技术完成何种任务,这都是要相关解释的,当解释下来后发现,CORS并不是种技术(下面会提到),并不是前端开发者针对跨域编程任务的技术。本文先就跨域编程的问题小结一下自己的理解,并以此作为跟随前一文,对找到有关「技术和任务理论」的新特例。文未略这篇文章作为对CORS的介绍。

跨域资源与多域程序

跨域Ajax问题最大的揭示不是新的编程任务,而是「web app的域属性的存在」[注]。最初Ajax操作(动态请求一个服务器资源)默认是同一「域」,开发者对「域」没有感觉,是透明的。跨域任务不是说程序有跨域请求的需要,而是说,一支复杂的现代的web程序,需要也可以有多个【域组成】,出于性能和开发的原因。简单的程序只需一个服务器(域),复杂的程序则可能要多个服务器(域)来构建程序形态。

注:一支wep app有哪些的形式属性?增加了一种新任务,任务输出的结果——程序也改变了性质,这符合「可用最终产品来概述任务」,因为描述一项从未完成过的任务,方式是很多的。

域属性与web app程序形式

从一个更逻辑层面看,页面上的所有东西,包括文字图片,和脚本代码,都是从服务器来的,都是程序的形式部分,http请求操作是web app特有的数据流。http协议是web app很重要的部分,web app本质就是一种分布式应用系统,它需在网络上交换数据。而开发者一直对域没有概念,因为在静态超链接的时代,浏览器都代理HTTP通讯;到了动态网页时代,会话概念出现,页面内容才有了域边界,引用别域页面属于出站,离开了程序。然而页面元素可引用域外资源,标准允许由浏览器代理,通过src属性引用域资源,例如图片,第三方js库(这也json with padding的技术基础);到了Ajax时代,越过浏览器,用JS请求域外资源成为危险动作,同源策略浮出水面,资源域概念更加的突出。

Using CORS for Cross-Domain Ajax Requests

当我们开发一支较复杂的web应用时,常常需要使用Ajax 请求「当前面页面所在服务器(域)之外」的资源。这种需求在开发大型企业应用时非常的常见 ,因为企业的数据常常分布在多个服务器,拥有多个子域(sub-domained)。

直到近期(写作时为2013年),请求域外资源还不是件容易的事,因为有同源策略的限制。开发者只能通过一些技巧来突破同源安全限制,实现跨域请求,例如 server-side proxies, JSONP, and iframe proxies using post message。

不过还好,随着新W3C标准的的出现,域外资源请求的问题得到“根本”解决,安全的使用Ajax 请求域外资源得到浏览器和服务器的支持。这个新标准就是「跨域资源共享 Cross Origin Resource Sharing (CORS)」。目前,主流浏览器基本都实现了这个标准,包括 Firefox 3.5, Safari 4,  Chrome 3 和  Internet Explorer 10 。

什么是跨域请求?

你们的页面(和JS脚本)作为一种网络资源都有一个域归属,例如_mydomain.com_ ,当你的页面内的JS代码使用XmlHttpRequest or XDomainRequst请求一个其它服务器( otherdomain.com)资源时,例如一条数据库记录,这个请求就是跨域请求(cross-origin request)。在过去,出于安全考虑,这种手动请求(非浏览器代理)默认是被浏览器禁止的(标准规定)。

CORS的原理

CORS的安全原理,主要靠「在跨域请求时」新增头部信息(HTTP headers )来实现的,例如,浏览器发现一个跨域XHR请求时会增加一个 origin的头,通知服务器这是个跨域请求;服务器回应的头部中也会有信息告诉浏览器跨域是否被接受。整个安全机制中,服务器起着关键作用,它决定哪些域来的请求可以被接受。

CORS是通过「更新HTTP协议本身」,根本的解决了跨域问题,所以,CORS 不是一种具体的新技术,而是既有技术,和既有使用方式使用了新标准。因此,对开发者,同域和跨域的请求没有区别,跨域安全机制靠浏览器和服务器在通信协议层面实现。CORS的安全原理对client side是透明的,因为由浏览器代理了,但是CORS还有服务器端逻辑,这看这个例子,CORS的服务器可以是公共的,也常是「应用特制的」,所以需要开发者介入,但是开发者配置服务器的CORS功能时,不是以「跨域应用」的角色,而是一个元角色,CORS依然不是前端开发者的完成跨域任务的工具或技术,是服务器配置的技术。

CORS的安全原理中,服务器配置起着关键作用,完成这个任务需要我们对「各类跨域请求」,以协议规定有相当的熟练。

简单请求

CORS标准规定了跨域HTTP交换中服务器和浏览器约定的行为;跨域请求主要分为两类,第一类是简单请求。简单的跨域请求的特征是请求方法、请求数据简单,没有额外头信息,浏览器(的行为)只是简单附加一个Origin头 和 一个Referrer头,标明页面域(与服务器的域不同)。

例如,下面的例子,请求 otherdomain.com 上的 some-resource

$.get( url: 'http://otherdomain.com/some-resource' ).done successFn

浏览器附加两个头信息:

GET http://otherdomain.com/some-resource/ HTTP/1.1 Referer: http://mydomain.com/myapp/ Origin: http://mydomain.com

CORS服务器收到请求后,如果接受请求会返回资源,并在回应的报文中附加以下头的信息:

1
2
Access-Control-Allow-Origin: http://mydomain.com
Content-Type: application/json

当浏览器读到Access-Control-Allow-Origin的值和页面的域相同时,知道服务器的请求成功了。如果读得到值是星号 (“*”),表明服务器是公共的资源服务器,可允许任何外域请求。

高级请求与预检请求 Preflighted Requests

与简单请求相对的,是复杂请求或高级请求,MDN认为「高级请求」最大的不同是对服务器产生side-effect,主要特征有:

第一,请求方法是GET, POST,  HEAD之外的;

第二,有自定义的头;

第三,请求体格式是text/plain, application/x-www-form-urlencoded, or multipart/form-data之外的

为了保证高级请求可用,CORS标准规定浏览器在高级请求在正式发出之前要有一次预检请求(preflighted request),目的为是确认服务器是「CORS服务器,并且支持复杂请求所需的请求类型」。请求类型由「请求方法」、「**头部」**和「**请求体格式」**三者定义。例如,如果我们更新一个域外(otherdomain.com)资源_some-resource_时,并且有个自定义头(X-Foo),代码如下:

$.ajax( url: 'http://otherdomain.com/some-resource' type: 'PUT' headers: 'X-Foo': 'bar' data: someResource ).done successFn

请求方法是PUT,有自定义头,属于高级类型,故浏览器会先发了一个预检请求(使用OPTIONS请求方法),头信息如下:

1
2
3
4
5
6
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Foo

如果CORS服务器完全支持,则会返回如下头信息(这次预检请求的结果被缓存3600秒):

1
2
3
4
5
HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://mydomain.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: X-Foo
Access-Control-Max-Age: 3600

Then the browser will send the actual PUT request.

确认服务器可用后,浏览器才发出高级请求的操作:

1
2
3
Content-Type: application/xml; charset=UTF-8
X-Foo: bar

预检机制是为了确保老式服务器(not CORS-enabled)不会接受高级类请求,以免对服务器的状态产生不完整性。

认证请求

还有一类请求需要注意的,就是认证请求(Credentialed Requests)。浏览器使用XHR发出跨域请求时,默认是不附带Cookies和HTTP Auth信息的。如果需要,你必须在配置XHR时打开 withCredentials 这个属性。例如上面的简单请求的例子,你需要如下的打开(注意jQuery必须 1.5.1+):

$.get( url: 'http://otherdomain.com/some-resource' xhrFields: 'withCredentials': true ).done successFn

小结

根据上一文,我们得到了有关Ajax这种技术和任务更细致的理解:

由此,我们得到对Ajax更进一步细致的理解:

  • 第一,Ajax任务可以叫「微HTTP数据交换」任务;
  • 第二,Ajax技术核心是XHR(一个提供HTTP传输功能的BOM对象)和数据格式;

然,跨域Ajax也是「微HTTP数据交换」,或者叫动态微交换任务——使用js自定义,绕过浏览器,发出微交换。跨域问题的研究让我们也进一步对「动态微交换任务」和web app的分布式结构有了新的认识。也包括对技术和任务种类的新认识(注意下图的两层任务的描述):

web app HTTP

另外,本文我们也认识到,在CORS之前,跨域Ajax是一件新任务,它需使用“新技术”——例如json with padding将DOM动态加载功能和src 属性的「挪作他用」。而有了CORS,对于微交换的任务,跨域与同域的任务没有区别,只是微交换分为了两种,程序的分布式形态变了,而且只有服务器可见,服务器配置CORS不属于微交换编程任务。

参考

裸男
Nakeman.cn 2023 Build by Gatsby and Tailwind, Deploy on Netlify.