액션 컨트롤러 개요

이 가이드에서는 컨트롤러가 작동하는 방식과 애플리케이션의 요청 주기에 어떻게 적합하는지 배울 것입니다.

이 가이드를 읽고 나면 다음을 할 수 있습니다:

  • 컨트롤러를 통한 요청의 흐름을 따르기.
  • 컨트롤러에 전달되는 매개변수 제한하기.
  • 세션 또는 쿠키에 데이터 저장하고 그 이유 이해하기.
  • 요청 처리 중에 코드를 실행하는 액션 콜백 작업하기.
  • 액션 컨트롤러의 내장 HTTP 인증 사용하기.
  • 사용자의 브라우저로 데이터를 직접 스트리밍하기.
  • 민감한 매개변수 필터링하여 애플리케이션 로그에 나타나지 않게 하기.
  • 요청 처리 중에 발생할 수 있는 예외 처리하기.
  • 로드 밸런서와 가동 시간 모니터를 위한 내장 상태 점검 엔드포인트 사용하기.

컨트롤러는 무엇을 하나요?

액션 컨트롤러는 MVC의 C입니다. 라우터가 어떤 컨트롤러를 사용할지 결정한 후, 컨트롤러는 요청을 이해하고 적절한 출력을 생성할 책임이 있습니다. 다행히도 액션 컨트롤러는 대부분의 기반 작업을 수행하고 스마트한 규칙을 사용하여 이 작업을 가능한 간단하게 만듭니다.

대부분의 일반적인 RESTful 애플리케이션의 경우, 컨트롤러는 요청을 받고(개발자에게는 보이지 않음), 모델에서 데이터를 가져오거나 저장하고, 뷰를 사용하여 HTML 출력을 생성합니다. 컨트롤러가 약간 다르게 작동해야 하는 경우에도 문제가 되지 않습니다. 이것이 컨트롤러가 일반적으로 작동하는 가장 일반적인 방식입니다.

따라서 컨트롤러는 모델과 뷰 사이의 중개자로 생각할 수 있습니다. 모델 데이터를 뷰에 사용할 수 있게 하여 사용자에게 표시하고, 사용자 데이터를 모델에 저장하거나 업데이트합니다.

참고: 라우팅 프로세스에 대한 자세한 내용은 Rails Routing from the Outside In을 참조하세요.

컨트롤러 명명 규칙

Rails의 컨트롤러 명명 규칙은 컨트롤러 이름의 마지막 단어를 복수형으로 사용하는 것을 선호하지만 엄격하게 요구되지는 않습니다(예: ApplicationController). 예를 들어 ClientsControllerClientController보다 선호되고, SiteAdminsControllerSiteAdminControllerSitesAdminsController보다 선호됩니다.

이 규칙을 따르면 :path 또는 :controller를 지정할 필요 없이 기본 경로 생성기(예: resources 등)를 사용할 수 있으며, 애플리케이션 전체에서 명명된 경로 헬퍼의 사용이 일관될 것입니다. 자세한 내용은 Layouts and Rendering Guide를 참조하세요.

참고: 모델 명명 규칙은 컨트롤러와 다르며, 단수형으로 명명되기를 기대합니다.

메서드와 액션

컨트롤러는 ApplicationController를 상속받는 Ruby 클래스이며 다른 클래스와 마찬가지로 메서드를 가지고 있습니다. 애플리케이션이 요청을 받으면 라우팅이 어떤 컨트롤러와 액션을 실행할지 결정하고, Rails는 해당 컨트롤러의 인스턴스를 생성하고 액션과 이름이 같은 메서드를 실행합니다.

class ClientsController < ApplicationController
  def new
  end
end

예를 들어, 사용자가 애플리케이션의 /clients/new로 이동하여 새 클라이언트를 추가하면 Rails는 ClientsController 인스턴스를 생성하고 new 메서드를 호출합니다. 위의 빈 메서드 예제도 잘 작동할 것입니다. 왜냐하면 Rails는 기본적으로 new.html.erb 뷰를 렌더링할 것이기 때문입니다. 새 Client를 만들면 new 메서드가 뷰에서 사용할 수 있는 @client 인스턴스 변수를 만들 수 있습니다:

def new
  @client = Client.new
end

Layouts and Rendering Guide에서 이에 대해 더 자세히 설명합니다.

ApplicationControllerActionController::Base를 상속받으며, 이는 많은 유용한 메서드를 정의합니다. 이 가이드에서는 일부를 다루지만, 궁금하다면 API 문서나 소스 코드에서 모두 확인할 수 있습니다.

공개 메서드만 액션으로 호출할 수 있습니다. 액션이 아닌 보조 메서드나 필터는 private 또는 protected로 가시성을 낮추는 것이 가장 좋습니다.

경고: 액션 컨트롤러에는 예약된 메서드 이름이 있습니다. 실수로 이를 액션이나 보조 메서드로 재정의하면 SystemStackError가 발생할 수 있습니다. RESTful Resource Routing의 액션만 사용한다면 이 문제를 걱정할 필요가 없습니다.

참고: 예약된 메서드 이름을 액션 이름으로 사용해야 하는 경우, 사용자 정의 경로를 사용하여 예약된 메서드 이름을 비예약 액션 메서드에 매핑하는 것이 한 가지 해결책입니다.

매개변수

사용자가 보낸 데이터나 다른 매개변수에 액세스해야 할 것입니다. 웹 애플리케이션에는 두 가지 유형의 매개변수가 있습니다. 첫 번째는 URL의 일부로 전송되는 쿼리 문자열 매개변수입니다. 쿼리 문자열은 URL에서 “?"이후의 모든 것입니다. 두 번째 유형의 매개변수는 일반적으로 POST 데이터라고 합니다. 이 정보는 일반적으로 사용자가 작성한 HTML 양식에서 옵니다. POST 데이터라고 불리는 이유는 HTTP POST 요청의 일부로만 전송될 수 있기 때문입니다. Rails는 쿼리 문자열 매개변수와 POST 매개변수 사이에 구분을 두지 않으며, 둘 다 컨트롤러의 params 해시에서 사용할 수 있습니다:

class ClientsController < ApplicationController
  # 이 액션은 쿼리 문자열 매개변수를 사용합니다. HTTP GET 요청으로 실행되기 때문입니다.
  # 그러나 매개변수에 액세스하는 방식에는 차이가 없습니다.
  # 이 액션의 URL은 활성화된 클라이언트를 나열하기 위해
  # 다음과 같이 보일 것입니다: /clients?status=activated
  def index
    if params[:status] == "activated"
      @clients = Client.activated
    else
      @clients = Client.inactivated
    end
  end

  # 이 액션은 POST 매개변수를 사용합니다.
  # 사용자가 제출한 HTML 양식에서 가장 많이 온 것 같습니다.
  # 이 RESTful 요청의 URL은 "/clients"이며,
  # 데이터는 요청 본문의 일부로 전송됩니다.
  def create
    @client = Client.new(params[:client])
    if @client.save
      redirect_to @client
    else
      # 이 줄은 기본 렌더링 동작을 재정의합니다.
      # "create" 뷰를 렌더링했을 것입니다.
      render "new"
    end
  end
end

해시 및 배열 매개변수

params 해시는 단일 차원의 키와 값으로 제한되지 않습니다. 중첩된 배열과 해시를 포함할 수 있습니다. 값 배열을 보내려면 키 이름에 빈 대괄호 ”[]“를 추가하세요:

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

참고: 이 예제의 실제 URL은 ”[“ 및 ”]“ 문자가 URL에 허용되지 않기 때문에 ”/clients?ids%5b%5d=1&ids%5b%5d=2&ids%5b%5d=3"으로 인코딩됩니다. 대부분의 경우 브라우저가 이를 자동으로 인코딩하고 Rails가 자동으로 디코딩하므로 걱정할 필요가 없지만, 직접 서버에 이러한 요청을 보내야 하는 경우 이 점을 염두에 두어야 합니다.

params[:ids]의 값은 ["1", "2", "3"]이 될 것입니다. 매개변수 값은 항상 문자열이라는 점에 유의하세요. Rails는 유형을 추측하거나 캐스팅하지 않습니다.

참고: [nil] 또는 [nil, nil, ...]과 같은 값은 보안 상의 이유로 기본적으로 []로 대체됩니다. 자세한 내용은 Security Guide를 참조하세요.

해시를 보내려면 키 이름을 괄호 안에 포함하세요:

<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와 같이 작동하지만 기호와 문자열을 키로 상호 교환할 수 있습니다.

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 젬으로 추출되었습니다.

라우팅 매개변수

params 해시에는 항상 :controller:action 키가 있지만, controller_nameaction_name을 사용하여 이 값에 액세스해야 합니다. 라우팅에 의해 정의된 다른 매개변수(예: :id)도 사용할 수 있습니다. 예를 들어, 활성 또는 비활성 클라이언트를 보여줄 수 있는 클라이언트 목록이 있다고 가정합시다. :status 매개변수를 캡처하는 "pretty” URL을 추가할 수 있습니다:

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

이 경우 사용자가 /clients/active를 열면 params[:status]가 “active"로 설정됩니다. 이 경로를 사용하면 params[:foo]도 "bar"로 설정됩니다. 컨트롤러는 params[:action]을 "index”, params[:controller]를 “clients"로 받게 됩니다.

복합 키 매개변수

복합 키 매개변수에는 하나의 매개변수에 여러 값이 포함됩니다. 따라서 각 값을 추출하고 Active Record에 전달할 수 있어야 합니다. extract_value 메서드를 활용할 수 있습니다.

다음과 같은 컨트롤러가 있다고 가정합시다:

class BooksController < ApplicationController
  def show
    # URL 매개변수에서 복합 ID 값을 추출합니다.
    id = params.extract_value(:id)
    # 복합 ID를 사용하여 책을 찾습니다.
    @book = Book.find(id)
    # 기본 렌더링 동작을 사용하여 show 뷰를 렌더링합니다.
  end
end

그리고 다음과 같은 경로가 있습니다:

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

사용자가 /books/4_2를 열면 컨트롤러가 복합 키 값 ["4", "2"]를 추출하고 Book.find에 전달하여 뷰에서 올바른 레코드를 렌더링합니다. extract_value 메서드는 모든 구분된 매개변수에서 배열을 추출하는 데 사용할 수 있습니다.

default_url_options

컨트롤러에서 default_url_options 메서드를 정의하여 URL 생성을 위한 전역 기본 매개변수를 설정할 수 있습니다. 이러한 메서드는 기호 키가 있는 해시를 반환해야 합니다:

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

이러한 옵션은 URL 생성 시 시작점으로 사용되므로 url_for 호출에 전달된 옵션으로 재정의될 수 있습니다.

ApplicationControllerdefault_url_options를 정의하면(위 예제와 같이) 모든 URL 생성에 이러한 기본값이 사용됩니다. 메서드는 특정 컨트롤러에도 정의할 수 있으며, 이 경우 해당 컨트롤러에서 생성된 URL에만 영향을 미칩니다.

주어진 요청에서 메서드는 실제로 모든 생성된 URL에 대해 호출되지 않습니다. 성능 이유로 반환된 해시가 캐시되며 요청당 최대 한 번 호출됩니다.

강력한 매개변수

강력한 매개변수를 사용하면 Action Controller 매개변수가 허용되기 전까지 Active Model 대량 할당에 사용될 수 없습니다. 즉, 대량 업데이트에 허용할 속성을 의식적으로 결정해야 합니다. 이는 민감한 모델 속성을 실수로 업데이트하는 것을 방지하는 더 나은 보안 관행입니다.

또한 매개변수를 필수로 표시할 수 있으며, 필수 매개변수가 모두 전달되지 않으면 400 Bad Request가 반환되는 미리 정의된 예외 처리 흐름을 통과하게 됩니다.

class PeopleController < ActionController::Base
  # 명시적 허용 단계 없이 대량 할당을 사용하므로
  # ActiveModel::ForbiddenAttributesError 예외가 발생합니다.
  def create
    Person.create(params[:person])
  end

  # params에 person 키가 있는 한 문제 없이 통과합니다.
  # 그렇지 않으면 ActionController::ParameterMissing 예외가
  # 발생하여 ActionController::Base에 의해 400 Bad Request 오류로 변환됩니다.
  def update
    person = current_account.people.find(params[:id])
    person.update!(person_params)
    redirect_to person
  end

  private
    # 허용 가능한 매개변수 목록을 캡슐화하는
    # 개인 메서드를 사용하는 것이 좋은 패턴입니다.
    # create와 update 간에 동일한 허용 목록을 재사용할 수 있습니다.
    # 또한 사용자별 허용 가능한 속성 검사를 특수화할 수 있습니다.
    def person_params
      params.require(:person).permit(:name, :age)
    end
end

허용된 스칼라 값

다음과 같이 permit을 호출하면:

params.permit(:id)

:id 키가 params에 나타나고 허용된 스칼라 값과 연결된 경우 해당 키가 허용됩니다. 그렇지 않으면 키가 필터링됩니다. 따라서 배열, 해시 또는 다른 객체는 주입될 수 없습니다.

허용된 스칼라 유형은 String, Symbol, NilClass, Numeric, TrueClass, FalseClass, Date, Time, DateTime, StringIO, IO, ActionDispatch::Http::UploadedFileRack::Test::UploadedFile입니다.

params의 값이 허용된 스칼라 값의 배열이어야 한다고 선언하려면 키를 빈 배열에 매핑하세요:

params.permit(id: [])

때로는 해시 매개변수의 유효한 키나 내부 구조를 선언하는 것이 불가능하거나 편리하지 않습니다. 그냥 빈 해시에 매핑하세요:

params.permit(preferences: {})

그러나 이렇게 하면 임의 입력이 허용되므로 주의해야 합니다. 이 경우 permit은 반환된 구조의 값이 허용된 스칼라인지 확인하고 그렇지 않은 것은 필터링합니다.

전체 매개변수 해시를 허용하려면 permit! 메서드를 사용할 수 있습니다:

params.require(:log_entry).permit!

이렇게 하면 :log_entry 매개변수 해시와 그 하위 해시가 허용되며 허용된 스칼라를 확인하지 않습니다. 모든 현재 및 향후 모델 속성이 대량 할당될 수 있으므로 permit!을 사용할 때는 극도의 주의가 필요합니다.

중첩된 매개변수

중첩된 매개변수에도 permit을 사용할 수 있습니다:

params.permit(:name, { emails: [] },
              friends: [ :name,
                         { family: [ :name ], hobbies: [] }])

이 선언은 name, emailsfriends 속성을 허용합니다. emails는 허용된 스칼라 값의 배열이 될 것으로 예상되며, friends는 특정 속성이 있는 리소스의 배열이 될 것으로 예상됩니다: name 속성(모든 허용된 스칼라 값 허용), hobbies 속성은 허용된 스칼라 값의 배열, family 속성은 name(모든 허용된 스칼라 값 허용)으로 제한됩니다.

더 많은 예제

new 액션에서도 허용된 속성을 사용하고 싶을 수 있습니다. 이는 일반적으로 new를 호출할 때 루트 키가 존재하지 않기 때문에 require를 사용할 수 없는 문제를 발생시킵니다:

# `fetch`를 사용하면 기본값을 제공하고
# 강력한 매개변수 API를 사용할 수 있습니다.
params.fetch(:blog, {}).permit(:title, :author)

accepts_nested_attributes_for 모델 클래스 메서드를 사용하면 관련 레코드를 업데이트하고 삭제할 수 있습니다. 이는 id_destroy 매개변수를 기반으로 합니다:

# :id와 :_destroy를 허용합니다.
params.require(:author).permit(:name, books_attributes: [:title, :id, :_destroy])

정수 키가 있는 해시는 다르게 처리되며 직접 자식인 것처럼 속성을 선언할 수 있습니다. 이러한 유형의 매개변수는 has_many 연관과 함께 accepts_nested_attributes_for를 사용할 때 발생합니다:

# 다음과 같은 데이터를 허용하려면:
# {"book" => {"title" => "Some Book",
#             "chapters_attributes" => { "1" => {"title" => "First Chapter"},
#                                        "2" => {"title" => "Second Chapter"}}}}

params.require(:book).permit(:title, chapters_attributes: [:title])

제품 이름을 나타내는 매개변수와 해당 제품과 관련된 임의의 데이터 해시가 있는 시나리오를 상상해 보세요. 제품 이름 속성과 전체 데이터 해시를 모두 허용하고 싶습니다:

def product_params
  params.require(:product).permit(:name, data: {})
end

강력한 매개변수 범위 밖

강력한 매개변수 API는 가장 일반적인 사용 사례를 염두에 두고 설계되었습니다. 모든 매개변수 필터링 문제를 해결하기 위한 만능 해결책이 아닙니다. 그러나 API와 자체 코드를 쉽계속해서 번역하겠습니다:

강력한 매개변수 범위 밖

강력한 매개변수 API는 가장 일반적인 사용 사례를 염두에 두고 설계되었습니다. 모든 매개변수 필터링 문제를 해결하기 위한 만능 해결책이 아닙니다. 그러나 API와 자체 코드를 쉽게 혼합하여 상황에 맞게 적응시킬 수 있습니다.

세션

애플리케이션에는 각 사용자에 대한 세션이 있으며, 여기에 요청 간에 지속되는 소량의 데이터를 저장할 수 있습니다. 세션은 컨트롤러와 뷰에서만 사용할 수 있으며 여러 가지 다른 저장 메커니즘을 사용할 수 있습니다:

모든 세션 저장소는 각 세션의 고유 ID를 저장하는 쿠키를 사용합니다(쿠키를 사용해야 하며 Rails는 세션 ID를 URL에 전달하는 것을 허용하지 않습니다. 이는 보안상 더 안전하지 않습니다).

대부분의 저장소의 경우 이 ID는 서버의 데이터베이스 테이블에서 세션 데이터를 조회하는 데 사용됩니다. 예외는 기본 및 권장 세션 저장소인 CookieStore입니다. CookieStore는 세션 데이터 전체를 쿠키에 저장합니다(ID는 여전히 사용할 수 있습니다). 이는 매우 가볍고 새 애플리케이션에서 세션을 사용하기 위한 설정이 전혀 필요하지 않다는 장점이 있습니다. 쿠키 데이터는 변조 방지를 위해 암호화 서명됩니다. 또한 암호화되어 있어 액세스할 수 있는 사람이 내용을 읽을 수 없습니다(Rails는 편집된 것을 허용하지 않습니다).

CookieStore는 약 4 kB의 데이터를 저장할 수 있습니다. 다른 저장소보다 훨씬 적지만 일반적으로 충분합니다. 세션에 많은 양의 데이터를 저장하는 것은 어떤 세션 저장소를 사용하든 권장되지 않습니다. 특히 모델 인스턴스와 같은 복잡한 객체를 세션에 저장하는 것은 피해야 합니다. 요청 간에 이를 다시 조립할 수 없어 오류가 발생할 수 있습니다.

사용자 세션에 중요한 데이터가 저장되어 있지 않거나 오래 지속될 필요가 없는 경우(예: 플래시 메시징에만 사용하는 경우) ActionDispatch::Session::CacheStore를 고려할 수 있습니다. 이렇게 하면 애플리케이션에 구성된 캐시 구현을 사용하여 세션을 저장할 수 있습니다. 장점은 추가 설정이나 관리 없이 기존 캐시 인프라를 사용할 수 있다는 것입니다. 단점은 세션이 임시적이며 언제든 사라질 수 있다는 것입니다.

보안 가이드에서 세션 저장에 대해 자세히 알아보세요.

다른 세션 저장 메커니즘이 필요한 경우 초기화기에서 변경할 수 있습니다:

Rails.application.config.session_store :cache_store

구성 가이드의 config.session_store에서 자세한 내용을 확인하세요.

Rails는 세션 데이터에 서명하는 데 사용되는 세션 키(쿠키의 이름)를 설정합니다. 이는 초기화기에서 변경할 수 있습니다:

# 이 파일을 수정하면 서버를 다시 시작해야 합니다.
Rails.application.config.session_store :cookie_store, key: '_your_app_session'

:domain 키를 전달하고 쿠키에 대한 도메인 이름을 지정할 수도 있습니다:

# 이 파일을 수정하면 서버를 다시 시작해야 합니다.
Rails.application.config.session_store :cookie_store, key: '_your_app_session', domain: ".example.com"

Rails는 (CookieStore의 경우) config/credentials.yml.enc에서 세션 데이터에 서명하는 데 사용되는 비밀 키를 설정합니다. bin/rails credentials:edit로 변경할 수 있습니다.

# aws:
#   access_key_id: 123
#   secret_access_key: 345

# Rails의 모든 MessageVerifiers를 보호하는 기본 비밀입니다. 쿠키도 포함됩니다.
secret_key_base: 492f...

참고: CookieStore를 사용할 때 secretkeybase를 변경하면 모든 기존 세션이 무효화됩니다.

세션 액세스

컨트롤러에서 session 인스턴스 메서드를 통해 세션에 액세스할 수 있습니다.

참고: 세션은 지연 로드됩니다. 액션 코드에서 세션을 액세스하지 않으면 로드되지 않습니다. 따라서 세션을 비활성화할 필요가 없습니다. 그냥 액세스하지 않으면 됩니다.

세션 값은 해시와 같은 키/값 쌍으로 저장됩니다:

class ApplicationController < ActionController::Base
  private
    # 세션에 저장된 :current_user_id 키로 사용자를 찾습니다.
    # Rails 애플리케이션에서 일반적인 사용자 로그인 처리 방식입니다.
    # 로그인하면 세션 값이 설정되고 로그아웃하면 제거됩니다.
    def current_user
      @_current_user ||= session[:current_user_id] &&
        User.find_by(id: session[:current_user_id])
    end
end

세션에 무언가를 저장하려면 해시처럼 키에 할당하면 됩니다:

class LoginsController < ApplicationController
  # 로그인 "생성", 즉 "사용자 로그인"
  def create
    if user = User.authenticate(params[:username], params[:password])
      # 이후 요청에서 사용할 수 있도록 사용자 ID를 세션에 저장합니다.
      session[:current_user_id] = user.id
      redirect_to root_url
    end
  end
end

세션에서 무언가를 제거하려면 키/값 쌍을 삭제하면 됩니다:

class LoginsController < ApplicationController
  # 로그인 "삭제", 즉 "사용자 로그아웃"
  def destroy
    # 세션에서 사용자 ID 제거
    session.delete(:current_user_id)
    # 현재 사용자 메모이제이션 지우기
    @_current_user = nil
    redirect_to root_url, status: :see_other
  end
end

전체 세션을 재설정하려면 reset_session을 사용하세요.

플래시

플래시는 각 요청마다 지워지는 세션의 특별한 부분입니다. 이는 오류 메시지 등을 다음 요청에 전달하는 데 유용합니다.

플래시는 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

리디렉션의 일부로 플래시 메시지를 할당할 수도 있습니다. :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로 리디렉션하며, 여기에 메시지가 표시됩니다. 다음 작업이 이전 작업이 플래시에 넣은 메시지를 어떻게 처리할지 결정하는 것은 전적으로 그들의 몫입니다. 일반적으로 레이아웃에서 플래시의 오류 경고 또는 알림을 표시하는 것이 관례입니다:

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

    <!-- 더 많은 콘텐츠 -->
  </body>
</html>

이렇게 하면 작업에서 알림 또는 경고 메시지를 설정하면 레이아웃에서 자동으로 표시됩니다.

세션에 저장할 수 있는 것이라면 무엇이든 전달할 수 있습니다. 알림과 경고로 제한되지 않습니다:

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

플래시 값을 다른 요청으로 전달하려면 flash.keep을 사용하세요:

class MainController < ApplicationController
  # 이 액션은 root_url에 해당하지만 모든 요청을 UsersController#index로
  # 리디렉션하려고 합니다. 작업에서 플래시를 설정하고 여기로 리디렉션하면
  # 다른 리디렉션이 발생할 때 값이 손실되지만 'keep'을 사용하면
  # 다음 요청에서도 유지됩니다.
  def index
    # 모든 플래시 값을 유지합니다.
    flash.keep

    # 특정 값만 유지할 수도 있습니다.
    # flash.keep(:notice)
    redirect_to users_url
  end
end

flash.now

기본적으로 플래시에 값을 추가하면 다음 요청에서 사용할 수 있게 됩니다. 그러나 때로는 같은 요청에서 해당 값에 액세스해야 할 수 있습니다. 예를 들어 create 작업이 리소스를 저장하지 못하고 new 템플릿을 직접 렌더링하는 경우, 이는 새 요청이 발생하지 않지만 계속해서 번역하겠습니다:

flash.now

기본적으로 플래시에 값을 추가하면 다음 요청에서 사용할 수 있게 됩니다. 그러나 때로는 같은 요청에서 해당 값에 액세스해야 할 수 있습니다. 예를 들어 create 작업이 리소스를 저장하지 못하고 new 템플릿을 직접 렌더링하는 경우, 이는 새 요청이 발생하지 않지만 메시지를 표시하고 싶을 수 있습니다. 이를 위해 일반 flash와 동일한 방식으로 flash.now를 사용할 수 있습니다:

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

쿠키

애플리케이션은 클라이언트에 작은 양의 데이터(쿠키라고 함)를 저장할 수 있으며, 이는 요청과 세션 간에 지속됩니다. Rails는 cookies 메서드를 통해 쿠키에 쉽게 액세스할 수 있으며, session과 마찬가지로 해시와 같이 작동합니다:

class CommentsController < ApplicationController
  def new
    # 쿠키에 저장된 작성자 이름으로 댓글 자동 채우기
    @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]
        # 작성자 이름 쿠키 기억하기
        cookies[:commenter_name] = @comment.author
      else
        # 작성자 이름 쿠키 삭제하기(있는 경우)
        cookies.delete(:commenter_name)
      end
      redirect_to @comment.article
    else
      render action: "new"
    end
  end
end

세션 값의 경우 키를 nil로 설정할 수 있지만, 쿠키 값을 삭제하려면 cookies.delete(:key)를 사용해야 합니다.

Rails는 또한 민감한 데이터를 저장하기 위한 서명된 쿠키 저장소와 암호화된 쿠키 저장소를 제공합니다. 서명된 쿠키 저장소는 쿠키 값의 무결성을 보호하기 위해 암호화 서명을 추가합니다. 암호화된 쿠키 저장소는 값을 암호화하고 서명하므로 최종 사용자가 읽을 수 없습니다. API 문서에서 자세한 내용을 확인하세요.

이러한 특수 쿠키 저장소는 할당된 값을 문자열로 직렬화하고 읽을 때 Ruby 객체로 역직렬화하는 직렬 변환기를 사용합니다. 사용할 직렬 변환기는 config.action_dispatch.cookies_serializer를 통해 지정할 수 있습니다.

새 애플리케이션의 기본 직렬 변환기는 :json입니다. JSON은 Ruby 객체의 라운드트립 지원이 제한적이라는 점에 유의해야 합니다. 예를 들어 Date, TimeSymbol 객체(해시 키 포함)는 문자열로 직렬화되고 역직렬화됩니다:

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

이러한 객체나 더 복잡한 객체를 저장해야 하는 경우 후속 요청에서 값을 읽을 때 수동으로 변환해야 할 수 있습니다.

쿠키 세션 저장소를 사용하는 경우 위 내용이 sessionflash 해시에도 적용됩니다.

렌더링

ActionController는 HTML, XML 또는 JSON 데이터를 렌더링하는 것을 매우 쉽게 만듭니다. 스캐폴딩을 사용하여 컨트롤러를 생성한 경우 다음과 같이 보일 것입니다:

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를 사용하는 것을 주목하세요. 객체가 문자열이 아닌 경우 Rails는 자동으로 to_xml을 호출합니다.

렌더링에 대해 자세히 알아보려면 Layouts and Rendering Guide를 참조하세요.

액션 콜백

액션 콜백은 컨트롤러 액션 "전”, “후” 또는 “주변"에 실행되는 메서드입니다.

액션 콜백은 상속되므로 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 # 요청 주기 중단
      end
    end
end

이 메서드는 사용자가 로그인되어 있지 않으면 오류 메시지를 플래시에 저장하고 로그인 폼으로 리디렉션합니다. “전” 액션 콜백이 렌더링하거나 리디렉션하면 컨트롤러 액션이 실행되지 않습니다. 그 후에 예약된 추가 액션 콜백도 취소됩니다.

이 예에서 액션 콜백은 ApplicationController에 추가되므로 애플리케이션의 모든 컨트롤러가 이를 상속받습니다. 이렇게 하면 애플리케이션의 모든 것이 사용자가 로그인해야 사용할 수 있게 됩니다. 명확한 이유로(사용자가 처음부터 로그인할 수 없음!) 모든 컨트롤러나 액션에 이 요구 사항이 필요하지는 않습니다. skip_before_action을 사용하여 특정 액션에서 이 액션 콜백을 방지할 수 있습니다:

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

이제 LoginsControllernewcreate 액션은 사용자가 로그인하지 않아도 작동합니다. :only 옵션은 이 액션 콜백을 이러한 액션에서만 건너뛰도록 사용됩니다. :except 옵션도 반대로 작동합니다. 이러한 옵션은 액션 콜백을 추가할 때도 사용할 수 있으므로 처음부터 선택된 액션에서만 실행되도록 할 수 있습니다.

참고: 다른 옵션으로 동일한 액션 콜백을 여러 번 호출하면 작동하지 않습니다. 마지막 액션 콜백 정의가 이전 것을 덮어씁니다.

후 액션 및 주변 액션 콜백

“전” 액션 콜백 외에도 컨트롤러 액션 실행 후 또는 전후에 실행되는 액션 콜백을 실행할 수 있습니다.

“후” 액션 콜백은 after_action을 통해 등록됩니다. “전” 액션 콜백과 유사하지만 컨트롤러 액션이 이미 실행되었기 때문에 클라이언트에 보내려는 응답 데이터에 액세스할 수 있습니다. 물론 “후” 액션 콜백은 액션 실행을 중지할 수 없습니다. “후” 액션 콜백은 성공적인 컨트롤러 액션 실행 후에만 실행되고 요청 주기에서 예외가 발생하면 실행되지 않습니다.

“주변” 액션 콜백은 around_action을 통해 등록됩니다. 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하지 않고 직접 응답을 빌드할 수 있습니다. 이 경우 컨트롤러 액션이 실행되지 않습니다.

액션 콜백 사용의 다른 방법

가장 일반적인 액션 콜백 사용 방법은 개인 메서드를 만들고 before_action, after_action 또는 around_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

이 경우 액션 콜백은 logged_in? 메서드가 개인이므로 send를 사용합니다. 컨트롤러 범위에서 실행되지 않습니다. 이 특정 액션 콜백의 경우 권장되는 방법은 아니지만 더 간단한 경우에는 유용할 수 있습니다.

특히 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를 실행해야 합니다.

요청 위조 보호

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 헬퍼를 사용하여 생성된 모든 양식에 이 토큰을 추가합니다. 따라서 대부분의 경우 걱정할 필요가 없습니다. 직접 양식을 작성하거나 다른 이유로 토큰을 추가해야 하는 경우 form_authenticity_token 메서드를 사용할 수 있습니다:

form_authenticity_token은 유효한 인증 토큰을 생성합니다. Rails에서 자동으로 추가되지 않는 사용자 지정 Ajax 호출과 같은 경우에 유용합니다.

보안 가이드에는 이에 대한 자세한 내용과 웹 애플리케이션을 개발할 때 알아야 할 다른 많은 보안 관련 문제가 나와 있습니다.

요청 및 응답 객체

모든 컨트롤러에는 현재 실행 중인 요청 주기와 관련된 요청 및 응답 객체를 가리키는 두 개의 액세서 메서드가 있습니다. request 메서드에는 ActionDispatch::Request 인스턴스가 포함되어 있고 response 메서드는 클라이언트에 다시 보내려는 응답 객체를 반환합니다.

request 객체

요청 객체에는 클라이언트에서 들어오는 요청에 대한 많은 유용한 정보가 포함되어 있습니다. 사용 가능한 메서드 전체 목록을 보려면 Rails API 문서Rack 문서를 참조하세요. 이 객체에서 액세스할 수 있는 속성은 다음과 같습니다:

request 속성 목적
host 이 요청에 사용된 호스트 이름.
domain(n=2) 호스트 이름의 첫 번째 n 세그먼트(오른쪽에서 시작, TLD).
format 클라이언트가 요청한 콘텐츠 유형.
method 요청에 사용된 HTTP 메서드.
get?, post?, patch?, put?, delete?, head? GET/POST/PATCH/PUT/DELETE/HEAD HTTP 메서드 여부를 반환합니다.
headers 요청과 연결된 헤더를 포함하는 해시를 반환합니다.
port 요청에 사용된 포트 번호(정수).
protocol “://"가 포함된 프로토콜 문자열을 반환합니다(예: "http://”).
query_string URL의 쿼리 문자열 부분, 즉 “?” 이후의 모든 것.
remote_ip 클라이언트의 IP 주소.
url 요청에 사용된 전체 URL.

path_parameters, query_parametersrequest_parameters

Rails는 요청과 함께 전송된 모든 매개변수를 params 해시에 수집합니다. 쿼리 문자열이나 게시 본문에서 온 것이든 상관없습니다. 요청 객체에는 이러한 매개변수에 액세스할 수 있는 세 가지 액세서가 있습니다. query_parameters 해시에는 쿼리 문자열의 일부로 전송된 매개변수가 포함되고 request_parameters 해시에는 게시 본문의 일부로 전송된 매개변수가 포함됩니다. path_parameters 해시에는 라우팅에 의해 이 특정 컨트롤러와 액션의 일부로 인식된 매개변수가 포함됩니다.

response 객체

응답 객체는 일반적으로 직접 사용되지 않지만, 작업 실행 및 사용자에게 보내는 데이터 렌더링 중에 구축됩니다. 그러나 때때로 액션 콜백과 같은 경우에는 응답에 직접 액세스하는 것이 유용할 수 있습니다. 일부 액세서 메서드에는 값을 변경할 수 있는 세터도 있습니다. 사용 가능한 메서드 전체 목록을 보려면 Rails API 문서Rack 문서를 참조하세요.

response 속성 목적
body 클라이언트에게 다시 보내는 데이터 문자열입니다. 대부분 HTML입니다.
status 성공적인 요청의 경우 200과 같은 HTTP 상태 코드 또는 파일을 찾을 수 없는 경우 404와 같은 코드.
location 클라이언트가 리디렉션되는 URL(있는 경우).
content_type 응답의 콘텐츠 유형.
charset 응답에 사용되는 문자 집합입니다. 기본값은 “utf-8"입니다.
headers 응답에 사용되는 헤더.

사용자 정의 헤더 설정

응답에 사용자 정의 헤더를 설정하려면 response.headers를 사용하면 됩니다. 헤더 속성은 헤더 이름과 해당 값을 매핑하는 해시이며, Rails는 일부를 자동으로 설정합니다. 헤더를 추가하거나 변경하려면 다음과 같이 response.headers에 할당하면 됩니다:

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

참고: 위의 경우 content_type 세터를 직접 사용하는 것이 더 합리적일 것입니다.

HTTP 인증

Rails에는 세 가지 내장 HTTP 인증 메커니즘이 있습니다:

  • 기본 인증
  • 다이제스트 인증
  • 토큰 인증

HTTP 기본 인증

HTTP 기본 인증은 대부분의 브라우저와 다른 HTTP 클라이언트에서 지원되는 인증 방식입니다. 예를 들어, 브라우저의 HTTP 기본 대화 상자에 사용자 이름과 비밀번호를 입력해야만 액세스할 수 있는 관리 섹션을 고려해 보세요. 내장 인증을 사용하려면 http_basic_authenticate_with를 사용하면 됩니다.

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

이렇게 하면 AdminsController에서 파생된 컨트롤러를 만들 수 있습니다. 액션 콜백이 이러한 컨트롤러의 모든 작업에서 실행되어 HTTP 기본 인증으로 보호합니다.

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에서 false 또는 nil을 반환하면 인증 실패가 됩니다.계속해서 번역하겠습니다:

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 헤더에서 구문 분석된 옵션이 포함된 해시 두 개의 인수를 받습니다. 블록은 인증이 성공하면 true를 반환해야 합니다. false 또는 nil을 반환하면 인증 실패가 됩니다.

스트리밍 및 파일 다운로드

때때로 HTML 페이지 대신 파일을 사용자에게 보내고 싶을 수 있습니다. 모든 컨트롤러에는 send_datasend_file 메서드가 있어 데이터를 클라이언트로 스트리밍할 수 있습니다. send_file은 디스크의 파일 이름을 제공하면 해당 파일의 내용을 스트리밍하는 편의 메서드입니다.

클라이언트에 데이터를 스트리밍하려면 send_data를 사용하세요:

require "prawn"
class ClientsController < ApplicationController
  # 클라이언트에 대한 정보가 포함된 PDF 문서를 생성하고
  # 파일 다운로드로 반환합니다.
  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"입니다.

파일 전송

디스크에 이미 존재하는 파일을 보내려면 send_file 메서드를 사용하세요.

class ClientsController < ApplicationController
  # 디스크에 이미 생성되어 있는 파일을 스트리밍합니다.
  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이 사용됩니다.

경고: 클라이언트 데이터(params, cookies 등)를 사용하여 디스크의 파일 위치를 찾는 것은 보안 위험이 있으므로 주의해야 합니다.

팁: 정적 파일을 Rails를 통해 스트리밍하는 것은 권장되지 않습니다. 대신 웹 서버의 공개 폴더에 두면 사용자가 직접 다운로드할 수 있어 Rails 스택 전체를 거치지 않아 훨씬 효율적입니다.

RESTful 다운로드

send_data는 잘 작동하지만 RESTful 애플리케이션을 만드는 경우 파일 다운로드를 위한 별도의 작업이 필요하지 않습니다. REST 용어로 볼 때 위의 PDF 파일은 클라이언트 리소스의 다른 표현으로 간주될 수 있습니다. Rails는 "RESTful” 다운로드를 수행하는 매끄러운 방법을 제공합니다. 다음과 같이 show 작업의 일부로 PDF 다운로드를 수행할 수 있습니다:

class ClientsController < ApplicationController
  # 사용자는 HTML 또는 PDF로 이 리소스를 요청할 수 있습니다.
  def show
    @client = Client.find(params[:id])

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

이 예제가 작동하려면 PDF MIME 유형을 Rails에 추가해야 합니다. config/initializers/mime_types.rb 파일에 다음 줄을 추가하면 됩니다:

Mime::Type.register "application/pdf", :pdf

참고: 구성 파일은 각 요청에 다시 로드되지 않으므로 변경 사항을 적용하려면 서버를 다시 시작해야 합니다.

이제 사용자는 URL에 “.pdf"를 추가하여 클라이언트의 PDF 버전을 요청할 수 있습니다:

GET /clients/1.pdf

임의 데이터의 실시간 스트리밍

Rails를 통해 파일 외에도 다양한 데이터를 스트리밍할 수 있습니다. 실제로 ActionController::Live 모듈을 사용하면 브라우저와 지속적인 연결을 만들 수 있습니다. 이 모듈을 사용하면 특정 시점에 브라우저에 임의 데이터를 보낼 수 있습니다.

실시간 스트리밍 통합

컨트롤러 클래스 내에 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

위 코드는 브라우저와 지속적인 연결을 유지하고 1초 간격으로 "hello world\n” 메시지를 100번 보냅니다.

위의 예에서 몇 가지 주목할 점이 있습니다. 응답 스트림을 반드시 닫아야 합니다. 스트림을 닫지 않으면 소켓이 영원히 열린 상태로 남습니다. 또한 response.committed?가 truthy 값을 반환하기 전(응답 스트림에 write 또는 commit을 호출할 때) 콘텐츠 유형을 text/event-stream으로 설정해야 합니다. 이는 헤더를 더 이상 쓸 수 없기 때문입니다.

예제 사용

노래방 기계를 만들고 있다고 가정해 보겠습니다. Song에는 특정 줄 수와 각 줄을 부르는 데 걸리는 시간(num_beats)이 있습니다.

가수가 이전 줄을 완료한 경우에만 다음 줄을 보내는 Karaoke 스타일로 가사를 반환하려면 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

위 코드는 가수가 이전 줄을 완료한 후에만 다음 줄을 보냅니다.

스트리밍 고려 사항

임의 데이터 스트리밍은 매우 강력한 도구입니다. 이전 예에서 보여준 것처럼 언제 어떤 데이터를 보낼지 선택할 수 있습니다. 그러나 다음과 같은 사항에도 유의해야 합니다:

  • 각 응답 스트림은 새 스레드를 생성하고 원래 스레드의 스레드 로컬 변수를 복사합니다. 스레드 로컬 변수가 너무 많으면 성능에 부정적인 영향을 줄 수 있습니다. 마찬가지로 많은 수의 스레드도 성능을 저하시킬 수 있습니다.
  • 응답 스트림을 닫지 않으면 해당 소켓이 영원히 열린 상태로 남습니다. 응답 스트림을 사용할 때는 항상 close를 호출해야 합니다.
  • WEBrick 서버는 모든 응답을 버퍼링하므로 ActionController::Live를 포함하면 작동하지 않습니다. 응답을 자동으로 버퍼링하지 않는 웹 서버를 사용해야 합니다.

로그 필터링

Rails는 각 환경의 log 폴더에 로그 파일을 유지합니다. 이는 애플리케이션에서 실제로 무슨 일이 일어나고 있는지 디버깅할 때 매우 유용합니다. 그러나 실제 애플리케이션에서는 모든 정보를 로그 파일에 저장하고 싶지 않을 수 있습니다.

매개변수 필터링

애플리케이션 구성에서 config.filter_parameters에 민감한 요청 매개변수를 추가하여 로그 파일에서 필터링할 수 있습니다. 이러한 매개변수는 로그에 [FILTERED]로 표시됩니다.

config.filter_parameters << :password

참고: 제공된 매개변수는 부분 일치 정규 표현식으로 필터링됩니다. Rails는 일반적인 애플리케이션 매개변수(예: password, password_confirmationmy_token)을 처리하기 위해 passw, secrettoken과 같은 기본 필터 목록을 적절한 초기화기(initializers/filter_parameter_logging.rb)에 추가합니다.

리디렉션 필터링

때로는 로그 파일에서 애플계속해서 번역하겠습니다:

리디렉션 필터링

때로는 로그 파일에서 애플리케이션이 리디렉션하는 일부 민감한 위치를 필터링하고 싶을 수 있습니다. config.filter_redirect 구성 옵션을 사용하여 이를 수행할 수 있습니다:

config.filter_redirect << 's3.amazonaws.com'

문자열, 정규 표현식 또는 둘 다의 배열로 설정할 수 있습니다.

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

일치하는 URL은 ‘[FILTERED]'로 대체됩니다. 그러나 전체 URL이 아닌 매개변수만 필터링하려는 경우 매개변수 필터링을 참조하세요.

예외 처리

애플리케이션에는 버그가 있거나 처리해야 할 예외가 발생할 것입니다. 예를 들어, 사용자가 더 이상 데이터베이스에 존재하지 않는 리소스에 대한 링크를 따라가면 Active Record에서 ActiveRecord::RecordNotFound 예외가 발생합니다.

Rails의 기본 예외 처리는 모든 예외에 대해 “500 서버 오류” 메시지를 표시합니다. 요청이 로컬에서 이루어진 경우 유용한 추적 및 추가 정보가 표시되어 문제를 파악하고 처리할 수 있습니다. 요청이 원격으로 이루어진 경우 Rails는 사용자에게 단순히 “500 서버 오류” 메시지 또는 라우팅 오류나 레코드를 찾을 수 없는 경우 “404 찾을 수 없음” 메시지를 표시합니다. 때로는 이러한 오류를 잡아내고 사용자에게 표시하는 방식을 사용자 정의하고 싶을 수 있습니다. Rails 애플리케이션에는 여러 수준의 예외 처리가 있습니다:

기본 404 및 500 템플릿

기본적으로 프로덕션 환경에서 애플리케이션은 404 또는 500 오류 메시지를 렌더링합니다. 개발 환경에서는 처리되지 않은 모든 예외가 단순히 발생합니다. 이러한 메시지는 public 폴더의 404.html500.html 정적 HTML 파일에 포함되어 있습니다. 정보와 스타일을 추가할 수 있지만 이는 정적 HTML이라는 점에 유의하세요. 즉, ERB, SCSS, CoffeeScript 또는 레이아웃을 사용할 수 없습니다.

rescue_from

좀 더 정교한 방식으로 오류를 잡고 싶다면 rescue_from을 사용할 수 있습니다. 이는 전체 컨트롤러와 해당 하위 클래스에서 특정 유형(또는 여러 유형)의 예외를 처리합니다.

rescue_from 지시문에 의해 잡힌 예외가 발생하면 예외 객체가 처리기에 전달됩니다. 처리기는 메서드 또는 :with 옵션에 전달된 Proc 객체일 수 있습니다. 명시적 Proc 객체 대신 블록을 직접 사용할 수도 있습니다.

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
  # 클라이언트에 액세스할 수 있는 권한이 있는지 확인합니다.
  before_action :check_authorization

  # 이제 작업에서 모든 인증 관련 내용을 걱정할 필요가 없습니다.
  def edit
    @client = Client.find(params[:id])
  end

  private
    # 사용자가 권한이 없는 경우 예외를 발생시킵니다.
    def check_authorization
      raise User::NotAuthorized unless current_user.admin?
    end
end

경고: Exception 또는 StandardErrorrescue_from을 사용하면 심각한 부작용이 발생할 수 있으므로 강력한 이유가 없는 한 권장되지 않습니다.

참고: 프로덕션 환경에서 모든 ActiveRecord::RecordNotFound 오류는 404 오류 페이지를 렌더링합니다. 사용자 정의 동작이 필요하지 않다면 이를 처리할 필요가 없습니다.

참고: 일부 예외는 ApplicationController 클래스에서만 구조화할 수 있습니다. 컨트롤러가 초기화되고 작업이 실행되기 전에 발생하기 때문입니다.

HTTPS 프로토콜 강제

통신이 HTTPS를 통해서만 가능하도록 하려면 ActionDispatch::SSL 미들웨어를 config.force_ssl을 통해 환경 구성에서 활성화해야 합니다.

내장 상태 점검 엔드포인트

Rails에는 /up 경로에서 액세스할 수 있는 내장 상태 점검 엔드포인트도 있습니다. 이 엔드포인트는 앱이 예외 없이 부팅되면 200 상태 코드를, 그렇지 않으면 500 상태 코드를 반환합니다.

프로덕션에서 많은 애플리케이션은 엔지니어에게 페이징할 때 문제가 발생했음을 알리는 가동 시간 모니터나, 포드의 상태를 결정하는 데 사용되는 로드 밸런서 또는 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"를 애플리케이션 특정 요구 사항에 맞는 자체 컨트롤러 작업으로 대체하세요.

어떤 것을 확인할지 신중히 고려해야 합니다. 그렇지 않으면 타사 서비스가 중단되어 애플리케이션이 다시 시작되는 상황이 발생할 수 있습니다. 이상적으로는 애플리케이션이 이러한 중단을 우아하게 처리할 수 있도록 설계해야 합니다.