Rails 路由的 API 版本控制

我正在尝试我的 API 的版本,像 Stripe 已经。下面给出的是最新的 API 版本是2。

/api/users/api/v2/users返回一个301

/api/v1/users在版本1返回200个用户索引

/api/v3/users/api/v2/users返回一个301

/api/asdf/users/api/v2/users返回一个301

所以基本上任何没有指定版本链接到最新版本的东西,除非指定的版本存在,然后重定向到它。

以下是我目前掌握的情况:

scope 'api', :format => :json do
scope 'v:api_version', :api_version => /[12]/ do
resources :users
end


match '/*path', :to => redirect { |params| "/api/v2/#{params[:path]}" }
end
36788 次浏览

这个答案的原始形式有很大的不同,可以在这里找到。只是证明剥猫皮的方法不止一种。

此后,我更新了答案,使用了名称空间和301重定向——而不是默认的302。感谢 Pixeltrix 和 Bo Jeanes 在这些事情上的提示。


你可能要戴一个 真的强力头盔,因为这是去 让你大吃一惊

Rails 3路由 API 超级邪恶。要编写 API 的路由,根据上面的要求,您只需要:

namespace :api do
namespace :v1 do
resources :users
end


namespace :v2 do
resources :users
end
match 'v:api/*path', :to => redirect("/api/v2/%{path}")
match '*path', :to => redirect("/api/v2/%{path}")
end

如果你的思想在这一点之后仍然是完整的,让我解释。

首先,我们调用 namespace,当您希望将一组路由作用域限定在类似名称的特定路径和模块时,它非常方便。在这种情况下,我们希望我们的 namespace块中的所有路由都作用于 Api模块中的控制器,并且对这个路由中的路径的所有请求都将以 api作为前缀。像 /api/v2/users这样的请求,你知道吗?

在名称空间中,我们定义了另外两个名称空间(哇!).这一次我们定义了“ v1”名称空间,因此这里控制器的所有路由都将位于 Api模块 V1模块中: Api::V1。通过在此路由中定义 resources :users,控制器将位于 Api::V1::UsersController。这是版本1,您可以通过发出像 /api/v1/users这样的请求来实现。

版本2只是一个 很小位不同。它现在的位置是 Api::V2::UsersController,而不是服务它的控制器位于 Api::V1::UsersController。你可以通过发出像 /api/v2/users这样的请求来达到目的。

接下来,使用 match。这将匹配所有到 /api/v3/users这样的地方的 API 路由。

这就是我要查的部分。:to =>选项允许您指定一个特定的请求应该被重定向到其他地方——我知道这一点——但是我不知道如何让它重定向到其他地方并传递原始请求的一部分。

为此,我们调用 redirect方法并向它传递一个带有特殊插值的 %{path}参数的字符串。当一个匹配最终 match的请求进来时,它将把 path参数插入到字符串中 %{path}的位置,并将用户重定向到他们需要去的地方。

最后,我们使用另一个 match来路由以 /api为前缀的所有剩余路径,并将它们重定向到 /api/v2/%{path}。这意味着像 /api/users这样的请求将转到 /api/v2/users

我不知道如何让 /api/asdf/users匹配,因为如何确定这应该是对 /api/<resource>/<identifier>还是 /api/<version>/<resource>的请求?

无论如何,这是有趣的研究,我希望它能帮助你!

还有几点要补充:

你的重定向匹配对某些路由不起作用-*api参数是贪婪的,会吞噬一切,例如 /api/asdf/users/1将重定向到 /api/v2/1。您最好使用像 :api这样的常规参数。诚然,它不会匹配像 /api/asdf/asdf/users/1这样的情况,但是如果您在 API 中有嵌套的资源,那么它是一个更好的解决方案。

Ryan 为什么你不喜欢 namespace:

current_api_routes = lambda do
resources :users
end


namespace :api do
scope :module => :v2, &current_api_routes
namespace :v2, &current_api_routes
namespace :v1, &current_api_routes
match ":api/*path", :to => redirect("/api/v2/%{path}")
end

它具有版本化和通用命名路由的额外好处。另外一个注意事项——使用 :module的约定是使用下划线表示法,例如: api/v1而不是‘ Api: : V1’。后者一度不起作用,但我相信它在 Rails 3.1中得到了修复。

此外,当您发布 API 的 v3版本时,路由会像下面这样更新:

current_api_routes = lambda do
resources :users
end


namespace :api do
scope :module => :v3, &current_api_routes
namespace :v3, &current_api_routes
namespace :v2, &current_api_routes
namespace :v1, &current_api_routes
match ":api/*path", :to => redirect("/api/v3/%{path}")
end

当然,你的 API 在不同版本之间有不同的路径,在这种情况下你可以这样做:

current_api_routes = lambda do
# Define latest API
end


namespace :api do
scope :module => :v3, &current_api_routes
namespace :v3, &current_api_routes


namespace :v2 do
# Define API v2 routes
end


namespace :v1 do
# Define API v1 routes
end


match ":api/*path", :to => redirect("/api/v3/%{path}")
end

如果可能的话,我建议您重新考虑您的网址,以便版本不在网址,而是放在接受标题。这个堆栈溢出的答案很好地解释了这个问题:

API 版本控制的最佳实践?

这个链接正好展示了如何使用轨道路由:

Http://freelancing-gods.com/posts/versioning_your_ap_is

我不知道为什么你要 再次询问到一个特定的版本,如果一个版本没有明确的要求。似乎您只是想定义一个默认版本,如果没有明确请求任何版本,就会提供该版本。我也同意 David Bock 的观点,将版本保留在 URL 结构之外是支持版本控制的更简洁的方法。

无耻的插件: Versionist 支持这些用例(以及更多)。

Https://github.com/bploetz/versionist

我不喜欢通过路由进行版本控制,我们构建 版本蛋糕是为了支持更简单的 API 版本控制。

通过在每个视图的文件名中包含 API 版本号(jBuilder,RABL 等) ,我们保持版本控制不显眼,并允许轻松降级以支持向后兼容性(例如,如果视图的 v5不存在,我们呈现视图的 v4)。

Ryan Bigg 的回答对我很有用。

如果还希望通过重定向保留查询参数,可以这样做:

match "*path", to: redirect{ |params, request| "/api/v2/#{params[:path]}?#{request.query_string}" }

今天实现了这一点,并找到了我认为对 RailsCast-REST API 版本控制是“正确的方式”。这么简单。太容易维持了。太有效了。

添加 lib/api_constraints.rb(甚至不必更改 vnd.example.)

class ApiConstraints
def initialize(options)
@version = options[:version]
@default = options[:default]
end


def matches?(req)
@default || req.headers['Accept'].include?("application/vnd.example.v#{@version}")
end
end

像这样设置 config/routes.rb

require 'api_constraints'


Rails.application.routes.draw do


# Squads API
namespace :api do
# ApiConstaints is a lib file to allow default API versions,
# this will help prevent having to change link names from /api/v1/squads to /api/squads, better maintainability
scope module: :v1, constraints: ApiConstraints.new(version:1, default: true) do
resources :squads do
# my stuff was here
end
end
end


resources :squads
root to: 'site#index'

编辑你的控制器(即 /controllers/api/v1/squads_controller.rb)

module Api
module V1
class SquadsController < BaseController
# my stuff was here
end
end
end

然后,你可以改变所有的链接在您的应用程序从 /api/v1/squads/api/squads,你可以 很简单实现新的 API 版本,甚至不需要改变链接