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는
ETag
와Last-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_content
와redirect_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
이렇게 하면 다음 세 가지 주요 작업이 수행됩니다:
- 일반 애플리케이션보다 더 제한적인 미들웨어 집합으로 애플리케이션을 구성합니다. 구체적으로는 브라우저 애플리케이션에 주로 유용한 미들웨어(쿠키 지원 등)를 기본적으로 포함하지 않습니다.
ApplicationController
가ActionController::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_only
가 true
로 설정되면 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
를 사용하려면 먼저 Gemfile
에 rack-cache
gem을 추가하고 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 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-Type
을 application/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::Request
는 Content-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::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 |
콘텐츠 없이 헤더만 반환하는 응답을 지원합니다. |
다른 플러그인은 추가 모듈을 추가할 수 있습니다. 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
:l
및t
지역화 및 번역 메서드에 대한 지원.- 기본, 다이제스트 또는 토큰 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
이지만 개별 컨트롤러에도 추가할 수 있습니다.