更多内容请访问 rubyonrails.org:

使用 Rails 构建仅 API 应用

在本指南中,您将了解

  • Rails 为仅 API 应用提供了什么
  • 如何配置 Rails 以在没有任何浏览器功能的情况下启动
  • 如何决定要包含哪些中间件
  • 如何决定在控制器中使用哪些模块

1 什么是 API 应用程序?

传统上,当人们说他们使用 Rails 作为“API”时,他们的意思是为他们的 Web 应用程序提供一个可编程访问的 API。例如,GitHub 提供了一个 API,您可以从您自己的自定义客户端使用它。

随着客户端框架的出现,越来越多的开发人员使用 Rails 构建一个由他们的 Web 应用程序和其他原生应用程序共享的后端。

例如,X 使用其 公共 API 在其 Web 应用程序中,该应用程序构建为一个静态站点,它使用 JSON 资源。

许多开发人员没有使用 Rails 生成与服务器通过表单和链接进行通信的 HTML,而是将他们的 Web 应用程序视为一个仅 API 客户端,以 HTML 和使用 JSON API 的 JavaScript 的形式交付。

本指南涵盖了构建一个 Rails 应用程序,该应用程序为 API 客户端(包括客户端框架)提供 JSON 资源。

2 为什么要使用 Rails 构建 JSON API?

许多人在考虑使用 Rails 构建 JSON API 时会提出的第一个问题是:“使用 Rails 生成一些 JSON 是不是有点过分?我是否应该使用 Sinatra 之类的东西?”

对于非常简单的 API,这可能是真的。但是,即使在非常依赖 HTML 的应用程序中,大多数应用程序的逻辑也存在于视图层之外。

大多数人使用 Rails 的原因是它提供了一组默认值,允许开发人员快速启动并运行,而无需做出许多琐碎的决策。

让我们看一下 Rails 开箱即用提供的一些仍然适用于 API 应用程序的内容。

在中间件层处理

  • 重新加载:Rails 应用程序支持透明的重新加载。即使您的应用程序变得很大,并且每次请求都重新启动服务器变得不可行,这也仍然有效。
  • 开发模式:Rails 应用程序带有针对开发的智能默认值,使开发变得愉快,而不会影响生产时的性能。
  • 测试模式:与开发模式相同。
  • 日志记录:Rails 应用程序记录每个请求,其详细程度适合当前模式。开发模式下的 Rails 日志包括有关请求环境、数据库查询和基本性能信息的信息。
  • 安全性:Rails 检测并阻止 IP 欺骗攻击 并在 时序攻击 意识的情况下处理加密签名。不知道什么是 IP 欺骗攻击或时序攻击?就是这样。
  • 参数解析:想要将参数指定为 JSON 而不是 URL 编码的字符串?没问题。Rails 会为您解码 JSON 并将其提供给 params。想要使用嵌套的 URL 编码参数?这也能做到。
  • 条件 GET:Rails 处理条件 GETETagLast-Modified)处理请求头并返回正确的响应头和状态码。您需要做的就是使用 stale? 检查您的控制器,Rails 会为您处理所有 HTTP 细节。
  • HEAD 请求:Rails 会透明地将 HEAD 请求转换为 GET 请求,并在返回时只返回头信息。这使得 HEAD 在所有 Rails API 中都能可靠地工作。

虽然您显然可以使用现有的 Rack 中间件来构建这些内容,但这份列表表明,即使您“只是生成 JSON”,默认的 Rails 中间件堆栈也提供了很多价值。

在 Action Pack 层处理

  • 资源路由:如果您正在构建一个 RESTful JSON API,您应该使用 Rails 路由器。从 HTTP 到控制器的清晰简洁的映射意味着您不必花费时间思考如何根据 HTTP 对您的 API 进行建模。
  • URL 生成:路由的另一面是 URL 生成。一个基于 HTTP 的良好 API 包括 URL(参见 GitHub Gist API 示例)。
  • 头和重定向响应:head :no_contentredirect_to user_url(current_user) 很方便。当然,您可以手动添加响应头,但为什么要这样做呢?
  • 缓存:Rails 提供了页面缓存、操作缓存和片段缓存。当构建嵌套的 JSON 对象时,片段缓存特别有用。
  • 基本认证、摘要认证和令牌认证:Rails 开箱即用地支持三种 HTTP 认证。
  • 检测:Rails 有一个检测 API,它为各种事件触发注册的处理程序,例如操作处理、发送文件或数据、重定向和数据库查询。每个事件的有效负载都包含相关信息(对于操作处理事件,有效负载包括控制器、操作、参数、请求格式、请求方法和请求的完整路径)。
  • 生成器:使用一个命令生成资源并获得您的模型、控制器、测试存根和路由,这通常很方便,以便进一步调整。对于迁移和其他内容也是如此。
  • 插件:许多第三方库都支持 Rails,这减少了或消除了设置和将库与 Web 框架粘合在一起的成本。这包括覆盖默认生成器、添加 Rake 任务和遵守 Rails 选择(例如日志记录器和缓存后端)。

当然,Rails 启动过程也会将所有注册的组件粘合在一起。例如,Rails 启动过程是在配置 Active Record 时使用 config/database.yml 文件的。

简而言之:您可能没有想过 Rails 的哪些部分即使在删除视图层后仍然适用,但答案是大部分都适用。

3 基本配置

如果您正在构建一个将首先作为 API 服务器的 Rails 应用程序,您可以从 Rails 的一个更有限的子集开始,并在需要时添加功能。

3.1 创建新应用程序

您可以生成一个新的 api Rails 应用程序

$ rails new my_api --api

这将为您完成三项主要工作

  • 配置您的应用程序,使其以比平时更少的中间件启动。具体来说,它默认情况下不会包含任何主要对浏览器应用程序有用的中间件(例如 cookie 支持)。
  • 使 ApplicationController 继承自 ActionController::API 而不是 ActionController::Base。与中间件一样,这将省略任何主要由浏览器应用程序使用的功能的 Action Controller 模块。
  • 配置生成器,以便在您生成新资源时跳过生成视图、助手和资产。

3.2 生成新资源

为了查看我们新创建的 API 如何处理生成新资源,让我们创建一个新的 Group 资源。每个组都将有一个名称。

$ bin/rails g scaffold Group name:string

在我们能够使用我们搭建的代码之前,我们需要更新我们的数据库模式。

$ bin/rails db:migrate

现在,如果我们打开我们的 GroupsController,我们会注意到在使用 API Rails 应用程序时,我们只渲染 JSON 数据。在 index 操作中,我们查询 Group.all 并将其分配给名为 @groups 的实例变量。将其与 :json 选项一起传递给 render 将自动将组渲染为 JSON。

# app/controllers/groups_controller.rb
class GroupsController < ApplicationController
  before_action :set_group, only: %i[ show update destroy ]

  # GET /groups
  def index
    @groups = Group.all

    render json: @groups
  end

  # GET /groups/1
  def show
    render json: @group
  end

  # POST /groups
  def create
    @group = Group.new(group_params)

    if @group.save
      render json: @group, status: :created, location: @group
    else
      render json: @group.errors, status: :unprocessable_entity
    end
  end

  # PATCH/PUT /groups/1
  def update
    if @group.update(group_params)
      render json: @group
    else
      render json: @group.errors, status: :unprocessable_entity
    end
  end

  # DELETE /groups/1
  def destroy
    @group.destroy
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_group
      @group = Group.find(params[:id])
    end

    # Only allow a list of trusted parameters through.
    def group_params
      params.expect(group: [:name])
    end
end

最后,我们可以从 Rails 控制台中向数据库中添加一些组

irb> Group.create(name: "Rails Founders")
irb> Group.create(name: "Rails Contributors")

在应用程序中添加了一些数据后,我们可以启动服务器并访问 https://127.0.0.1:3000/groups.json 以查看我们的 JSON 数据。

[
{"id":1, "name":"Rails Founders", "created_at": ...},
{"id":2, "name":"Rails Contributors", "created_at": ...}
]

3.3 更改现有应用程序

如果您想将现有应用程序变成 API 应用程序,请阅读以下步骤。

config/application.rb 中,在 Application 类定义的顶部添加以下行

config.api_only = true

在 `config/environments/development.rb` 中,设置 config.debug_exception_response_format 来配置在开发模式下发生错误时响应中使用的格式。

要渲染包含调试信息的 HTML 页面,请使用值 :default

config.debug_exception_response_format = :default

要渲染保留响应格式的调试信息,请使用值 :api

config.debug_exception_response_format = :api

默认情况下,当 config.api_only 设置为 true 时,config.debug_exception_response_format 设置为 :api

最后,在 `app/controllers/application_controller.rb` 中,而不是

class ApplicationController < ActionController::Base
end

do

class ApplicationController < ActionController::API
end

4 选择中间件

API 应用程序默认情况下包含以下中间件

  • ActionDispatch::HostAuthorization
  • Rack::Sendfile
  • ActionDispatch::Static
  • ActionDispatch::Executor
  • ActionDispatch::ServerTiming
  • ActiveSupport::Cache::Strategy::LocalCache::Middleware
  • Rack::Runtime
  • ActionDispatch::RequestId
  • ActionDispatch::RemoteIp
  • Rails::Rack::Logger
  • ActionDispatch::ShowExceptions
  • ActionDispatch::DebugExceptions
  • ActionDispatch::ActionableExceptions
  • ActionDispatch::Reloader
  • ActionDispatch::Callbacks
  • ActiveRecord::Migration::CheckPending
  • Rack::Head
  • Rack::ConditionalGet
  • Rack::ETag

有关它们的更多信息,请参见 Rack 指南的 内部中间件 部分。

其他插件(包括 Active Record)可能会添加额外的中间件。通常,这些中间件与您正在构建的应用程序类型无关,并且在仅 API 的 Rails 应用程序中很有意义。

您可以通过以下方式获取应用程序中所有中间件的列表

$ bin/rails middleware

4.1 使用 Rack::Cache

当与 Rails 一起使用时,Rack::Cache 使用 Rails 缓存存储来存储其实体和元数据。这意味着,例如,如果您对 Rails 应用程序使用 memcache,则内置的 HTTP 缓存将使用 memcache。

要使用 Rack::Cache,您首先需要将 rack-cache gem 添加到 Gemfile 中,并将 config.action_dispatch.rack_cache 设置为 true。要启用其功能,您将需要在控制器中使用 stale?。以下是在使用中 stale? 的示例。

def show
  @post = Post.find(params[:id])

  if stale?(last_modified: @post.updated_at)
    render json: @post
  end
end

stale? 的调用会将请求中的 If-Modified-Since 标头与 @post.updated_at 进行比较。如果标头比最后修改时间更新,则此操作将返回“304 未修改”响应。否则,它将渲染响应并在其中包含 Last-Modified 标头。

通常,此机制用于每个客户端。Rack::Cache 允许我们在多个客户端之间共享此缓存机制。我们可以在对 stale? 的调用中启用跨客户端缓存

def show
  @post = Post.find(params[:id])

  if stale?(last_modified: @post.updated_at, public: true)
    render json: @post
  end
end

这意味着 Rack::Cache 将在 Rails 缓存中为 URL 存储 Last-Modified 值,并将 If-Modified-Since 标头添加到针对同一 URL 的任何后续入站请求中。

可以将其视为使用 HTTP 语义的页面缓存。

4.2 使用 Rack::Sendfile

当您在 Rails 控制器中使用 send_file 方法时,它会设置 X-Sendfile 标头。Rack::Sendfile 负责实际发送文件。

如果您的前端服务器支持加速文件发送,Rack::Sendfile 将将实际的文件发送工作卸载到前端服务器。这使得 Rails 可以更早地完成请求处理并释放资源。

您可以在适当环境的配置文件中使用 config.action_dispatch.x_sendfile_header 配置前端服务器为此目的使用的标头名称。

您可以在 Rack::Sendfile 文档 中了解有关如何将 Rack::Sendfile 与流行前端一起使用的更多信息。

以下是一些流行服务器的标头值,在这些服务器配置为支持加速文件发送后。

# Apache and lighttpd
config.action_dispatch.x_sendfile_header = "X-Sendfile"

# Nginx
config.action_dispatch.x_sendfile_header = "X-Accel-Redirect"

确保按照 Rack::Sendfile 文档中的说明配置您的服务器以支持这些选项。

4.3 使用 ActionDispatch::Request

ActionDispatch::Request#params 将采用来自客户端的 JSON 格式的参数,并将其在控制器内部的 params 中提供。

要使用它,您的客户端需要使用 JSON 编码的参数发出请求,并将 Content-Type 指定为 application/json

以下是一个示例

fetch('/people', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ person: { firstName: 'Yehuda', lastName: 'Katz' } })
}).then(response => response.json())

ActionDispatch::Request 将看到 Content-Type,您的参数将是

{ person: { firstName: "Yehuda", lastName: "Katz" } }

4.4 使用 Session 中间件

以下用于会话管理的中间件已从 API 应用程序中排除,因为它们通常不需要会话。如果您的 API 客户端之一是浏览器,您可能希望将其中一个添加回来

  • ActionDispatch::Session::CacheStore
  • ActionDispatch::Session::CookieStore
  • ActionDispatch::Session::MemCacheStore

将这些添加回来的诀窍是,默认情况下,在添加时会将 session_options 传递给它们(包括会话密钥),因此您不能只添加一个 session_store.rb 初始化器,添加 use ActionDispatch::Session::CookieStore 并且让会话像往常一样工作。(明确地说:会话可能会起作用,但您的会话选项将被忽略,即会话密钥将默认设置为 _session_id

您需要在构建中间件之前(例如,在 config/application.rb 中)设置相关选项,并将它们传递给您喜欢的中间件,例如

# This also configures session_options for use below
config.session_store :cookie_store, key: "_your_app_session"

# Required for all session management (regardless of session_store)
config.middleware.use ActionDispatch::Cookies

config.middleware.use config.session_store, config.session_options

4.5 其他中间件

Rails 附带了许多其他中间件,您可能希望在 API 应用程序中使用,尤其是如果您的 API 客户端之一是浏览器

  • Rack::MethodOverride
  • ActionDispatch::Cookies
  • ActionDispatch::Flash

任何这些中间件都可以通过以下方式添加

config.middleware.use Rack::MethodOverride

4.6 移除中间件

如果您不想使用 API 仅中间件集中默认包含的中间件,则可以使用以下方法将其删除

config.middleware.delete ::Rack::Sendfile

请记住,删除这些中间件将删除对 Action Controller 中某些功能的支持。

5 选择控制器模块

API 应用程序(使用 ActionController::API)默认情况下包含以下控制器模块

ActionController::UrlFor 使 url_for 和类似的帮助器可用。
ActionController::Redirecting 支持 redirect_to
AbstractController::RenderingActionController::ApiRendering 对渲染的基本支持。
ActionController::Renderers::All 支持 render :json 及其朋友。
ActionController::ConditionalGet 支持 stale?
ActionController::BasicImplicitRender 确保在没有明确响应的情况下返回空响应。
ActionController::StrongParameters 与 Active Model 批量分配结合使用,支持参数过滤。
ActionController::DataStreaming 支持 send_filesend_data
AbstractController::Callbacks 支持 before_action 和类似的帮助器。
ActionController::Rescue 支持 rescue_from
ActionController::Instrumentation 支持 Action Controller 定义的检测挂钩(有关此的更多信息,请参见 检测指南)。
ActionController::ParamsWrapper 将参数哈希包装成嵌套哈希,这样您就不必指定发送 POST 请求的根元素。
ActionController::Head 支持返回仅包含标头的无内容响应。

其他插件可能会添加其他模块。您可以在 rails 控制台中获取包含在 ActionController::API 中的所有模块的列表

irb> ActionController::API.ancestors - ActionController::Metal.ancestors
=> [ActionController::API,
    ActiveRecord::Railties::ControllerRuntime,
    ActionDispatch::Routing::RouteSet::MountedHelpers,
    ActionController::ParamsWrapper,
    ... ,
    AbstractController::Rendering,
    ActionView::ViewPaths]

5.1 添加其他模块

所有 Action Controller 模块都知道它们依赖的模块,因此您可以随意将任何模块包含到您的控制器中,所有依赖项也将被包含并设置。

您可能想要添加的一些常用模块

  • AbstractController::Translation:支持 lt 本地化和翻译方法。
  • 支持基本、摘要或令牌 HTTP 身份验证
    • ActionController::HttpAuthentication::Basic::ControllerMethods
    • ActionController::HttpAuthentication::Digest::ControllerMethods
    • ActionController::HttpAuthentication::Token::ControllerMethods
  • ActionView::Layouts:支持渲染时的布局。
  • ActionController::MimeResponds:支持 respond_to
  • ActionController::Cookies:支持 cookies,其中包括对签名和加密 cookie 的支持。这需要 cookie 中间件。
  • ActionController::Caching:支持 API 控制器的视图缓存。请注意,您需要在控制器中手动指定缓存存储,如下所示

    class ApplicationController < ActionController::API
      include ::ActionController::Caching
      self.cache_store = :mem_cache_store
    end
    

    Rails 不会自动传递此配置。

添加模块的最佳位置是您的 ApplicationController,但您也可以将模块添加到各个控制器中。



返回顶部