Rails 如何使用 Rails 建立可跨域访问的 API?

cassiuschen · 2015年02月26日 · 最后由 cassiuschen 回复于 2015年03月01日 · 7531 次阅读

我在一个项目中前后端彻底分离,前端由 AngularJS 的$http发起 Ajax 请求 API 数据,请求如下:

$http
        method: 'GET'
        url: "#{window.ApiBaseUrl}books.json"
        isArray: true
    .success (data) ->
        $s.books = data

而该 API 由 Rails 提供,显而易见,会发生如下错误

XMLHttpRequest cannot load http://localhost:3000/api/books.json. Origin http://localhost:8080 is not allowed by Access-Control-Allow-Origin.

如果希望允许跨域,显然是需要在 API 返回值的 Header 中加入数据,如我猜想的做法是这样的:

class Api::BaseController < ActionController::Base
  protect_from_forgery with: :null_session
  respond_to :json
  before_action :allow_cross_domain_access
  before_action :cors_preflight_check
  after_action :cors_set_access_control_headers

  def allow_cross_domain_access
    headers['Access-Control-Allow-Origin'] = '*'
    headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE, OPTIONS'
    headers['Access-Control-Allow-Headers'] = %w{Origin Accept Content-Type X-Requested-With X-CSRF-Token}.join(',')
    headers['Access-Control-Max-Age'] = '1728000'
  end

  def cors_set_access_control_headers
    headers['Access-Control-Allow-Origin'] = '*'
    headers['Access-Control-Allow-Methods'] = 'POST, GET, OPTIONS'
    headers['Access-Control-Allow-Headers'] = %w{Origin Accept Content-Type X-Requested-With X-CSRF-Token}.join(',')
    headers['Access-Control-Max-Age'] = "1728000"
  end

  def cors_preflight_check
    if request.method == :options
      headers['Access-Control-Allow-Origin'] = '*'
      headers['Access-Control-Allow-Methods'] = 'POST, GET, OPTIONS'
      headers['Access-Control-Allow-Headers'] = 'X-Requested-With, X-Prototype-Version'
      headers['Access-Control-Max-Age'] = '1728000'
      render :text => '', :content_type => 'text/plain'
    end
  end
end

对于:cors_preflight_check方法,是用来处理发起 Ajax 请求前先行的OPTIONS请求的,但如上操作没有正确运行,因为 Rails 的 Routs 爆出错误:

Started OPTIONS "/api/books.json" for ::1 at 2015-02-26 23:29:29 +0800
ActionController::RoutingError (No route matches [OPTIONS] "/api/books.json"):
... ...

于是我考虑可能是我的思路有问题,因为 rack 是先找到 http method 与 url 对应的方法之后才会由 controller 执行 before_action 的,所以一般遇到这样的情况,都会如何解决呢?或有什么案例可供借鉴一下的么?

一般直接用 rack-cors 即可。

rack-cors

angular resource 很不错啊,可以试试

#2 楼 @miclle resource 的实现不还是$http 么?这二者有区别?

#3 楼 @cassiuschen 省事 rails 天然就是支持 restful 的,如果用 angular resource 去对接的话相当方便,省了很多代码

angular.module('app').factory 'Customer', [
  '$resource', 'SERVICE'
  ($resource, SERVICE) ->

    $resource "#{SERVICE.API}/customers/:id", {id: '@id'},
      update:
        method: 'PUT'
]
$scope.customers = Customer.query()

https://docs.angularjs.org/api/ngResource/service/$resource

#5 楼 @quakewang 恩……谢谢~ #6 楼 @miclle 恩……但是用$resource 不能直接解决跨域的问题吧…………

#7 楼 @cassiuschen 前端都不可能解决跨域的问题必须要后端配合,只是看到你的代码后,给你推荐下 angular resource,与主题无关

window.ApiBaseUrl 这个可以使用 angular constant 来做

我是新手,没看懂。。。。cors_preflight_check 能解决跨域访问的问题吗?

#11 楼 @hammer it's me 下面是我的 application.css 的内容,rake assets:precompile 之后 只有.right { float: right; },没有别的,是怎么回事?另外怎么发帖啊?

/*
 * This is a manifest file that'll be compiled into application.css, which will include all the files
 * listed below.
 *
 * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
 * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
 *
 * You're free to add application-wide styles to this file and they'll appear at the top of the
 * compiled file, but it's generally better to create a new file per style scope.
 *
 *= require_self
 *= require bootstrap_and_overrides
 *= require nested_sortable
 *= require tag_it
 *= require jquery.lightbox-0.5
 *= require morris
 *= require jquery.ui.datepicker
 *= require articles
 *= require messages
 *= require workorder
 *= require community
 *= require homepage
 */

/* require_tree . */
.right { float: right; }

#12 楼 @saxer 发帖有权限
注册天数 必须大于 X 天 才能发帖

#10 楼 @saxer 不能…最简便的解决方案是用 rack-cors,因为本地测试的时候不一定用 nginx 进行代理…

需要 登录 后方可回复, 如果你还没有账号请 注册新账号