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
(ETag
和Last-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_content
和redirect_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-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 使用工作階段中介軟體
由於 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::Rendering 和 ActionController::ApiRendering |
針對呈現的基本支援。 |
ActionController::Renderers::All |
支援 render :json 和相關程式碼。 |
ActionController::ConditionalGet |
支援 stale? 。 |
ActionController::BasicImplicitRender |
如果沒有明確的回應,請務必傳回空的回應。 |
ActionController::StrongParameters |
支援與 Active Model 大量指派結合使用的參數篩選。 |
ActionController::DataStreaming |
支援 send_file 和 send_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
:支援l
和t
本地化和翻譯方法。- 支援基本、摘要或權杖 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
中,但您也可以將模組加入個別的控制器。