요즘은 API(응용 프로그래밍 인터페이스)에 크게 의존하는 것이 일반적입니다. Facebook 및 Twitter와 같은 대형 서비스에서 사용할 뿐만 아니라 API는 React, Angular 및 기타 여러 클라이언트 측 프레임워크의 확산으로 인해 매우 인기가 있습니다. Ruby on Rails는 이러한 추세를 따르고 있으며 최신 버전에서는 API 전용 애플리케이션을 만들 수 있는 새로운 기능을 제공합니다.
처음에 이 기능은 rails-api라는 별도의 gem으로 포장되었지만, Rails 5가 출시된 이후로는 프레임워크의 핵심 부분이 되었습니다. ActionCable과 함께 이 기능은 아마도 가장 기대되는 기능이었을 것이므로 오늘 논의할 것입니다.
이 문서에서는 API 전용 Rails 애플리케이션을 만드는 방법을 설명하고 경로와 컨트롤러를 구성하고 JSON 형식으로 응답하고 직렬 변환기를 추가하고 CORS(Cross-Origin Resource Sharing)를 설정하는 방법을 설명합니다. 또한 API를 보호하고 남용으로부터 보호하기 위한 몇 가지 옵션에 대해서도 배우게 됩니다.
이 기사의 출처는 GitHub에서 확인할 수 있습니다.
API 전용 애플리케이션 만들기
시작하려면 다음 명령을 실행하십시오.
rails new RailsApiDemo --api
RailsApiDemo
라는 새로운 API 전용 Rails 애플리케이션을 생성할 것입니다. . --api
에 대한 지원을 잊지 마십시오. 옵션은 Rails 5에만 추가되었으므로 이 버전 또는 최신 버전이 설치되어 있는지 확인하세요.
Gemfile을 엽니다. 평소보다 훨씬 작습니다. coffee-rails
과 같은 보석 , turbolinks
및 sass-rails
사라졌습니다.
config/application.rb 파일에 새 줄이 포함되어 있습니다.
config.api_only = true
이는 Rails가 더 작은 미들웨어 세트를 로드한다는 것을 의미합니다. 예를 들어 쿠키 및 세션 지원이 없습니다. 또한 스캐폴드를 생성하려고 하면 뷰와 자산이 생성되지 않습니다. 실제로 보기/레이아웃을 확인하면 디렉토리에서 application.html.erb 파일도 누락되었습니다.
또 다른 중요한 차이점은 ApplicationController
ActionController::API
에서 상속 ActionController::Base
가 아닌 .
이것으로 충분합니다. 대체로 이것은 여러 번 본 기본 Rails 애플리케이션입니다. 이제 작업할 수 있는 몇 가지 모델을 추가해 보겠습니다.
rails g model User name:string rails g model Post title:string body:text user:belongs_to rails db:migrate
제목이 있는 게시물과 본문은 사용자의 것입니다.
적절한 연결이 설정되었는지 확인하고 몇 가지 간단한 유효성 검사도 제공합니다.
모델/사용자.rb
has_many :posts validates :name, presence: true
모델/post.rb
belongs_to :user validates :title, presence: true validates :body, presence: true
훌륭한! 다음 단계는 몇 가지 샘플 레코드를 새로 생성된 테이블에 로드하는 것입니다.
데모 데이터 로드
일부 데이터를 로드하는 가장 쉬운 방법은 seeds.rb를 활용하는 것입니다. db 내부의 파일 예배 규칙서. 그러나 나는 게으르며(많은 프로그래머가 그렇듯이) 샘플 콘텐츠를 생각하고 싶지 않습니다. 따라서 이름, 이메일, 힙스터 단어, "lorem ipsum" 텍스트 등 다양한 종류의 임의 데이터를 생성할 수 있는 가짜 보석을 활용하지 않겠습니까?
젬파일
group :development do gem 'faker' end
보석 설치:
bundle install
이제 seeds.rb를 수정하세요. :
db/seeds.rb
5.times do user = User.create({name: Faker::Name.name}) user.posts.create({title: Faker::Book.title, body: Faker::Lorem.sentence}) end
마지막으로 데이터를 로드합니다.
rails db:seed
JSON으로 응답
물론 이제 API를 만들기 위해 몇 가지 경로와 컨트롤러가 필요합니다. api/
아래에 API 경로를 중첩하는 것이 일반적입니다. 길. 또한 개발자는 일반적으로 경로에 API 버전을 제공합니다(예:api/v1/
). . 나중에 몇 가지 주요 변경 사항을 도입해야 하는 경우 새 네임스페이스(v2
) 및 별도의 컨트롤러.
경로는 다음과 같습니다.
config/routes.rb
namespace 'api' do namespace 'v1' do resources :posts resources :users end end
이것은 다음과 같은 경로를 생성합니다:
api_v1_posts GET /api/v1/posts(.:format) api/v1/posts#index POST /api/v1/posts(.:format) api/v1/posts#create api_v1_post GET /api/v1/posts/:id(.:format) api/v1/posts#show
scope
를 사용할 수 있습니다. namespace
대신 메소드 , 하지만 기본적으로 UsersController
및 PostsController
컨트롤러 내부 controllers/api/v1 내부가 아닌 디렉토리 , 그래서 조심하십시오.
API 생성 중첩 디렉토리가 있는 폴더 v1 컨트롤러 내부 . 컨트롤러로 채우기:
controllers/api/v1/users_controller.rb
module Api module V1 class UsersController < ApplicationController end end end
controllers/api/v1/posts_controller.rb
module Api module V1 class PostsController < ApplicationController end end end
api/v1 아래에 컨트롤러 파일을 중첩해야 할 뿐만 아니라 경로이지만 클래스 자체도 Api
내부에 네임스페이스가 지정되어야 합니다. 및 V1
모듈.
다음 질문은 JSON 형식의 데이터로 올바르게 응답하는 방법입니다. 이 기사에서는 jBuilder 및 active_model_serializers gem과 같은 솔루션을 시도해 볼 것입니다. 따라서 다음 섹션으로 진행하기 전에 Gemfile :
젬파일
gem 'jbuilder', '~> 2.5' gem 'active_model_serializers', '~> 0.10.0'
그런 다음 실행:
bundle install
jBuilder Gem 사용
jBuilder는 보기에서 JSON 구조를 정의할 수 있는 간단한 DSL(도메인별 언어)을 제공하는 Rails 팀에서 유지 관리하는 인기 있는 보석입니다.
사용자가 index
에 도달할 때 모든 게시물을 표시하고 싶다고 가정합니다. 액션:
controllers/api/v1/posts_controller.rb
def index @posts = Post.order('created_at DESC') end
.json.jbuilder를 사용하여 해당 작업의 이름을 따서 명명된 보기를 생성하기만 하면 됩니다. 확대. 보기는 api/v1 아래에 있어야 합니다. 경로:
보기/api/v1/posts/index.json.jbuilder
json.array! @posts do |post| json.id post.id json.title post.title json.body post.body end
json.array!
@posts
순회 정렬. json.id
, json.title
및 json.body
인수를 값으로 설정하는 해당 이름으로 키를 생성합니다. https://localhost:3000/api/v1/posts.json으로 이동하면 다음과 유사한 출력이 표시됩니다.
[ {"id": 1, "title": "Title 1", "body": "Body 1"}, {"id": 2, "title": "Title 2", "body": "Body 2"} ]
각 게시물에 대한 작성자도 표시하려면 어떻게 해야 할까요? 간단합니다:
json.array! @posts do |post| json.id post.id json.title post.title json.body post.body json.user do json.id post.user.id json.name post.user.name end end
출력은 다음과 같이 변경됩니다.
[ {"id": 1, "title": "Title 1", "body": "Body 1", "user": {"id": 1, "name": "Username"}} ]
.jbuilder의 내용 파일은 일반 Ruby 코드이므로 평소와 같이 모든 기본 작업을 활용할 수 있습니다.
jBuilder는 일반 Rails 보기와 마찬가지로 부분을 지원하므로 다음과 같이 말할 수도 있습니다.
json.partial! partial: 'posts/post', collection: @posts, as: :post
그런 다음 view/api/v1/posts/_post.json.jbuilder를 만듭니다. 다음 내용이 포함된 파일:
json.id post.id json.title post.title json.body post.body json.user do json.id post.user.id json.name post.user.name end
보시다시피 jBuilder는 쉽고 편리합니다. 그러나 대안으로 직렬 변환기를 계속 사용할 수 있으므로 다음 섹션에서 이에 대해 논의하겠습니다.
시리얼라이저 사용
rails_model_serializers gem은 처음에 rails-api를 관리했던 팀에서 만들었습니다. 문서에 명시된 바와 같이 rails_model_serializers는 구성에 대한 규칙을 JSON 생성에 적용합니다. 기본적으로 직렬화(즉, JSON 생성) 시 사용할 필드를 정의합니다.
다음은 첫 번째 직렬 변환기입니다.
직렬 변환기/post_serializer.rb
class PostSerializer < ActiveModel::Serializer attributes :id, :title, :body end
여기서 우리는 이러한 모든 필드가 결과 JSON에 있어야 한다고 말합니다. 이제 to_json
과 같은 메소드 및 as_json
게시물에 대한 호출은 이 구성을 사용하고 적절한 콘텐츠를 반환합니다.
작동하는 모습을 보려면 index
를 수정하세요. 다음과 같은 작업:
controllers/api/v1/posts_controller.rb
def index @posts = Post.order('created_at DESC') render json: @posts end
as_json
@posts
에서 자동으로 호출됩니다. 개체.
사용자는 어떻습니까? 직렬 변환기를 사용하면 모델처럼 관계를 나타낼 수 있습니다. 또한 직렬 변환기를 중첩할 수 있습니다.
직렬 변환기/post_serializer.rb
class PostSerializer < ActiveModel::Serializer attributes :id, :title, :body belongs_to :user class UserSerializer < ActiveModel::Serializer attributes :id, :name end end
이제 게시물을 직렬화하면 중첩된 user
가 자동으로 포함됩니다. id와 이름이 있는 키. 나중에 :id
를 사용하여 사용자에 대해 별도의 직렬 변환기를 만드는 경우 제외된 속성:
직렬 변환기/post_serializer.rb
class UserSerializer < ActiveModel::Serializer attributes :name end
그런 다음 @user.as_json
사용자의 ID를 반환하지 않습니다. 그래도 @post.as_json
사용자의 이름과 ID를 모두 반환하므로 명심하십시오.
API 보안
많은 경우 우리는 누군가가 API를 사용하여 어떤 작업도 수행하는 것을 원하지 않습니다. 따라서 간단한 보안 검사를 제공하고 사용자가 게시물을 작성 및 삭제할 때 토큰을 보내도록 합시다.
토큰은 수명이 무제한이며 사용자 등록 시 생성됩니다. 우선 새 token
을 추가합니다. users
열 표:
rails g migration add_token_to_users token:string:index
이 인덱스는 동일한 토큰을 가진 두 명의 사용자가 있을 수 없으므로 고유성을 보장해야 합니다.
db/migrate/xyz_add_token_to_users.rb
add_index :users, :token, unique: true
마이그레이션 적용:
rails db:migrate
이제 before_save
를 추가하세요. 콜백:
모델/사용자.rb
before_create -> {self.token = generate_token}
generate_token
private 메소드는 무한 주기로 토큰을 생성하고 고유한지 여부를 확인합니다. 고유한 토큰을 찾으면 즉시 반환하십시오.
모델/사용자.rb
private def generate_token loop do token = SecureRandom.hex return token unless User.exists?({token: token}) end end
예를 들어 사용자 이름의 MD5 해시와 약간의 소금을 기반으로 다른 알고리즘을 사용하여 토큰을 생성할 수 있습니다.
사용자 등록
물론 사용자가 등록할 수 있도록 허용해야 합니다. 그렇지 않으면 사용자가 토큰을 얻을 수 없기 때문입니다. 우리 애플리케이션에 HTML 보기를 도입하고 싶지 않으므로 대신 새 API 메소드를 추가하겠습니다.
controllers/api/v1/users_controller.rb
def create @user = User.new(user_params) if @user.save render status: :created else render json: @user.errors, status: :unprocessable_entity end end private def user_params params.require(:user).permit(:name) end
개발자가 진행 상황을 정확히 이해할 수 있도록 의미 있는 HTTP 상태 코드를 반환하는 것이 좋습니다. 이제 사용자에게 새 직렬 변환기를 제공하거나 .json.jbuilder를 고수할 수 있습니다. 파일. 나는 후자의 변형을 선호합니다(그래서 :json
render
옵션 방법), 그러나 당신은 그들 중 하나를 자유롭게 선택할 수 있습니다. 그러나 토큰은 다음이 아니어야 합니다. 예를 들어 모든 사용자 목록을 반환할 때 항상 직렬화되므로 안전하게 보관해야 합니다!
보기/api/v1/users/create.json.jbuilder
json.id @user.id json.name @user.name json.token @user.token
다음 단계는 모든 것이 제대로 작동하는지 테스트하는 것입니다. curl
을 사용할 수 있습니다. 명령을 내리거나 Ruby 코드를 작성하십시오. 이 문서는 Ruby에 관한 것이므로 코딩 옵션을 사용하겠습니다.
사용자 등록 테스트
HTTP 요청을 수행하기 위해 많은 어댑터를 통해 공통 인터페이스를 제공하는 Faraday gem을 사용할 것입니다(기본값은 Net::HTTP
). 별도의 Ruby 파일을 만들고 Faraday를 포함하고 클라이언트를 설정합니다.
api_client.rb
require 'faraday' client = Faraday.new(url: 'https://localhost:3000') do |config| config.adapter Faraday.default_adapter end response = client.post do |req| req.url '/api/v1/users' req.headers['Content-Type'] = 'application/json' req.body = '{ "user": {"name": "test user"} }' end
이 모든 옵션은 매우 자명합니다. 기본 어댑터를 선택하고 요청 URL을 https://localhost:300/api/v1/users로 설정하고 콘텐츠 유형을 application/json
으로 변경합니다. , 요청의 본문을 제공하십시오.
서버의 응답에는 JSON이 포함될 것이므로 파싱하기 위해 Oj gem을 사용하겠습니다.
api_client.rb
require 'oj' # client here... puts Oj.load(response.body) puts response.status
구문 분석된 응답 외에도 디버깅 목적으로 상태 코드도 표시합니다.
이제 이 스크립트를 간단히 실행할 수 있습니다.
ruby api_client.rb
받은 토큰을 어딘가에 저장합니다. 다음 섹션에서 사용하겠습니다.
토큰으로 인증
토큰 인증을 시행하려면 authenticate_or_request_with_http_token
방법을 사용할 수 있습니다. ActionController::HttpAuthentication::Token::ControllerMethods 모듈의 일부이므로 포함하는 것을 잊지 마세요.
controllers/api/v1/posts_controller.rb
class PostsController < ApplicationController include ActionController::HttpAuthentication::Token::ControllerMethods # ... end
새 before_action
추가 해당 방법:
controllers/api/v1/posts_controller.rb
before_action :authenticate, only: [:create, :destroy] # ... private # ... def authenticate authenticate_or_request_with_http_token do |token, options| @user = User.find_by(token: token) end end
이제 토큰이 설정되지 않았거나 해당 토큰을 가진 사용자를 찾을 수 없으면 401 오류가 반환되어 작업 실행이 중지됩니다.
클라이언트와 서버 간의 통신은 HTTPS를 통해 이루어져야 합니다. 그렇지 않으면 토큰이 쉽게 스푸핑될 수 있습니다. 물론 제공된 솔루션은 이상적이지 않으며 많은 경우 인증을 위해 OAuth 2 프로토콜을 사용하는 것이 좋습니다. 이 기능을 지원하는 프로세스를 크게 단순화하는 최소한 두 개의 보석이 있습니다. 도어키퍼와 oPRO입니다.
게시물 만들기
인증 작업을 보려면 create
를 추가하세요. PostsController
에 대한 작업 :
controllers/api/v1/posts_controller.rb
def create @post = @user.posts.new(post_params) if @post.save render json: @post, status: :created else render json: @post.errors, status: :unprocessable_entity end end
여기서 직렬 변환기를 활용하여 적절한 JSON을 표시합니다. @user
before_action
내부에 이미 설정되었습니다. .
이제 이 간단한 코드를 사용하여 모든 것을 테스트하십시오:
api_client.rb
client = Faraday.new(url: 'https://localhost:3000') do |config| config.adapter Faraday.default_adapter config.token_auth('127a74dbec6f156401b236d6cb32db0d') end response = client.post do |req| req.url '/api/v1/posts' req.headers['Content-Type'] = 'application/json' req.body = '{ "post": {"title": "Title", "body": "Text"} }' end
token_auth
에 전달된 인수를 교체합니다. 등록 시 받은 토큰으로 스크립트를 실행합니다.
ruby api_client.rb
게시물 삭제
게시물 삭제도 같은 방식으로 이루어집니다. destroy
추가 액션:
controllers/api/v1/posts_controller.rb
def destroy @post = @user.posts.find_by(params[:id]) if @post @post.destroy else render json: {post: "not found"}, status: :not_found end end
우리는 사용자가 실제로 소유한 게시물만 파기하도록 허용합니다. 게시물이 성공적으로 제거되면 204 상태 코드(내용 없음)가 반환됩니다. 또는 메모리에서 계속 사용할 수 있으므로 삭제된 게시물의 ID로 응답할 수 있습니다.
다음은 이 새로운 기능을 테스트하기 위한 코드입니다.
api_client.rb
response = client.delete do |req| req.url '/api/v1/posts/6' req.headers['Content-Type'] = 'application/json' end
게시물의 ID를 자신에게 맞는 번호로 바꾸세요.
CORS 설정
다른 웹 서비스가 (클라이언트 측에서) API에 액세스할 수 있도록 하려면 CORS(Cross-Origin Resource Sharing)가 올바르게 설정되어야 합니다. 기본적으로 CORS를 사용하면 웹 응용 프로그램이 AJAX 요청을 타사 서비스에 보낼 수 있습니다. 운 좋게도 모든 것을 쉽게 설정할 수 있는 rack-cor 라는 보석이 있습니다. Gemfile에 추가 :
젬파일
gem 'rack-cors'
설치:
bundle install
그런 다음 config/initializers/cors.rb 내부에 구성을 제공합니다. 파일. 실제로 이 파일은 이미 생성되었으며 사용 예가 포함되어 있습니다. gem 페이지에서 꽤 자세한 문서를 찾을 수도 있습니다.
예를 들어 다음 구성을 사용하면 모든 사람이 모든 방법을 사용하여 API에 액세스할 수 있습니다.
config/initializers/cors.rb
Rails.application.config.middleware.insert_before 0, Rack::Cors do allow do origins '*' resource '/api/*', headers: :any, methods: [:get, :post, :put, :patch, :delete, :options, :head] end end
남용 방지
이 가이드에서 마지막으로 언급할 것은 남용 및 서비스 거부 공격으로부터 API를 보호하는 방법입니다. Rack-attack(Kickstarter의 사람들이 작성)이라는 멋진 보석이 있어 클라이언트를 블랙리스트 또는 화이트리스트에 추가하고 서버에 요청이 넘쳐나는 것을 방지하는 등의 작업을 수행할 수 있습니다.
보석을 Gemfile에 드롭하세요. :
젬파일
gem 'rack-attack'
설치:
bundle install
그런 다음 rack_attack.rb 내부에 구성을 제공합니다. 초기화 파일. gem의 문서는 사용 가능한 모든 옵션을 나열하고 몇 가지 사용 사례를 제안합니다. 다음은 귀하를 제외한 모든 사용자가 서비스에 액세스하지 못하도록 제한하고 최대 요청 수를 초당 5개로 제한하는 샘플 구성입니다.
config/initializers/rack_attack.rb
class Rack::Attack safelist('allow from localhost') do |req| # Requests are allowed if the return value is truthy '127.0.0.1' == req.ip || '::1' == req.ip end throttle('req/ip', :limit => 5, :period => 1.second) do |req| req.ip end end
수행해야 할 또 다른 작업은 RackAttack을 미들웨어로 포함하는 것입니다.
구성/응용 프로그램.rb
config.middleware.use Rack::Attack
결론
우리는 이 기사의 끝에 왔습니다. 이제 Rails를 사용하여 API를 만드는 데 더 자신감을 느끼셨기를 바랍니다! 이것이 유일하게 사용 가능한 옵션이 아니라는 점에 유의하십시오. 꽤 오랫동안 주변에 있었던 또 다른 인기 있는 솔루션은 Grape 프레임워크이므로 확인하는 데 관심이 있을 수 있습니다.
명확하지 않은 것이 있으면 주저하지 말고 질문을 게시하십시오. 함께해주셔서 감사하고 행복한 코딩입니다!