更多資訊請參考 rubyonrails.org:

Action Controller 概觀

在本指南中,您將學習控制器如何運作,以及它們如何在應用程式的請求週期中發揮作用。

閱讀本指南後,您將了解如何:

  • 追蹤請求通過控制器的流程。
  • 限制傳遞給控制器的參數。
  • 將資料儲存在會話或 Cookie 中,以及為什麼要這樣做。
  • 使用動作回呼在請求處理期間執行程式碼。
  • 使用 Action Controller 的內建 HTTP 驗證。
  • 將資料直接串流傳輸到使用者的瀏覽器。
  • 篩選敏感參數,使其不會出現在應用程式的記錄中。
  • 處理請求處理期間可能引發的例外狀況。
  • 使用內建的健康檢查端點進行負載平衡器和正常執行時間監視器。

1 控制器有什麼作用?

Action Controller 是 MVC 中的 C。在路由器確定要使用哪個控制器處理請求之後,控制器負責理解請求並產生適當的輸出。幸運的是,Action Controller 為您做了大部分的基礎工作,並使用智慧型慣例讓這一切盡可能簡單明瞭。

對於大多數傳統的 RESTful 應用程式,控制器將接收請求(身為開發人員的您是看不到的),從模型中擷取或儲存資料,並使用視圖建立 HTML 輸出。如果您的控制器需要做一些不同的事情,這也不是問題,這只是控制器最常見的運作方式。

因此,可以將控制器視為模型和視圖之間的中介。它使模型資料可供視圖使用,以便將該資料顯示給使用者,並將使用者資料儲存或更新到模型中。

有關路由流程的更多詳細資訊,請參閱由外而內的 Rails 路由

2 控制器命名慣例

Rails 中控制器的命名慣例傾向於將控制器名稱中的最後一個字詞複數化,但這並非嚴格要求(例如 ApplicationController)。例如,ClientsController 優於 ClientControllerSiteAdminsController 優於 SiteAdminControllerSitesAdminsController,依此類推。

遵循此慣例將允許您使用預設的路由產生器(例如 resources 等),而無需限定每個 :path:controller,並將使具名路由輔助方法的用法在整個應用程式中保持一致。有關更多詳細資訊,請參閱版面配置和渲染指南

控制器命名慣例與模型命名慣例不同,後者預期以單數形式命名。

3 方法和動作

控制器是一個繼承自 ApplicationController 的 Ruby 類別,並且具有與任何其他類別相同的方法。當您的應用程式收到請求時,路由將確定要執行哪個控制器和動作,然後 Rails 會建立該控制器的實例,並執行與動作名稱相同的方法。

class ClientsController < ApplicationController
  def new
  end
end

例如,如果使用者在您的應用程式中前往 /clients/new 以新增一個新的客戶,Rails 會建立 ClientsController 的實例並呼叫其 new 方法。請注意,由於 Rails 預設會渲染 new.html.erb 視圖,除非動作另有說明,否則上述範例中的空方法可以正常運作。透過建立新的 Clientnew 方法可以在視圖中存取 @client 實例變數

def new
  @client = Client.new
end

版面配置和渲染指南會更詳細地說明這一點。

ApplicationController 繼承自 ActionController::Base,後者定義了許多有用的方法。本指南將涵蓋其中一些方法,但是如果您想知道其中有哪些方法,可以在API 文件或原始碼中查看所有這些方法。

只有公用方法可以作為動作呼叫。降低不打算作為動作的方法(例如輔助方法或篩選器)的可見性(使用 privateprotected)是一種最佳做法。

某些方法名稱由 Action Controller 保留。意外地將它們重新定義為動作,甚至作為輔助方法,都可能導致 SystemStackError。如果您將控制器限制為僅限 RESTful 資源路由動作,則無需擔心這一點。

如果您必須使用保留的方法作為動作名稱,一種解決方法是使用自訂路由將保留的方法名稱對應到您的非保留動作方法。

4 參數

您可能希望在控制器動作中存取使用者傳入的資料或其他參數。Web 應用程式中可能存在兩種參數。第一種是作為 URL 一部分傳送的參數,稱為查詢字串參數。查詢字串是 URL 中 "?" 之後的所有內容。第二種參數通常稱為 POST 資料。此資訊通常來自使用者填寫的 HTML 表單。它之所以稱為 POST 資料,是因為它只能作為 HTTP POST 請求的一部分傳送。Rails 不會區分查詢字串參數和 POST 參數,這兩者都可以在您控制器的 params 雜湊中取得

class ClientsController < ApplicationController
  # This action uses query string parameters because it gets run
  # by an HTTP GET request, but this does not make any difference
  # to how the parameters are accessed. The URL for
  # this action would look like this to list activated
  # clients: /clients?status=activated
  def index
    if params[:status] == "activated"
      @clients = Client.activated
    else
      @clients = Client.inactivated
    end
  end

  # This action uses POST parameters. They are most likely coming
  # from an HTML form that the user has submitted. The URL for
  # this RESTful request will be "/clients", and the data will be
  # sent as part of the request body.
  def create
    @client = Client.new(params[:client])
    if @client.save
      redirect_to @client
    else
      # This line overrides the default rendering behavior, which
      # would have been to render the "create" view.
      render "new"
    end
  end
end

params 雜湊不是純粹的 Ruby 雜湊;相反地,它是一個 ActionController::Parameters 物件。雖然它的行為類似於雜湊,但它不繼承自雜湊。

4.1 雜湊和陣列參數

params 雜湊不僅限於一維的鍵和值。它可以包含巢狀陣列和雜湊。要傳送一組值,請在鍵名稱後附加一對空的方括號 "[]"

GET /clients?ids[]=1&ids[]=2&ids[]=3

此範例中的實際 URL 將編碼為 "/clients?ids%5b%5d=1&ids%5b%5d=2&ids%5b%5d=3",因為 "[" 和 "]" 字元在 URL 中是不允許的。大多數情況下,您不必擔心這一點,因為瀏覽器會為您編碼,而 Rails 會自動解碼,但是如果您發現自己必須手動將這些請求傳送到伺服器,則應記住這一點。

現在,params[:ids] 的值將為 ["1", "2", "3"]。請注意,參數值永遠是字串;Rails 不會嘗試猜測或轉換類型。

為了安全起見,預設情況下,params 中的 [nil][nil, nil, ...] 等值會被替換為 []。請參閱安全指南以獲取更多資訊。

若要發送雜湊(hash),請將鍵名包含在方括號內。

<form accept-charset="UTF-8" action="/clients" method="post">
  <input type="text" name="client[name]" value="Acme" />
  <input type="text" name="client[phone]" value="12345" />
  <input type="text" name="client[address][postcode]" value="12345" />
  <input type="text" name="client[address][city]" value="Carrot City" />
</form>

當提交此表單時,params[:client] 的值將會是 { "name" => "Acme", "phone" => "12345", "address" => { "postcode" => "12345", "city" => "Carrot City" } }。請注意 params[:client][:address] 中的巢狀雜湊。

params 物件的作用類似於雜湊(Hash),但允許您將符號(symbol)和字串(string)交替用作鍵。

4.2 JSON 參數

如果您的應用程式公開了 API,您很可能會接受 JSON 格式的參數。如果您的請求的 "Content-Type" 標頭設定為 "application/json",Rails 會自動將您的參數載入到 params 雜湊中,您可以像平常一樣存取它。

例如,如果您發送了以下 JSON 內容

{ "company": { "name": "acme", "address": "123 Carrot Street" } }

您的控制器將收到 params[:company]{ "name" => "acme", "address" => "123 Carrot Street" }

此外,如果您在初始化程式中啟用了 config.wrap_parameters,或在控制器中呼叫了 wrap_parameters,您可以安全地省略 JSON 參數中的根元素。在這種情況下,參數將被複製並使用根據您的控制器名稱選擇的鍵包裝。所以上面的 JSON 請求可以寫成

{ "name": "acme", "address": "123 Carrot Street" }

並且,假設您正在將資料發送到 CompaniesController,它將會像這樣被包裝在 :company 鍵中

{ name: "acme", address: "123 Carrot Street", company: { name: "acme", address: "123 Carrot Street" } }

您可以透過查閱 API 文件,自訂鍵的名稱或您想要包裝的特定參數。

解析 XML 參數的支援已提取到名為 actionpack-xml_parser 的 gem 中。

4.3 路由參數

params 雜湊將始終包含 :controller:action 鍵,但您應該使用方法 controller_nameaction_name 來存取這些值。路由定義的任何其他參數,例如 :id,也將可用。舉例來說,考慮一個客戶列表,該列表可以顯示啟用或停用的客戶。我們可以新增一個路由,在一個「美觀」的 URL 中捕獲 :status 參數

get "/clients/:status", to: "clients#index", foo: "bar"

在這種情況下,當使用者開啟 URL /clients/active 時,params[:status] 將會被設定為 "active"。當使用此路由時,params[:foo] 也會被設定為 "bar",就像它在查詢字串中傳遞的一樣。您的控制器也將收到 params[:action] 為 "index",而 params[:controller] 為 "clients"。

4.4 複合鍵參數

複合鍵參數在一個參數中包含多個值。因此,我們需要能夠提取每個值並將它們傳遞給 Active Record。我們可以利用 extract_value 方法來達到此目的。

考慮以下控制器

class BooksController < ApplicationController
  def show
    # Extract the composite ID value from URL parameters.
    id = params.extract_value(:id)
    # Find the book using the composite ID.
    @book = Book.find(id)
    # use the default rendering behaviour to render the show view.
  end
end

和以下路由

get "/books/:id", to: "books#show"

當使用者開啟 URL /books/4_2 時,控制器將會提取複合鍵值 ["4", "2"] 並將其傳遞給 Book.find 以在視圖中呈現正確的紀錄。extract_value 方法可用於從任何分隔參數中提取陣列。

4.5 default_url_options

您可以透過在控制器中定義一個名為 default_url_options 的方法,來設定 URL 產生的全域預設參數。這樣的方法必須返回一個包含所需預設值的雜湊,其鍵必須是符號。

class ApplicationController < ActionController::Base
  def default_url_options
    { locale: I18n.locale }
  end
end

這些選項將在產生 URL 時作為起點,因此它們可能會被傳遞給 url_for 呼叫的選項覆蓋。

如果您在 ApplicationController 中定義了 default_url_options,如上面的範例所示,則這些預設值將用於所有 URL 產生。該方法也可以在特定的控制器中定義,在這種情況下,它只會影響在那裡產生的 URL。

在給定的請求中,該方法實際上不會針對每個產生的 URL 呼叫。出於效能考量,返回的雜湊會被快取,且每個請求最多只會呼叫一次。

4.6 強參數

使用強參數時,Action Controller 的參數在被允許之前,禁止在 Active Model 大量指派中使用。這表示您必須有意識地決定允許哪些屬性進行大量更新。這是一種更好的安全實踐,可協助防止意外允許使用者更新敏感的模型屬性。

此外,參數可以標記為必要,並且將會通過預定義的 raise/rescue 流程,如果不是所有的必要參數都傳入,則會返回 400 Bad Request。

class PeopleController < ActionController::Base
  # This will raise an ActiveModel::ForbiddenAttributesError exception
  # because it's using mass assignment without an explicit permit
  # step.
  def create
    Person.create(params[:person])
  end

  # This will pass with flying colors as long as there's a person key
  # in the parameters, otherwise it'll raise an
  # ActionController::ParameterMissing exception, which will get
  # caught by ActionController::Base and turned into a 400 Bad
  # Request error.
  def update
    person = current_account.people.find(params[:id])
    person.update!(person_params)
    redirect_to person
  end

  private
    # Using a private method to encapsulate the permissible parameters
    # is just a good pattern since you'll be able to reuse the same
    # permit list between create and update. Also, you can specialize
    # this method with per-user checking of permissible attributes.
    def person_params
      params.expect(person: [:name, :age])
    end
end

4.6.1 允許的純量值

像這樣呼叫 permit

params.permit(:id)

如果指定的鍵 (:id) 出現在 params 中,且它有一個相關聯的允許純量值,則允許包含該鍵。否則,該鍵將會被過濾掉,因此無法注入陣列、雜湊或任何其他物件。

允許的純量類型為 StringSymbolNilClassNumericTrueClassFalseClassDateTimeDateTimeStringIOIOActionDispatch::Http::UploadedFileRack::Test::UploadedFile

若要宣告 params 中的值必須是允許純量值的陣列,請將鍵映射到空陣列

params.permit(id: [])

有時無法或不方便宣告雜湊參數或其內部結構的有效鍵。只需映射到空雜湊即可

params.permit(preferences: {})

但要小心,因為這會為任意輸入開啟大門。在這種情況下,permit 會確保傳回結構中的值是允許的純量,並過濾掉其他任何值。

expect 提供了一種簡潔且安全的方式來要求和允許參數。

id = params.expect(:id)

expect 可確保傳回的類型不會受到參數篡改的影響。上面的 expect 將始終返回一個純量值,而不是陣列或雜湊。當從表單中期望參數時,請使用 expect 來確保存在根鍵且允許屬性。

user_params = params.expect(user: [:username, :password])
user_params.has_key?(:username) # => true

當 user 鍵不是具有預期鍵的巢狀雜湊時,expect 會引發錯誤並返回 400 Bad Request 回應。

若要要求和允許整個參數雜湊,可以使用 expect,如下所示。

params.expect(log_entry: {})

這會將 :log_entry 參數雜湊及其任何子雜湊標記為允許,並且不會檢查允許的純量,任何值都會被接受。使用 permit! 或呼叫帶有空雜湊的 expect 時,應格外小心,因為它將允許使用外部使用者控制的參數大量指派所有目前和未來模型屬性。

4.6.2 巢狀參數

您也可以在巢狀參數上使用 expect(或 permit),例如

# Given the example expected params:
params = ActionController::Parameters.new(
  name: "Martin",
  emails: ["me@example.com"],
  friends: [
    { name: "André", family: { name: "RubyGems" }, hobbies: ["keyboards", "card games"] },
    { name: "Kewe", family: { name: "Baroness" }, hobbies: ["video games"] },
  ]
)
# the following expect will ensure the params are permitted
name, emails, friends = params.expect(
  :name,                 # permitted scalar
  emails: [],            # array of permitted scalars
  friends: [[            # array of permitted Parameter hashes
    :name,               # permitted scalar
    family: [:name],     # family: { name: "permitted scalar" }
    hobbies: []          # array of permitted scalars
  ]]
)

此宣告允許 nameemailsfriends 屬性,並分別返回它們。預期 emails 將會是允許的純量值陣列,而 friends 將會是具有特定屬性的資源陣列(請注意新的雙陣列語法,以明確要求陣列):它們應該有一個 name 屬性(允許任何允許的純量值)、一個 hobbies 屬性(作為允許的純量值陣列),以及一個 family 屬性,該屬性僅限於具有 name 鍵和任何允許純量值的雜湊。

4.6.3 更多範例

模型類別方法 accepts_nested_attributes_for 允許您更新和刪除相關聯的記錄。這是基於 id_destroy 參數。

# permit :id and :_destroy
params.expect(author: [ :name, books_attributes: [[ :title, :id, :_destroy ]] ])

具有整數鍵的雜湊會被以不同的方式對待,您可以像宣告直接子項一樣宣告屬性。當您將 accepts_nested_attributes_forhas_many 關聯結合使用時,您會得到這種類型的參數。

# To permit the following data:
# {"book" => {"title" => "Some Book",
#             "chapters_attributes" => { "1" => {"title" => "First Chapter"},
#                                        "2" => {"title" => "Second Chapter"}}}}

params.expect(book: [ :title, chapters_attributes: [[ :title ]] ])

想像一下這樣一個情境:您有代表產品名稱的參數,以及與該產品相關聯的任意資料雜湊,並且您想要允許產品名稱屬性以及整個資料雜湊

def product_params
  params.expect(product: [ :name, data: {} ])
end

4.6.4 強參數的範圍之外

強參數 API 的設計考慮了最常見的使用案例。它並非旨在作為處理所有參數篩選問題的萬靈丹。但是,您可以輕鬆地將 API 與您自己的程式碼混合使用,以適應您的情況。

5 會話

您的應用程式為每個使用者都有一個會話,您可以在其中儲存少量在請求之間持續存在的資料。會話僅在控制器和視圖中可用,並且可以使用幾種不同的儲存機制之一

所有會話儲存都使用 Cookie 來儲存每個會話的唯一 ID(您必須使用 Cookie,Rails 不允許您在 URL 中傳遞會話 ID,因為這樣比較不安全)。

對於大多數的商店來說,這個 ID 用於在伺服器上查找會話資料,例如在資料庫表格中。但有一個例外,那就是預設且建議使用的會話儲存方式 - CookieStore - 它將所有會話資料儲存在 cookie 本身中(如果您需要,仍然可以取得 ID)。這樣做的好處是非常輕量,而且在新應用程式中使用會話時不需要任何設定。cookie 資料經過密碼學簽章,以確保其防篡改。此外,它也被加密,因此任何有權存取它的人都無法讀取其內容。(如果經過編輯,Rails 將不會接受它)。

CookieStore 可以儲存約 4 kB 的資料,比其他方式少得多,但通常足夠使用。無論您的應用程式使用哪種會話儲存方式,都不建議在會話中儲存大量資料。您尤其應該避免在會話中儲存複雜的物件(例如模型實例),因為伺服器可能無法在請求之間重新組裝它們,這將導致錯誤。

如果您的使用者會話不儲存重要資料,或不需要長時間保留(例如,您只使用 flash 來傳遞訊息),您可以考慮使用 ActionDispatch::Session::CacheStore。這將使用您為應用程式設定的快取實作來儲存會話。這樣做的好處是,您可以使用現有的快取基礎架構來儲存會話,而無需額外設定或管理。當然,缺點是會話是暫時性的,可能隨時消失。

請參閱安全性指南,以了解有關會話儲存的更多資訊。

如果您需要不同的會話儲存機制,可以在初始化器中進行變更。

Rails.application.config.session_store :cache_store

請參閱設定指南中的config.session_store,以取得更多資訊。

Rails 在簽署會話資料時會設定一個會話金鑰(cookie 的名稱)。這些也可以在初始化器中變更。

# Be sure to restart your server when you modify this file.
Rails.application.config.session_store :cookie_store, key: "_your_app_session"

您也可以傳遞 :domain 金鑰,並指定 cookie 的網域名稱。

# Be sure to restart your server when you modify this file.
Rails.application.config.session_store :cookie_store, key: "_your_app_session", domain: ".example.com"

Rails 會在 config/credentials.yml.enc 中設定 (針對 CookieStore) 用於簽署會話資料的密鑰。可以使用 bin/rails credentials:edit 來變更。

# aws:
#   access_key_id: 123
#   secret_access_key: 345

# Used as the base secret for all MessageVerifiers in Rails, including the one protecting cookies.
secret_key_base: 492f...

在使用 CookieStore 時變更 secret_key_base 將會使所有現有的會話失效。

5.1 存取會話

在您的控制器中,您可以透過 session 實例方法存取會話。

會話是延遲載入的。如果您在動作的程式碼中沒有存取會話,它們將不會被載入。因此,您永遠不需要停用會話,只要不存取它們即可。

會話值會像雜湊一樣,使用鍵/值對來儲存。

class ApplicationController < ActionController::Base
  private
    # Finds the User with the ID stored in the session with the key
    # :current_user_id This is a common way to handle user login in
    # a Rails application; logging in sets the session value and
    # logging out removes it.
    def current_user
      @_current_user ||= session[:current_user_id] &&
        User.find_by(id: session[:current_user_id])
    end
end

若要在會話中儲存某些內容,只需將其指定給像雜湊的鍵即可。

class LoginsController < ApplicationController
  # "Create" a login, aka "log the user in"
  def create
    if user = User.authenticate_by(email: params[:email], password: params[:password])
      # Save the user ID in the session so it can be used in
      # subsequent requests
      session[:current_user_id] = user.id
      redirect_to root_url
    end
  end
end

若要從會話中移除某些內容,請刪除鍵/值對。

class LoginsController < ApplicationController
  # "Delete" a login, aka "log the user out"
  def destroy
    # Remove the user id from the session
    session.delete(:current_user_id)
    # Clear the memoized current user
    @_current_user = nil
    redirect_to root_url, status: :see_other
  end
end

若要重設整個會話,請使用 reset_session

5.2 Flash

Flash 是會話的一個特殊部分,它會在每次請求時清除。這表示儲存在那裡的值僅在下一次請求中可用,這對於傳遞錯誤訊息等情況很有用。

Flash 是透過 flash 方法存取的。就像會話一樣,flash 也以雜湊表示。

讓我們以登出動作為例。控制器可以傳送一則訊息,該訊息將在下一次請求中顯示給使用者。

class LoginsController < ApplicationController
  def destroy
    session.delete(:current_user_id)
    flash[:notice] = "You have successfully logged out."
    redirect_to root_url, status: :see_other
  end
end

請注意,也可以在重新導向時指定 flash 訊息。您可以指定 :notice:alert 或通用的 :flash

redirect_to root_url, notice: "You have successfully logged out."
redirect_to root_url, alert: "You're stuck here!"
redirect_to root_url, flash: { referral_code: 1234 }

destroy 動作會重新導向至應用程式的 root_url,訊息將會在該處顯示。請注意,由下一個動作完全決定如何處理(如果有)前一個動作放入 flash 中的內容。習慣上會在應用程式的版面配置中顯示來自 flash 的任何錯誤警示或通知。

<html>
  <!-- <head/> -->
  <body>
    <% flash.each do |name, msg| -%>
      <%= content_tag :div, msg, class: name %>
    <% end -%>

    <!-- more content -->
  </body>
</html>

這樣一來,如果動作設定了通知或警示訊息,版面配置將會自動顯示它們。

您可以傳遞會話可以儲存的任何內容;您不限於通知和警示。

<% if flash[:just_signed_up] %>
  <p class="welcome">Welcome to our site!</p>
<% end %>

如果您希望 flash 值被帶到另一個請求,請使用 flash.keep

class MainController < ApplicationController
  # Let's say this action corresponds to root_url, but you want
  # all requests here to be redirected to UsersController#index.
  # If an action sets the flash and redirects here, the values
  # would normally be lost when another redirect happens, but you
  # can use 'keep' to make it persist for another request.
  def index
    # Will persist all flash values.
    flash.keep

    # You can also use a key to keep only some kind of value.
    # flash.keep(:notice)
    redirect_to users_url
  end
end

5.2.1 flash.now

預設情況下,將值新增至 flash 將使其在下一次請求中可用,但有時您可能希望在同一個請求中存取這些值。例如,如果 create 動作無法儲存資源,而且您直接轉譯 new 範本,這將不會導致新的請求,但您可能仍然希望使用 flash 顯示訊息。若要執行此動作,您可以使用 flash.now,就像您使用一般 flash 的方式一樣。

class ClientsController < ApplicationController
  def create
    @client = Client.new(client_params)
    if @client.save
      # ...
    else
      flash.now[:error] = "Could not save client"
      render action: "new"
    end
  end
end

6 Cookies

您的應用程式可以在用戶端上儲存少量資料,稱為 cookies,這些資料將在請求甚至會話之間保持存在。Rails 透過 cookies 方法提供對 cookies 的輕鬆存取,該方法(與 session 非常相似)像雜湊一樣運作。

class CommentsController < ApplicationController
  def new
    # Auto-fill the commenter's name if it has been stored in a cookie
    @comment = Comment.new(author: cookies[:commenter_name])
  end

  def create
    @comment = Comment.new(comment_params)
    if @comment.save
      flash[:notice] = "Thanks for your comment!"
      if params[:remember_name]
        # Remember the commenter's name.
        cookies[:commenter_name] = @comment.author
      else
        # Delete cookie for the commenter's name cookie, if any.
        cookies.delete(:commenter_name)
      end
      redirect_to @comment.article
    else
      render action: "new"
    end
  end
end

請注意,雖然對於會話值,您可以將鍵設定為 nil,但若要刪除 cookie 值,您應該使用 cookies.delete(:key)

Rails 還提供簽章的 cookie 容器和加密的 cookie 容器,用於儲存敏感資料。簽章的 cookie 容器會在 cookie 值上附加密碼學簽章,以保護其完整性。加密的 cookie 容器除了簽署之外,還會加密值,因此終端使用者無法讀取它們。請參閱 API 文件,以取得更多詳細資訊。

這些特殊的 cookie 容器會使用序列化器將指定的值序列化為字串,並在讀取時將它們還原序列化為 Ruby 物件。您可以透過 config.action_dispatch.cookies_serializer 指定要使用的序列化器。

新應用程式的預設序列化器為 :json。請注意,JSON 對於來回轉換 Ruby 物件的支援有限。例如,DateTimeSymbol 物件(包括 Hash 鍵)將會被序列化和還原序列化為 String

class CookiesController < ApplicationController
  def set_cookie
    cookies.encrypted[:expiration_date] = Date.tomorrow # => Thu, 20 Mar 2014
    redirect_to action: "read_cookie"
  end

  def read_cookie
    cookies.encrypted[:expiration_date] # => "2014-03-20"
  end
end

如果您需要儲存這些或更複雜的物件,您可能需要在後續請求中讀取它們時手動轉換它們的值。

如果您使用 cookie 會話儲存,則上述內容也適用於 sessionflash 雜湊。

7 轉譯

ActionController 使轉譯 HTML、XML 或 JSON 資料變得毫不費力。如果您使用 scaffolding 產生了一個控制器,它看起來會像這樣

class UsersController < ApplicationController
  def index
    @users = User.all
    respond_to do |format|
      format.html # index.html.erb
      format.xml  { render xml: @users }
      format.json { render json: @users }
    end
  end
end

您可能會在上面的程式碼中注意到,我們使用的是 render xml: @users,而不是 render xml: @users.to_xml。如果物件不是字串,則 Rails 會自動為我們呼叫 to_xml

您可以在版面配置和轉譯指南中了解更多有關轉譯的資訊。

8 動作回呼

動作回呼是在控制器動作「之前」、「之後」或「周圍」執行的方法。

動作回呼是繼承的,因此如果您在 ApplicationController 上設定了一個動作回呼,它將會在您應用程式中的每個控制器上執行。

「之前」動作回呼是透過 before_action 註冊的。它們可能會中止請求週期。「之前」動作回呼的一個常見範例是要求使用者必須登入才能執行動作。您可以使用這種方式定義該方法

class ApplicationController < ActionController::Base
  before_action :require_login

  private
    def require_login
      unless logged_in?
        flash[:error] = "You must be logged in to access this section"
        redirect_to new_login_url # halts request cycle
      end
    end
end

如果使用者未登入,該方法只會在 flash 中儲存錯誤訊息並重新導向至登入表單。如果「之前」動作回呼轉譯或重新導向,則控制器動作將不會執行。如果有其他動作回呼排定在該回呼之後執行,它們也會被取消。

在此範例中,動作回呼會新增至 ApplicationController,因此應用程式中的所有控制器都會繼承它。這將使得應用程式中的所有內容都要求使用者必須登入才能使用。出於顯而易見的原因(使用者首先無法登入!),並非所有控制器或動作都應該要求這樣。您可以使用 skip_before_action 防止此動作回呼在特定動作之前執行

class LoginsController < ApplicationController
  skip_before_action :require_login, only: [:new, :create]
end

現在,LoginsControllernewcreate 動作將會像以前一樣運作,而不需要使用者登入。:only 選項用於僅為這些動作跳過此動作回呼,還有一個 :except 選項,其運作方式與之相反。這些選項也可以在新增動作回呼時使用,因此您可以新增一個回呼,該回呼首先僅針對選定的動作執行。

使用不同的選項多次呼叫相同的動作回呼將無法運作,因為最後一個動作回呼定義將會覆寫先前的動作回呼。

8.1 之後動作和周圍動作回呼

除了「之前」動作回呼之外,您還可以在執行控制器動作之後,或在之前和之後都執行動作回呼。

「之後」動作回呼是透過 after_action 註冊的。它們與「之前」動作回呼類似,但由於控制器動作已經執行,因此它們可以存取即將傳送至用戶端的響應資料。顯然,「之後」動作回呼無法阻止動作執行。請注意,「之後」動作回呼僅在成功的控制器動作之後執行,而不會在請求週期中引發例外狀況時執行。

「周圍」動作回呼是透過 around_action 註冊的。它們負責透過產生 (yield) 來執行相關的動作,類似於 Rack 中介軟體的工作方式。

例如,在一個變更需要批准工作流程的網站中,管理員可以透過在交易中套用它們來輕鬆預覽它們。

class ChangesController < ApplicationController
  around_action :wrap_in_transaction, only: :show

  private
    def wrap_in_transaction
      ActiveRecord::Base.transaction do
        begin
          yield
        ensure
          raise ActiveRecord::Rollback
        end
      end
    end
end

請注意,「周圍」動作回呼也會包裝轉譯。特別是,在上面的範例中,如果視圖本身從資料庫讀取資料(例如,透過範圍),它將在交易內執行,因此會呈現要預覽的資料。

您可以選擇不產生 (yield) 並自行建立響應,在這種情況下,控制器動作將不會執行。

8.2 使用動作回呼的其他方式

雖然使用動作回呼最常見的方式是建立私有方法並使用 before_actionafter_actionaround_action 來新增它們,但還有其他兩種方式可以執行相同的操作。

第一種方式是直接將區塊與 *_action 方法一起使用。該區塊會接收控制器作為引數。上面的 require_login 動作回呼可以改寫為使用區塊

class ApplicationController < ActionController::Base
  before_action do |controller|
    unless controller.send(:logged_in?)
      flash[:error] = "You must be logged in to access this section"
      redirect_to new_login_url
    end
  end
end

請注意,在這種情況下,動作回呼使用 send,因為 logged_in? 方法是私有的,並且動作回呼不在控制器的範圍內執行。這不是實作此特定動作回呼的建議方式,但在較簡單的情況下,它可能很有用。

特別是對於 around_action,區塊也會在 action 中產生 (yield)。

around_action { |_controller, action| time(&action) }

第二種方式是使用類別(實際上,任何回應正確方法物件都可以)來處理回呼動作。這在比較複雜且無法使用其他兩種方法以可讀且可重複使用的方式實作的情況下很有用。例如,您可以再次改寫登入動作回呼以使用類別

class ApplicationController < ActionController::Base
  before_action LoginActionCallback
end

class LoginActionCallback
  def self.before(controller)
    unless controller.send(:logged_in?)
      controller.flash[:error] = "You must be logged in to access this section"
      controller.redirect_to controller.new_login_url
    end
  end
end

再次強調,這並非此動作回呼的理想範例,因為它並非在控制器的範圍內執行,而是將控制器作為參數傳遞。類別必須實作與動作回呼同名的方法,因此對於 before_action 動作回呼,類別必須實作 before 方法,以此類推。around 方法必須 yield 才能執行動作。

9 請求偽造保護

跨網站請求偽造(Cross-site request forgery)是一種攻擊類型,其中網站會欺騙使用者在另一個網站上發出請求,可能在使用者不知情或未經許可的情況下,新增、修改或刪除該網站上的資料。

避免這種情況的第一步是確保所有「破壞性」動作(建立、更新和刪除)只能透過非 GET 請求存取。如果您遵循 RESTful 慣例,您已經在這樣做了。然而,惡意網站仍然可以很容易地向您的網站發送非 GET 請求,這就是請求偽造保護的用武之地。顧名思義,它可以防止偽造的請求。

其運作方式是在每個請求中加入只有您的伺服器知道的不可猜測的權杖。這樣,如果請求在沒有正確權杖的情況下進入,將會被拒絕存取。

如果您產生像這樣的表單

<%= form_with model: @user do |form| %>
  <%= form.text_field :username %>
  <%= form.text_field :password %>
<% end %>

您將會看到權杖是如何以隱藏欄位的方式被加入的

<form accept-charset="UTF-8" action="/users/1" method="post">
<input type="hidden"
       value="67250ab105eb5ad10851c00a5621854a23af5489"
       name="authenticity_token"/>
<!-- fields -->
</form>

Rails 會將此權杖加入到使用 表單輔助方法產生的每個表單中,所以大多數時候您不必擔心它。如果您要手動編寫表單或因其他原因需要加入權杖,可以透過 form_authenticity_token 方法取得。

form_authenticity_token 會產生有效的驗證權杖。這在 Rails 不會自動加入權杖的地方很有用,例如在自訂 Ajax 呼叫中。

安全指南 有更多關於這方面的資訊,以及許多在開發網路應用程式時您應該注意的其他與安全相關的問題。

10 請求和回應物件

在每個控制器中,都有兩個存取器方法指向目前正在執行的請求週期相關聯的請求和回應物件。request 方法包含 ActionDispatch::Request 的實例,而 response 方法會傳回表示將傳回給用戶端的物件。

10.1 request 物件

請求物件包含許多關於來自用戶端的請求的有用資訊。要取得可用方法的完整清單,請參閱 Rails API 文件Rack 文件。您可以存取此物件的屬性包括

request 的屬性 目的
host 用於此請求的主機名稱。
domain(n=2) 從右邊(頂級網域)開始,主機名稱的前 n 個區段。
format 用戶端請求的內容類型。
method 用於請求的 HTTP 方法。
get?post?patch?put?delete?head? 如果 HTTP 方法是 GET/POST/PATCH/PUT/DELETE/HEAD,則傳回 true。
headers 傳回包含與請求相關聯的標頭的雜湊。
port 用於請求的連接埠號碼(整數)。
protocol 傳回包含所使用協定的字串加上 "://",例如 "http://"。
query_string URL 的查詢字串部分,即 "?" 後面的所有內容。
remote_ip 用戶端的 IP 位址。
url 用於請求的整個 URL。

10.1.1 path_parametersquery_parametersrequest_parameters

Rails 將與請求一起傳送的所有參數收集在 params 雜湊中,無論它們是作為查詢字串的一部分還是 post body 發送的。請求物件有三個存取器,讓您可以根據參數的來源存取這些參數。query_parameters 雜湊包含作為查詢字串的一部分發送的參數,而 request_parameters 雜湊包含作為 post body 一部分發送的參數。path_parameters 雜湊包含由路由識別為屬於導向此特定控制器和動作的路徑一部分的參數。

10.2 response 物件

回應物件通常不直接使用,而是在動作執行和將傳回給使用者的資料呈現期間建立,但有時(例如在 after action 回呼中)直接存取回應可能很有用。其中一些存取器方法也有設定器,讓您可以變更它們的值。要取得可用方法的完整清單,請參閱 Rails API 文件Rack 文件

response 的屬性 目的
body 這是傳回給用戶端的資料字串。這通常是 HTML。
status 回應的 HTTP 狀態碼,例如成功請求的 200 或找不到檔案的 404。
location 用戶端被重新導向的 URL(如果有的話)。
content_type 回應的內容類型。
charset 用於回應的字元集。預設值為「utf-8」。
headers 用於回應的標頭。

10.2.1 設定自訂標頭

如果您想為回應設定自訂標頭,那麼 response.headers 是您應該去的地方。headers 屬性是一個將標頭名稱對應到其值的雜湊,而 Rails 會自動設定其中一些。如果您想新增或變更標頭,只需以這種方式將其指派給 response.headers 即可

response.headers["Content-Type"] = "application/pdf"

在上述情況下,直接使用 content_type 設定器會更有意義。

11 HTTP 驗證

Rails 內建三種 HTTP 驗證機制

  • 基本驗證
  • 摘要驗證
  • 權杖驗證

11.1 HTTP 基本驗證

HTTP 基本驗證是一種驗證方案,大多數瀏覽器和其他 HTTP 用戶端都支援此方案。例如,考慮一個管理區塊,該區塊只能透過在瀏覽器的 HTTP 基本對話視窗中輸入使用者名稱和密碼才能存取。使用內建驗證只需使用一個方法,http_basic_authenticate_with

class AdminsController < ApplicationController
  http_basic_authenticate_with name: "humbaba", password: "5baa61e4"
end

有了這個,您可以建立繼承自 AdminsController 的命名空間控制器。因此,動作回呼將會為這些控制器中的所有動作執行,並使用 HTTP 基本驗證保護它們。

11.2 HTTP 摘要驗證

HTTP 摘要驗證優於基本驗證,因為它不需要用戶端透過網路傳送未加密的密碼(雖然 HTTP 基本驗證在 HTTPS 上是安全的)。在 Rails 中使用摘要驗證只需要使用一個方法,authenticate_or_request_with_http_digest

class AdminsController < ApplicationController
  USERS = { "lifo" => "world" }

  before_action :authenticate

  private
    def authenticate
      authenticate_or_request_with_http_digest do |username|
        USERS[username]
      end
    end
end

如上面的範例所示,authenticate_or_request_with_http_digest 區塊只接受一個參數 - 使用者名稱。而該區塊會傳回密碼。從 authenticate_or_request_with_http_digest 傳回 falsenil 會導致驗證失敗。

11.3 HTTP 權杖驗證

HTTP 權杖驗證是一種在 HTTP Authorization 標頭中啟用 Bearer 權杖使用的方案。有許多可用的權杖格式,描述它們超出本文檔的範圍。

例如,假設您想使用預先發行的驗證權杖來執行驗證和存取。在 Rails 中實作權杖驗證只需要使用一個方法,authenticate_or_request_with_http_token

class PostsController < ApplicationController
  TOKEN = "secret"

  before_action :authenticate

  private
    def authenticate
      authenticate_or_request_with_http_token do |token, options|
        ActiveSupport::SecurityUtils.secure_compare(token, TOKEN)
      end
    end
end

如上面的範例所示,authenticate_or_request_with_http_token 區塊接受兩個參數 - 權杖和包含從 HTTP Authorization 標頭解析的選項的 Hash。如果驗證成功,該區塊應傳回 true。傳回 falsenil 將會導致驗證失敗。

12 串流和檔案下載

有時您可能想要將檔案傳送給使用者,而不是呈現 HTML 頁面。Rails 中的所有控制器都有 send_datasend_file 方法,這兩種方法都會將資料串流傳送到用戶端。send_file 是一個方便的方法,可讓您提供磁碟上檔案的名稱,它會為您串流該檔案的內容。

若要將資料串流傳送到用戶端,請使用 send_data

require "prawn"
class ClientsController < ApplicationController
  # Generates a PDF document with information on the client and
  # returns it. The user will get the PDF as a file download.
  def download_pdf
    client = Client.find(params[:id])
    send_data generate_pdf(client),
              filename: "#{client.name}.pdf",
              type: "application/pdf"
  end

  private
    def generate_pdf(client)
      Prawn::Document.new do
        text client.name, align: :center
        text "Address: #{client.address}"
        text "Email: #{client.email}"
      end.render
    end
end

上面範例中的 download_pdf 動作將會呼叫一個私有方法,該方法實際上會產生 PDF 文件並將其作為字串傳回。然後,此字串將會作為檔案下載串流傳送到用戶端,並向使用者建議一個檔案名稱。有時在將檔案串流傳送到使用者時,您可能不希望他們下載檔案。例如,圖像可以嵌入到 HTML 頁面中。若要告知瀏覽器該檔案不應下載,您可以將 :disposition 選項設定為「inline」。此選項的相反和預設值是「attachment」。

12.1 傳送檔案

如果您想傳送磁碟上已存在的檔案,請使用 send_file 方法。

class ClientsController < ApplicationController
  # Stream a file that has already been generated and stored on disk.
  def download_pdf
    client = Client.find(params[:id])
    send_file("#{Rails.root}/files/clients/#{client.id}.pdf",
              filename: "#{client.name}.pdf",
              type: "application/pdf")
  end
end

這將會一次讀取並串流 4 kB 的檔案,避免一次將整個檔案載入記憶體。您可以使用 :stream 選項關閉串流,或使用 :buffer_size 選項調整區塊大小。

如果未指定 :type,則會從 :filename 中指定的副檔名猜測。如果內容類型未針對副檔名註冊,將會使用 application/octet-stream

請小心使用來自用戶端(參數、Cookie 等)的資料在磁碟上找到檔案,因為這是一種安全風險,可能會讓某人存取他們不應該存取的檔案。

如果您可以將靜態檔案保留在 Web 伺服器上的公用資料夾中,則不建議您透過 Rails 串流靜態檔案。讓使用者直接使用 Apache 或其他 Web 伺服器下載檔案會更有效率,避免請求不必要地經過整個 Rails 堆疊。

12.2 RESTful 下載

雖然 send_data 可以正常運作,但如果您正在建立 RESTful 應用程式,通常不需要為檔案下載提供單獨的動作。在 REST 術語中,上述範例中的 PDF 檔案可以被視為用戶端資源的另一種表示形式。Rails 提供了一種執行「RESTful」下載的靈活方式。以下是如何重寫範例,以便 PDF 下載成為 show 動作的一部分,而無需任何串流

class ClientsController < ApplicationController
  # The user can request to receive this resource as HTML or PDF.
  def show
    @client = Client.find(params[:id])

    respond_to do |format|
      format.html
      format.pdf { render pdf: generate_pdf(@client) }
    end
  end
end

您可以對 format 呼叫任何方法,該方法是在 Rails 中註冊為 MIME 類型的副檔名。Rails 已註冊常見的 MIME 類型,例如 "text/html""application/pdf"

Mime::Type.lookup_by_extension(:pdf)
# => "application/pdf"

如果您需要其他 MIME 類型,請在檔案 config/initializers/mime_types.rb 中呼叫 Mime::Type.register。例如,這就是您如何註冊富文本格式 (RTF)

Mime::Type.register("application/rtf", :rtf)

設定檔不會在每次請求時重新載入,因此您必須重新啟動伺服器,變更才會生效。

現在使用者只需在 URL 後面加上 ".pdf" 即可請求取得客戶端的 PDF 版本

GET /clients/1.pdf

12.3 即時串流任意資料

Rails 允許您串流的不僅僅是檔案。事實上,您可以在回應物件中串流任何您想要的東西。 ActionController::Live 模組允許您與瀏覽器建立持續連線。使用此模組,您將能夠在特定的時間點將任意資料發送到瀏覽器。

12.3.1 整合即時串流

在您的控制器類別中加入 ActionController::Live 將使控制器內的所有動作都具有串流資料的能力。您可以像這樣混入模組

class MyController < ActionController::Base
  include ActionController::Live

  def stream
    response.headers["Content-Type"] = "text/event-stream"
    100.times {
      response.stream.write "hello world\n"
      sleep 1
    }
  ensure
    response.stream.close
  end
end

上述程式碼將與瀏覽器保持持續連線,並發送 100 則 "hello world\n" 訊息,每則訊息間隔一秒。

在上面的範例中,有幾件事需要注意。我們需要確保關閉回應串流。忘記關閉串流將使 socket 永遠保持開啟狀態。我們還必須在寫入回應串流之前將內容類型設定為 text/event-stream。這是因為標頭不能在回應被提交後(當 response.committed? 回傳真值時)寫入,這發生在您 writecommit 回應串流時。

12.3.2 使用範例

假設您正在製作卡拉 OK 機,使用者想要取得特定歌曲的歌詞。每首 Song 都有特定的行數,而每行需要 num_beats 的時間來完成演唱。

如果我們想要以卡拉 OK 方式回傳歌詞(僅在歌手完成上一行後才發送下一行),那麼我們可以如下使用 ActionController::Live

class LyricsController < ActionController::Base
  include ActionController::Live

  def show
    response.headers["Content-Type"] = "text/event-stream"
    song = Song.find(params[:id])

    song.each do |line|
      response.stream.write line.lyrics
      sleep line.num_beats
    end
  ensure
    response.stream.close
  end
end

上面的程式碼僅在歌手完成上一行後才發送下一行。

12.3.3 串流注意事項

串流任意資料是一個非常強大的工具。如前面的範例所示,您可以選擇何時以及發送什麼內容到回應串流。但是,您也應該注意以下幾點

  • 每個回應串流都會建立一個新的執行緒,並從原始執行緒複製執行緒區域變數。擁有太多執行緒區域變數可能會對效能產生負面影響。同樣地,大量的執行緒也會阻礙效能。
  • 未能關閉回應串流將使相應的 socket 永遠保持開啟狀態。每當您使用回應串流時,請務必呼叫 close
  • WEBrick 伺服器會緩衝所有回應,因此包含 ActionController::Live 將無法運作。您必須使用不會自動緩衝回應的 Web 伺服器。

13 日誌篩選

Rails 在 log 資料夾中為每個環境保留一個日誌檔案。這些在除錯應用程式中實際發生的事情時非常有用,但在實際運作的應用程式中,您可能不希望將所有資訊都儲存在日誌檔案中。

13.1 參數篩選

您可以透過將敏感的請求參數附加到應用程式設定中的 config.filter_parameters 來篩選日誌檔案中的敏感請求參數。這些參數在日誌中會被標記為 [FILTERED]。

config.filter_parameters << :password

提供的參數將透過部分比對正規表示式來篩選。Rails 會在適當的初始化程式(initializers/filter_parameter_logging.rb)中加入預設篩選器清單,包括 :passw:secret:token,以處理典型的應用程式參數,例如 passwordpassword_confirmationmy_token

13.2 重新導向篩選

有時,您可能希望從日誌檔案中篩選掉您的應用程式重新導向到的一些敏感位置。您可以使用 config.filter_redirect 設定選項來執行此操作

config.filter_redirect << "s3.amazonaws.com"

您可以將其設定為字串、正規表示式或兩者的陣列。

config.filter_redirect.concat ["s3.amazonaws.com", /private_path/]

符合的 URL 將被替換為 '[FILTERED]'。但是,如果您只想篩選參數,而不是整個 URL,請查看參數篩選

14 救援

您的應用程式很可能會包含錯誤,或拋出需要處理的例外狀況。例如,如果使用者點擊了一個指向資料庫中已不存在資源的連結,Active Record 將拋出 ActiveRecord::RecordNotFound 例外狀況。

Rails 預設的例外狀況處理會針對所有例外狀況顯示「500 伺服器錯誤」訊息。如果請求是在本機發出的,則會顯示一個不錯的追溯和一些額外資訊,讓您可以找出問題所在並加以處理。如果請求是遠端發出的,Rails 只會向使用者顯示一個簡單的「500 伺服器錯誤」訊息,如果發生路由錯誤,或找不到記錄,則會顯示「404 找不到」。有時您可能想要自訂如何捕獲這些錯誤,以及如何向使用者顯示這些錯誤。Rails 應用程式中有多個層級的例外狀況處理可供使用

14.1 預設的 500 和 404 範本

依預設,在生產環境中,應用程式將會呈現 404 或 500 錯誤訊息。在開發環境中,所有未處理的例外狀況都會直接拋出。這些訊息包含在 public 資料夾中的靜態 HTML 檔案中,分別為 404.html500.html。您可以自訂這些檔案以新增一些額外資訊和樣式,但請記住它們是靜態 HTML;也就是說,您不能將它們使用 ERB、SCSS、CoffeeScript 或版面配置。

14.2 rescue_from

如果您想在捕獲錯誤時執行更精細的操作,您可以使用 rescue_from,它會處理整個控制器及其子類別中特定類型(或多種類型)的例外狀況。

當發生一個被 rescue_from 指令捕獲的例外狀況時,例外狀況物件會被傳遞到處理程式。處理程式可以是方法或傳遞給 :with 選項的 Proc 物件。您也可以直接使用區塊,而不是明確的 Proc 物件。

以下是如何使用 rescue_from 攔截所有 ActiveRecord::RecordNotFound 錯誤並對它們執行某些操作的方法。

class ApplicationController < ActionController::Base
  rescue_from ActiveRecord::RecordNotFound, with: :record_not_found

  private
    def record_not_found
      render plain: "404 Not Found", status: 404
    end
end

當然,這個範例一點也不精細,並且根本沒有改進預設的例外狀況處理,但是一旦您可以捕獲所有這些例外狀況,您就可以隨意對它們執行任何操作。例如,您可以建立自訂的例外狀況類別,當使用者無法存取您的應用程式的某個部分時,將會拋出這些類別

class ApplicationController < ActionController::Base
  rescue_from User::NotAuthorized, with: :user_not_authorized

  private
    def user_not_authorized
      flash[:error] = "You don't have access to this section."
      redirect_back(fallback_location: root_path)
    end
end

class ClientsController < ApplicationController
  # Check that the user has the right authorization to access clients.
  before_action :check_authorization

  # Note how the actions don't have to worry about all the auth stuff.
  def edit
    @client = Client.find(params[:id])
  end

  private
    # If the user is not authorized, just throw the exception.
    def check_authorization
      raise User::NotAuthorized unless current_user.admin?
    end
end

rescue_fromExceptionStandardError 一起使用會導致嚴重的副作用,因為它會阻止 Rails 正確處理例外狀況。因此,除非有強烈的理由,否則不建議這樣做。

在生產環境中執行時,所有 ActiveRecord::RecordNotFound 錯誤都會呈現 404 錯誤頁面。除非您需要自訂行為,否則您不需要處理此問題。

某些例外狀況只能從 ApplicationController 類別中救援,因為它們是在控制器初始化之前和動作執行之前引發的。

15 強制 HTTPS 協定

如果您想確保只能透過 HTTPS 與您的控制器進行通訊,您應該透過在您的環境設定中啟用 ActionDispatch::SSL 中介軟體,並使用 config.force_ssl 來執行。

16 內建的健康檢查端點

Rails 還配備了一個內建的健康檢查端點,可透過 /up 路徑訪問。如果應用程式在沒有例外狀況的情況下啟動,此端點將會回傳 200 狀態碼,否則會回傳 500 狀態碼。

在生產環境中,許多應用程式都需要向上報告其狀態,無論是向上傳時間監視器,當發生問題時會通知工程師,還是用於決定 Pod 健康狀況的負載平衡器或 Kubernetes 控制器。此健康檢查旨在成為一種適用於許多情況的通用方法。

雖然任何新產生的 Rails 應用程式都將在 /up 處進行健康檢查,但您可以在 "config/routes.rb" 中設定路徑為您想要的任何內容

Rails.application.routes.draw do
  get "healthz" => "rails/health#show", as: :rails_health_check
end

現在可以透過 /healthz 路徑存取健康檢查。

此端點不會反映您應用程式所有相依性的狀態,例如資料庫或 redis 叢集。如果您有應用程式特定的需求,請將 "rails/health#show" 替換為您自己的控制器動作。

請仔細考慮您要檢查的內容,因為這可能會導致因第三方服務發生故障而重新啟動應用程式的情況。理想情況下,您應該設計您的應用程式來優雅地處理這些停機情況。



回到頂端