from: https://segmentfault.com/a/1190000000709909

理由:在操作层面详细的讲解了跨域的操作。尤其是对于option请求的详解。收藏。

在构建Public APIs的过程中,首先要解决的第一个问题就是跨域请求的问题。

网络应用安全模型中很重要的一个概念是“同源准则”(same-origin policy)。该准则要求一个网站(由协议+主机名+端口号三者确定)的脚本(Script)、XMLHttpRequest和Websocket无权去访问另一个网站的内容。在未正确设置的情况下,跨域访问会提示如下错误:No 'Access-Control-Allow-Origin' header is present on the requested resource. 这项限制对于跨域的Ajax请求带来了很多不便。

典型的对于跨域请求的解决方案如下:

  • document.domain property
  • Cross-Origin Resource Sharing (CORS)
  • Cross-document messaging
  • JSONP

本文重点讲述的则是其中Cross-Origin Resource Sharing (CORS)的原理和在rails下的配置方式

Cross-Origin Resource Sharing (CORS)

CORS的基本原理是通过设置HTTP请求和返回中header,告知浏览器该请求是合法的。这涉及到服务器端和浏览器端双方的设置:请求的发起(Http Request Header)和服务器对请求正确的响应(Http response header)。

发起CORS请求

CORS兼容以下浏览器:

  • Internet Explorer 8+
  • Firefox 3.5+
  • Safari 4+
  • Chrome

原生Javascript可以通过XMLHttpRequest Object或XDomainRequest发起请求,详细的方式可以参见这篇文章:http://www.html5rocks.com/en/tutorials/cors/

JQuery的$.ajax()可以用来发起XHR或者CORS请求。然而该方法不支持IE下的XDomainRequest,需要使用JQuery的插件来实现IE下的兼容性(http://bugs.jquery.com/ticket/8283

  1. $.ajax({
  2. // The 'type' property sets the HTTP method.
  3. // A value of 'PUT' or 'DELETE' will trigger a preflight request.
  4. type: 'GET',
  5. // The URL to make the request to.
  6. url: 'http://updates.html5rocks.com',
  7. // The 'contentType' property sets the 'Content-Type' header.
  8. // The JQuery default for this property is
  9. // 'application/x-www-form-urlencoded; charset=UTF-8', which does not trigger
  10. // a preflight. If you set this value to anything other than
  11. // application/x-www-form-urlencoded, multipart/form-data, or text/plain,
  12. // you will trigger a preflight request.
  13. contentType: 'text/plain',
  14. xhrFields: {
  15. // The 'xhrFields' property sets additional fields on the XMLHttpRequest.
  16. // This can be used to set the 'withCredentials' property.
  17. // Set the value to 'true' if you'd like to pass cookies to the server.
  18. // If this is enabled, your server must respond with the header
  19. // 'Access-Control-Allow-Credentials: true'.
  20. withCredentials: false
  21. },
  22. headers: {
  23. // Set any custom headers here.
  24. // If you set any non-simple headers, your server must include these
  25. // headers in the 'Access-Control-Allow-Headers' response header.
  26. },
  27. success: function() {
  28. // Here's where you handle a successful response.
  29. },
  30. error: function() {
  31. // Here's where you handle an error response.
  32. // Note that if the error was due to a CORS issue,
  33. // this function will still fire, but there won't be any additional
  34. // information about the error.
  35. }
  36. });

服务器正确响应CORS请求

根据请求内容的不同,浏览器会需要添加对应的Header或者发起额外的请求。其中的细节都由浏览器负责处理,对于用户来讲是透明的。我们只需要了解如何针对差异的请求做出适当的响应即可。
我们将CORS请求分成以下两种类型:
1、简单请求
2、不是那么简单的请求

其中简单请求要求:
请求类型必须是GET,POST,HEAD三者中的一种
请求头(Header)中仅可以包含:

  • Accept
  • Accept Language
  • Content Language
  • Last Event ID
  • Content Type:仅接受application/x-www-form-urlencoded,multipart/form-data,text/plain

不满足上述条件的所有请求,例如PUT,DELETE或者是Content Type是application/json,均为“不是那么简单的请求”。针对这种请求,浏览器会在真实请求前,额外发起一次类型为OPTIONS的请求(Preflight request),只有服务器正确响应了OPTIONS请求后,浏览器才会发起该请求。(参见下图)

下文将针对b.com向a.com发起跨域请求说明服务器如何正确响应这两种类型的请求。

简单请求

浏览器在发出请求前为请求添加Origin来标明请求的来源,用户不可更改此内容。但Header中是否有Origin并不能作为判断是否是CORS请求的标准,因为不同浏览器对于此内容的处理方式并不完全一致,同源请求中也有可能出现Origin。
下面是一个b.com向a.com发起的一次GET请求。

  1. GET /cors HTTP/1.1
  2. Origin: http://b.com
  3. Host: a.com
  4. Accept-Language: en-US
  5. Connection: keep-alive
  6. User-Agent: Mozilla/5.0...

正确响应的返回如下,均由Access-Control-*开头:

  1. Access-Control-Allow-Origin: http://b.com
  2. Access-Control-Allow-Credentials: true
  3. Access-Control-Expose-Headers: FooBar
  4. Content-Type: text/html; charset=utf-8

Access-Control-Allow-origin: 此处是Server同意跨域访问的域名列表。如果允许任意网站请求资源,此处可以写为'*'
Access-Control-Expose-Headers: 可以设置返回的Header以传递数据。简单请求中允许使用的Header包括:Cache-Control,Content-Language,Content-Type,Expires,Last-Modified,Pragma。

不是那么简单的请求

如果希望使用PUT,DELETE等RESTful等超出了简单请求的范围的请求,浏览器则会在发起真实请求前先向服务器发起一次称作Preflight的OPTIONS的请求,以确保服务器接受该类型请求。其后才会发起真实要求的请求。请求的发起与简单请求并无差异,而服务器端则要针对Preflight Request做额外的响应。
下面是一次典型的Preflight请求:

  1. OPTIONS /cors HTTP/1.1
  2. Origin: http://b.com
  3. Access-Control-Request-Method: PUT
  4. Access-Control-Request-Headers: X-Custom-Header
  5. Host: a.com
  6. Accept-Language: en-US
  7. Connection: keep-alive
  8. User-Agent: Mozilla/5.0...

Access-Control-Request-Method代表真实请求的类型。Access-Control-Request-Headers则代表真实请求的请求头key内容。服务器仅在验证了这两项内容的合法性之后才会同意浏览器发起真实的请求。

  1. Access-Control-Allow-Origin: http://b.com
  2. Access-Control-Allow-Methods: GET, POST, PUT
  3. Access-Control-Allow-Headers: X-Custom-Header
  4. Content-Type: text/html; charset=utf-8

此处并未列举的一项返回头是Access-Control-Max-Age。因为每次请求均要发起一次额外的OPTIONS请求是非常低效的,因此可以为浏览器保存该返回头设置一个缓存的时间,单位为秒。在缓存过期以前,浏览器无需再次验证同一类型的请求是否合法。

真实请求的内容则和简单请求的内容完全一致,此处不再赘述。

下图非常详细的再次描述了服务器对于不同类型的请求如何做出正确的响应。

Rails下对CORS请求的配置

首先要确保在Routes.rb中加上对于OPTIONS请求的正确响应。
OPTIONS请求会发至真实请求的同一位置。如果未正确设置route,则会出现404无法找到请求地址的错误。
响应该请求的Controller的action方法可以设置为空,因为该请求的关键仅是正确返回请求头。
例如:真实请求/api/trips PUT,OPTIONS请求将发送至/api/trips OPTIONS。
match '/trips', to: 'trips#index', via: [:options]
或者可以使用:
match '*all' => 'application#cor', :constraints => {:method => 'OPTIONS'}

确保了OPTIONS请求可以正确被响应之后,在applicationController.rb中如下配置:

  1. before_filter :cors_preflight_check
  2. after_filter :cors_set_access_control_headers
  3. def cors_set_access_control_headers
  4. headers['Access-Control-Allow-Origin'] = '*'
  5. headers['Access-Control-Allow-Methods'] = 'POST, GET, PUT, DELETE, OPTIONS'
  6. headers['Access-Control-Max-Age'] = '1728000'
  7. end
  8. def cors_preflight_check
  9. if request.method == 'OPTIONS'
  10. headers['Access-Control-Allow-Origin'] = '*'
  11. headers['Access-Control-Allow-Methods'] = 'POST, PUT, DELETE, GET, OPTIONS'
  12. headers['Access-Control-Request-Method'] = '*'
  13. headers['Access-Control-Allow-Headers'] = 'Origin, X-Requested-With, Content-Type, Accept, Authorization'
  14. headers['Access-Control-Max-Age'] = '1728000'
  15. render :text => '', :content_type => 'text/plain'
  16. end
  17. end

对于简单请求,由cors_set_access_control_headers做出正确的响应。对于不是那么简单的请求,cors_preflight_check则会发现若请求是OPTIONS的时候,在实际执行cors_set_access_control_headers之前,拦截下该请求并返回text/plain的内容和正确的请求头。

总结

CORS请求作为构建Public API中很重要的一环,理解其大致的工作原理还是非常有意义的。不过在Chrome中,时常会出现provision header shown这样奇怪的错误,而这个错误出现的原因说法不一,基本上可以理解为跨域访问过程中如果请求出现问题chrome并没有办法很好的了解错误原因,无法准确的给出错误状态。另外,CORS调试也是一个问题,基于非浏览器的POSTMAN调试,有时不能够准确的反映出请求在浏览器下的真实工作状态,不知道如何才能更有效果的测试和调试CORS。
有兴趣的话,还可以进一步通过https://dvcs.w3.org/hg/cors/raw-file/tip/Overview.html了解W3C的CORS协议内容。http://arunranga.com/examples/access-control/

Reference:
http://en.wikipedia.org/wiki/Same_origin_policy
http://www.html5rocks.com/en/tutorials/cors/
http://www.nczonline.net/blog/2010/05/25/cross-domain-ajax-with-cross-origin-resource-sharing/
http://www.tsheffler.com/blog/?p=428
http://blog.rudylee.com/2013/10/29/rails-4-cors/
http://stackoverflow.com/questions/17858178/allow-anything-through-cors-policy

(转载)构建public APIs与CORS的更多相关文章

  1. spring cloud 集成 swagger2 构建Restful APIS 说明文档

    在Pom.xml文件中引用依赖 <dependencies> <dependency> <groupId>org.springframework.cloud< ...

  2. Spring Boot中使用Swagger2构建RESTful APIs

    关于 Swagger Swagger能成为最受欢迎的REST APIs文档生成工具之一,有以下几个原因: Swagger 可以生成一个具有互动性的API控制台,开发者可以用来快速学习和尝试API. S ...

  3. Spring Boot中使用Swagger2构建RESTful APIs介绍

    1.添加相关依赖 <!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 --> <depen ...

  4. Why we don’t recommend using List<T> in public APIs

    不推荐List<T>做API原因有如下两点:1.首先List<T> 设计之初就没有设计成可扩展的,我们不能重新其任何方法.这就意味着,我们操作List<T>的时候却 ...

  5. [转载] 构建微服务:使用API Gateway

    原文: http://mp.weixin.qq.com/s?__biz=MzA5OTAyNzQ2OA==&mid=206889381&idx=1&sn=478ccb35294c ...

  6. vue + vue-resource 跨域访问

    使用vue + vue-resource进行数据提交,后台使用RESTful API的方式存取数据,搞了一天,终于把后台搞好了.进行联合调试时,数据不能提交,报403错误: XMLHttpReques ...

  7. Laravel 精选资源大全

    原文链接  必备品 文档:Documentation API:API Reference 视频:Laracasts 新闻:Laravel News 中文文档 Laravel学院– Laravel 5. ...

  8. Spring Boot中使用Swagger2构建强大的RESTful API文档

    由于Spring Boot能够快速开发.便捷部署等特性,相信有很大一部分Spring Boot的用户会用来构建RESTful API.而我们构建RESTful API的目的通常都是由于多终端的原因,这 ...

  9. Spring MVC中使用 Swagger2 构建Restful API

    1.Spring MVC配置文件中的配置 [java] view plain copy <!-- 设置使用注解的类所在的jar包,只加载controller类 --> <contex ...

随机推荐

  1. linux 上安装redis

    下载地址:http://redis.io/download,下载最新文档版本. 本教程使用的最新文档版本为 2.8.17,下载并安装: $ wget http://download.redis.io/ ...

  2. xcode gdb/lldb调试命令

    命令                        解释 break NUM               在指定的行上设置断点. bt                      显示所有的调用栈帧.该 ...

  3. .NET Core 构建配置文件从 project.json 到 .csproj

    从 .NET Core SDK 1.0 Preview 3 build 004056 开始,.NET Core 弃用 project.json,回归 .csproj,主要原因是为了兼容 MSBuild ...

  4. HTML5- Canvas入门(一)

    周老虎落网的时候,网易跟腾讯都推出了牛逼轰轰的HTML5页面来展示其关系网(网易http://news.163.com/special/data_zyk/  ,腾讯http://news.qq.com ...

  5. 玩转Asp.net MVC 的八个扩展点

    MVC模型以低耦合.可重用.可维护性高等众多优点已逐渐代替了WebForm模型.能够灵活使用MVC提供的扩展点可以达到事半功倍的效果,另一方面Asp.net MVC优秀的设计和高质量的代码也值得我们去 ...

  6. .NET开发笔记(二十三) 谷歌地图下载

    关于如何将地球经纬度坐标系统转换成程序中常用到的平面2D坐标系统,网上的文章很多,参考http://www.cnblogs.com/beniao/archive/2010/04/18/1714544. ...

  7. 不要对外公开泛型List成员

    最近在阅读Framework Design Guidelines,本着现学现用的原则,于是就用FxCop工具对代码进行规范性检查时,发现了很多问题,其中包括命名以及一些设计上的规范. 其中,Do no ...

  8. 去年做了什么?OA。

    假前一天下午被经理和PM叫上楼,首要一个问题是我去年干了啥,我大致支吾了几句描述了下,一时也说不出个大概.后面就是一片悠长的面谈,什么没达到期望,公司状况不好.......哦,这是KPI评价啊,剩下的 ...

  9. Azure SQL Database Active Geo-Replication简介

    笔者在<迁移SQL Server 数据库到 Azure SQL 实战>一文中,介绍了如何把一个本地版的 SQL Server 数据库迁移到 Azure SQL Database.迁移虽然顺 ...

  10. 面向属性的CSS命名

    自从开始做前端开发以来,我发现在开发页面的时候,总是有一个问题十分影响自己的开发效率,这个问题就是css的命名,主要是指css类选择器的命名.这个问题主要体现在:第一,有的内容你压根想不出用什么名字来 ...