Creazione di API con Rails

Oggigiorno è una pratica comune fare molto affidamento sulle API (interfacce di programmazione delle applicazioni). Non solo i grandi servizi come Facebook e Twitter li utilizzano: le API sono molto popolari grazie alla diffusione di strutture client-side come React, Angular e molti altri. Ruby on Rails sta seguendo questa tendenza e l'ultima versione presenta una nuova funzionalità che consente di creare applicazioni solo API. 

Inizialmente questa funzionalità era racchiusa in una gemma separata chiamata rails-api, ma dal rilascio di Rails 5, ora fa parte del nucleo del framework. Questa funzione, insieme a ActionCable, era probabilmente la più attesa, e quindi oggi ne discuteremo.

Questo articolo spiega come creare applicazioni Rails solo API e spiega come strutturare i percorsi e i controller, rispondere con il formato JSON, aggiungere serializzatori e configurare CORS (Cross-Origin Resource Sharing). Imparerai anche alcune opzioni per proteggere l'API e proteggerla dall'abuso.

La fonte di questo articolo è disponibile su GitHub.

Creazione di un'applicazione solo API

Per iniziare, eseguire il seguente comando:

rotaie nuove RailsApiDemo --api

Creerà una nuova applicazione Rails solo API chiamata RailsApiDemo. Non dimenticare che il supporto per il --api l'opzione è stata aggiunta solo in Rails 5, quindi assicurati di aver installato questa o una versione più recente.

Apri il Gemfile e nota che è molto più piccolo del solito: gemme come coffee-rails, turbolinks, e Sass-rails sono andati.

Il config / application.rb il file contiene una nuova riga:

config.api_only = true

Significa che Rails sta per caricare un piccolo insieme di middleware: ad esempio, non ci sono cookie e supporto alle sessioni. Inoltre, se provi a generare uno scaffold, le viste e le risorse non verranno create. In realtà, se controlli il Visto / layout directory, noterai che il application.html.erb manca anche il file.

Un'altra importante differenza è che il ApplicationController eredita dal ActionController :: API, non ActionController :: Base.

Questo è praticamente tutto - tutto sommato, questa è un'applicazione di Rails di base che hai visto molte volte. Ora aggiungiamo un paio di modelli in modo che abbiamo qualcosa su cui lavorare:

rails g model Nome utente: string rails g model Titolo del post: string body: text user: belongs_to rails db: migrate

Niente di speciale sta succedendo qui: un post con un titolo e un corpo appartiene a un utente.

Assicurati che le associazioni appropriate siano configurate e fornisci anche alcuni semplici controlli di convalida:

modelli / user.rb

 has_many: i post convalidano: name, presence: true

modelli / post.rb

 belongs_to: l'utente conferma: title, presence: true validates: body, presence: true

Brillante! Il prossimo passo è caricare un paio di record di esempio nelle nuove tabelle create.

Caricamento dei dati demo

Il modo più semplice per caricare alcuni dati è utilizzando il seeds.rb file all'interno del db directory. Tuttavia, sono pigro (come molti programmatori lo sono) e non voglio pensare a nessun contenuto di esempio. Pertanto, perché non sfruttiamo la gemma falsa che può produrre dati casuali di vario tipo: nomi, e-mail, parole a vita bassa, testi "lorem ipsum" e molto altro.

Gemfile

gruppo: sviluppo do gemma 'finta' fine

Installa la gemma:

installazione bundle

Ora modificare il seeds.rb:

db / seeds.rb

5.times do user = User.create (nome: Faker :: Name.name) user.posts.create (title: Faker :: Book.title, body: Faker :: Lorem.sentence) end

Infine, carica i tuoi dati:

rotaie db: seme

Rispondendo a JSON

Ora, ovviamente, abbiamo bisogno di alcuni percorsi e controller per creare la nostra API. È prassi comune annidare i percorsi dell'API sotto il API / sentiero. Inoltre, gli sviluppatori di solito forniscono la versione dell'API nel percorso, ad esempio api / v1 /. In seguito, se è necessario introdurre alcune modifiche urgenti, è sufficiente creare un nuovo spazio dei nomi (v2) e un controller separato.

Ecco come possono essere visualizzati i tuoi percorsi:

config / routes.rb

namespace 'api' fa namespace 'v1' fa risorse: posts resources: users end end

Questo genera percorsi come:

api_v1_posts GET /api/v1/posts(.:format) api / v1 / posts # indice POST /api/v1/posts(.:format) api / v1 / posts # crea api_v1_post GET / api / v1 / posts /: id (.: format) api / v1 / posts # show

Puoi usare a scopo metodo invece del namespace, ma di default cercherà il UsersController e PostsController dentro il controllori directory, non dentro il controllori / api / v1, quindi sii attento.

Crea il api cartella con la directory nidificata v1 dentro il controllori. Compilalo con i tuoi controller:

controllori / api / v1 / users_controller.rb

modulo Api modulo V1 classe UsersController < ApplicationController end end end

controllori / api / v1 / posts_controller.rb

modulo Api modulo V1 classe PostsController < ApplicationController end end end

Si noti che non solo è necessario nidificare il file del controller sotto il api / v1 percorso, ma anche la classe stessa deve essere denominata nello spazio Api e V1 moduli.

La prossima domanda è come rispondere correttamente con i dati in formato JSON? In questo articolo proveremo queste soluzioni: le gemme di jBuilder e active_model_serializers. Quindi, prima di passare alla sezione successiva, rilasciarli nella Gemfile:

Gemfile

gem 'jbuilder', '~> 2.5' gem 'active_model_serializers', '~> 0.10.0'

Quindi esegui:

installazione bundle

Usando la gemma di JBuilder

jBuilder è una gemma popolare gestita dal team Rails che fornisce un semplice DSL (linguaggio specifico del dominio) che consente di definire strutture JSON nelle viste.

Supponiamo di voler visualizzare tutti i post quando un utente preme il indice azione:

controllori / api / v1 / posts_controller.rb

 def index @posts = Post.order ('created_at DESC') fine

Tutto ciò che devi fare è creare la vista che prende il nome dall'azione corrispondente con il .json.jbuilder estensione. Si noti che la vista deve essere posizionata sotto api / v1 percorso pure:

views / api / v1 / messaggi / index.json.jbuilder

json.array! @posts do | post | json.id post.id json.title post.title json.body post.body end

json.array! attraversa il @posts schieramento. json.id, json.title e json.body generare le chiavi con i nomi corrispondenti impostando gli argomenti come valori. Se navighi su http: // localhost: 3000 / api / v1 / posts.json, vedrai un output simile a questo:

["id": 1, "titolo": "Titolo 1", "corpo": "Corpo 1", "id": 2, "titolo": "Titolo 2", "corpo": "Corpo 2 "]

E se volessimo mostrare l'autore per ogni post? È semplice:

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

L'output cambierà in:

["id": 1, "titolo": "Titolo 1", "corpo": "Corpo 1", "utente": "id": 1, "nome": "Nome utente"]

Il contenuto del .JBuilder i file sono semplici codice Ruby, quindi puoi utilizzare tutte le operazioni di base come al solito.

Nota che jBuilder supporta i partial come qualsiasi normale visualizzazione di Rails, quindi potresti anche dire: 

json.partial! parziale: "post / post", collezione: @posts, come:: post

e quindi creare il views / api / v1 / messaggi / _post.json.jbuilder file con il seguente contenuto:

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

Quindi, come vedi, jBuilder è facile e conveniente. Tuttavia, in alternativa, puoi utilizzare i serializzatori, quindi discutiamoli nella prossima sezione.

Utilizzo dei serializzatori

La gemma rails_model_serializers è stata creata da un team che inizialmente gestiva le rails-api. Come indicato nella documentazione, rails_model_serializers porta la convenzione sulla configurazione alla tua generazione JSON. Fondamentalmente, si definiscono i campi da utilizzare in caso di serializzazione (ovvero generazione JSON).

Ecco il nostro primo serializzatore:

serializzatori / post_serializer.rb

class PostSerializer < ActiveModel::Serializer attributes :id, :title, :body end

Qui diciamo che tutti questi campi dovrebbero essere presenti nel JSON risultante. Adesso metodi come to_json e as_json chiamato un post utilizzerà questa configurazione e restituirà il contenuto corretto.

Per vederlo in azione, modifica il indice azione come questa:

controllori / api / v1 / posts_controller.rb

def index @posts = Post.order ('created_at DESC') render json: @posts end

as_json verrà automaticamente chiamato su @posts oggetto.

E gli utenti? I serializzatori consentono di indicare le relazioni, proprio come fanno i modelli. Inoltre, i serializzatori possono essere nidificati:

serializzatori / post_serializer.rb

class PostSerializer < ActiveModel::Serializer attributes :id, :title, :body belongs_to :user class UserSerializer < ActiveModel::Serializer attributes :id, :name end end

Ora, quando serializzi il post, conterrà automaticamente l'annidato utente chiave con il suo id e nome. Se in seguito si crea un serializzatore separato per l'utente con : id attributo escluso:

serializzatori / post_serializer.rb

class UserSerializer < ActiveModel::Serializer attributes :name end

poi @ user.as_json non restituirà l'id dell'utente. Ancora, @ post.as_json restituirà sia il nome che l'ID dell'utente, quindi ricordalo.

Protezione dell'API

In molti casi, non vogliamo che nessuno esegua alcuna azione utilizzando l'API. Quindi presentiamo un semplice controllo di sicurezza e costringiamo i nostri utenti a inviare i propri token durante la creazione e l'eliminazione di post.

Il token avrà una durata illimitata e verrà creato al momento della registrazione dell'utente. Prima di tutto, aggiungi un nuovo gettone colonna al utenti tavolo:

rails g migrazione add_token_to_users token: string: index

Questo indice dovrebbe garantire l'unicità in quanto non possono esserci due utenti con lo stesso token:

db / migrate / xyz_add_token_to_users.rb

add_index: users,: token, unique: true

Applica la migrazione:

rails db: migrate

Ora aggiungi il before_save richiama:

modelli / user.rb

before_create -> self.token = generate_token

Il generate_token il metodo privato creerà un token in un ciclo infinito e controllerà se è unico o meno. Non appena viene trovato un token univoco, restituiscilo:

modelli / user.rb

private def generate_token loop do token = SecureRandom.hex return token a meno che User.exists? (token: token) end end

È possibile utilizzare un altro algoritmo per generare il token, ad esempio basato sull'hash MD5 del nome dell'utente e un po 'di sale.

Registrazione Utente

Ovviamente, dobbiamo anche consentire agli utenti di registrarsi, perché altrimenti non saranno in grado di ottenere il loro token. Non voglio introdurre alcuna vista HTML nella nostra applicazione, quindi aggiungiamo un nuovo metodo API:

controllori / api / v1 / users_controller.rb

def create @user = User.new (user_params) se @ user.save lo stato di rendering:: creato else render json: @ user.errors, stato:: unprocessable_entity end end private def user_params params.require (: user) .permit (: nome) fine

È una buona idea restituire codici di stato HTTP significativi in ​​modo che gli sviluppatori capiscano esattamente cosa sta succedendo. Ora è possibile fornire un nuovo serializzatore per gli utenti o utilizzare un .json.jbuilder file. Preferisco quest'ultima variante (è per questo che non passo il : json opzione per il rendere metodo), ma sei libero di sceglierne uno. Nota, tuttavia, che il token non deve essere sempre serializzato, ad esempio quando si restituisce un elenco di tutti gli utenti, dovrebbe essere tenuto al sicuro!

views / api / v1 / utenti / create.json.jbuilder

json.id @ user.id json.name @ user.name json.token @ user.token

Il prossimo passo è verificare se tutto funziona correttamente. È possibile utilizzare il arricciare comando o scrivi del codice Ruby. Dato che questo articolo parla di Ruby, andrò con l'opzione di codifica.

Test della registrazione dell'utente

Per eseguire una richiesta HTTP, impiegheremo la gemma di Faraday, che fornisce un'interfaccia comune su molti adattatori (l'impostazione predefinita è Net :: HTTP). Creare un file Ruby separato, includere Faraday e configurare il client:

api_client.rb

richiede 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 / users' req.headers ['Content-Type'] = 'application / json' req.body = '"utente": "name": "test user"' end

Tutte queste opzioni sono piuttosto auto-esplicative: scegliamo l'adattatore predefinito, impostiamo l'URL di richiesta a http: // localhost: 300 / api / v1 / users, cambia il tipo di contenuto in application / json, e fornire il corpo della nostra richiesta.

La risposta del server conterrà JSON, quindi per analizzarla userò la gemma Oj:

api_client.rb

richiede 'oj' # client qui ... puts Oj.load (response.body) mette response.status

Oltre alla risposta analizzata, visualizzo anche il codice di stato per scopi di debug.

Ora puoi semplicemente eseguire questo script:

ruby api_client.rb

e memorizza il token ricevuto da qualche parte, lo useremo nella prossima sezione.

Autenticazione con il token

Per applicare l'autenticazione del token, il authenticate_or_request_with_http_token metodo può essere utilizzato. Fa parte del modulo ActionController :: HttpAuthentication :: Token :: ControllerMethods, quindi non dimenticare di includerlo:

controllori / api / v1 / posts_controller.rb 

classe PostsController < ApplicationController include ActionController::HttpAuthentication::Token::ControllerMethods #… end

Aggiungi un nuovo before_action e il metodo corrispondente:

controllori / api / v1 / posts_controller.rb 

before_action: autenticate, solo: [: create,: destroy] # ... private # ... def autenticate authenticate_or_request_with_http_token do | token, opzioni | @user = User.find_by (token: token) end end

Ora se il token non è impostato o se non è possibile trovare un utente con tale token, verrà restituito un errore 401, interrompendo l'esecuzione dall'esecuzione.

Si noti che la comunicazione tra il client e il server deve essere effettuata tramite HTTPS, altrimenti i token potrebbero essere facilmente falsificati. Ovviamente, la soluzione fornita non è l'ideale e in molti casi è preferibile utilizzare il protocollo OAuth 2 per l'autenticazione. Ci sono almeno due gemme che semplificano notevolmente il processo di supporto di questa funzione: Doorkeeper e oPRO.

Creare un post

Per vedere la nostra autenticazione in azione, aggiungi il creare azione per il PostsController:

controllori / api / v1 / posts_controller.rb 

def create @post = @ user.posts.new (post_params) se @ post.save restituisce json: @post, stato:: creato else render json: @ post.errors, stato:: unprocessable_entity end end

Approfittiamo del serializzatore qui per visualizzare il JSON corretto. Il @utente era già ambientato nel before_action.

Ora prova tutto usando questo semplice codice:

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": "Testo" 'fine

Sostituisci l'argomento passato a token_auth con il token ricevuto al momento della registrazione ed eseguire lo script.

ruby api_client.rb

Eliminazione di un post

La cancellazione di un post viene eseguita allo stesso modo. Aggiungi il distruggere azione:

controllori / api / v1 / posts_controller.rb 

def destroy @post = @ user.posts.find_by (params [: id]) se @post @ post.destroy else restituisce json: post: "non trovato", stato:: not_found end end

Permettiamo solo agli utenti di distruggere i post che possiedono. Se il post viene rimosso con successo, verrà restituito il codice di stato 204 (nessun contenuto). In alternativa, puoi rispondere con l'ID del post che è stato eliminato poiché sarà ancora disponibile dalla memoria.

Ecco la parte di codice per testare questa nuova funzionalità:

api_client.rb

response = client.delete do | req | req.url '/ api / v1 / posts / 6' req.headers ['Content-Type'] = 'application / json' end

Sostituisci l'ID del post con un numero che funzioni per te.

Impostazione CORS

Se vuoi consentire ad altri servizi web di accedere alla tua API (dal lato client), allora CORS (Cross-Origin Resource Sharing) dovrebbe essere configurato correttamente. Fondamentalmente, CORS consente alle applicazioni Web di inviare richieste AJAX ai servizi di terze parti. Fortunatamente, c'è una gemma chiamata rack-cors che ci consente di impostare facilmente tutto. Aggiungilo al Gemfile:

Gemfile

gemma 'rack-cors'

Installalo:

installazione bundle

E quindi fornire la configurazione all'interno del config / inizializzatori / cors.rb file. In realtà, questo file è già stato creato per te e contiene un esempio di utilizzo. Puoi anche trovare una documentazione abbastanza dettagliata sulla pagina della gemma.

La seguente configurazione, ad esempio, consentirà a chiunque di accedere all'API utilizzando qualsiasi metodo:

config / inizializzatori / cors.rb

Rails.application.config.middleware.insert_before 0, Rack :: Cors consente l'uso delle origini '*' resource '/ api / *', intestazioni:: any, metodi: [: get,: post,: put,: patch, : delete,: options,: head] end end

Prevenire gli abusi

L'ultima cosa che accennerò a questa guida è come proteggere la tua API dagli attacchi di abuso e denial of service. C'è una bella gemma chiamata rack-attack (creata da persone di Kickstarter) che ti permette di inserire nella blacklist o nella lista bianca i client, prevenire l'inondazione di un server con richieste e altro.

Lascia cadere la gemma Gemfile:

Gemfile

gemma "rack-attack"

Installalo:

installazione bundle

E quindi fornire la configurazione all'interno del rack_attack.rb file di inizializzazione. La documentazione della gem elenca tutte le opzioni disponibili e suggerisce alcuni casi d'uso. Ecco la configurazione di esempio che limita chiunque ad accedere al servizio e limita il numero massimo di richieste a 5 al secondo:

config / inizializzatori / rack_attack.rb

class Rack :: Attack safelist ('allow from localhost') do | req | # Le richieste sono consentite se il valore restituito è truey '127.0.0.1' == req.ip || ':: 1' == req.ip end throttle ('req / ip',: limit => 5,: periodo => 1.secondo) do | req | req.ip end end

Un'altra cosa che deve essere fatta è includere RackAttack come middleware:

config / application.rb

config.middleware.use Rack :: Attack

Conclusione

Siamo arrivati ​​alla fine di questo articolo. Speriamo che ormai ti senti più sicuro di creare API con Rails! Si noti che questa non è l'unica opzione disponibile - un'altra soluzione popolare che esiste da un po 'di tempo è il framework Grape, quindi potresti essere interessato a verificarlo.

Non esitare a pubblicare le tue domande se qualcosa ti è sembrato poco chiaro. Ti ringrazio per essere stato con me e felice codifica!