Hoy en día es una práctica común depender en gran medida de las API (interfaces de programación de aplicaciones). No solo los servicios grandes como Facebook y Twitter los emplean, las API son muy populares debido a la expansión de marcos del lado del cliente como React, Angular y muchos otros. Ruby on Rails sigue esta tendencia y la última versión presenta una nueva función que le permite crear aplicaciones solo para API.
Inicialmente, esta funcionalidad se empaquetó en una gema separada llamada rails-api, pero desde el lanzamiento de Rails 5, ahora es parte del núcleo del marco. Esta característica junto con ActionCable fue probablemente la más esperada, por lo que hoy vamos a discutirla.
Este artículo trata sobre cómo crear aplicaciones Rails solo para API y explica cómo estructurar sus rutas y controladores, responder con el formato JSON, agregar serializadores y configurar CORS (Intercambio de recursos entre orígenes). También aprenderá acerca de algunas opciones para asegurar la API y protegerla contra el abuso.
La fuente de este artículo está disponible en GitHub..
Para comenzar, ejecute el siguiente comando:
rieles nuevos RailsApiDemo --api
Va a crear una nueva aplicación Rails solo para API llamada RailsApiDemo
. No olvides que el soporte para el --api
La opción se agregó solo en Rails 5, así que asegúrese de tener esta o una versión más reciente instalada.
Abre el Gemfile y tenga en cuenta que es mucho más pequeño de lo habitual: gemas como barandillas de cafe
, enlaces turbo
, y barandillas
se fueron.
los config / application.rb archivo contiene una nueva línea:
config.api_only = true
Esto significa que Rails va a cargar un conjunto más pequeño de middleware: por ejemplo, no hay cookies y soporte de sesiones. Además, si intenta generar un andamio, las vistas y los activos no se crearán. En realidad, si marca la vistas / diseños directorio, usted notará que el aplicacion.html.erb Falta el archivo también.
Otra diferencia importante es que la Controlador de aplicaciones
hereda de la ActionController :: API
, no ActionController :: Base
.
Eso es prácticamente todo, esta es una aplicación básica de Rails que has visto muchas veces. Ahora agreguemos un par de modelos para que tengamos algo con qué trabajar:
rieles modelo g Nombre de usuario: cadena rieles modelo g Título de la publicación: cadena cuerpo: texto usuario: belongs_ to rails db: migrate
No hay nada especial aquí: una publicación con un título, y un cuerpo pertenece a un usuario.
Asegúrese de que las asociaciones correctas estén configuradas y también proporcione algunas verificaciones de validación simples:
has_many: mensajes valida: nombre, presencia: verdadero
belongs_to: usuario valida: título, presencia: verdadero valida: cuerpo, presencia: verdadero
¡Brillante! El siguiente paso es cargar un par de registros de muestra en las tablas recién creadas.
La forma más fácil de cargar algunos datos es utilizando el semillas.rb archivo dentro de la db directorio. Sin embargo, soy perezoso (como lo son muchos programadores) y no quiero pensar en ningún contenido de muestra. Por lo tanto, ¿por qué no aprovechamos la gema falsa que puede producir datos aleatorios de varios tipos: nombres, correos electrónicos, palabras inconformistas, textos de "lorem ipsum" y mucho más?.
grupo: desarrollo do gema 'faker' final
Instala la gema:
instalación de paquete
Ahora pellizque el semillas.rb:
5.times do user = User.create (name: Faker :: Name.name) user.posts.create (title: Faker :: Book.title, body: Faker :: Lorem.sentence) end
Por último, carga tus datos:
rieles db: semilla
Ahora, por supuesto, necesitamos algunas rutas y controladores para diseñar nuestra API. Es una práctica común anidar las rutas de la API bajo el api /
camino. Además, los desarrolladores suelen proporcionar la versión de la API en la ruta, por ejemplo api / v1 /
. Más adelante, si hay que introducir algunos cambios de última hora, simplemente puede crear un nuevo espacio de nombres (v2
) y un controlador separado.
Aquí es cómo pueden verse sus rutas:
espacio de nombres 'api' hacer espacio de nombres 'v1' hacer recursos: publica recursos: los usuarios finalizan
Esto genera rutas como:
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
Puedes usar un alcance
método en lugar de espacio de nombres
, pero entonces por defecto buscará el Controlador de usuarios
y PostsController
dentro de controladores directorio, no dentro de la controladores / api / v1, así que ten cuidado.
Crear el api carpeta con el directorio anidado v1 dentro de controladores. Llénalo con tus controladores:
módulo Api módulo V1 clase UsersController < ApplicationController end end end
módulo Api módulo V1 clase PostsController < ApplicationController end end end
Tenga en cuenta que no solo tiene que anidar el archivo del controlador bajo la api / v1 camino, pero la clase en sí también tiene que estar espaciada dentro de la Api
y V1
módulos.
La siguiente pregunta es ¿cómo responder correctamente con los datos en formato JSON? En este artículo probaremos estas soluciones: las gemas jBuilder y active_model_serializers. Así que antes de pasar a la siguiente sección, colóquelos en la Gemfile:
gema 'jbuilder', '~> 2.5' gema 'active_model_serializers', '~> 0.10.0'
Entonces corre:
instalación de paquete
jBuilder es una joya popular mantenida por el equipo de Rails que proporciona un simple DSL (lenguaje específico del dominio) que le permite definir estructuras JSON en sus vistas.
Supongamos que queremos mostrar todas las publicaciones cuando un usuario toca el índice
acción:
def index @posts = Post.order ('created_at DESC') final
Todo lo que necesita hacer es crear la vista que lleva el nombre de la acción correspondiente con la .json.jbuilder extensión. Tenga en cuenta que la vista se debe colocar debajo de la api / v1 camino también
json.array! @posts hacer | publicar | json.id post.id json.title post.title json.body post.body end
json.array!
atraviesa el @posts
formación. json.id
, json.title
y json.body
generar las claves con los nombres correspondientes estableciendo los argumentos como los valores. Si navega a http: // localhost: 3000 / api / v1 / posts.json, verá una salida similar a esta:
["id": 1, "title": "Title 1", "body": "Body 1", "id": 2, "title": "Title 2", "body": "Body 2 "]
¿Y si también quisiéramos mostrar al autor para cada publicación? Es sencillo:
json.array! @posts hacer | publicar | 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
La salida cambiará a:
["id": 1, "title": "Title 1", "body": "Body 1", "user": "id": 1, "name": "Username"]
Los contenidos de la .jbuilder Los archivos son código Ruby simple, por lo que puede utilizar todas las operaciones básicas como de costumbre..
Tenga en cuenta que jBuilder soporta parciales como cualquier vista de Rails ordinaria, por lo que también puede decir:
json.partial! parcial: 'posts / post', colección: @posts, as:: post
y luego crear el vistas / api / v1 / posts / _post.json.jbuilder Archivo con los siguientes contenidos:
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
Entonces, como ves, jBuilder es fácil y conveniente. Sin embargo, como alternativa, puede quedarse con los serializadores, por lo que vamos a analizarlos en la siguiente sección..
La gema rails_model_serializers fue creada por un equipo que inicialmente administró rails-api. Como se indica en la documentación, rails_model_serializers trae la convención sobre la configuración a su generación JSON. Básicamente, usted define qué campos deben usarse en la serialización (es decir, generación JSON).
Aquí está nuestro primer serializador:
clase PostSerializer < ActiveModel::Serializer attributes :id, :title, :body end
Aquí decimos que todos estos campos deben estar presentes en el JSON resultante. Ahora metodos como to_json
y as_json
llamado a una publicación usará esta configuración y devolverá el contenido adecuado.
Para verlo en acción, modifique el índice
acción como esta:
def index @posts = Post.order ('created_at DESC') render json: @posts end
as_json
será automáticamente llamado a la @posts
objeto.
¿Qué pasa con los usuarios? Los serializadores le permiten indicar relaciones, al igual que los modelos. Además, los serializadores se pueden anidar:
clase PostSerializer < ActiveModel::Serializer attributes :id, :title, :body belongs_to :user class UserSerializer < ActiveModel::Serializer attributes :id, :name end end
Ahora, cuando serialice la publicación, contendrá automáticamente el contenido anidado. usuario
clave con su id y nombre. Si más tarde creas un serializador separado para el usuario con el :carné de identidad
atributo excluido:
clase UserSerializer < ActiveModel::Serializer attributes :name end
entonces @ usuario.as_json
no devolverá la identificación del usuario Todavía, @ post.as_json
devolverá tanto el nombre del usuario como la identificación, así que téngalo en cuenta.
En muchos casos, no queremos que nadie realice ninguna acción utilizando la API. Así que presentemos un simple control de seguridad y obligemos a nuestros usuarios a enviar sus tokens al crear y eliminar publicaciones..
El token tendrá una vida útil ilimitada y se creará en el registro del usuario. En primer lugar, añadir una nueva. simbólico
columna a la usuarios
mesa:
carriles g migración add_token_to_users token: cadena: índice
Este índice debe garantizar la exclusividad ya que no puede haber dos usuarios con el mismo token:
add_index: users,: token, unique: true
Aplicar la migración:
rieles db: migrar
Ahora agregue el antes_save
llamar de vuelta:
before_create -> self.token = generate_token
los generar
El método privado creará un token en un ciclo sin fin y verificará si es único o no. Tan pronto como se encuentre un token único, devuélvalo:
private def genera_token loop do token = SecureRandom.hex devolver token a menos que User.exists? (token: token) end end end
Puede usar otro algoritmo para generar el token, por ejemplo, basado en el hash MD5 del nombre del usuario y algo de sal.
Por supuesto, también debemos permitir que los usuarios se registren, porque de lo contrario no podrán obtener su token. No quiero introducir ninguna vista HTML en nuestra aplicación, así que en lugar de eso, agreguemos un nuevo método API:
def create @user = User.new (user_params) si @ user.save render status:: created else render json: @ user.errors, status:: unprocessable_entity end end private def user_params params.require (: user) .permit (: nombre) fin
Es una buena idea devolver códigos de estado HTTP significativos para que los desarrolladores entiendan exactamente lo que está sucediendo. Ahora puede proporcionar un nuevo serializador para los usuarios o seguir con un .json.jbuilder expediente. Prefiero la última variante (por eso no paso la : json
opción a la hacer
Método), pero usted es libre de elegir cualquiera de ellos. Tenga en cuenta, sin embargo, que el token no debe ser siempre serializado, por ejemplo, cuando devuelve una lista de todos los usuarios, debe mantenerse segura!
json.id @ user.id json.name @ user.name json.token @ user.token
El siguiente paso es probar si todo funciona correctamente. Usted puede usar el rizo
Manda o escribe algún código Ruby. Ya que este artículo es sobre Ruby, iré con la opción de codificación.
Para realizar una solicitud HTTP, emplearemos la gema de Faraday, que proporciona una interfaz común sobre muchos adaptadores (el valor predeterminado es Net :: HTTP
). Cree un archivo Ruby separado, incluya Faraday y configure el cliente:
requiere cliente 'faraday' = 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
Todas estas opciones se explican por sí mismas: elegimos el adaptador predeterminado, configuramos la URL de solicitud en http: // localhost: 300 / api / v1 / users, cambiamos el tipo de contenido a aplicación / json
, y proporcionar el cuerpo de nuestra solicitud..
La respuesta del servidor contendrá JSON, así que para analizarlo usaré la gema Oj:
requiere 'oj' # cliente aquí ... pone Oj.load (response.body) pone response.status
Además de la respuesta analizada, también muestro el código de estado para fines de depuración.
Ahora puedes simplemente ejecutar este script:
rubi api_client.rb
y almacene el token recibido en algún lugar; lo usaremos en la siguiente sección.
Para imponer la autenticación del token, el authenticate_or_request_with_http_token
Se puede utilizar el método. Es parte del módulo ActionController :: HttpAuthentication :: Token :: ControllerMethods, así que no olvide incluirlo:
clase PostsController < ApplicationController include ActionController::HttpAuthentication::Token::ControllerMethods #… end
Añadir un nuevo antes_acción
y el método correspondiente:
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 end
Ahora, si el token no está establecido o si no se puede encontrar un usuario con dicho token, se devolverá un error 401, lo que detiene la ejecución de la acción..
Tenga en cuenta que la comunicación entre el cliente y el servidor debe realizarse a través de HTTPS, ya que de lo contrario los tokens se pueden falsificar fácilmente. Por supuesto, la solución provista no es ideal, y en muchos casos es preferible emplear el protocolo OAuth 2 para la autenticación. Hay al menos dos gemas que simplifican enormemente el proceso de compatibilidad con esta función: Doorkeeper y oPRO.
Para ver nuestra autenticación en acción, agregue el crear
acción al PostsController
:
def create @post = @ user.posts.new (post_params) si @ post.save render json: @post, status:: creado else render json: @ post.errors, status:: unprocessable_entity end end
Aprovechamos el serializador aquí para mostrar el JSON adecuado. los @usuario
ya estaba dentro de la antes_acción
.
Ahora prueba todo usando este simple código:
cliente = 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": "Texto" 'final
Reemplace el argumento pasado a la token_auth
con el token recibido en el registro, y ejecute el script.
rubi api_client.rb
La eliminación de una publicación se realiza de la misma manera. Añade el destruir
acción:
def destroy @post = @ user.posts.find_by (params [: id]) si @post @ post.destroy else render json: post: "no encontrado", estado:: not_found end end
Solo permitimos que los usuarios destruyan las publicaciones que realmente poseen. Si la publicación se elimina con éxito, se devolverá el código de estado 204 (sin contenido). Alternativamente, puede responder con la identificación de la publicación que se eliminó, ya que todavía estará disponible en la memoria.
Aquí está el código para probar esta nueva característica:
response = client.delete do | req | req.url '/ api / v1 / posts / 6' req.headers ['Content-Type'] = 'application / json' end
Reemplaza la identificación de la publicación con un número que funcione para ti.
Si desea permitir que otros servicios web accedan a su API (desde el lado del cliente), entonces CORS (Intercambio de recursos entre orígenes) debe configurarse correctamente. Básicamente, CORS permite que las aplicaciones web envíen solicitudes AJAX a los servicios de terceros. Por suerte, hay una gema llamada rack-cors que nos permite configurar todo fácilmente. Añadirlo a la Gemfile:
gema 'rack-cors'
Instalarlo:
instalación de paquete
Y luego proporcionar la configuración dentro de la config / initializers / cors.rb expediente. En realidad, este archivo ya está creado para ti y contiene un ejemplo de uso. También puede encontrar documentación bastante detallada en la página de la gema..
La siguiente configuración, por ejemplo, permitirá a cualquier persona acceder a su API utilizando cualquier método:
Rails.application.config.middleware.insert_before 0, Rack :: Cors permite hacer orígenes '*' recurso '/ api / *', encabezados:: cualquiera, métodos: [: get,: post,: put,: patch, : delete,: options,: head] end end
Lo último que voy a mencionar en esta guía es cómo proteger su API de los ataques de abuso y denegación de servicio. Hay una bonita gema llamada rack-attack (creada por personas de Kickstarter) que le permite a la lista negra o lista de clientes, evitar la inundación de un servidor con solicitudes y más.
Soltar la gema en Gemfile:
gema 'rack-attack'
Instalarlo:
instalación de paquete
Y luego proporcionar la configuración dentro de la rack_attack.rb archivo inicializador. La documentación de la gema enumera todas las opciones disponibles y sugiere algunos casos de uso. Aquí está la configuración de muestra que restringe a cualquier persona, excepto a usted, que no pueda acceder al servicio y limita el número máximo de solicitudes a 5 por segundo:
class Rack :: Attack safelist ('allow from localhost') do | req | # Se permiten solicitudes si el valor de retorno es verdadero '127.0.0.1' == req.ip || ':: 1' == req.ip end throttle ('req / ip',: limit => 5,: period => 1.second) do | req | req.ip end end
Otra cosa que hay que hacer es incluir RackAttack como un middleware:
config.middleware.use Rack :: Attack
Hemos llegado al final de este artículo. Con suerte, ¡ya te sientes más confiado en la creación de API con Rails! Tenga en cuenta que esta no es la única opción disponible, otra solución popular que existió durante bastante tiempo es el marco de Grape, por lo que puede estar interesado en comprobarlo también..
No dude en publicar sus preguntas si algo le parece poco claro. Te agradezco por estar conmigo, y feliz codificación!