De nos jours, il est courant de faire largement appel à des API (interfaces de programmation d'applications). Non seulement les grands services comme Facebook et Twitter les utilisent: les API sont très populaires en raison de la multiplication des infrastructures côté client telles que React, Angular et bien d'autres. Ruby on Rails suit cette tendance et la dernière version présente une nouvelle fonctionnalité vous permettant de créer des applications uniquement API..
Initialement, cette fonctionnalité était intégrée à une gemme séparée appelée rails-api, mais depuis la publication de Rails 5, elle fait désormais partie du noyau du framework. Cette fonctionnalité, avec ActionCable, était probablement la plus attendue. Nous allons donc en discuter aujourd’hui..
Cet article explique comment créer des applications Rails uniquement basées sur des API et explique comment structurer vos routes et vos contrôleurs, répondre au format JSON, ajouter des sérialiseurs et configurer le partage de ressources CORS (Cross-Origin Resource Sharing). Vous apprendrez également certaines options pour sécuriser l'API et la protéger contre les abus..
La source de cet article est disponible sur GitHub.
Pour commencer, lancez la commande suivante:
rails nouveaux RailsApiDemo --api
Il va créer une nouvelle application Rails réservée à l’API, appelée RailsApiDemo
. N'oubliez pas que le soutien à la --api
Cette option a été ajoutée uniquement dans Rails 5, assurez-vous donc que cette version ou une version plus récente est installée..
Ouvrez le Gemfile et notez qu'il est beaucoup plus petit que d'habitude: des gemmes comme bar à café
, turbolinks
, et sass-rails
sont partis.
le config / application.rb Le fichier contient une nouvelle ligne:
config.api_only = true
Cela signifie que Rails va charger un plus petit ensemble de middleware: par exemple, il n'y a pas de cookies et de support de sessions. De plus, si vous essayez de générer un échafaudage, les vues et les actifs ne seront pas créés. En fait, si vous cochez la case vues / mises en page répertoire, vous remarquerez que le application.html.erb le fichier est manquant aussi.
Une autre différence importante est que le ApplicationController
hérite de la ActionController :: API
, ne pas ActionController :: Base
.
En gros, il s’agit là d’une application Rails de base que vous avez vue à plusieurs reprises. Ajoutons maintenant quelques modèles pour que nous puissions travailler avec:
rails g model Nom d'utilisateur: string rails g model Titre du message: string body: text utilisateur: appartient_to rails db: migrate
Rien de spécial ne se passe ici: un article avec un titre et un corps appartenant à un utilisateur.
Assurez-vous que les associations appropriées sont configurées et fournissez également quelques contrôles de validation simples:
has_many: posts valide: nom, présence: true
appartient_à: l'utilisateur valide: titre, présence: vrai valide: corps, présence: vrai
Brillant! L'étape suivante consiste à charger quelques exemples d'enregistrements dans les tables nouvellement créées..
Le moyen le plus simple de charger certaines données consiste à utiliser le seeds.rb déposer à l'intérieur du db annuaire. Cependant, je suis paresseux (comme le sont beaucoup de programmeurs) et ne veux pas penser à un exemple de contenu. Par conséquent, pourquoi ne profitons-nous pas de la fausse pierre précieuse qui peut produire des données aléatoires de différentes sortes: noms, courriels, mots branchés, textes de "lorem ipsum", et bien plus encore.
groupe: développement do gem 'faker' end
Installez la gemme:
installation groupée
Maintenant, modifiez le seeds.rb:
5. Parfois, user = User.create (name: Faker :: Name.name) user.posts.create (title: Faker :: Book.title, body: Faker :: Lorem.sentence) end
Enfin, chargez vos données:
rails db: seed
Maintenant, bien sûr, nous avons besoin de routes et de contrôleurs pour concevoir notre API. Il est courant d’implémenter les routes de l’API sous le api /
chemin. En outre, les développeurs fournissent généralement la version de l'API dans le chemin, par exemple api / v1 /
. Plus tard, si des changements radicaux doivent être introduits, vous pouvez simplement créer un nouvel espace de nom (v2
) et un contrôleur séparé.
Voici à quoi peuvent ressembler vos itinéraires:
namespace 'api' do namespace 'v1' do resources: publie des ressources: les utilisateurs end end
Cela génère des itinéraires comme:
api_v1_posts GET /api/v1/posts(.:format) api / v1 / posts # index POST /api/v1/posts(.:format) api / v1 / posts # créer api_v1_post GET / api / v1 / posts /: id (.: format) api / v1 / posts # show
Vous pouvez utiliser un portée
méthode au lieu de espace de noms
, mais par défaut, il cherchera le UtilisateursContrôleur
et PostsController
à l'intérieur de contrôleurs répertoire, pas à l'intérieur du contrôleurs / api / v1, donc sois prudent.
Créer le api dossier avec le répertoire imbriqué v1 à l'intérieur de contrôleurs. Remplissez-le avec vos contrôleurs:
module Api module classe V1 UsersController < ApplicationController end end end
module Api module V1 class PostsController < ApplicationController end end end
Notez que vous devez non seulement imbriquer le fichier du contrôleur sous le api / v1 chemin, mais la classe elle-même doit également être namespaced à l'intérieur du Api
et V1
modules.
La prochaine question est de savoir comment répondre correctement avec les données au format JSON. Dans cet article, nous allons essayer ces solutions: les gems jBuilder et active_model_serializers. Donc, avant de passer à la section suivante, déposez-les dans le Gemfile:
gem 'jbuilder', '~> 2.5' gem 'active_model_serializers', '~> 0.10.0'
Puis lancez:
installation groupée
jBuilder est un joyau populaire géré par l'équipe Rails qui fournit un simple DSL (langage spécifique au domaine) vous permettant de définir des structures JSON dans vos vues..
Supposons que nous voulions afficher tous les articles lorsqu'un utilisateur clique sur le indice
action:
def index @posts = Post.order ('created_at DESC') end
Tout ce que vous avez à faire est de créer la vue nommée d'après l'action correspondante avec le .json.jbuilder extension. Notez que la vue doit être placée sous le api / v1 chemin aussi:
json.array! @posts do | post | json.id post.id json.title post.title json.body post.body end
json.array!
traverse le @des postes
tableau. json.id
, json.title
et json.body
générer les clés avec les noms correspondants en définissant les arguments comme valeurs. Si vous accédez à http: // localhost: 3000 / api / v1 / posts.json, vous verrez une sortie similaire à celle-ci:
["id": 1, "titre": "Titre 1", "corps": "Corps 1", "id": 2, "titre": "Titre 2", "corps": "Corps 2 "]
Et si nous voulions aussi afficher l'auteur pour chaque message? C'est simple:
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 fin fin
La sortie deviendra:
["id": 1, "titre": "Titre 1", "corps": "Corps 1", "utilisateur": "id": 1, "nom": "nom d'utilisateur"]
Le contenu de la .constructeur fichiers est du code Ruby simple, vous pouvez donc utiliser toutes les opérations de base comme d'habitude.
Notez que jBuilder prend en charge les partiels comme n'importe quelle vue Rails ordinaire. Vous pouvez donc également indiquer:
json.partial! partiel: 'posts / post', collection: @posts, as:: post
puis créer le views / api / v1 / posts / _post.json.jbuilder fichier avec le contenu suivant:
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
Donc, comme vous le voyez, jBuilder est simple et pratique. Cependant, au lieu de cela, vous pouvez vous en tenir aux sérialiseurs. Discutons-en dans la section suivante..
La gem rails_model_serializers a été créé par une équipe qui gérait initialement le rail-api. Comme indiqué dans la documentation, rails_model_serializers apporte la convention sur la configuration à votre génération JSON. En gros, vous définissez quels champs doivent être utilisés lors de la sérialisation (c'est-à-dire, la génération JSON).
Voici notre premier sérialiseur:
classe PostSerializer < ActiveModel::Serializer attributes :id, :title, :body end
Nous disons ici que tous ces champs doivent être présents dans le JSON résultant. Maintenant des méthodes comme to_json
et as_json
appelé un message utilisera cette configuration et renverra le contenu approprié.
Pour le voir en action, modifiez le indice
action comme celle-ci:
def index @posts = Post.order ('created_at DESC') render json: @posts end
as_json
sera automatiquement appelé à la @des postes
objet.
Qu'en est-il des utilisateurs? Les sérialiseurs vous permettent d'indiquer des relations, tout comme les modèles. De plus, les sérialiseurs peuvent être imbriqués:
classe PostSerializer < ActiveModel::Serializer attributes :id, :title, :body belongs_to :user class UserSerializer < ActiveModel::Serializer attributes :id, :name end end
Maintenant, lorsque vous sérialisez la publication, elle contiendra automatiquement la utilisateur
clé avec son identifiant et son nom. Si plus tard vous créez un sérialiseur séparé pour l’utilisateur avec le : id
attribut exclu:
classe UserSerializer < ActiveModel::Serializer attributes :name end
puis @ user.as_json
ne retournera pas l'identifiant de l'utilisateur. Encore, @ post.as_json
renverra à la fois le nom et l'identifiant de l'utilisateur, alors gardez-le à l'esprit.
Dans de nombreux cas, nous ne voulons pas que quiconque exécute une action en utilisant l'API. Présentons donc un simple contrôle de sécurité et obligeons nos utilisateurs à envoyer leurs jetons lors de la création et de la suppression de messages..
Le jeton aura une durée de vie illimitée et sera créé lors de l'enregistrement de l'utilisateur. Tout d’abord, ajoutez un nouveau jeton
colonne à la utilisateurs
table:
rails g migration jeton add_token_to_users: chaîne: index
Cet index devrait garantir l'unicité car il ne peut y avoir deux utilisateurs avec le même jeton:
add_index: utilisateurs,: jeton, unique: true
Appliquer la migration:
rails db: migrer
Maintenant, ajoutez le before_save
rappeler:
before_create -> self.token = generate_token
le generate_token
méthode privée va créer un jeton dans un cycle sans fin et vérifier si c'est unique ou pas. Dès qu'un jeton unique est trouvé, retournez-le:
private def generate_token loop doken = SecureRandom.hex renvoie un jeton sauf si User.exists? (token: token) end end
Vous pouvez utiliser un autre algorithme pour générer le jeton, par exemple en vous basant sur le hachage MD5 du nom de l'utilisateur et du sel..
Bien sûr, nous devons également permettre aux utilisateurs de s’inscrire, sinon ils ne pourront pas obtenir leur jeton. Je ne souhaite pas introduire de vues HTML dans notre application, ajoutons donc une nouvelle méthode API:
def create @user = User.new (user_params) if @ user.save état du rendu:: created else rendu json: @ user.errors, statut:: UNNENTABLE_ENTENT_FR End end private def user_params params.require (: user) .permit (: nom) fin
Il est judicieux de renvoyer des codes d'état HTTP significatifs afin que les développeurs comprennent exactement ce qui se passe. Maintenant, vous pouvez soit fournir un nouveau sérialiseur aux utilisateurs, soit vous en tenir à une .json.jbuilder fichier. Je préfère cette dernière variante (c’est pourquoi je ne passe pas la : json
option au rendre
méthode), mais vous êtes libre de choisir l’un d’eux. Notez cependant que le jeton ne doit pas être toujours sérialisé, par exemple lorsque vous renvoyez une liste de tous les utilisateurs, il convient de la garder en sécurité!
json.id @ user.id json.name @ user.name json.token @ user.token
La prochaine étape consiste à vérifier si tout fonctionne correctement. Vous pouvez soit utiliser le boucle
commande ou écrit du code Ruby. Puisque cet article concerne Ruby, je vais aller avec l'option de codage.
Pour effectuer une requête HTTP, nous allons utiliser la gem Faraday, qui fournit une interface commune sur plusieurs adaptateurs (la valeur par défaut est Net :: HTTP
). Créez un fichier Ruby distinct, incluez Faraday et configurez le client:
Requiert le client 'faraday' = Faraday.new (url: 'http: // localhost: 3000') do | config | config.adapter Faraday.default_adapter end response = client.post do | req | req.url '/ api / v1 / user' req.headers ['Content-Type'] = 'application / json' req.body = '"utilisateur": "nom": "utilisateur test" "fin
Toutes ces options sont assez explicites: nous choisissons l’adaptateur par défaut, définissons l’URL de la demande sur http: // localhost: 300 / api / v1 / users, modifions le type de contenu en application / json
, et fournir le corps de notre demande.
La réponse du serveur va contenir JSON, donc pour l'analyse, j'utiliserai la gemme Oj:
require 'oj' # client here… met Oj.load (response.body) met response.status
Outre la réponse analysée, j'affiche également le code d'état à des fins de débogage.
Maintenant, vous pouvez simplement exécuter ce script:
ruby api_client.rb
et stocker le jeton reçu quelque part, nous l'utilisons dans la section suivante.
Pour appliquer l’authentification par jeton, le authenticate_or_request_with_http_token
méthode peut être utilisé. Il fait partie du module ActionController :: HttpAuthentication :: Token :: ControllerMethods, alors n'oubliez pas de l'inclure:
classe PostsController < ApplicationController include ActionController::HttpAuthentication::Token::ControllerMethods #… end
Ajouter un nouveau before_action
et la méthode correspondante:
before_action: authentifier, uniquement: [: créer,: détruire] #… privé #… def authentifier authentifier_ou_réqueter_avec_http_token faire | jeton, options | @user = User.find_by (token: token) end end
Maintenant, si le jeton n'est pas défini ou si un utilisateur avec ce jeton est introuvable, une erreur 401 sera renvoyée, interrompant l'exécution de l'action..
Notez que la communication entre le client et le serveur doit être établie via HTTPS, sinon les jetons peuvent être facilement usurpés. Bien entendu, la solution fournie n’est pas idéale et, dans de nombreux cas, il est préférable d’utiliser le protocole OAuth 2 pour l’authentification. Il existe au moins deux joyaux qui simplifient grandement le processus de prise en charge de cette fonctionnalité: Doorkeeper et oPRO..
Pour voir notre authentification en action, ajoutez le créer
action à la PostsController
:
def create @post = @ user.posts.new (post_params) si @ post.save rend json: @post, statut:: créé autrement rend json: @ post.errors, statut: :Unksable_entity end end
Nous profitons du sérialiseur ici pour afficher le bon JSON. le @utilisateur
était déjà placé à l'intérieur du before_action
.
Maintenant, testez tout avec ce code simple:
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": "titre": "Titre", "corps": "Texte" 'fin
Remplacer l'argument passé à la token_auth
avec le jeton reçu lors de l'inscription, et lancez le script.
ruby api_client.rb
La suppression d'un message se fait de la même manière. Ajouter le détruire
action:
def destroy @post = @ user.posts.find_by (params [: id]) si @post @ post.destroy sinon restitue json: post: "non trouvé", statut:: not_found fin
Nous n'autorisons que les utilisateurs à détruire les publications qu'ils possèdent. Si la publication est supprimée avec succès, le code de statut 204 (pas de contenu) sera renvoyé. Alternativement, vous pouvez répondre avec l'identifiant de l'article qui a été supprimé car il sera toujours disponible à partir de la mémoire..
Voici le morceau de code pour tester cette nouvelle fonctionnalité:
response = client.delete do | req | req.url '/ api / v1 / posts / 6' req.headers ['Content-Type'] = 'application / json' end
Remplacez l'identifiant de la publication par un numéro qui vous convient.
Si vous souhaitez autoriser d'autres services Web à accéder à votre API (côté client), le partage de ressources CORS (origine croisée) doit être correctement configuré. Fondamentalement, CORS permet aux applications Web d’envoyer des demandes AJAX aux services tiers. Heureusement, il existe un joyau appelé rack-cors qui nous permet de tout configurer facilement. Ajoutez-le dans le Gemfile:
bijou 'rack-cors'
Installez-le:
installation groupée
Et puis fournir la configuration à l'intérieur du config / initializers / cors.rb fichier. En fait, ce fichier est déjà créé pour vous et contient un exemple d'utilisation. Vous pouvez également trouver de la documentation assez détaillée sur la page de la gemme.
La configuration suivante, par exemple, permettra à quiconque d'accéder à votre API par n'importe quelle méthode:
Rails.application.config.middleware.insert_before 0, Rack :: Cors autorise les origines '*' ressource '/ api / *', en-têtes:: tous, méthodes: [: obtenir,: publier,: mettre,: patch, : supprimer,: options,: tête] fin fin
La dernière chose que je vais mentionner dans ce guide concerne la protection de votre API contre les attaques par abus et par déni de service. Il existe un joyau appelé rack-attack (créé par des personnes de Kickstarter) qui vous permet de mettre des clients sur une liste noire ou blanche, d'empêcher le serveur d'inonder de requêtes, etc..
Déposer la gemme dans Gemfile:
bijou 'rack-attaque'
Installez-le:
installation groupée
Et puis fournir la configuration à l'intérieur du rack_attack.rb fichier d'initialisation. La documentation de la gemme répertorie toutes les options disponibles et suggère des cas d'utilisation. Voici l'exemple de configuration qui empêche toute personne autre que vous d'accéder au service et limite le nombre maximal de demandes à 5 par seconde:
class Rack :: Attack safelist ('allow from localhost') fait | req | # Les requêtes sont autorisées si la valeur de retour est true '127.0.0.1' == req.ip || ':: 1' == req.ip end throttle ('req / ip',: limit => 5,: period => 1.second) do | req | req.ip end end
Une autre chose à faire consiste à inclure RackAttack en tant que middleware:
config.middleware.use Rack :: Attack
Nous sommes arrivés à la fin de cet article. Si tout va bien, vous êtes maintenant plus confiant dans la création d’API avec Rails! Notez que ce n’est pas la seule option disponible. Le framework Grape est une autre solution populaire utilisée depuis un certain temps déjà. Vous voudrez peut-être aussi le vérifier..
N'hésitez pas à poster vos questions si quelque chose vous semblait incertain. Je vous remercie de rester avec moi et bon codage!