如今,一种常见的做法是严重依赖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-rails
, turbolinks
和sass-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内部查找UsersController
和PostsController
,因此请小心。
在控制器内部使用嵌套目录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路径下,而且类本身还必须在Api
和V1
模块内进行命名空间。
下一个问题是如何正确处理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.id
, json.title
和json.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_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执行任何操作。 因此,让我们提出一个简单的安全检查,并在创建和删除帖子时强制我们的用户发送其令牌。
令牌将具有无限的寿命,并在用户注册后创建。 首先,向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可以大大简化支持此功能的过程: Doorkeeper和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: '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