Rails路线的API版本控制


141

我正在尝试像Stripe一样对我的API进行版本控制。下面给出的最新API版本是2。

/api/users 将301返回 /api/v2/users

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

/api/v3/users 将301返回 /api/v2/users

/api/asdf/users 将301返回 /api/v2/users

因此,除非指定的版本存在,否则基本上没有指定版本的内容都将链接到最新版本,然后重定向到最新版本。

这是我到目前为止的内容:

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

Answers:


280

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

从那以后,我已经更新了答案,以使用名称空间并使用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”名称空间,因此此处控制器的所有路由都将在V1模块内部的Api模块内部:Api::V1。通过resources :users在此路线内定义,控制器将位于Api::V1::UsersController。这是第1版,您可以通过发出类似请求来到达那里/api/v1/users

第2版只是一个很小的有点不同。而不是Api::V1::UsersController现在为它服务的控制器,现在是Api::V2::UsersController。您可以通过发出以下请求来到达那里/api/v2/users

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

这是我必须查找的部分。该: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>

无论如何,这很有趣,希望对您有所帮助!


24
亲爱的瑞安·比格(Ryan Bigg)。你太聪明了。
maletor 2012年

18
人们不能简单地衡量Ruby英雄的声誉。
Waseem

1
瑞安...我认为这实际上是不正确的。这将使/ api和/ api / v2提供相同的内容,而不是使用单个规范的URL。/ api应该重定向到/ api / v2(按照原始作者的指定)。我希望正确的路由看起来像gist.github.com/2044335(当然,我还没有测试过)。只有 / api / v [12]应该返回200,/ api和/ api / <坏版本>应该返回301s到/ api / v2
Bo Jeanes

2
值得注意的是,在路由文件301中已将其设置为默认重定向,这是有充分理由的。从指南: Please note that this redirection is a 301 “Moved Permanently” redirect. Keep in mind that some web browsers or proxy servers will cache this type of redirect, making the old page inaccessible.
maletor 2012年

3
如果路径不正确,是否会创建无限重定向?例如,请求/ api / v3 / path_that_dont_match_the_routes将创建无限重定向,对吗?
罗宾

38

要添加的几件事:

您的重定向匹配不适用于某些路线-该*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/asdf/users?以及/api/users/1?我无法在更新后的答案中找到答案,因此想出了一种可能的方式
Ryan Bigg 2012年

没有简单的方法-您必须在捕获所有内容之前定义所有重定向,但只需要为每个父资源执行每个重定向,例如/ api / users / * path => / api / v2 / users /%{path}
pixeltrix 2012年


9

我不是按路线进行版本控制的忠实拥护者。我们构建了VersionCake以支持更简单的API版本控制形式。

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


8

如果不确定未明确请求版本,我不确定为什么要重定向到特定版本。似乎您只是想定义一个默认版本,如果未明确请求没有版本,则该版本将被提供。我也同意David Bock的观点,即将版本保留在URL结构之外是一种支持版本控制的更干净的方法。

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

https://github.com/bploetz/versionist


2

Ryan Bigg的答案对我有用。

如果您还希望通过重定向保留查询参数,则可以执行以下操作:

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

2

今天实现了这一点,并发现了我认为是RailsCasts-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版本,甚至无需更改链接

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.