1 API 애플리케이션이란 무엇인가?
전통적으로, 사람들이 Rails를 "API"로 사용한다고 말할 때는 웹 애플리케이션과 함께 프로그래밍 방식으로 접근 가능한 API를 제공한다는 의미였습니다. 예를 들어, GitHub는 자체 커스텀 클라이언트에서 사용할 수 있는 API를 제공합니다.
클라이언트 사이드 프레임워크의 등장으로, 더 많은 개발자들이 웹 애플리케이션과 다른 네이티브 애플리케이션 간에 공유되는 백엔드를 구축하기 위해 Rails를 사용하고 있습니다.
예를 들어, X는 JSON 리소스를 소비하는 정적 사이트로 구축된 웹 애플리케이션에서 public API를 사용합니다.
폼과 링크를 통해 서버와 통신하는 HTML을 생성하기 위해 Rails를 사용하는 대신, 많은 개발자들은 자신의 웹 애플리케이션을 JSON API를 소비하는 JavaScript가 포함된 HTML로 전달되는 API 클라이언트로 취급하고 있습니다.
이 가이드는 클라이언트 사이드 프레임워크를 포함한 API 클라이언트에 JSON 리소스를 제공하는 Rails 애플리케이션을 구축하는 것을 다룹니다.
2 JSON API를 위해 Rails를 사용하는 이유?
Rails를 사용하여 JSON API를 구축하는 것을 고려할 때 많은 사람들이 가장 먼저 하는 질문은: "단순히 JSON을 출력하기 위해 Rails를 사용하는 것이 과하지 않나요? Sinatra 같은 것을 사용하는 게 낫지 않나요?"입니다.
매우 단순한 API의 경우 이것이 사실일 수 있습니다. 하지만 HTML이 많이 사용되는 애플리케이션에서도 대부분의 애플리케이션 로직은 뷰 레이어 외부에 존재합니다.
대부분의 사람들이 Rails를 사용하는 이유는 개발자들이 많은 사소한 결정을 하지 않고도 빠르게 시작할 수 있게 해주는 기본값 세트를 제공하기 때문입니다.
Rails가 기본적으로 제공하는 기능들 중 API 애플리케이션에도 여전히 적용할 수 있는 것들을 살펴보겠습니다.
middleware 계층에서 처리되는 것들:
- Reloading: Rails 애플리케이션은 투명한 리로딩을 지원합니다. 이는 애플리케이션이 커져서 매 요청마다 서버를 재시작하는 것이 불가능해질 때도 작동합니다.
- Development Mode: Rails 애플리케이션은 개발에 적합한 스마트한 기본값들을 제공하여, 프로덕션 성능을 저하시키지 않으면서도 쾌적한 개발 환경을 제공합니다.
- Test Mode: 개발 모드와 동일합니다.
- Logging: Rails 애플리케이션은 현재 모드에 적합한 상세 수준으로 모든 요청을 로깅합니다. 개발 환경에서의 Rails 로그는 요청 환경, 데이터베이스 쿼리, 기본적인 성능 정보를 포함합니다.
- Security: Rails는 IP spoofing attacks를 감지하고 차단하며, timing attack에 대비한 방식으로 암호화 서명을 처리합니다. IP spoofing attack이나 timing attack이 뭔지 모르시나요? 바로 그겁니다.
- Parameter Parsing: 파라미터를 URL-encoded String 대신 JSON으로 지정하고 싶으신가요? 문제없습니다. Rails가 JSON을 디코딩해서
params
에서 사용할 수 있게 해줍니다. 중첩된 URL-encoded 파라미터를 사용하고 싶으신가요? 그것도 가능합니다. - Conditional GETs: Rails는 조건부
GET
(ETag
와Last-Modified
) 처리 요청 헤더를 처리하고 적절한 응답 헤더와 상태 코드를 반환합니다. 컨트롤러에서stale?
체크만 사용하면 Rails가 모든 HTTP 세부사항을 처리해줍니다. - HEAD requests: Rails는
HEAD
요청을 자동으로GET
요청으로 변환하고, 응답으로 헤더만 반환합니다. 이를 통해 모든 Rails API에서HEAD
가 안정적으로 작동합니다.
이러한 기능들을 기존 Rack middleware로 구축할 수도 있겠지만, 이 목록은 "단순히 JSON을 생성하는 것"이라도 기본 Rails middleware 스택이 많은 가치를 제공한다는 것을 보여줍니다.
Action Pack 계층에서 처리되는 것들:
- Resourceful Routing: RESTful JSON API를 구축하고 있다면 Rails 라우터를 사용하고 싶으실 겁니다. HTTP에서 컨트롤러로의 깔끔하고 관례적인 매핑은 API를 HTTP 관점에서 어떻게 모델링할지 고민하는 시간을 줄여줍니다.
- URL Generation: 라우팅의 반대쪽은 URL 생성입니다. HTTP 기반의 좋은 API는 URL을 포함합니다(예시로 GitHub Gist API를 참고하세요).
Header and Redirection Responses:
head :no_content
와redirect_to user_url(current_user)
가 유용합니다. 물론 수동으로 응답 헤더를 추가할 수도 있지만, 굳이 그럴 필요가 있을까요?Caching: Rails는 page, action 및 fragment caching을 제공합니다. Fragment caching은 중첩된 JSON 객체를 구축할 때 특히 유용합니다.
Basic, Digest 및 Token Authentication: Rails는 세 가지 유형의 HTTP 인증에 대한 즉시 사용 가능한 지원을 제공합니다.
Instrumentation: Rails는 action 처리, 파일 또는 데이터 전송, 리다이렉션, 데이터베이스 쿼리와 같은 다양한 이벤트에 대해 등록된 핸들러를 트리거하는 instrumentation API를 가지고 있습니다. 각 이벤트의 payload에는 관련 정보가 포함됩니다(action 처리 이벤트의 경우 payload에는 컨트롤러, 액션, 파라미터, 요청 형식, 요청 메소드 및 요청의 전체 경로가 포함됩니다).
Generators: 리소스를 생성하고 모델, 컨트롤러, 테스트 스텁 및 라우트를 한 번의 명령으로 생성하여 추가 수정을 할 수 있도록 하는 것이 편리한 경우가 많습니다. 마이그레이션 및 기타 항목에도 동일하게 적용됩니다.
Plugins: 많은 서드파티 라이브러리들은 라이브러리와 웹 프레임워크를 설정하고 연결하는 비용을 줄이거나 제거하는 Rails 지원과 함께 제공됩니다. 여기에는 기본 generator 재정의, Rake 작업 추가, Rails 선택 사항(로거 및 캐시 백엔드와 같은) 준수가 포함됩니다.
물론 Rails 부팅 프로세스도 등록된 모든 구성 요소를 연결합니다. 예를 들어, Rails 부팅 프로세스는 Active Record를 구성할 때 config/database.yml
파일을 사용합니다.
요약하자면: view 레이어를 제거하더라도 Rails의 어떤 부분이 여전히 적용 가능한지 생각해보지 않았을 수 있지만, 대부분이 적용 가능하다는 것이 답입니다.
3 기본 구성
API 서버를 최우선으로 하는 Rails 애플리케이션을 구축하는 경우, Rails의 더 제한된 하위 집합으로 시작하고 필요에 따라 기능을 추가할 수 있습니다.
3.1 새로운 Application 만들기
새로운 api Rails app을 생성할 수 있습니다:
$ rails new my_api --api
이는 다음과 같은 세 가지 주요한 작업을 수행합니다:
- 애플리케이션이 일반적인 것보다 더 제한된 middleware 세트로 시작하도록 구성합니다. 구체적으로, 기본적으로 브라우저 애플리케이션에서 주로 사용되는 middleware(예: cookies 지원)를 포함하지 않습니다.
ApplicationController
가ActionController::Base
대신ActionController::API
를 상속하도록 합니다. middleware와 마찬가지로, 브라우저 애플리케이션에서 주로 사용되는 기능을 제공하는 Action Controller 모듈들을 제외합니다.- 새로운 resource를 생성할 때 views, helpers, assets를 생성하지 않도록 generators를 구성합니다.
3.2 새로운 리소스 생성하기
새로 생성된 API가 어떻게 새로운 리소스를 생성하는지 살펴보기 위해, 새로운 Group 리소스를 만들어보겠습니다. 각 그룹은 name을 가지게 됩니다.
$ bin/rails g scaffold Group name:string
scaffolding으로 생성된 코드를 사용하기 전에, 우리의 database schema를 업데이트해야 합니다.
$ bin/rails db:migrate
이제 GroupsController
를 열어보면 API Rails 앱에서는 JSON 데이터만 렌더링하고 있음을 알 수 있습니다. index 액션에서는 Group.all
을 쿼리하여 @groups
라는 인스턴스 변수에 할당합니다. :json
옵션과 함께 render
에 전달하면 자동으로 group들을 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
# 액션 사이에서 공통 설정이나 제약사항을 공유하기 위해 콜백을 사용합니다.
def set_group
@group = Group.find(params[:id])
end
# 신뢰할 수 있는 파라미터 목록만 허용합니다.
def group_params
params.expect(group: [:name])
end
end
마지막으로 Rails console을 통해 데이터베이스에 몇 개의 group을 추가할 수 있습니다:
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": ...}
]
3.3 기존 애플리케이션 변경하기
기존 애플리케이션을 API로 만들고 싶다면, 다음 단계들을 읽어보세요.
config/application.rb
에서 Application
class 정의의 최상단에 다음 라인을 추가하세요:
config.api_only = true
config/environments/development.rb
에서 [config.debug_exception_response_format
][]를 설정하여 development 모드에서 오류가 발생할 때 응답에 사용되는 포맷을 구성할 수 있습니다.
디버깅 정보가 포함된 HTML 페이지를 렌더링하려면 :default
값을 사용하세요.
config.debug_exception_response_format = :default
예외 발생 시 응답 포맷을 정의합니다. :default
옵션은 웹 브라우저 요청에는 HTML 페이지를, API 요청에는 텍스트 또는 요청된 포맷으로 응답을 반환합니다.
응답 형식을 유지하면서 디버깅 정보를 render하려면, :api
값을 사용하세요.
config.debug_exception_response_format = :api
development 환경에서 :default 또는 :api로 debug 예외 응답의 형식을 설정합니다. :api로 설정된 경우에는 예외가 HTTP 헤더로 렌더링됩니다.
기본적으로 config.api_only
가 true로 설정되어 있을 때 config.debug_exception_response_format
은 :api
로 설정됩니다.
마지막으로 app/controllers/application_controller.rb
내부에서는 다음과 같이 하는 대신:
class ApplicationController < ActionController::Base
end
죄송하지만 번역할 텍스트가 제공되지 않았습니다. 번역을 원하시는 Rails 가이드 문서를 공유해 주시면 요청하신 방식대로 번역해 드리겠습니다.
class ApplicationController < ActionController::API
end
4 Middleware 선택하기
API 애플리케이션은 기본적으로 다음의 middleware를 포함합니다:
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 가이드의 internal middleware 섹션을 참조하세요.
Active Record를 포함한 다른 플러그인들도 추가 middleware를 더할 수 있습니다. 일반적으로 이러한 middleware들은 여러분이 구축하는 애플리케이션의 유형과 무관하며, API 전용 Rails 애플리케이션에서도 적절히 동작합니다.
애플리케이션의 전체 middleware 목록은 다음과 같이 확인할 수 있습니다:
$ bin/rails middleware
4.1 Rack::Cache 사용하기
Rails와 함께 사용할 때, Rack::Cache
는 entity store와 meta store를 위해 Rails cache store를 사용합니다. 즉 Rails 애플리케이션에서 memcache를 사용하는 경우, 내장된 HTTP cache도 memcache를 사용하게 됩니다.
Rack::Cache
를 사용하려면 먼저 Gemfile
에 rack-cache
gem을 추가하고 config.action_dispatch.rack_cache
를 true
로 설정해야 합니다. 이 기능을 활성화하려면 controller에서 stale?
을 사용해야 합니다. 다음은 stale?
을 사용하는 예시입니다.
def show
@post = Post.find(params[:id])
if stale?(last_modified: @post.updated_at)
render json: @post
end
end
이것은 caching과 HTTP caching 헤더를 세팅하는 또다른 방법입니다. 브라우저나 proxy caching이 현재 캐시가 적절한지 확인할 수 있도록 Last-Modified
헤더를 설정합니다. stale?
이 false
를 반환하면 render가 실행되지 않고 기본적으로 304 Not Modified
응답을 리턴합니다.
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
이 메서드는 client의 캐시가 만료되었는지 확인합니다. stale?
method는 request의 If-Modified-Since
헤더를 확인하고 리소스가 그 이후로 수정되었는지를 판단합니다.
public
cache control 지시문을 설정하면 중간 cache들이 응답을 cache할 수 있도록 합니다.
이는 Rack::Cache
가 URL에 대한 Last-Modified
값을 Rails cache에 저장하고, 동일한 URL에 대한 후속 요청에 If-Modified-Since
헤더를 추가한다는 것을 의미합니다.
HTTP 의미론을 사용한 page caching이라고 생각하시면 됩니다.
4.2 Rack::Sendfile 사용하기
Rails 컨트롤러 내에서 send_file
메서드를 사용하면 X-Sendfile
헤더가 설정됩니다. Rack::Sendfile
은 실제 파일을 전송하는 역할을 담당합니다.
프런트엔드 서버가 가속화된 파일 전송을 지원하는 경우, Rack::Sendfile
은 실제 파일 전송 작업을 프런트엔드 서버에 위임합니다. 이를 통해 Rails는 요청 처리를 더 빨리 완료하고 리소스를 더 일찍 확보할 수 있습니다.
해당 환경의 설정 파일에서 config.action_dispatch.x_sendfile_header
를 사용하여 프런트엔드 서버가 이 목적으로 사용하는 헤더의 이름을 설정할 수 있습니다.
인기 있는 프런트엔드와 함께 Rack::Sendfile
을 사용하는 방법에 대해 the Rack::Sendfile documentation에서 자세히 알아볼 수 있습니다.
가속화된 파일 전송을 지원하도록 설정된 인기 있는 서버들의 이 헤더에 대한 값들은 다음과 같습니다:
# Apache와 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 Session Middleware 사용하기
다음의 middleware들은 session 관리에 사용되는데, 일반적으로 session이 필요하지 않기 때문에 API 앱에서는 제외됩니다. 만약 API 클라이언트 중 하나가 브라우저라면 이들 중 하나를 다시 추가하고 싶을 수 있습니다:
ActionDispatch::Session::CacheStore
ActionDispatch::Session::CookieStore
ActionDispatch::Session::MemCacheStore
이들을 다시 추가하는 요령은, 기본적으로 추가될 때 (session key를 포함한) session_options
가 전달된다는 것입니다. 따라서 단순히 session_store.rb
initializer를 추가하고 use ActionDispatch::Session::CookieStore
를 추가하는 것만으로는 session이 평소처럼 작동하지 않습니다. (명확히 하자면: session은 작동할 수 있지만, session 옵션들은 무시됩니다 - 즉, session key는 기본값인 _session_id
가 됩니다)
initializer 대신, middleware가 구축되기 전에 관련 옵션들을 어딘가에 설정하고(config/application.rb
와 같은 곳에) 선호하는 middleware에 다음과 같이 전달해야 합니다:
# 아래에서 사용할 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
4.5 기타 Middleware
Rails는 API 애플리케이션에서 사용할 수 있는 다수의 middleware를 제공합니다. 특히 API 클라이언트 중 하나가 브라우저인 경우 유용할 수 있습니다:
Rack::MethodOverride
ActionDispatch::Cookies
ActionDispatch::Flash
이러한 middleware는 다음과 같이 추가할 수 있습니다:
config.middleware.use Rack::MethodOverride
PUT 및 DELETE HTTP 메소드를 웹 브라우저가 보내는 POST 요청으로부터 에뮬레이트하기 위해 Rack::MethodOverride
middleware를 사용합니다. HTTP 메소드는 _method
라는 이름의 숨겨진 input 필드의 값이나 X-HTTP-Method-Override
헤더를 통해 지정할 수 있습니다.
4.6 Middleware 제거하기
만약 API-only middleware 세트에 기본적으로 포함된 middleware를 사용하지 않으려면 다음과 같이 제거할 수 있습니다:
config.middleware.delete ::Rack::Sendfile
Action Controller의 특정 기능에 대한 지원을 제거하면 해당 미들웨어들도 제거된다는 점을 유의하세요.
5 Controller Modules 선택하기
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에 의해 정의된 계측 훅 지원 (이에 대한 자세한 정보는 instrumentation guide를 참조하세요). |
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
지역화 및 번역 메서드를 지원합니다.- 기본, digest, 또는 토큰 HTTP 인증 지원:
ActionController::HttpAuthentication::Basic::ControllerMethods
ActionController::HttpAuthentication::Digest::ControllerMethods
ActionController::HttpAuthentication::Token::ControllerMethods
ActionView::Layouts
: 렌더링 시 레이아웃 지원.ActionController::MimeResponds
:respond_to
지원.ActionController::Cookies
:cookies
지원으로, 서명된 쿠키와 암호화된 쿠키 지원을 포함합니다. 이는 cookies 미들웨어가 필요합니다.ActionController::Caching
: API 컨트롤러를 위한 view 캐싱을 지원합니다. 컨트롤러 내에서 다음과 같이 캐시 저장소를 수동으로 지정해야 합니다:class ApplicationController < ActionController::API include ::ActionController::Caching self.cache_store = :mem_cache_store end
Rails는 이 설정을 자동으로 전달하지 않습니다.
모듈을 추가하기 가장 좋은 위치는 ApplicationController
이지만, 개별 컨트롤러에도 모듈을 추가할 수 있습니다.