Computer >> 컴퓨터 >  >> 프로그램 작성 >> Ruby

Rails로 API 제작하기

요즘은 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과 같은 보석 , turbolinkssass-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 대신 메소드 , 하지만 기본적으로 UsersControllerPostsController 컨트롤러 내부 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.titlejson.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 프레임워크이므로 확인하는 데 관심이 있을 수 있습니다.

명확하지 않은 것이 있으면 주저하지 말고 질문을 게시하십시오. 함께해주셔서 감사하고 행복한 코딩입니다!