更多資訊請參閱 rubyonrails.org:

使用 Rails 於僅限 API 的應用程式

在本指南中,您將學習

  • Rails 為僅限 API 的應用程式提供的內容
  • 如何設定 Rails 以在沒有任何瀏覽器功能的情況下啟動
  • 如何決定要包含哪些中介軟體
  • 如何決定要在控制器中使用哪些模組

1 什麼是 API 應用程式?

傳統上,當人們說他們使用 Rails 作為「API」時,他們指的是在他們的 Web 應用程式旁邊提供一個可以透過程式設計存取的 API。例如,GitHub 提供 一個 API,您可以從自己的自訂用戶端使用它。

隨著用戶端框架的出現,越來越多的開發人員使用 Rails 來建構一個在他們的 Web 應用程式和其他原生應用程式之間共享的後端。

例如,X 在其 Web 應用程式中使用其 公開 API,該應用程式建構為一個靜態網站,該網站會取用 JSON 資源。

許多開發人員沒有使用 Rails 來產生 HTML,並透過表單和連結與伺服器進行通訊,而是將他們的 Web 應用程式視為一個 API 用戶端,以 HTML 和 JavaScript 的形式傳遞,並使用 JSON API。

本指南涵蓋建構一個 Rails 應用程式,該應用程式將 JSON 資源提供給 API 用戶端,包括用戶端框架。

2 為什麼要使用 Rails 於 JSON API?

很多人在思考使用 Rails 建構 JSON API 時,第一個問題是:「使用 Rails 吐出一些 JSON 不是多此一舉嗎?我不應該只使用像 Sinatra 這樣的東西嗎?」

對於非常簡單的 API 來說,這可能是真的。然而,即使在 HTML 繁重的應用程式中,大多數應用程式的邏輯都存在於檢視層之外。

大多數人使用 Rails 的原因是它提供了一組預設值,讓開發人員可以快速啟動並執行,而無需做出許多瑣碎的決定。

讓我們來看看 Rails 提供的一些開箱即用功能,這些功能仍然適用於 API 應用程式。

在 middleware 層處理

  • 重新載入:Rails 應用程式支援透明重新載入。即使您的應用程式變大,並且每次請求都重新啟動伺服器變得不可行,這也可以運作。
  • 開發模式:Rails 應用程式針對開發提供智慧預設值,使開發愉快,而不會影響生產時的效能。
  • 測試模式:同開發模式。
  • 記錄:Rails 應用程式會記錄每個請求,並具有適用於目前模式的詳細程度。Rails 在開發中的記錄包括有關請求環境、資料庫查詢和基本效能資訊的資訊。
  • 安全性:Rails 會偵測並阻止 IP 詐騙攻擊,並以 計時攻擊 感知的方式處理加密簽章。不知道什麼是 IP 詐騙攻擊或計時攻擊?正是如此。
  • 參數解析:想要將參數指定為 JSON 而不是 URL 編碼的字串?沒問題。Rails 會為您解碼 JSON,並使其在 params 中可用。想要使用巢狀 URL 編碼參數?那也行。
  • 條件式 GET:Rails 處理條件式 GET (ETagLast-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,該 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

在我們可以使用 scaffold 程式碼之前,我們需要更新資料庫綱要。

$ 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

改為

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-Typeapplication/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 使用工作階段中介軟體

由於 API 應用程式通常不需要工作階段,因此用於工作階段管理的以下中介軟體已排除在 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 的支援。這需要 cookies 中介軟體。
  • ActionController::Caching:支援 API 控制器的視圖快取。請注意,您需要在控制器內手動指定快取儲存,如下所示:

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

    Rails 並會自動傳遞此設定。

加入模組的最佳位置是在您的 ApplicationController 中,但您也可以將模組加入個別的控制器。



回到頂端