Rails를 API 전용 애플리케이션으로 사용하기

이 가이드에서 다음을 배우게 됩니다:

  • API 전용 애플리케이션을 위해 Rails가 제공하는 기능
  • 브라우저 기능 없이 Rails를 시작하도록 구성하는 방법
  • 어떤 미들웨어를 포함할지 결정하는 방법
  • 컨트롤러에서 어떤 모듈을 사용할지 결정하는 방법

API 애플리케이션이란?

전통적으로 사람들이 Rails를 “API"로 사용한다고 말할 때는 웹 애플리케이션과 함께 프로그래밍 방식으로 접근 가능한 API를 제공한다는 의미였습니다. 예를 들어 GitHub은 API를 제공하여 사용자 정의 클라이언트에서 사용할 수 있습니다.

클라이언트 측 프레임워크의 등장으로 더 많은 개발자들이 웹 애플리케이션과 다른 네이티브 애플리케이션 간에 공유되는 백엔드를 구축하기 위해 Rails를 사용하고 있습니다.

예를 들어 Twitter는 자사의 웹 애플리케이션에서 공개 API를 사용하며, 이는 JSON 리소스를 소비하는 정적 사이트로 구축되어 있습니다.

양식과 링크를 통해 서버와 통신하는 HTML을 생성하는 대신, 많은 개발자들이 웹 애플리케이션을 단순히 JSON API를 소비하는 HTML과 JavaScript의 클라이언트로 취급하고 있습니다.

이 가이드는 JSON 리소스를 API 클라이언트에 제공하는 Rails 애플리케이션 구축에 대해 다룹니다.

왜 Rails로 JSON API를 만들까요?

JSON API를 구축할 때 Rails를 사용하는 것이 적절한지에 대해 많은 사람들이 의문을 가집니다. "Rails로 JSON을 출력하는 것은 과도하지 않나요? Sinatra 같은 것을 사용하는 게 더 좋지 않을까요?”

매우 간단한 API의 경우 이것이 사실일 수 있습니다. 그러나 대부분의 HTML 중심 애플리케이션에서도 애플리케이션 로직의 대부분은 뷰 계층 외부에 있습니다.

대부분의 사람들이 Rails를 사용하는 이유는 개발자가 많은 사소한 결정을 하지 않고도 빨리 시작할 수 있게 해주는 기본값을 제공하기 때문입니다.

API 애플리케이션에도 여전히 적용되는 Rails가 제공하는 기능들을 살펴보겠습니다.

미들웨어 계층에서 처리되는 기능:

  • 자동 재로드: Rails 애플리케이션은 투명한 재로드를 지원합니다. 애플리케이션이 커져서 요청마다 서버를 다시 시작하는 것이 실용적이지 않더라도 이 기능이 작동합니다.
  • 개발 모드: Rails 애플리케이션은 개발을 위한 현명한 기본값을 제공하여 프로덕션 성능을 저해하지 않고 개발 환경을 쾌적하게 만듭니다.
  • 테스트 모드: 개발 모드와 마찬가지입니다.
  • 로깅: Rails 애플리케이션은 모든 요청을 로깅하며, 현재 모드에 적합한 상세 수준을 제공합니다. Rails 로그는 요청 환경, 데이터베이스 쿼리, 기본적인 성능 정보를 포함합니다.
  • 보안: Rails는 IP 스푸핑 공격을 탐지하고 방지하며, 타이밍 공격에 안전한 방식으로 암호화 서명을 처리합니다. IP 스푸핑 공격이나 타이밍 공격이 무엇인지 모르시나요? 바로 그것입니다.
  • 파라미터 구문 분석: JSON으로 파라미터를 지정하고 싶나요? 문제 없습니다. Rails는 JSON을 디코딩하여 params에서 사용할 수 있게 해줍니다. 중첩된 URL 인코딩 파라미터도 작동합니다.
  • 조건부 GET: Rails는 ETagLast-Modified 요청 헤더를 처리하고 적절한 응답 헤더와 상태 코드를 반환합니다. 컨트롤러에서 stale? 체크만 하면 모든 HTTP 세부 사항을 처리해 줍니다.
  • HEAD 요청: Rails는 HEAD 요청을 투명하게 GET 요청으로 변환하고 헤더만 반환합니다. 이를 통해 모든 Rails API에서 HEAD가 안정적으로 작동합니다.

물론 이러한 기능들을 기존의 Rack 미들웨어로 구축할 수 있지만, 이 목록은 “단순히 JSON을 생성"하는 경우에도 기본 Rails 미들웨어 스택이 제공하는 많은 가치를 보여줍니다.

Action Pack 계층에서 처리되는 기능:

  • 리소스 기반 라우팅: RESTful JSON API를 구축하려면 Rails 라우터를 사용해야 합니다. 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 지원을 제공하여 라이브러리와 웹 프레임워크를 설정하고 연결하는 비용을 줄이거나 제거합니다. 이에는 기본 생성기 재정의, Rake 작업 추가, Rails 선택 사항(로거 및 캐시 백엔드 등) 준수 등이 포함됩니다.

물론 Rails 부팅 프로세스는 등록된 모든 구성 요소를 연결합니다. 예를 들어 Rails 부팅 프로세스는 config/database.yml 파일을 사용하여 Active Record를 구성합니다.

요약하면: 뷰 계층을 제거하더라도 여전히 적용되는 Rails의 부분이 대부분이라는 것입니다.

기본 구성

API 서버를 주요 목적으로 하는 Rails 애플리케이션을 구축하려면 Rails의 더 제한적인 하위 집합으로 시작하고 필요에 따라 기능을 추가할 수 있습니다.

새 애플리케이션 생성하기

새로운 API Rails 앱을 생성할 수 있습니다:

$ rails new my_api --api

이렇게 하면 다음 세 가지 주요 작업이 수행됩니다:

  • 일반 애플리케이션보다 더 제한적인 미들웨어 집합으로 애플리케이션을 구성합니다. 구체적으로는 브라우저 애플리케이션에 주로 유용한 미들웨어(쿠키 지원 등)를 기본적으로 포함하지 않습니다.
  • ApplicationControllerActionController::Base 대신 ActionController::API를 상속하도록 합니다. 미들웨어와 마찬가지로, 이를 통해 브라우저 애플리케이션에 주로 사용되는 기능성을 제공하는 Action Controller 모듈을 제외할 수 있습니다.
  • 새 리소스를 생성할 때 뷰, 헬퍼, 자산을 생성하지 않도록 생성기를 구성합니다.

새 리소스 생성하기

API 전용 Rails가 새 리소스를 어떻게 처리하는지 확인해 보겠습니다. Group 리소스를 생성해 보겠습니다. 각 그룹에는 이름이 있습니다.

$ bin/rails g scaffold Group name:string

스캐폴드된 코드를 사용하려면 먼저 데이터베이스 스키마를 업데이트해야 합니다.

$ bin/rails db:migrate

이제 GroupsController를 열어보면 API Rails 앱에서는 JSON 데이터만 렌더링한다는 것을 알 수 있습니다. 인덱스 액션에서 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.require(:group).permit(:name)
    end
end

마지막으로 Rails 콘솔에서 일부 그룹을 데이터베이스에 추가할 수 있습니다:

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

데이터가 있으면 서버를 시작하고 http://localhost:3000/groups.json을 방문하여 JSON 데이터를 확인할 수 있습니다.

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

기존 애플리케이션 변경하기계속해서 기존 애플리케이션을 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_onlytrue로 설정되면 config.debug_exception_response_format:api로 설정됩니다.

마지막으로 app/controllers/application_controller.rb에서 다음과 같이 변경합니다:

class ApplicationController < ActionController::Base
end

대신 다음과 같이 변경합니다:

class ApplicationController < ActionController::API
end

미들웨어 선택

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

Rack::Cache 사용하기

Rails와 함께 사용할 때 Rack::Cache는 Rails 캐시 저장소를 엔티티 및 메타 저장소로 사용합니다. 즉, Rails 앱에서 memcache를 사용하는 경우 기본 HTTP 캐시가 memcache를 사용합니다.

Rack::Cache를 사용하려면 먼저 Gemfilerack-cache gem을 추가하고 config.action_dispatch.rack_cachetrue로 설정해야 합니다. 기능을 활성화하려면 컨트롤러에서 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 Not Modified” 응답을 반환합니다. 그렇지 않으면 응답을 렌더링하고 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 값을 저장하고 동일한 URL에 대한 후속 수신 요청에 If-Modified-Since 헤더를 추가합니다.

HTTP 의미론을 사용하여 페이지 캐싱과 유사하게 생각할 수 있습니다.

Rack::Sendfile 사용하기

Rails 컨트롤러 내에서 send_file 메서드를 사용하면 X-Sendfile 헤더가 설정됩니다. Rack::Sendfile은 실제 파일 전송을 담당합니다.

프런트엔드 서버에서 가속화된 파일 전송을 지원하는 경우 Rack::Sendfile은 실제 파일 전송 작업을 프런트엔드 서버에 오프로드합니다.

프런트엔드 서버에서 사용하는 헤더 이름은 적절한 환경 구성 파일에서 config.action_dispatch.x_sendfile_header를 사용하여 구성할 수 있습니다.

인기 있는 프런트엔드와 함께 Rack::Sendfile을 사용하는 방법에 대해서는 Rack::Sendfile 문서에서 자세히 알아볼 수 있습니다.

다음은 이러한 헤더에 대한 일부 값으로, 프런트엔드 서버가 가속화된 파일 전송을 지원하도록 구성된 경우:

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

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

Rack::Sendfile 문서의 지침에 따라 서버를 구성했는지 확인하세요.

ActionDispatch::Request 사용하기

ActionDispatch::Request#params는 클라이언트에서 JSON 형식으로 전달된 파라미터를 처리하여 컨트롤러의 params에서 사용할 수 있게 합니다.

이를 사용하려면 클라이언트가 JSON 인코딩 파라미터로 요청을 보내고 Content-Typeapplication/json으로 지정해야 합니다.

jQuery에서의 예:

jQuery.ajax({
  type: 'POST',
  url: '/people',
  dataType: 'json',
  contentType: 'application/json',
  data: JSON.stringify({ person: { firstName: "Yehuda", lastName: "Katz" } }),
  success: function(json) { }
});

ActionDispatch::RequestContent-Type을 확인하고 파라미터는 다음과 같이 사용할 수 있습니다:

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

세션 미들웨어 사용하기

일반적으로 세션이 필요 없는 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와 같은 곳에서 설정하고 선호하는 미들웨어에 전달해야 합니다:

# 아래에서 session_options를 구성하기도 합니다
config.session_store :cookie_store, key: '_your_app_session'

# 모든 세션 관리에 필요합니다(session_store와 관계없음)
config.middleware.use ActionDispatch::Cookies

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

기타 미들웨어

Rails에는 브라우저가 API 클라이언트 중 하나인 경우 API 애플리케이션에서 사용할 수 있는 다른 미들웨어가 몇 가지 있습니다:

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

이러한 미들웨어는 다음과 같이 추가할 수 있습니다:

config.middleware.use Rack::MethodOverride

미들웨어 제거하기

API 전용 미들웨어 집합에 포함된 미들웨어를 사용하지 않으려면 다음과 같이 제거할 수 있습니다:

config.middleware.delete ::Rack::Sendfile

이러한 미들웨어를 제거하면 Action Controller의 특정 기능에 대한 지원이 제거됩니다.

컨트롤러 모듈 선택

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 콘텐츠 없이 헤더만 반환하는 응답을 지원합니다.

다른 플러그인은 추가 모듈을 추가할 수 있습니다. ActionController::API에 포함된 모든 모듈 목록은 Rails 콘솔에서 확인할 수 있습니다:

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

다른 모듈 추가하기

모든 Action Controller 모듈은 종속 모듈을 알고 있으므로 컨트롤러에 원하는 모듈을 포함시키기만 하면 모든 종속성계속해서 다른 모듈을 추가하는 방법을 설명하겠습니다.

다른 모듈 추가하기

모든 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에 대한 지원(서명 및 암호화된 쿠키 포함). 이 기능을 사용하려면 쿠키 미들웨어가 필요합니다.
  • ActionController::Caching: API 컨트롤러에 대한 뷰 캐싱 지원. 단, 컨트롤러 내에서 캐시 저장소를 수동으로 지정해야 한다는 점에 유의하세요:

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

    Rails는 이 구성을 자동으로 전달하지 않습니다.

모듈을 추가할 가장 좋은 장소는 ApplicationController이지만 개별 컨트롤러에도 추가할 수 있습니다.