使用Rails制作API

如今,一种常见的做法是严重依赖API(应用程序编程接口)。 不仅像Facebook和Twitter这样的大型服务都采用了它们,而且由于客户端框架(如React,Angular等)的广泛传播,API非常受欢迎。 Ruby on Rails遵循了这一趋势,最新版本提供了一项新功能,使您可以创建仅API的应用程序。

最初,此功能打包在一个名为rails-api的单独的gem中,但是自从Rails 5发布以来,它现在已成为框架核心的一部分 。 此功能与ActionCable一起可能是最令人期待的,因此今天我们将讨论它。

本文介绍了如何创建仅API的Rails应用程序,并说明了如何构建路由和控制器,以JSON格式响应,添加序列化程序以及设置CORS(跨源资源共享)。 您还将了解一些保护API并防止滥用的选项。

这篇文章的源代码可以在GitHub上找到

创建仅API应用程序

要开始,请运行以下命令:

rails new RailsApiDemo --api

它将创建一个名为RailsApiDemo的新的仅API的Rails应用程序。 不要忘记,仅在Rails 5中添加了对--api选项的支持,因此请确保已安装此版本或更新版本。

打开Gemfile ,注意它比平常小得多: coffee-railsturbolinkssass-rails等宝石都消失了。

config / application.rb文件包含新行:

config.api_only = true

这意味着Rails将加载较小的中间件集:例如,没有cookie和会话支持。 此外,如果您尝试生成支架,则不会创建视图和资产。 实际上,如果您查看views / layouts目录,您会注意到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

这里没有什么幻想:带有标题的帖子和正文属于用户。

确保设置了正确的关联,并提供一些简单的验证检查:

型号/user.rb
has_many :posts

  validates :name, presence: true
模型/ post.rb

belongs_to :user

  validates :title, presence: true
  validates :body, presence: true

辉煌! 下一步是将几个样本记录加载到新创建的表中。

加载演示数据

加载某些数据的最简单方法是利用db目录中的seed.rb文件。 但是,我很懒惰(与许多程序员一样),并且不想考虑任何示例内容。 因此,为什么我们不利用可产生各种随机数据的伪造宝石 :名称,电子邮件,时髦单词,“ lorem ipsum”文本等等。

宝石文件
group :development do
    gem 'faker'
end

安装gem:

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 ,但是默认情况下,它将在controllers目录而不是controllers / api / v1内部查找UsersControllerPostsController ,因此请小心。

控制器内部使用嵌套目录v1创建api文件夹。 用您的控制器填充它:

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路径下,而且类本身还必须在ApiV1模块内进行命名空间。

下一个问题是如何正确处理JSON格式的数据? 在本文中,我们将尝试以下解决方案:jBuilder和active_model_serializers gem。 因此,在进行下一部分之前,请将它们放入Gemfile中

宝石文件
gem 'jbuilder', '~> 2.5'
gem 'active_model_serializers', '~> 0.10.0'

然后运行:

bundle install

使用jBuilder宝石

jBuilder是Rails团队维护的一种流行的宝石,它提供了一种简单的DSL(特定于域的语言),使您可以在视图中定义JSON结构。

假设我们想在用户点击index动作时显示所有帖子:

controllers / api / v1 / posts_controller.rb
def index
    @posts = Post.order('created_at DESC')
end

您所需要做的就是创建以.json.jbuilder扩展名的相应动作命名的视图。 请注意,该视图也必须放在api / v1路径下:

views / 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.idjson.titlejson.body生成具有相应名称的密钥,并将参数设置为值。 如果导航到http:// 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

然后使用以下内容创建views / 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_jsonas_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​​执行任何操作。 因此,让我们提出一个简单的安全检查,并在创建和删除帖子时强制我们的用户发送其令牌。

令牌将具有无限的寿命,并在用户注册后创建。 首先,向users表添加一个新的token列:

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回调:

型号/user.rb
before_create -> {self.token = generate_token}

generate_token私有方法将无休止地创建令牌,并检查其是否唯一。 找到唯一令牌后,立即将其返回:

型号/user.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方法的原因),但是您可以自由选择其中任何一个。 但是请注意,令牌不能总是序列化的,例如,当您返回所有用户的列表时-应该保持安全!

views / 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: 'http://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设置为http:// 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协议进行身份验证。 至少有两个gem可以大大简化支持此功能的过程: DoorkeeperoPRO

创建帖子

要查看实际的身份验证,请将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: 'http://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

如果要启用其他Web服务(从客户端)访问API,则应正确设置CORS(跨源资源共享) 。 基本上,CORS 允许Web应用程序将AJAX请求发送到第三方服务。 幸运的是,有一个名为rack-cors的宝石,使我们能够轻松地设置所有内容。 将其添加到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免受滥用和拒绝服务攻击。 有一个名为“ 机架攻击”的好东西 (由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作为中间件:

config / application.rb
config.middleware.use Rack::Attack

结论

我们到了本文的结尾。 希望到目前为止,您对使用Rails制作API更有信心! 请注意,这不是唯一可用的选项-相当久以来出现的另一个流行解决方案是Grape框架 ,因此您可能也有兴趣对其进行检验。

如果您觉得不清楚,请随时提出您的问题。 感谢您与我在一起,并祝您编程愉快!

翻译自: https://code.tutsplus.com/articles/crafting-apis-with-rails--cms-27695

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值